/* code48.c */
/*****************************************************************************/
/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only                     */
/*                                                                           */
/* AS-Portierung                                                             */
/*                                                                           */
/* Codegeneratormodul MCS-48-Familie                                         */
/*                                                                           */
/*****************************************************************************/

#include "stdinc.h"
#include "bpemu.h"
#include <string.h>
#include <ctype.h>

#include "nls.h"
#include "strutil.h"
#include "stringlists.h"
#include "asmdef.h"
#include "asmsub.h"
#include "asmpars.h"
#include "asmallg.h"
#include "asmitree.h"
#include "asmcode.h"
#include "assume.h"
#include "codepseudo.h"
#include "intpseudo.h"
#include "codevars.h"
#include "errmsg.h"
#include "headids.h"

#include "code48.h"

typedef struct
{
  const char *Name;
  Byte Code;
} SelOrder;

typedef enum
{
  ModImm = 0,
  ModReg = 1,
  ModInd = 2,
  ModAcc = 3,
  ModNone = -1
} tAdrMode;

#define MModImm (1 << ModImm)
#define MModReg (1 << ModReg)
#define MModInd (1 << ModInd)
#define MModAcc (1 << ModAcc)

#define eCPUFlagCMOS (1ul << 0)
#define eCPUFlagSiemens (1ul << 1)
#define eCPUFlagDEC_DJNZ_IREG (1ul << 2)
#define eCPUFlagXMem (1ul << 3)
#define eCPUFlagUPIPort (1ul << 4)
#define eCPUFlagPort0 (1ul << 5)
#define eCPUFlagPort1 (1ul << 6)
#define eCPUFlagPort2 (1ul << 7)
#define eCPUFlagIOExpander (1ul << 8)
#define eCPUFlagUserFlags (1ul << 9)
#define eCPUFlagT0 (1ul << 10)
#define eCPUFlagT0CLK (1ul << 11)
#define eCPUFlagCondBitJmp (1ul << 12)
#define eCPUFlagTransferA_PSW (1ul << 13)
#define eCPUFlagBUS (1ul << 14)
#define eCPUFlagRegBanks (1ul << 15)
#define eCPUFlagADConv (1ul << 16)
#define eCPUFlagLogToPort (1ul << 17)
#define eCPUFlagDEC_REG (1ul << 18)
#define eCPUFlagMOVP3 (1ul << 19)
#define eCPUFlagINTLogic (1ul << 20)
#define eCPUFlagOKI (1ul << 21)
#define eCPUFlagSerial (1ul << 22)
#define eCPUFlag84xx (1ul << 23)

#define MB_NOTHING 0xff

typedef struct
{
  const char Name[9];
  Word CodeSize;
  LongWord Flags;
} tCPUProps;

typedef struct
{
  const char *p_val;
  Byte code;
} clr_cpl_t;

typedef struct
{
  Byte value;
  tSymbolFlags flags;
} adr_vals_t;

static const tCPUProps *pCurrCPUProps;
clr_cpl_t *clr_cpl_args;
static SelOrder *SelOrders;
static LongInt Reg_MB;

/****************************************************************************/

/*!------------------------------------------------------------------------
 * \fn     DecodeRegCore(const char *pAsc, tRegInt *pValue, tSymbolSize *pSize)
 * \brief  check whether argument describes a CPU register
 * \param  pAsc argument
 * \param  pValue resulting register # if yes
 * \param  pSize resulting register size if yes
 * \return true if yes
 * ------------------------------------------------------------------------ */

static Boolean DecodeRegCore(const char *pAsc, tRegInt *pValue, tSymbolSize *pSize)
{
  if ((strlen(pAsc) != 2)
   || (as_toupper(pAsc[0]) != 'R')
   || (!isdigit(pAsc[1])))
    return False;

  *pValue = pAsc[1] - '0';
  *pSize = eSymbolSize8Bit;
  return (*pValue <= 7);
}

/*!------------------------------------------------------------------------
 * \fn     DissectReg_48(char *pDest, size_t DestSize, tRegInt Value, tSymbolSize InpSize)
 * \brief  dissect register symbols - 8048 variant
 * \param  pDest destination buffer
 * \param  DestSize destination buffer size
 * \param  Value numeric register value
 * \param  InpSize register size
 * ------------------------------------------------------------------------ */

static void DissectReg_48(char *pDest, size_t DestSize, tRegInt Value, tSymbolSize InpSize)
{
  switch (InpSize)
  {
    case eSymbolSize8Bit:
      as_snprintf(pDest, DestSize, "R%u", (unsigned)Value);
      break;
    default:
      as_snprintf(pDest, DestSize, "%d-%u", (int)InpSize, (unsigned)Value);
  }
}

/*!------------------------------------------------------------------------
 * \fn     DecodeReg(const tStrComp *pArg, Byte *pValue, Boolean MustBeReg)
 * \brief  check whether argument is a CPU register or user-defined register alias
 * \param  pArg argument
 * \param  pValue resulting register # if yes
 * \param  MustBeReg expect register at this arg?
 * \return eIsReg/eIsNoReg/eRegAbort
 * ------------------------------------------------------------------------ */

static tRegEvalResult DecodeReg(const tStrComp *pArg, Byte *pValue, Boolean MustBeReg)
{
  tSymbolSize Size;
  tRegDescr RegDescr;
  tEvalResult EvalResult;
  tRegEvalResult RegEvalResult;

  if (DecodeRegCore(pArg->str.p_str, &RegDescr.Reg, &Size))
  {
    *pValue = RegDescr.Reg;
    return eIsReg;
  }

  RegEvalResult = EvalStrRegExpressionAsOperand(pArg, &RegDescr, &EvalResult, eSymbolSizeUnknown, MustBeReg);
  *pValue = RegDescr.Reg;
  return RegEvalResult;
}

static Boolean IsPort(const char *pArg, Word PortMask, Byte *pPortNum)
{
  if (!as_strcasecmp(pArg, "BUS"))
    *pPortNum = 8;
  else if ((strlen(pArg) == 2)
        && (as_toupper(pArg[0]) == 'P')
        && isdigit(pArg[1]))
    *pPortNum = pArg[1] - '0';
  else
    return False;

  return !!(PortMask & (1 << *pPortNum));
}

static Boolean IsSerialPort(const char *pArg, Word PortMask, Byte *pPortNum)
{
  if ((strlen(pArg) == 2)
   && (as_toupper(pArg[0]) == 'S')
   && isdigit(pArg[1]))
    *pPortNum = pArg[1] - '0';
  else
    return False;

  return !!(PortMask & (1 << *pPortNum));
}

static void reset_adr_vals(adr_vals_t *p_vals)
{
  p_vals->value = 0;
  p_vals->flags = eSymbolFlag_None;
}

static void append_adr_vals(const adr_vals_t *p_vals)
{
  set_b_guessed(p_vals->flags, CodeLen, 1, 0xff);
  BAsmCode[CodeLen++] = p_vals->value;
}

static tAdrMode DecodeAdr(const tStrComp *pArg, adr_vals_t *p_vals, unsigned Mask)
{
  tAdrMode AdrMode = ModNone;

  reset_adr_vals(p_vals);
  if (*pArg->str.p_str == '\0') return ModNone;

  if (!as_strcasecmp(pArg->str.p_str, "A"))
  {
    AdrMode = ModAcc;
    goto found;
  }

  if (*pArg->str.p_str == '#')
  {
    Boolean OK;

    p_vals->value = EvalStrIntExpressionOffsWithFlags(pArg, 1, Int8, &OK, &p_vals->flags);
    if (OK)
    {
      AdrMode = ModImm;
      goto found;
    }
  }

  switch (DecodeReg(pArg, &p_vals->value, False))
  {
    case eIsReg:
      AdrMode = ModReg;
      goto found;
    case eRegAbort:
      return ModNone;
    default:
      break;
  }

  if (*pArg->str.p_str == '@')
  {
    tStrComp Arg;

    StrCompRefRight(&Arg, pArg, 1);
    if (!DecodeReg(&Arg, &p_vals->value, True))
      return ModNone;
    if (p_vals->value > 1)
    {
      WrStrErrorPos(ErrNum_InvReg, &Arg);
      return ModNone;
    }
    AdrMode = ModInd;
    goto found;
  }

  WrStrErrorPos(ErrNum_InvAddrMode, pArg);

found:
  if ((AdrMode != ModNone) && !(Mask & (1 << AdrMode)))
  {
    WrStrErrorPos(ErrNum_InvAddrMode, pArg);
    reset_adr_vals(p_vals);
    AdrMode = ModNone;
  }
  return AdrMode;
}

static Boolean ChkCPUFlags(LongWord CPUFlags)
{
  if (pCurrCPUProps->Flags & CPUFlags)
    return True;
  WrStrErrorPos(ErrNum_InstructionNotSupported, &OpPart);
  return False;
}

static Boolean AChkCPUFlags(LongWord CPUFlags, const tStrComp *pArg)
{
  if (pCurrCPUProps->Flags & CPUFlags)
    return True;
  WrStrErrorPos(ErrNum_InvAddrMode, pArg);
  return False;
}

static void ChkPx(Byte PortNum, const tStrComp *pArg)
{
  if (!(pCurrCPUProps->Flags & (eCPUFlagPort0 << PortNum)))
  {
    WrStrErrorPos(ErrNum_InvAddrMode, pArg);
    CodeLen = 0;
  }
}

static Boolean IsIReg3(const tStrComp *pArg)
{
  tStrComp Arg;
  Byte RegNum;

  if (*pArg->str.p_str != '@')
    return False;
  StrCompRefRight(&Arg, pArg, 1);
  if (!DecodeReg(&Arg, &RegNum, True))
    return False;
  if (RegNum != 3)
  {
    WrStrErrorPos(ErrNum_InvAddrMode, pArg);
    return False;
  }
  return True;
}

/****************************************************************************/

static void DecodeADD_ADDC(Word Code)
{
  if (!ChkArgCnt(2, 2));
  else if (as_strcasecmp(ArgStr[1].str.p_str, "A")) WrStrErrorPos(ErrNum_InvAddrMode, &ArgStr[1]);
  else
  {
    adr_vals_t adr_vals;
    switch (DecodeAdr(&ArgStr[2], &adr_vals, MModImm | MModReg | MModInd))
    {
      case ModImm:
        BAsmCode[CodeLen++] = Code + 0x03;
        append_adr_vals(&adr_vals);
        break;
      case ModReg:
        BAsmCode[CodeLen++] = Code + 0x68 + adr_vals.value;
        break;
      case ModInd:
        BAsmCode[CodeLen++] = Code + 0x60 + adr_vals.value;
        break;
      default:
        break;
    }
  }
}

static void DecodeANL_ORL_XRL(Word Code)
{
  Byte PortNum;
  Word PortMask = 0x06;

  if (pCurrCPUProps->Flags & eCPUFlagBUS)
    PortMask |= 0x100;
  if (pCurrCPUProps->Flags & eCPUFlag84xx)
    PortMask |= 0x01;

  if (!ChkArgCnt(2, 2));
  else if (!as_strcasecmp(ArgStr[1].str.p_str, "A"))
  {
    adr_vals_t adr_vals;
    switch (DecodeAdr(&ArgStr[2], &adr_vals, MModImm | MModReg | MModInd))
    {
      case ModImm:
        BAsmCode[CodeLen++] = Code + 0x43;
        append_adr_vals(&adr_vals);
        break;
      case ModReg:
        BAsmCode[CodeLen++] = Code + 0x48 + adr_vals.value;
        break;
      case ModInd:
        BAsmCode[CodeLen++] = Code + 0x40 + adr_vals.value;
        break;
      default:
        break;
    }
  }
  else if (IsPort(ArgStr[1].str.p_str, PortMask, &PortNum))
  {
    if (Code == 0x90) WrStrErrorPos(ErrNum_InvAddrMode, &ArgStr[1]); /* no XRL to ports */
    else if (AChkCPUFlags(eCPUFlagLogToPort, &ArgStr[1]))
    {
      adr_vals_t adr_vals;
      if (DecodeAdr(&ArgStr[2], &adr_vals, MModImm) == ModImm)
      {
        BAsmCode[CodeLen++] = Code + 0x88 + (PortNum & 3);
        append_adr_vals(&adr_vals);
        if (PortNum)
          ChkPx(PortNum, &ArgStr[1]);
      }
    }
  }
  else
    WrStrErrorPos(ErrNum_InvAddrMode, &ArgStr[1]);
}

static void DecodeCALL_JMP(Word Code)
{
  if (!ChkArgCnt(1, 1));
  else if ((EProgCounter() & 0x7fe) == 0x7fe) WrError(ErrNum_NotOnThisAddress);
  else
  {
    tEvalResult EvalResult;
    Word AdrWord = EvalStrIntExpressionWithResult(&ArgStr[1], UInt13, &EvalResult);

    if (EvalResult.OK)
    {
      if (AdrWord > SegLimits[SegCode]) WrStrErrorPos(ErrNum_OverRange, &ArgStr[1]);
      else
      {
        Word DestBank = (AdrWord >> 11) & 3,
             CurrBank = (EProgCounter() >> 11) & 3;

        if (Reg_MB == MB_NOTHING)
        {
          if (CurrBank != DestBank)
          {
            BAsmCode[0] = SelOrders[DestBank].Code;
            CodeLen = 1;
          }
        }
        else if ((DestBank != Reg_MB) && !mFirstPassUnknownOrQuestionable(EvalResult.Flags))
          WrStrErrorPos(ErrNum_InAccPage, &ArgStr[1]);
        set_b_guessed(EvalResult.Flags, CodeLen + 1, 1, 0xff);
        BAsmCode[CodeLen + 1] = AdrWord & 0xff;
        set_b_guessed(EvalResult.Flags, CodeLen, 1, 0xe0);
        BAsmCode[CodeLen] = Code + ((AdrWord & 0x700) >> 3);
        CodeLen += 2;
        ChkSpace(SegCode, EvalResult.AddrSpaceMask);
      }
    }
  }
}

static void DecodeCLR_CPL(Word Code)
{
  if (!ChkArgCnt(1, 1));
  else
  {
    int z = 0;
    Boolean OK = False;

    NLS_UpString(ArgStr[1].str.p_str);
    do
    {
      if (!strcmp(clr_cpl_args[z].p_val, ArgStr[1].str.p_str))
      {
        if ((*ArgStr[1].str.p_str == 'F') && !(pCurrCPUProps->Flags & eCPUFlagUserFlags)) WrStrErrorPos(ErrNum_InvAddrMode, &ArgStr[1]);
        else
        {
          CodeLen = 1;
          BAsmCode[0] = clr_cpl_args[z].code;
          OK = True;
        }
      }
      z++;
    }
    while ((clr_cpl_args[z].p_val) && (CodeLen != 1));
    if (!OK)
      WrStrErrorPos(ErrNum_InvAddrMode, &ArgStr[1]);
    else
      BAsmCode[0] += Code;
  }
}

static void DecodeAcc(Word Code)
{
  if (!ChkArgCnt(1, 1));
  else if (as_strcasecmp(ArgStr[1].str.p_str, "A")) WrStrErrorPos(ErrNum_InvAddrMode, &ArgStr[1]);
  else
  {
    CodeLen = 1;
    BAsmCode[0] = Code;
  }
}

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

  if (!ChkArgCnt(1, 1));
  else
  {
    adr_vals_t adr_vals;
    switch (DecodeAdr(&ArgStr[1], &adr_vals, MModAcc | MModReg | MModInd))
    {
      case ModAcc:
        BAsmCode[CodeLen++] = 0x07;
        break;
      case ModReg:
        if (AChkCPUFlags(eCPUFlagDEC_REG, &ArgStr[1]))
          BAsmCode[CodeLen++] = 0xc8 + adr_vals.value;
        break;
      case ModInd:
        if (AChkCPUFlags(eCPUFlagDEC_DJNZ_IREG, &ArgStr[1]))
          BAsmCode[CodeLen++] = 0xc0 | adr_vals.value;
        break;
      default:
        break;
    }
  }
}

static void DecodeDIS_EN(Word Code)
{
  if (ChkArgCnt(1, 1))
  {
    NLS_UpString(ArgStr[1].str.p_str);
    if (!strcmp(ArgStr[1].str.p_str, "I"))
    {
      if (AChkCPUFlags(eCPUFlagINTLogic, &ArgStr[1]))
      {
        CodeLen = 1;
        BAsmCode[0] = Code + 0x05;
      }
    }
    else if (!strcmp(ArgStr[1].str.p_str, "TCNTI"))
    {
      if (AChkCPUFlags(eCPUFlagINTLogic, &ArgStr[1]))
      {
        CodeLen = 1;
        BAsmCode[0] = Code + 0x25;
      }
    }
    else if (!strcmp(ArgStr[1].str.p_str, "SI"))
    {
      if (AChkCPUFlags(eCPUFlagSerial, &ArgStr[1]))
      {
        CodeLen = 1;
        BAsmCode[0] = Code + 0x85;
      }
    }
    else if (!Code && !strcmp(ArgStr[1].str.p_str, "DMA"))
    {
      if (AChkCPUFlags(eCPUFlagUPIPort, &ArgStr[1]))
      {
        BAsmCode[0] = 0xe5;
        CodeLen = 1;
      }
    }
    else if (!Code && !strcmp(ArgStr[1].str.p_str, "FLAGS"))
    {
      if (AChkCPUFlags(eCPUFlagUPIPort, &ArgStr[1]))
      {
        BAsmCode[0] = 0xf5;
        CodeLen = 1;
      }
    }
    else
      WrStrErrorPos(ErrNum_InvAddrMode, &ArgStr[1]);
  }
}

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

  if (!ChkArgCnt(2, 2));
  else
  {
    adr_vals_t adr_vals;
    switch (DecodeAdr(&ArgStr[1], &adr_vals, MModReg | MModInd))
    {
      case ModReg:
        BAsmCode[CodeLen++] = 0xe8 + adr_vals.value;
        break;
      case ModInd:
        if (AChkCPUFlags(eCPUFlagDEC_DJNZ_IREG, &ArgStr[1]))
          BAsmCode[CodeLen++] = 0xe0 + adr_vals.value;
        break;
      default:
        break;
    }
    if (CodeLen > 0)
    {
      Boolean OK;
      Word AdrWord;
      tSymbolFlags Flags;

      AdrWord = EvalStrIntExpressionWithFlags(&ArgStr[2], Int16, &OK, &Flags);
      if (OK)
      {
        if (ChkSamePage(EProgCounter() + CodeLen, AdrWord, 8, Flags))
        {
          set_b_guessed(Flags, CodeLen, 1, 0xff);
          BAsmCode[CodeLen++] = AdrWord & 0xff;
        }
      }
      else
        CodeLen = 0;
    }
  }
}

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

  if (ChkArgCnt(1, 1) && ChkCPUFlags(eCPUFlagT0CLK))
  {
    if (as_strcasecmp(ArgStr[1].str.p_str, "CLK")) WrStrErrorPos(ErrNum_InvAddrMode, &ArgStr[1]);
    else
    {
      CodeLen = 1;
      BAsmCode[0] = 0x75;
    }
  }
}

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

  if (!ChkArgCnt(1, 1));
  else
  {
    adr_vals_t adr_vals;
    switch (DecodeAdr(&ArgStr[1], &adr_vals, MModAcc | MModReg | MModInd))
    {
      case ModAcc:
        BAsmCode[CodeLen++] = 0x17;
        break;
      case ModReg:
        BAsmCode[CodeLen++] = 0x18 + adr_vals.value;
        break;
      case ModInd:
        BAsmCode[CodeLen++] = 0x10 + adr_vals.value;
        break;
      default:
        break;
    }
  }
}

static void DecodeIN(Word Code)
{
  Byte PortNum;

  UNUSED(Code);

  if (!ChkArgCnt(2, 2));
  else if (as_strcasecmp(ArgStr[1].str.p_str, "A")) WrStrErrorPos(ErrNum_InvAddrMode, &ArgStr[1]);
  else if (!as_strcasecmp(ArgStr[2].str.p_str, "DBB"))
  {
    if (AChkCPUFlags(eCPUFlagUPIPort, &ArgStr[2]))
    {
      CodeLen = 1;
      BAsmCode[0] = 0x22;
    }
  }
  else if (!IsPort(ArgStr[2].str.p_str, 0x07, &PortNum)) WrStrErrorPos(ErrNum_InvAddrMode, &ArgStr[2]);
  else
  {
    CodeLen = 1;
    BAsmCode[0] = 0x08 + PortNum;
    ChkPx(PortNum, &ArgStr[2]);
  }
}

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

  if (!ChkArgCnt(2, 2));
  else if (as_strcasecmp(ArgStr[1].str.p_str, "A")) WrStrErrorPos(ErrNum_InvAddrMode, &ArgStr[1]);
  else if (as_strcasecmp(ArgStr[2].str.p_str, "BUS")) WrStrErrorPos(ErrNum_InvAddrMode, &ArgStr[2]);
  else if (AChkCPUFlags(eCPUFlagBUS, &ArgStr[2]))
  {
    CodeLen = 1;
    BAsmCode[0] = 0x08;
  }
}

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

  if (!ChkArgCnt(1, 1));
  else if (as_strcasecmp(ArgStr[1].str.p_str, "@A")) WrStrErrorPos(ErrNum_InvAddrMode, &ArgStr[1]);
  else
  {
    CodeLen = 1;
    BAsmCode[0] = 0xb3;
  }
}

static void DecodeCond(Word Code)
{
  if (ChkArgCnt(1, 1))
  {
    tEvalResult EvalResult;
    Word AdrWord;

    AdrWord = EvalStrIntExpressionWithResult(&ArgStr[1], UInt12, &EvalResult);
    if (EvalResult.OK && ChkSamePage(EProgCounter() + 1, AdrWord, 8, EvalResult.Flags))
    {
      ChkSpace(SegCode, EvalResult.AddrSpaceMask);
      BAsmCode[CodeLen++] = Code;
      set_b_guessed(EvalResult.Flags, CodeLen, 1, 0xff);
      BAsmCode[CodeLen++] = AdrWord & 0xff;
    }
  }
}

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

  if (ChkArgCnt(2, 2) && ChkCPUFlags(eCPUFlagCondBitJmp))
  {
    Boolean OK;
    tSymbolFlags bit_flags;
    Byte bit_pos;

    bit_pos = EvalStrIntExpressionWithFlags(&ArgStr[1], UInt3, &OK, &bit_flags);
    if (OK)
    {
      Word AdrWord;
      tSymbolFlags addr_flags;

      AdrWord = EvalStrIntExpressionWithFlags(&ArgStr[2], UInt12, &OK, &addr_flags);
      if (OK && ChkSamePage(EProgCounter() + 1, AdrWord, 8, addr_flags))
      {
        set_b_guessed(bit_flags, CodeLen, 1, 0xe0);
        BAsmCode[CodeLen++] = 0x12 + (bit_pos << 5);
        set_b_guessed(addr_flags, CodeLen, 1, 0xff);
        BAsmCode[CodeLen++] = AdrWord & 0xff;
      }
    }
  }
}

static void DecodeMOV(Word Code)
{
  Byte PortNum;

  UNUSED(Code);

  if (!ChkArgCnt(2, 2));
  else if (!as_strcasecmp(ArgStr[1].str.p_str, "A"))
  {
    if (!as_strcasecmp(ArgStr[2].str.p_str, "T"))
    {
      CodeLen = 1;
      BAsmCode[0] = 0x42;
    }
    else if (IsPort(ArgStr[2].str.p_str, 0x06, &PortNum))
    {
      if (AChkCPUFlags(eCPUFlagOKI, &ArgStr[2]))
      {
        CodeLen = 1;
        BAsmCode[0] = 0x53 + (PortNum << 4);
      }
    }
    else if (!as_strcasecmp(ArgStr[2].str.p_str, "PSW"))
    {
      if (AChkCPUFlags(eCPUFlagTransferA_PSW, &ArgStr[2]))
      {
        CodeLen = 1;
        BAsmCode[0] = 0xc7;
      }
    }
    else if (IsSerialPort(ArgStr[2].str.p_str, 0x03, &PortNum))
    {
      if (AChkCPUFlags(eCPUFlagSerial, &ArgStr[2]))
      {
        CodeLen = 1;
        BAsmCode[0] = 0x0c + PortNum;
      }
    }
    else
    {
      adr_vals_t adr_vals;
      switch (DecodeAdr(&ArgStr[2], &adr_vals, MModReg | MModInd | MModImm))
      {
        case ModReg:
          BAsmCode[CodeLen++] = 0xf8 + adr_vals.value;
          break;
        case ModInd:
          BAsmCode[CodeLen++] = 0xf0 + adr_vals.value;
          break;
        case ModImm:
          BAsmCode[CodeLen++] = 0x23;
          append_adr_vals(&adr_vals);
          break;
        default:
          break;
      }
    }
  }
  else if (IsPort(ArgStr[1].str.p_str, 0x02, &PortNum))
  {
    if (IsIReg3(&ArgStr[2]))
    {
      if (AChkCPUFlags(eCPUFlagOKI, &ArgStr[1]))
      {
        CodeLen = 1;
        BAsmCode[0] = 0xe3 | (PortNum << 4);
      }
    }
  }
  else if (IsSerialPort(ArgStr[1].str.p_str, 0x07, &PortNum))
  {
    if (AChkCPUFlags(eCPUFlagSerial, &ArgStr[1]))
    {
      adr_vals_t adr_vals;
      switch (DecodeAdr(&ArgStr[2], &adr_vals, MModAcc | MModImm))
      {
        case ModAcc:
          BAsmCode[CodeLen++] = 0x3c + PortNum;
          break;
        case ModImm:
          BAsmCode[CodeLen++] = 0x9c + PortNum;
          append_adr_vals(&adr_vals);
          break;
        default:
          break;
      }
    }
  }
  else if (!as_strcasecmp(ArgStr[2].str.p_str, "A"))
  {
    if (!as_strcasecmp(ArgStr[1].str.p_str, "STS"))
    {
      if (AChkCPUFlags(eCPUFlagUPIPort, &ArgStr[1]))
      {
        CodeLen = 1;
        BAsmCode[0] = 0x90;
      }
    }
    else if (!as_strcasecmp(ArgStr[1].str.p_str, "T"))
    {
      CodeLen = 1;
      BAsmCode[0] = 0x62;
    }
    else if (!as_strcasecmp(ArgStr[1].str.p_str, "PSW"))
    {
      if (AChkCPUFlags(eCPUFlagTransferA_PSW, &ArgStr[1]))
      {
        CodeLen = 1;
        BAsmCode[0] = 0xd7;
      }
    }
    else
    {
      adr_vals_t adr_vals;
      switch (DecodeAdr(&ArgStr[1], &adr_vals, MModReg | MModInd))
      {
        case ModReg:
          BAsmCode[CodeLen++] = 0xa8 + adr_vals.value;
          break;
        case ModInd:
          BAsmCode[CodeLen++] = 0xa0 + adr_vals.value;
          break;
        default:
          break;
      }
    }
  }
  else if (*ArgStr[2].str.p_str == '#')
  {
    Boolean OK;
    tSymbolFlags flags;
    Word AdrWord = EvalStrIntExpressionOffsWithFlags(&ArgStr[2], 1, Int8, &OK, &flags);
    if (OK)
    {
      adr_vals_t adr_vals;
      switch (DecodeAdr(&ArgStr[1], &adr_vals, MModReg | MModInd))
      {
        case ModReg:
          BAsmCode[CodeLen++] = 0xb8 + adr_vals.value;
          set_b_guessed(flags, CodeLen, 1, 0xff);
          BAsmCode[CodeLen++] = AdrWord;
          break;
        case ModInd:
          BAsmCode[CodeLen++] = 0xb0 + adr_vals.value;
          set_b_guessed(flags, CodeLen, 1, 0xff);
          BAsmCode[CodeLen++] = AdrWord;
          break;
        default:
          break;
      }
    }
  }
  else
    WrStrErrorPos(ErrNum_InvAddrMode, &ArgStr[1]);
}

static void DecodeANLD_ORLD_MOVD(Word Code)
{
  Byte PortNum;

  if (ChkArgCnt(2, 2) && ChkCPUFlags(eCPUFlagIOExpander))
  {
    const tStrComp *pArg1 = &ArgStr[1],
                   *pArg2 = &ArgStr[2];

    if ((Code == 0x3c) && (!as_strcasecmp(ArgStr[1].str.p_str, "A"))) /* MOVD */
    {
      pArg1 = &ArgStr[2];
      pArg2 = &ArgStr[1];
      Code = 0x0c;
    }
    if (as_strcasecmp(pArg2->str.p_str, "A")) WrStrErrorPos(ErrNum_InvAddrMode, pArg2);
    else if (!IsPort(pArg1->str.p_str, 0xf0, &PortNum)) WrStrErrorPos(ErrNum_InvAddrMode, pArg1);
    else
    {
      PortNum -= 4;

      if ((PortNum == 3) && (pCurrCPUProps->Flags & eCPUFlagSiemens)) WrStrErrorPos(ErrNum_InvAddrMode, pArg2);
      else
      {
        CodeLen = 1;
        BAsmCode[0] = Code + PortNum;
      }
    }
  }
}

static void DecodeMOVP_MOVP3(Word Code)
{
  if (!ChkArgCnt(2, 2));
  else if ((Code == 0xe3) && !ChkCPUFlags(eCPUFlagMOVP3));
  else if (as_strcasecmp(ArgStr[1].str.p_str, "A")) WrStrErrorPos(ErrNum_InvAddrMode, &ArgStr[1]);
  else if (as_strcasecmp(ArgStr[2].str.p_str, "@A")) WrStrErrorPos(ErrNum_InvAddrMode, &ArgStr[2]);
  else
  {
    CodeLen = 1;
    BAsmCode[0] = Code;
  }
}

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

  if (!ChkArgCnt(2, 2));
  else if (!ChkCPUFlags(eCPUFlagOKI));
  else if (as_strcasecmp(ArgStr[1].str.p_str, "P")) WrStrErrorPos(ErrNum_InvAddrMode, &ArgStr[1]);
  else if (IsIReg3(&ArgStr[2]))
  {
    CodeLen = 1;
    BAsmCode[0] = 0xc3;
  }
}

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

  if (ChkArgCnt(2, 2)
   && ChkCPUFlags(eCPUFlagXMem))
  {
    const tStrComp *pArg1 = &ArgStr[1], *pArg2 = &ArgStr[2];
    Byte Code = 0x80;

    if (!as_strcasecmp(pArg2->str.p_str, "A"))
    {
      pArg2 = &ArgStr[1];
      pArg1 = &ArgStr[2];
      Code += 0x10;
    }
    if (as_strcasecmp(pArg1->str.p_str, "A")) WrStrErrorPos(ErrNum_InvAddrMode, pArg1);
    else
    {
      adr_vals_t adr_vals;
      if (DecodeAdr(pArg2, &adr_vals, MModInd) == ModInd)
        BAsmCode[CodeLen++] = Code + adr_vals.value;
    }
  }
}

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

  if (!ChkArgCnt(0, 0));
  else
  {
    CodeLen = 1;
    BAsmCode[0] = 0x00;
  }
}

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

  if (!ChkArgCnt(2, 2));
  else if (as_strcasecmp(ArgStr[1].str.p_str, "DBB")) WrStrErrorPos(ErrNum_InvAddrMode, &ArgStr[1]);
  else if (as_strcasecmp(ArgStr[2].str.p_str, "A")) WrStrErrorPos(ErrNum_InvAddrMode, &ArgStr[2]);
  else if (AChkCPUFlags(eCPUFlagUPIPort, &ArgStr[1]))
  {
    BAsmCode[0] = 0x02;
    CodeLen = 1;
  }
}

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

  NLS_UpString(ArgStr[1].str.p_str);
  if (!ChkArgCnt(2, 2));
  else
  {
    Word PortMask = 0x07;
    Byte PortNum;

    if (pCurrCPUProps->Flags & eCPUFlagBUS)
      PortMask |= 0x100;
    if (as_strcasecmp(ArgStr[2].str.p_str, "A")) WrStrErrorPos(ErrNum_InvAddrMode, &ArgStr[2]);
    else if (!IsPort(ArgStr[1].str.p_str, PortMask, &PortNum)) WrStrErrorPos(ErrNum_InvAddrMode, &ArgStr[1]);
    else if (PortNum == 8)
    {
      CodeLen = 1;
      BAsmCode[0] = 0x02;
    }
    else
    {
      CodeLen = 1;
      BAsmCode[0] = PortNum ? (0x38 + PortNum) : 0x90;
      ChkPx(PortNum, &ArgStr[1]);
    }
  }
}

static void DecodeRET_RETR(Word Code)
{
  if (ChkArgCnt(0, 0))
  {
    /* RETR not present if no interrupts at all (8021), or replaced by RETI (8022) */
    if ((Code == 0x93) && (!(pCurrCPUProps->Flags & eCPUFlagINTLogic) || (pCurrCPUProps->Flags & eCPUFlagADConv))) WrStrErrorPos(ErrNum_InstructionNotSupported, &OpPart);
    else
    {
      CodeLen = 1;
      BAsmCode[0] = Code;
    }
  }
}

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

  if (ChkArgCnt(1, 1))
  {
    Boolean OK = False;
    int z;

    NLS_UpString(ArgStr[1].str.p_str);
    for (z = 0; SelOrders[z].Name; z++)
      if (!strcmp(ArgStr[1].str.p_str, SelOrders[z].Name))
      {
        /* SEL MBx not allowed if program memory cannot be larger than 2K.
           Similar is true for the Philips-specific MB2/MB3 arguments if
           less than 6K/8K ROM is present: */

        if (!strncmp(SelOrders[z].Name, "MB", 2) && (pCurrCPUProps->CodeSize <= 0x7ff));
        else if (!strcmp(SelOrders[z].Name, "MB2") && (pCurrCPUProps->CodeSize <= 0xfff));
        else if (!strcmp(SelOrders[z].Name, "MB32") && (pCurrCPUProps->CodeSize <= 0x17ff));

        else if (!strncmp(SelOrders[z].Name, "RB", 2) && !(pCurrCPUProps->Flags & eCPUFlagRegBanks));

        else if (!strncmp(SelOrders[z].Name, "AN", 2) && !(pCurrCPUProps->Flags & eCPUFlagADConv));

        else
        {
          CodeLen = 1;
          BAsmCode[0] = SelOrders[z].Code;
          OK = True;
        }
      }
    if (!OK)
      WrStrErrorPos(ErrNum_InvAddrMode, &ArgStr[1]);
  }
}

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

  if (!ChkArgCnt(1, 1));
  else if (as_strcasecmp(ArgStr[1].str.p_str, "TCNT")) WrStrErrorPos(ErrNum_InvAddrMode, &ArgStr[1]);
  else
  {
    CodeLen = 1;
    BAsmCode[0] = 0x65;
  }
}

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

  if (!ChkArgCnt(1, 1));
  else
  {
    NLS_UpString(ArgStr[1].str.p_str);
    if (!strcmp(ArgStr[1].str.p_str, "CNT"))
    {
      CodeLen = 1;
      BAsmCode[0] = 0x45;
    }
    else if (!strcmp(ArgStr[1].str.p_str, "T"))
    {
      CodeLen = 1;
      BAsmCode[0] = 0x55;
    }
    else
      WrStrErrorPos(ErrNum_InvAddrMode, &ArgStr[1]);
  }
}

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

  if (!ChkArgCnt(2, 2));
  else
  {
    const tStrComp *pArg1 = &ArgStr[1], *pArg2 = &ArgStr[2];

    if (!as_strcasecmp(pArg2->str.p_str, "A"))
    {
      pArg2 = &ArgStr[1];
      pArg1 = &ArgStr[2];
    }
    if (as_strcasecmp(pArg1->str.p_str, "A")) WrStrErrorPos(ErrNum_InvAddrMode, pArg1);
    else
    {
      adr_vals_t adr_vals;
      switch (DecodeAdr(pArg2, &adr_vals, MModReg | MModInd))
      {
        case ModReg:
          BAsmCode[CodeLen++] = 0x28 + adr_vals.value;
          break;
        case ModInd:
          BAsmCode[CodeLen++] = 0x20 + adr_vals.value;
          break;
        default:
          break;
      }
    }
  }
}

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

  if (!ChkArgCnt(2, 2));
  else
  {
    const tStrComp *pArg1 = &ArgStr[1], *pArg2 = &ArgStr[2];

    if (!as_strcasecmp(pArg2->str.p_str, "A"))
    {
      pArg2 = &ArgStr[1];
      pArg1 = &ArgStr[2];
    }
    if (as_strcasecmp(pArg1->str.p_str, "A")) WrStrErrorPos(ErrNum_InvAddrMode, pArg1);
    else
    {
      adr_vals_t adr_vals;
      if (DecodeAdr(pArg2, &adr_vals, MModInd) == ModInd)
        BAsmCode[CodeLen++] = 0x30 + adr_vals.value;
    }
  }
}

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

  if (ChkArgCnt(0, 0) && ChkCPUFlags(eCPUFlagADConv))
  {
    CodeLen = 1;
    BAsmCode[0] = 0x80;
  }
}

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

  if (ChkArgCnt(0, 0) && ChkCPUFlags(eCPUFlagADConv)) /* check for 8022 */
  {
    CodeLen = 1;
    BAsmCode[0] = 0x93;
  }
}

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

  if (ChkArgCnt(0, 0)
   && ChkCPUFlags(eCPUFlagCMOS))
  {
    CodeLen = 1;
    BAsmCode[0] = (pCurrCPUProps->Flags & eCPUFlagSiemens) ? 0xf3 : 0x01;
  }
}

static void DecodeOKIFixed(Word Code)
{
  if (ChkArgCnt(0, 0) && ChkCPUFlags(eCPUFlagOKI))
  {
    CodeLen = 1;
    BAsmCode[0] = Code;
  }
}

/****************************************************************************/

static void AddAcc(const char *Name, Byte Code)
{
  AddInstTable(InstTable, Name, Code, DecodeAcc);
}

static void AddCond(const char *Name, Byte Code)
{
  AddInstTable(InstTable, Name, Code, DecodeCond);
}

static void AddSel(const char *Name, Byte Code)
{
  order_array_rsv_end(SelOrders, SelOrder);
  SelOrders[InstrZ].Name = Name;
  SelOrders[InstrZ].Code = Code;
  InstrZ++;
}

static void add_clr_cpl(const char *p_name, Byte code)
{
  order_array_rsv_end(clr_cpl_args, clr_cpl_t);
  clr_cpl_args[InstrZ].p_val = p_name;
  clr_cpl_args[InstrZ].code = code;
  InstrZ++;
}

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

  add_null_pseudo(InstTable);

  AddInstTable(InstTable, "ADD", 0x00, DecodeADD_ADDC);
  AddInstTable(InstTable, "ADDC", 0x10, DecodeADD_ADDC);
  AddInstTable(InstTable, "ORL", 0x00, DecodeANL_ORL_XRL);
  AddInstTable(InstTable, "ANL", 0x10, DecodeANL_ORL_XRL);
  AddInstTable(InstTable, "XRL", 0x90, DecodeANL_ORL_XRL);
  AddInstTable(InstTable, "CALL", 0x14, DecodeCALL_JMP);
  AddInstTable(InstTable, "JMP", 0x04, DecodeCALL_JMP);
  AddInstTable(InstTable, "CLR", 0x00, DecodeCLR_CPL);
  AddInstTable(InstTable, "CPL", 0x10, DecodeCLR_CPL);
  AddInstTable(InstTable, "DEC", 0, DecodeDEC);
  AddInstTable(InstTable, "DIS", 0x10, DecodeDIS_EN);
  AddInstTable(InstTable, "EN", 0x00, DecodeDIS_EN);
  AddInstTable(InstTable, "DJNZ", 0x00, DecodeDJNZ);
  AddInstTable(InstTable, "ENT0", 0x00, DecodeENT0);
  AddInstTable(InstTable, "INC", 0x00, DecodeINC);
  AddInstTable(InstTable, "IN", 0x00, DecodeIN);
  AddInstTable(InstTable, "INS", 0x00, DecodeINS);
  AddInstTable(InstTable, "JMPP", 0x00, DecodeJMPP);
  AddInstTable(InstTable, "JB", 0x00, DecodeJB);
  AddInstTable(InstTable, "MOV", 0x00, DecodeMOV);
  AddInstTable(InstTable, "ANLD", 0x9c, DecodeANLD_ORLD_MOVD);
  AddInstTable(InstTable, "ORLD", 0x8c, DecodeANLD_ORLD_MOVD);
  AddInstTable(InstTable, "MOVD", 0x3c, DecodeANLD_ORLD_MOVD);
  AddInstTable(InstTable, "MOVP", 0xa3, DecodeMOVP_MOVP3);
  AddInstTable(InstTable, "MOVP3", 0xe3, DecodeMOVP_MOVP3);
  AddInstTable(InstTable, "MOVP1", 0x00, DecodeMOVP1);
  AddInstTable(InstTable, "MOVX", 0x00, DecodeMOVX);
  AddInstTable(InstTable, "NOP", 0x00, DecodeNOP);
  AddInstTable(InstTable, "OUT", 0x00, DecodeOUT);
  AddInstTable(InstTable, "OUTL", 0x00, DecodeOUTL);
  AddInstTable(InstTable, "RET", 0x83, DecodeRET_RETR);
  AddInstTable(InstTable, "RETR", 0x93, DecodeRET_RETR);
  AddInstTable(InstTable, "SEL", 0x00, DecodeSEL);
  AddInstTable(InstTable, "STOP", 0x00, DecodeSTOP);
  AddInstTable(InstTable, "STRT", 0x00, DecodeSTRT);
  AddInstTable(InstTable, "XCH", 0x00, DecodeXCH);
  AddInstTable(InstTable, "XCHD", 0x00, DecodeXCHD);
  AddInstTable(InstTable, "RAD", 0x00, DecodeRAD);
  AddInstTable(InstTable, "RETI", 0x00, DecodeRETI);
  AddInstTable(InstTable, "IDL", 0x00, DecodeIDL_HALT);
  AddInstTable(InstTable, "HALT", 0x00, DecodeIDL_HALT);
  AddInstTable(InstTable, "HLTS", 0x82, DecodeOKIFixed);
  AddInstTable(InstTable, "FLT", 0xa2, DecodeOKIFixed);
  AddInstTable(InstTable, "FLTT", 0xc2, DecodeOKIFixed);
  AddInstTable(InstTable, "FRES", 0xe2, DecodeOKIFixed);

  InstrZ = 0;
  add_clr_cpl("A", 0x27);
  add_clr_cpl("C", 0x97);
  add_clr_cpl("F0", 0x85);
  add_clr_cpl("F1", 0xa5);

  AddCond("JTF"  , 0x16);
  AddCond("JC"   , 0xf6);
  AddCond("JNC"  , 0xe6);
  AddCond("JZ"   , 0xc6);
  AddCond("JNZ"  , 0x96);
  if (pCurrCPUProps->Flags & eCPUFlagT0)
  {
    AddCond("JT0"  , 0x36);
    AddCond("JNT0" , 0x26);
  }
  AddCond("JT1"  , 0x56);
  AddCond("JNT1" , 0x46);
  if (pCurrCPUProps->Flags & eCPUFlagUserFlags)
  {
    AddCond("JF0"  , 0xb6);
    AddCond("JF1"  , 0x76);
  }
  if (pCurrCPUProps->Flags & eCPUFlagUPIPort)
  {
    AddCond("JNIBF", 0xd6);
    AddCond("JOBF" , 0x86);
  }
  else
    AddCond("JNI"  , (pCurrCPUProps->Flags & eCPUFlagSiemens) ? 0x66 : 0x86);
  if (pCurrCPUProps->Flags & eCPUFlagCondBitJmp)
  {
    AddCond("JB0"  , 0x12);
    AddCond("JB1"  , 0x32);
    AddCond("JB2"  , 0x52);
    AddCond("JB3"  , 0x72);
    AddCond("JB4"  , 0x92);
    AddCond("JB5"  , 0xb2);
    AddCond("JB6"  , 0xd2);
    AddCond("JB7"  , 0xf2);
  }
  if (pCurrCPUProps->Flags & eCPUFlag84xx)
    AddCond("JNTF", 0x06);

  AddAcc("DA"  , 0x57);
  AddAcc("RL"  , 0xe7);
  AddAcc("RLC" , 0xf7);
  AddAcc("RR"  , 0x77);
  AddAcc("RRC" , 0x67);
  AddAcc("SWAP", 0x47);

  /* Leave MBx first, used by CALL/JMP! */

  InstrZ = 0;
  AddSel("MB0" , 0xe5);
  AddSel("MB1" , 0xf5);
  AddSel("MB2" , 0xa5);
  AddSel("MB3" , 0xb5);
  AddSel("RB0" , 0xc5);
  AddSel("RB1" , 0xd5);
  AddSel("AN0" , 0x95);
  AddSel("AN1" , 0x85);
  AddSel(NULL  , 0);

  AddInstTable(InstTable, "REG", 0, CodeREG);
  AddIntelPseudo(InstTable, eIntPseudoFlag_LittleEndian);
}

static void DeinitFields(void)
{
  DestroyInstTable(InstTable);
  order_array_free(clr_cpl_args);
  order_array_free(SelOrders);
}

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

static Boolean IsDef_48(void)
{
  return Memo("REG");
}

/*!------------------------------------------------------------------------
 * \fn     InternSymbol_48(char *pArg, TempResult *pResult)
 * \brief  handle built-in symbols on MCS-48
 * \param  pArg source argument
 * \param  pResult result buffer
 * ------------------------------------------------------------------------ */

static void InternSymbol_48(char *pArg, TempResult *pResult)
{
  tRegInt Erg;
  tSymbolSize Size;

  if (DecodeRegCore(pArg, &Erg, &Size))
  {
    pResult->Typ = TempReg;
    pResult->DataSize = Size;
    pResult->Contents.RegDescr.Reg = Erg;
    pResult->Contents.RegDescr.Dissect = DissectReg_48;
    pResult->Contents.RegDescr.compare = NULL;
  }
}

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

static void InitCode_48(void)
{
  Reg_MB = MB_NOTHING;
}

static void SwitchTo_48(void *pUser)
{
  static as_assume_rec_t ASSUME48s[] =
  {
    { "MB"   , &Reg_MB   , 0,  1, MB_NOTHING, NULL },
  };
  const TFamilyDescr *p_descr = FindFamilyByName("MCS-48");

  pCurrCPUProps = (const tCPUProps*)pUser;
  ASSUME48s[0].max_value = (pCurrCPUProps->CodeSize >> 11) & 3;

  TurnWords = False;
  SetIntConstMode(eIntConstModeIntel);

  PCSymbol = "$";
  HeaderID = p_descr->Id;
  NOPCode = 0x00;
  DivideChars = ",";
  HasAttrs = False;

  /* limit code segement size only vor variants known to have no
     external program memory */

  ValidSegs = (1 << SegCode) | (1 << SegIData);
  Grans[SegCode ] = 1; ListGrans[SegCode ] = 1; SegInits[SegCode ] = 0;
  SegLimits[SegCode] = pCurrCPUProps->CodeSize;
  Grans[SegIData] = 1; ListGrans[SegIData] = 1; SegInits[SegIData] = 0x20;
  SegLimits[SegIData] = 0xff;
  if (pCurrCPUProps->Flags & eCPUFlagXMem)
  {
    ValidSegs |= (1 << SegXData);
    Grans[SegXData] = 1; ListGrans[SegXData] = 1; SegInits[SegXData] = 0;
    SegLimits[SegXData] = 0xff;
  }

  MakeCode = MakeCode_48;
  IsDef = IsDef_48;
  InternSymbol = InternSymbol_48;
  DissectReg = DissectReg_48;
  SwitchFrom = SwitchFrom_48;
  InitFields();

  assume_set(ASSUME48s, as_array_size(ASSUME48s));
}

/* Limit code segment size only for variants known to have no
   external program memory: */

static tCPUProps CPUProps[] =
{
  { "8021"     , 0x3ff, eCPUFlagPort0 | eCPUFlagPort1 | eCPUFlagPort2 },
  { "8022"     , 0x7ff, eCPUFlagPort0 | eCPUFlagPort1 | eCPUFlagPort2 | eCPUFlagT0 | eCPUFlagBUS | eCPUFlagADConv | eCPUFlagINTLogic },
  { "8401"     , 0x1fff,eCPUFlagDEC_DJNZ_IREG | eCPUFlagPort0 | eCPUFlagPort1 | eCPUFlagPort2 | eCPUFlagT0 | eCPUFlagCondBitJmp | eCPUFlagTransferA_PSW | eCPUFlagRegBanks | eCPUFlagLogToPort | eCPUFlagDEC_REG | eCPUFlagINTLogic | eCPUFlagSerial | eCPUFlag84xx },
  { "8421"     , 0x7ff, eCPUFlagDEC_DJNZ_IREG | eCPUFlagPort0 | eCPUFlagPort1 | eCPUFlagPort2 | eCPUFlagT0 | eCPUFlagCondBitJmp | eCPUFlagTransferA_PSW | eCPUFlagRegBanks | eCPUFlagLogToPort | eCPUFlagDEC_REG | eCPUFlagINTLogic | eCPUFlagSerial | eCPUFlag84xx },
  { "8441"     , 0xfff, eCPUFlagDEC_DJNZ_IREG | eCPUFlagPort0 | eCPUFlagPort1 | eCPUFlagPort2 | eCPUFlagT0 | eCPUFlagCondBitJmp | eCPUFlagTransferA_PSW | eCPUFlagRegBanks | eCPUFlagLogToPort | eCPUFlagDEC_REG | eCPUFlagINTLogic | eCPUFlagSerial | eCPUFlag84xx },
  { "8461"     , 0x17ff,eCPUFlagDEC_DJNZ_IREG | eCPUFlagPort0 | eCPUFlagPort1 | eCPUFlagPort2 | eCPUFlagT0 | eCPUFlagCondBitJmp | eCPUFlagTransferA_PSW | eCPUFlagRegBanks | eCPUFlagLogToPort | eCPUFlagDEC_REG | eCPUFlagINTLogic | eCPUFlagSerial | eCPUFlag84xx },
  { "8039"     , 0xfff, eCPUFlagXMem | eCPUFlagPort1 | eCPUFlagPort2 | eCPUFlagIOExpander | eCPUFlagUserFlags | eCPUFlagT0 | eCPUFlagT0CLK | eCPUFlagCondBitJmp | eCPUFlagTransferA_PSW | eCPUFlagRegBanks | eCPUFlagLogToPort | eCPUFlagDEC_REG | eCPUFlagMOVP3 | eCPUFlagINTLogic },
  { "8048"     , 0xfff, eCPUFlagXMem | eCPUFlagPort1 | eCPUFlagPort2 | eCPUFlagIOExpander | eCPUFlagUserFlags | eCPUFlagT0 | eCPUFlagT0CLK | eCPUFlagCondBitJmp | eCPUFlagTransferA_PSW | eCPUFlagBUS | eCPUFlagRegBanks | eCPUFlagLogToPort | eCPUFlagDEC_REG | eCPUFlagMOVP3 | eCPUFlagINTLogic },
  { "80C39"    , 0xfff, eCPUFlagCMOS | eCPUFlagXMem | eCPUFlagPort1 | eCPUFlagPort2 | eCPUFlagIOExpander | eCPUFlagUserFlags | eCPUFlagT0 | eCPUFlagT0CLK | eCPUFlagCondBitJmp | eCPUFlagTransferA_PSW | eCPUFlagRegBanks | eCPUFlagLogToPort | eCPUFlagDEC_REG | eCPUFlagMOVP3 | eCPUFlagINTLogic },
  { "80C48"    , 0xfff, eCPUFlagCMOS | eCPUFlagXMem | eCPUFlagPort1 | eCPUFlagPort2 | eCPUFlagIOExpander | eCPUFlagUserFlags | eCPUFlagT0 | eCPUFlagT0CLK | eCPUFlagCondBitJmp | eCPUFlagTransferA_PSW | eCPUFlagBUS | eCPUFlagRegBanks | eCPUFlagLogToPort | eCPUFlagDEC_REG | eCPUFlagMOVP3 | eCPUFlagINTLogic },
  { "8041"     , 0x3ff, eCPUFlagUPIPort | eCPUFlagPort1 | eCPUFlagPort2 | eCPUFlagIOExpander | eCPUFlagUserFlags | eCPUFlagT0 | eCPUFlagCondBitJmp | eCPUFlagTransferA_PSW | eCPUFlagBUS | eCPUFlagRegBanks | eCPUFlagLogToPort | eCPUFlagDEC_REG | eCPUFlagMOVP3 | eCPUFlagINTLogic },
  { "8042"     , 0x7ff, eCPUFlagUPIPort | eCPUFlagPort1 | eCPUFlagPort2 | eCPUFlagIOExpander | eCPUFlagUserFlags | eCPUFlagT0 | eCPUFlagCondBitJmp | eCPUFlagTransferA_PSW | eCPUFlagBUS | eCPUFlagRegBanks | eCPUFlagLogToPort | eCPUFlagDEC_REG | eCPUFlagMOVP3 | eCPUFlagINTLogic },
  { "80C382"   , 0xfff, eCPUFlagCMOS | eCPUFlagSiemens | eCPUFlagDEC_DJNZ_IREG | eCPUFlagXMem | eCPUFlagPort1 | eCPUFlagIOExpander | eCPUFlagT0 | eCPUFlagCondBitJmp | eCPUFlagBUS | eCPUFlagRegBanks | eCPUFlagLogToPort | eCPUFlagDEC_REG | eCPUFlagMOVP3 | eCPUFlagINTLogic },
  { "MSM80C39" , 0xfff, eCPUFlagCMOS | eCPUFlagDEC_DJNZ_IREG | eCPUFlagXMem | eCPUFlagPort1 | eCPUFlagPort2 | eCPUFlagIOExpander | eCPUFlagUserFlags | eCPUFlagT0 | eCPUFlagT0CLK | eCPUFlagCondBitJmp | eCPUFlagTransferA_PSW | eCPUFlagRegBanks | eCPUFlagLogToPort | eCPUFlagDEC_REG | eCPUFlagMOVP3 | eCPUFlagINTLogic | eCPUFlagOKI },
  { "MSM80C48" , 0xfff, eCPUFlagCMOS | eCPUFlagDEC_DJNZ_IREG | eCPUFlagXMem | eCPUFlagPort1 | eCPUFlagPort2 | eCPUFlagIOExpander | eCPUFlagUserFlags | eCPUFlagT0 | eCPUFlagT0CLK | eCPUFlagCondBitJmp | eCPUFlagTransferA_PSW | eCPUFlagBUS | eCPUFlagRegBanks | eCPUFlagLogToPort | eCPUFlagDEC_REG | eCPUFlagMOVP3 | eCPUFlagINTLogic | eCPUFlagOKI },
  { "", 0, 0 }
};

void code48_init(void)
{
  tCPUProps *pProp;

  for (pProp = CPUProps; pProp->Name[0]; pProp++)
    (void)AddCPUUser(pProp->Name, SwitchTo_48, (void*)pProp, NULL);

  AddInitPassProc(InitCode_48);
}
