/* code16c5x.c */
/*****************************************************************************/
/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only                     */
/*                                                                           */
/* AS-Portierung                                                             */
/*                                                                           */
/* AS - Codegenerator fuer PIC16C5x                                          */
/*                                                                           */
/*****************************************************************************/

#include "stdinc.h"

#include <string.h>

#include "strutil.h"
#include "chunks.h"
#include "asmdef.h"
#include "asmsub.h"
#include "asmpars.h"
#include "asmitree.h"
#include "codepseudo.h"
#include "fourpseudo.h"
#include "codevars.h"
#include "errmsg.h"
#include "headids.h"

#include "code16c5x.h"

static CPUVar CPU16C54, CPU16C55, CPU16C56, CPU16C57;

/*-------------------------------------------------------------------------*/

static void DecodeFixed(Word Code)
{
  if (ChkArgCnt(0, 0))
  {
    CodeLen = 1;
    WAsmCode[0] = Code;
  }
}

static void DecodeLit(Word Code)
{
  if (ChkArgCnt(1, 1))
  {
    Boolean OK;
    Word AdrWord = EvalStrIntExpression(&ArgStr[1], Int8, &OK);
    if (OK)
    {
      CodeLen = 1;
      WAsmCode[0] = Code + (AdrWord & 0xff);
    }
  }
}

static void DecodeAri(Word Code)
{
  Word DefaultDir = (Code >> 15) & 1, AdrWord;

  Code &= 0x7fff;
  if (ChkArgCnt(1, 2))
  {
    tEvalResult EvalResult;

    AdrWord = EvalStrIntExpressionWithResult(&ArgStr[1], UInt5, &EvalResult);
    if (EvalResult.OK)
    {
      ChkSpace(SegData, EvalResult.AddrSpaceMask);
      WAsmCode[0] = Code + (AdrWord & 0x1f);
      if (ArgCnt == 1)
      {
        CodeLen = 1;
        WAsmCode[0] += DefaultDir << 5;
      }
      else if (!as_strcasecmp(ArgStr[2].str.p_str, "W"))
        CodeLen = 1;
      else if (!as_strcasecmp(ArgStr[2].str.p_str, "F"))
      {
        CodeLen = 1;
        WAsmCode[0] += 0x20;
      }
      else
      {
        AdrWord = EvalStrIntExpressionWithResult(&ArgStr[2], UInt1, &EvalResult);
        if (EvalResult.OK)
        {
          CodeLen = 1;
          WAsmCode[0] += AdrWord << 5;
        }
      }
    }
  }
}

static void DecodeBit(Word Code)
{
  if (ChkArgCnt(2, 2))
  {
    tEvalResult EvalResult;
    Word AdrWord = EvalStrIntExpressionWithResult(&ArgStr[2], UInt3, &EvalResult);

    if (EvalResult.OK)
    {
      WAsmCode[0] = EvalStrIntExpressionWithResult(&ArgStr[1], UInt5, &EvalResult);
      if (EvalResult.OK)
      {
        CodeLen = 1;
        WAsmCode[0] += Code + (AdrWord << 5);
        ChkSpace(SegData, EvalResult.AddrSpaceMask);
      }
    }
  }
}

static void DecodeF(Word Code)
{
  if (ChkArgCnt(1, 1))
  {
    tEvalResult EvalResult;
    Word AdrWord = EvalStrIntExpressionWithResult(&ArgStr[1], UInt5, &EvalResult);

    if (EvalResult.OK)
    {
      CodeLen = 1;
      WAsmCode[0] = Code + AdrWord;
      ChkSpace(SegData, EvalResult.AddrSpaceMask);
    }
  }
}

static void DecodeTRIS(Word Code)
{
  UNUSED(Code);

  if (ChkArgCnt(1, 1))
  {
    tEvalResult EvalResult;
    Word AdrWord = EvalStrIntExpressionWithResult(&ArgStr[1], UInt3, &EvalResult);

    if (EvalResult.OK)
     if (ChkRange(AdrWord, 5, 7))
     {
       CodeLen = 1;
       WAsmCode[0] = 0x000 + AdrWord;
       ChkSpace(SegData, EvalResult.AddrSpaceMask);
     }
  }
}

static void DecodeCALL_GOTO(Word Code)
{
  if (ChkArgCnt(1, 1))
  {
    tEvalResult EvalResult;
    Word AdrWord = EvalStrIntExpressionWithResult(&ArgStr[1], UInt16, &EvalResult);

    if (EvalResult.OK)
    {
      if (AdrWord > SegLimits[SegCode]) WrError(ErrNum_OverRange);
      else if ((Code & 0x100) && ((AdrWord & 0x100) != 0)) WrError(ErrNum_NotFromThisAddress);
      else
      {
        ChkSpace(SegCode, EvalResult.AddrSpaceMask);
        if (((ProgCounter() ^ AdrWord) & 0x200) != 0)
          WAsmCode[CodeLen++] = 0x4a3 + ((AdrWord & 0x200) >> 1); /* BCF/BSF 3,5 */
        if (((ProgCounter() ^ AdrWord) & 0x400) != 0)
          WAsmCode[CodeLen++] = 0x4c3 + ((AdrWord & 0x400) >> 2); /* BCF/BSF 3,6 */
        WAsmCode[CodeLen++] = Code + (AdrWord & (Code & 0x100 ? 0xff : 0x1ff));
      }
    }
  }
}

static void DecodeSFR(Word Code)
{
  UNUSED(Code);

  code_equate_type(SegData, UInt5);
}

static void DecodeDATA_16C5x(Word Code)
{
  UNUSED(Code);

  DecodeDATA(Int12, Int8);
}

static void DecodeZERO(Word Code)
{
  Word Size;
  Boolean ValOK;
  tSymbolFlags Flags;

  UNUSED(Code);

  if (ChkArgCnt(1, 1))
  {
    Size = EvalStrIntExpressionWithFlags(&ArgStr[1], Int16, &ValOK, &Flags);
    if (mFirstPassUnknown(Flags)) WrError(ErrNum_FirstPassCalc);
    if (ValOK && !mFirstPassUnknown(Flags))
    {
      if (SetMaxCodeLen(Size << 1)) WrError(ErrNum_CodeOverflow);
      else
      {
        CodeLen = Size;
        memset(WAsmCode, 0, 2 * Size);
      }
    }
  }
}

/*-------------------------------------------------------------------------*/

static void AddFixed(const char *NName, Word NCode)
{
  AddInstTable(InstTable, NName, NCode, DecodeFixed);
}

static void AddLit(const char *NName, Word NCode)
{
  AddInstTable(InstTable, NName, NCode, DecodeLit);
}

static void AddAri(const char *NName, Word NCode, Word NDef)
{
  AddInstTable(InstTable, NName, NCode | (NDef << 15), DecodeAri);
}

static void AddBit(const char *NName, Word NCode)
{
  AddInstTable(InstTable, NName, NCode, DecodeBit);
}

static void AddF(const char *NName, Word NCode)
{
  AddInstTable(InstTable, NName, NCode, DecodeF);
}

static void InitFields(void)
{
  InstTable = CreateInstTable(103);

  add_null_pseudo(InstTable);

  AddInstTable(InstTable, "TRIS", 0, DecodeTRIS);
  AddInstTable(InstTable, "CALL", 0x0900, DecodeCALL_GOTO);
  AddInstTable(InstTable, "GOTO", 0x0a00, DecodeCALL_GOTO);
  AddInstTable(InstTable, "SFR", 0, DecodeSFR);
  AddInstTable(InstTable, "RES", 0, DecodeRES);
  AddInstTable(InstTable, "DATA", 0, DecodeDATA_16C5x);
  AddInstTable(InstTable, "ZERO", 0, DecodeZERO);

  AddFixed("CLRW"  , 0x040);
  AddFixed("NOP"   , 0x000);
  AddFixed("CLRWDT", 0x004);
  AddFixed("OPTION", 0x002);
  AddFixed("SLEEP" , 0x003);

  AddLit("ANDLW", 0xe00);
  AddLit("IORLW", 0xd00);
  AddLit("MOVLW", 0xc00);
  AddLit("RETLW", 0x800);
  AddLit("XORLW", 0xf00);

  AddAri("ADDWF" , 0x1c0, 0);
  AddAri("ANDWF" , 0x140, 0);
  AddAri("COMF"  , 0x240, 1);
  AddAri("DECF"  , 0x0c0, 1);
  AddAri("DECFSZ", 0x2c0, 1);
  AddAri("INCF"  , 0x280, 1);
  AddAri("INCFSZ", 0x3c0, 1);
  AddAri("IORWF" , 0x100, 0);
  AddAri("MOVF"  , 0x200, 0);
  AddAri("RLF"   , 0x340, 1);
  AddAri("RRF"   , 0x300, 1);
  AddAri("SUBWF" , 0x080, 0);
  AddAri("SWAPF" , 0x380, 1);
  AddAri("XORWF" , 0x180, 0);

  AddBit("BCF"  , 0x400);
  AddBit("BSF"  , 0x500);
  AddBit("BTFSC", 0x600);
  AddBit("BTFSS", 0x700);

  AddF("CLRF" , 0x060);
  AddF("MOVWF", 0x020);
}

static void DeinitFields(void)
{
  DestroyInstTable(InstTable);
}

/*-------------------------------------------------------------------------*/

static void MakeCode_16C5X(void)
{
  if (!LookupInstTable(InstTable, OpPart.str.p_str))
    WrStrErrorPos(ErrNum_UnknownInstruction, &OpPart);
}

static Boolean IsDef_16C5X(void)
{
  return Memo("SFR");
}

static void SwitchFrom_16C5X(void)
{
  DeinitFields();
}

static void SwitchTo_16C5X(void)
{
  const TFamilyDescr *p_descr = FindFamilyByName("16C5x");
  TurnWords = False;
  SetIntConstMode(eIntConstModeMoto);

  PCSymbol = "*";
  HeaderID = p_descr->Id;
  NOPCode = 0x000;
  DivideChars = ",";
  HasAttrs = False;

  ValidSegs = (1 << SegCode) + (1 << SegData);
  Grans[SegCode] = 2; ListGrans[SegCode] = 2; SegInits[SegCode] = 0;
  if (MomCPU == CPU16C56)
    SegLimits[SegCode] = 1023;
  else if (MomCPU == CPU16C57)
    SegLimits[SegCode] = 2047;
  else
    SegLimits[SegCode] = 511;
  Grans[SegData] = 1; ListGrans[SegData] = 1; SegInits[SegCode] = 0;
  SegLimits[SegData] = 0x1f;

  MakeCode = MakeCode_16C5X;
  IsDef = IsDef_16C5X;
  SwitchFrom = SwitchFrom_16C5X;
  InitFields();
}

void code16c5x_init(void)
{
  CPU16C54 = AddCPU("16C54", SwitchTo_16C5X);
  CPU16C55 = AddCPU("16C55", SwitchTo_16C5X);
  CPU16C56 = AddCPU("16C56", SwitchTo_16C5X);
  CPU16C57 = AddCPU("16C57", SwitchTo_16C5X);
}
