/* code16c8x.c */
/*****************************************************************************/
/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only                     */
/*                                                                           */
/* AS-Portierung                                                             */
/*                                                                           */
/* AS-Codegenerator PIC16C8x                                                 */
/*                                                                           */
/*****************************************************************************/

#include "stdinc.h"

#include <string.h>

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

#include "code16c8x.h"

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

#define AddCodeSpace 0x300

static CPUVar CPU16C64, CPU16C84, CPU16C873, CPU16C874, CPU16C876, CPU16C877;

/*--------------------------------------------------------------------------*/
/* helper functions */

static Word EvalFExpression(tStrComp *pArg, tEvalResult *pEvalResult)
{
  LongInt h;

  h = EvalStrIntExpressionWithResult(pArg, UInt9, pEvalResult);
  if (pEvalResult->OK)
  {
    ChkSpace(SegData, pEvalResult->AddrSpaceMask);
    return (h & 0x7f);
  }
  else
    return 0;
}

/*--------------------------------------------------------------------------*/
/* instruction decoders */

static void DecodeFixed(Word Code)
{
  if (ChkArgCnt(0, 0))
  {
    WAsmCode[CodeLen++] = Code;
    if (Memo("OPTION"))
      WrError(ErrNum_Obsolete);
  }
}

static void DecodeLit(Word Code)
{
  Word AdrWord;
  Boolean OK;

  if (ChkArgCnt(1, 1))
  {
    AdrWord = EvalStrIntExpression(&ArgStr[1], Int8, &OK);
    if (OK)
      WAsmCode[CodeLen++] = Code | Lo(AdrWord);
  }
}

static void DecodeAri(Word Code)
{
  Word DefaultDir = (Code >> 8) & 0x80;
  tEvalResult EvalResult;
  Word AdrWord;

  Code &= 0x7fff;

  if (ChkArgCnt(1, 2))
  {
    AdrWord = EvalFExpression(&ArgStr[1], &EvalResult);
    if (EvalResult.OK)
    {
      WAsmCode[0] = Code | AdrWord;
      if (1 == ArgCnt)
      {
        WAsmCode[0] |= DefaultDir;
        CodeLen = 1;
      }
      else if (!as_strcasecmp(ArgStr[2].str.p_str, "W"))
        CodeLen = 1;
      else if (!as_strcasecmp(ArgStr[2].str.p_str, "F"))
      {
         WAsmCode[0] |= 0x80;
        CodeLen = 1;
      }
      else
      {
        AdrWord = EvalStrIntExpressionWithResult(&ArgStr[2], UInt1, &EvalResult);
        if (EvalResult.OK)
        {
          WAsmCode[0] |= AdrWord << 7;
          CodeLen = 1;
        }
      }
    }
  }
}

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

    if (EvalResult.OK)
    {
      WAsmCode[0] = EvalFExpression(&ArgStr[1], &EvalResult);
      if (EvalResult.OK)
      {
        WAsmCode[0] |= Code | (AdrWord << 7);
        CodeLen = 1;
      }
    }
  }
}

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

    if (EvalResult.OK)
      WAsmCode[CodeLen++] = Code | AdrWord;
  }
}

static void DecodeTRIS(Word Index)
{
  Word AdrWord;
  tEvalResult EvalResult;

  UNUSED(Index);

  if (ChkArgCnt(1, 1))
  {
    AdrWord = EvalStrIntExpressionWithResult(&ArgStr[1], UInt3, &EvalResult);
    if (mFirstPassUnknown(EvalResult.Flags))
      AdrWord = 5;
    if (EvalResult.OK)
      if (ChkRange(AdrWord, 5, 6))
      {
        WAsmCode[CodeLen++] = 0x0060 | AdrWord;
        ChkSpace(SegData, EvalResult.AddrSpaceMask);
        WrError(ErrNum_Obsolete);
      }
  }
}

static void DecodeJump(Word Index)
{
  if (ChkArgCnt(1, 1))
  {
    tEvalResult EvalResult;
    Word AdrWord = EvalStrIntExpressionWithResult(&ArgStr[1], Int16, &EvalResult);
    if (EvalResult.OK)
    {
      if (AdrWord > (SegLimits[SegCode] - AddCodeSpace)) WrError(ErrNum_OverRange);
      else
      {
        Word XORVal, Mask, RegBit;

        ChkSpace(SegCode, EvalResult.AddrSpaceMask);

        XORVal = (ProgCounter() ^ AdrWord) & ~0x7ff;

        /* add BCF/BSF instruction for non-matching upper address bits
           - we might need to extend this for the PICs with more than
             8K of program space */

        for (RegBit = 3, Mask = 0x800; RegBit <= 4; RegBit++, Mask <<= 1)
          if (XORVal & Mask)
            WAsmCode[CodeLen++] = 0x100a
                                | (RegBit << 7)
                                | ((AdrWord & Mask) >> (RegBit - 2));

        WAsmCode[CodeLen++] = Index | (AdrWord & 0x7ff);
      }
    }
  }
}

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

  code_equate_type(SegData, UInt9);
}

static void DecodeDATA_16C8x(Word Index)
{
  UNUSED(Index);

  DecodeDATA(Int14, Int8);
}

static void DecodeZERO(Word Index)
{
  Word Size, Shift = (ActPC == SegCode) ? 1 : 0;
  Boolean ValOK;
  tSymbolFlags Flags;

  UNUSED(Index);

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

static void DecodeBANKSEL(Word Index)
{
  Word Adr;
  Boolean ValOK;

  UNUSED(Index);

  if (ChkArgCnt(1, 1))
  {
    Adr = EvalStrIntExpression(&ArgStr[1], UInt9, &ValOK);
    if (ValOK)
    {
      WAsmCode[0] = 0x1283 | ((Adr &  0x80) << 3); /* BxF Status, 5 */
      WAsmCode[1] = 0x1303 | ((Adr & 0x100) << 2); /* BxF Status, 6 */
      CodeLen = 2;
    }
  }
}

/*--------------------------------------------------------------------------*/
/* dynamic code table handling */

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 NDir)
{
  AddInstTable(InstTable, NName, NCode | (NDir << 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(201);

  add_null_pseudo(InstTable);

  AddFixed("CLRW"  , 0x0100);
  AddFixed("NOP"   , 0x0000);
  AddFixed("CLRWDT", 0x0064);
  AddFixed("OPTION", 0x0062);
  AddFixed("SLEEP" , 0x0063);
  AddFixed("RETFIE", 0x0009);
  AddFixed("RETURN", 0x0008);

  AddLit("ADDLW", 0x3e00);
  AddLit("ANDLW", 0x3900);
  AddLit("IORLW", 0x3800);
  AddLit("MOVLW", 0x3000);
  AddLit("RETLW", 0x3400);
  AddLit("SUBLW", 0x3c00);
  AddLit("XORLW", 0x3a00);

  AddAri("ADDWF" , 0x0700, 0);
  AddAri("ANDWF" , 0x0500, 0);
  AddAri("COMF"  , 0x0900, 1);
  AddAri("DECF"  , 0x0300, 1);
  AddAri("DECFSZ", 0x0b00, 1);
  AddAri("INCF"  , 0x0a00, 1);
  AddAri("INCFSZ", 0x0f00, 1);
  AddAri("IORWF" , 0x0400, 0);
  AddAri("MOVF"  , 0x0800, 0);
  AddAri("RLF"   , 0x0d00, 1);
  AddAri("RRF"   , 0x0c00, 1);
  AddAri("SUBWF" , 0x0200, 0);
  AddAri("SWAPF" , 0x0e00, 1);
  AddAri("XORWF" , 0x0600, 0);

  AddBit("BCF"  , 0x1000);
  AddBit("BSF"  , 0x1400);
  AddBit("BTFSC", 0x1800);
  AddBit("BTFSS", 0x1c00);

  AddF("CLRF" , 0x0180);
  AddF("MOVWF", 0x0080);

  AddInstTable(InstTable, "TRIS", 0, DecodeTRIS);
  AddInstTable(InstTable, "GOTO", 0x2800, DecodeJump);
  AddInstTable(InstTable, "CALL", 0x2000, DecodeJump);
  AddInstTable(InstTable, "SFR" , 0, DecodeSFR);
  AddInstTable(InstTable, "RES" , 0, DecodeRES);
  AddInstTable(InstTable, "DATA", 0, DecodeDATA_16C8x);
  AddInstTable(InstTable, "ZERO", 0, DecodeZERO);

  AddInstTable(InstTable, "BANKSEL", 0, DecodeBANKSEL);
}

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

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

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

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

static Boolean ChkPC_16c8x(LargeWord Addr)
{
  if ((ActPC == SegCode) && (Addr > (LargeWord)SegLimits[SegCode]))
    return ((Addr >= 0x2000) && (Addr <= 0x2007));
  else
    return (Addr <= (LargeWord)SegLimits[ActPC]);
}

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

static void SwitchTo_16c8x(void)
{
  const TFamilyDescr *pDescr;

  TurnWords = False;
  SetIntConstMode(eIntConstModeMoto);

  pDescr = FindFamilyByName("16C8x");
  PCSymbol = "*";
  HeaderID = pDescr->Id;
  NOPCode = 0x0000;
  DivideChars = ",";
  HasAttrs = False;

  ValidSegs = (1 << SegCode) | (1 << SegData);
  Grans[SegCode] = 2; ListGrans[SegCode] = 2; SegInits[SegCode] = 0;
  if (MomCPU == CPU16C64)
    SegLimits[SegCode] = 0x7ff;
  else if (MomCPU == CPU16C873)
    SegLimits[SegCode] = 0x0fff;
  else if (MomCPU == CPU16C874)
    SegLimits[SegCode] = 0x0fff;
  else if (MomCPU == CPU16C876)
    SegLimits[SegCode] = 0x1fff;
  else if (MomCPU == CPU16C877)
    SegLimits[SegCode] = 0x1fff;
  else
    SegLimits[SegCode] = 0x3ff;

  SegLimits[SegCode] += AddCodeSpace;
  Grans[SegData] = 1; ListGrans[SegData] = 1; SegInits[SegData] = 0;
  SegLimits[SegData] = 0x1ff;
  Grans[SegEEData] = 1; ListGrans[SegEEData] = 1; SegInits[SegEEData] = 0;
  if ((MomCPU == CPU16C877) || (MomCPU == CPU16C876))
    SegLimits[SegEEData] = 0xff;
  else if ((MomCPU == CPU16C874) || (MomCPU == CPU16C873))
    SegLimits[SegEEData] = 0x7f;
  else
    SegLimits[SegEEData] = 0x3f;
  ChkPC = ChkPC_16c8x;

  MakeCode = MakeCode_16c8x;
  IsDef = IsDef_16c8x;
  SwitchFrom = SwitchFrom_16c8x;
  InitFields();
}

void code16c8x_init(void)
{
  CPU16C64  = AddCPU("16C64",  SwitchTo_16c8x);
  CPU16C84  = AddCPU("16C84",  SwitchTo_16c8x);
  CPU16C873 = AddCPU("16C873", SwitchTo_16c8x);
  CPU16C874 = AddCPU("16C874", SwitchTo_16c8x);
  CPU16C876 = AddCPU("16C876", SwitchTo_16c8x);
  CPU16C877 = AddCPU("16C877", SwitchTo_16c8x);
}
