/*
 *      Interactive disassembler (IDA).
 *      Copyright (c) 1990-98 by Ilfak Guilfanov.
 *      ALL RIGHTS RESERVED.
 *                              E-mail: ig@estar.msk.su, ig@datarescue.com
 *                              FIDO:   2:5020/209
 *
 */

#include "i51.hpp"

//----------------------------------------------------------------------
inline uint32 get_next_24bits(insn_t &insn)
{
  uint32 high = insn.get_next_byte();
  uint32 low  = insn.get_next_word();
  return low | (high<<16);
}

//----------------------------------------------------------------------
static void operand1(insn_t &insn, int nibble)
{
  switch ( nibble )
  {
    case 4:
      insn.Op1.type = o_reg;
      insn.Op1.reg = rAcc;
      break;
    case 5:
      insn.Op1.type = o_mem;
      insn.Op1.addr = insn.get_next_byte();
      break;
    case 6:
      insn.Op1.type = o_phrase;
      insn.Op1.phrase = fR0;
      break;
    case 7:
      insn.Op1.type = o_phrase;
      insn.Op1.phrase = fR1;
      break;
    default:
      insn.Op1.type = o_reg;
      insn.Op1.phrase = uint16(rR0 + (nibble-8));
      break;
  }
}

//----------------------------------------------------------------------
static void operand2(insn_t &insn, ushort nibble)
{
  switch ( nibble )
  {
    case 4:
      insn.Op2.type = o_imm;
      insn.Op2.value = insn.get_next_byte();
      break;
    case 5:
      insn.Op2.type = o_mem;
      insn.Op2.addr = insn.get_next_byte();
      break;
    case 6:
      insn.Op2.type = o_phrase;
      insn.Op2.phrase = fR0;
      break;
    case 7:
      insn.Op2.type = o_phrase;
      insn.Op2.phrase = fR1;
      break;
    default:
      insn.Op2.type = o_reg;
      insn.Op2.phrase = rR0 + (nibble-8);
      break;
  }
}

//----------------------------------------------------------------------
inline void opAcc(op_t &op)
{
  op.type = o_reg;
  op.reg = rAcc;
}

//----------------------------------------------------------------------
inline void opC(op_t &op)
{
  op.type = o_reg;
  op.reg = rC;
}

//----------------------------------------------------------------------
// register direct
static int op_rd(op_t &x, uint16 reg, uchar dtyp)
{
  if ( reg >= rDR32 && reg <= rDR52 )
    return 0;
  x.type = o_reg;
  x.dtype = dtyp;
  x.reg  = reg;
  return 1;
}

//----------------------------------------------------------------------
// register indirect (fRi registers)
static int op_ph(op_t &x, int reg, uchar dtyp)
{
  if ( reg >= rDR32 && reg <= rDR52 )
    return 0;
  x.type = o_phrase;
  x.dtype = dtyp;
  x.reg = fRi;
  x.indreg = uchar(reg);
  return 1;
}

//----------------------------------------------------------------------
// register indirect
static int op_ph(op_t &op, i51_phrases phr, uchar dtyp, uval_t disp = 0)
{
  op.type = o_phrase;
  op.dtype = dtyp;
  op.phrase = phr;
  if ( disp > 0 )
  {
    op.imm_disp = 1;
    op.value = disp;
  }
  return 1;
}


//----------------------------------------------------------------------
// register indirect with displacement
static int op_ds(op_t &x, uint16 phrase, uval_t disp, uchar dtyp)
{
  if ( phrase >= rDR32 && phrase <= rDR52 )
    return 0;
  x.type = o_displ;
  x.dtype = dtyp;
  x.phrase = phrase;
  x.addr = disp;
  return 1;
}

//----------------------------------------------------------------------
inline void op_mm(op_t &x, uval_t addr, uchar dtyp)
{
  x.type = o_mem;
  x.dtype = dtyp;
  x.addr = addr;
}

//----------------------------------------------------------------------
inline void op_near(op_t &x, uval_t addr)
{
  x.type = o_near;
  x.dtype = dt_word;
  x.addr = addr;
}

//----------------------------------------------------------------------
inline void op_im(op_t &x, uval_t value, uchar dtyp)
{
  x.type = o_imm;
  x.dtype = dtyp;
  x.value = value;
}

//----------------------------------------------------------------------
uint32 i51_t::truncate(uval_t addr) const
{
  if ( ptype == prc_51 )
    return addr & 0xFFFF;
  else
    return addr & 0xFFFFFF;
}

//----------------------------------------------------------------------
static int make_short(insn_t &insn, uint16 itype, uchar b)
{
  insn.itype = itype;
  static const uchar bregs[] = { rR0, rWR0, 0, rDR0 };
  static const uchar dtyps[] = { dt_byte, dt_word, dt_dword };
  int idx = (b >> 2) & 3;
  if ( !op_rd(insn.Op1, bregs[idx] + (b>>4), dtyps[idx]) )
    return 0;
  b &= 3;
  if ( b == 3 )
    return 0;
  op_im(insn.Op2, uval_t(1)<<b, dt_byte);
  return insn.size;
}

//----------------------------------------------------------------------
// analyze extended instruction set

int i51_t::ana_extended(insn_t &insn)
{
  int code = insn.get_next_byte();
  if ( (code & 8) == 0 )
    return 0;
  if ( (code & 0xF0) >= 0xE0 )
    return 0;

  static const uchar itypes[] =
  {
/*      8         9          A          B         C         D         E         F  */
/* 0 */ I51_jsle, I51_mov,   I51_movz,  I51_mov,  I51_null, I51_null, I51_sra,  I51_null,
/* 1 */ I51_jsg,  I51_mov,   I51_movs,  I51_mov,  I51_null, I51_null, I51_srl,  I51_null,
/* 2 */ I51_jle,  I51_mov,   I51_null,  I51_null, I51_add,  I51_add,  I51_add,  I51_add,
/* 3 */ I51_jg,   I51_mov,   I51_null,  I51_null, I51_null, I51_null, I51_sll,  I51_null,
/* 4 */ I51_jsl,  I51_mov,   I51_null,  I51_null, I51_orl,  I51_orl,  I51_orl,  I51_null,
/* 5 */ I51_jsge, I51_mov,   I51_null,  I51_null, I51_anl,  I51_anl,  I51_anl,  I51_null,
/* 6 */ I51_je,   I51_mov,   I51_null,  I51_null, I51_xrl,  I51_xrl,  I51_xrl,  I51_null,
/* 7 */ I51_jne,  I51_mov,   I51_mov,   I51_null, I51_mov,  I51_mov,  I51_mov,  I51_mov,
/* 8 */ I51_null, I51_ljmp,  I51_ejmp,  I51_null, I51_div,  I51_div,  I51_null, I51_null,
/* 9 */ I51_null, I51_lcall, I51_ecall, I51_null, I51_sub,  I51_sub,  I51_sub,  I51_sub,
/* A */ I51_null, I51_last,  I51_eret,  I51_null, I51_mul,  I51_mul,  I51_null, I51_null,
/* B */ I51_null, I51_trap,  I51_null,  I51_null, I51_cmp,  I51_cmp,  I51_cmp,  I51_cmp,
/* C */ I51_null, I51_null,  I51_push,  I51_null, I51_null, I51_null, I51_null, I51_null,
/* D */ I51_null, I51_null,  I51_pop,   I51_null, I51_null, I51_null, I51_null, I51_null,
  };
  insn.itype = itypes[ ((code&0xF0)>>1) | (code & 7) ];
  if ( insn.itype == I51_null )
    return 0;

  uchar b1, b2;
  int oax = 0;
  switch ( code )
  {
    case 0x08:          // rel
    case 0x18:
    case 0x28:
    case 0x38:
    case 0x48:
    case 0x58:
    case 0x68:
    case 0x78:
      {
        insn.Op1.type = o_near;
        insn.Op1.dtype = dt_word;
        signed char off = insn.get_next_byte();
        insn.Op1.addr = truncate(insn.ip + insn.size + off); // signed addition
      }
      break;

    case 0x09:          // mov Rm, @WRj+dis
      b1 = insn.get_next_byte();
      op_rd(insn.Op1, rR0 +(b1>>4), dt_byte);
      insn.Op2.offb = (uchar)insn.size;
      op_ds(insn.Op2, rWR0+(b1&15), insn.get_next_word(), dt_byte);
      break;

    case 0x49:          // mov WRk, @WRj+dis
      b1 = insn.get_next_byte();
      op_rd(insn.Op1, rWR0+(b1>>4), dt_word);
      insn.Op2.offb = (uchar)insn.size;
      op_ds(insn.Op2, rWR0+(b1&15), insn.get_next_word(), dt_word);
      break;

    case 0x29:          // mov Rm, @DRj+dis
      b1 = insn.get_next_byte();
      op_rd(insn.Op1, rR0 +(b1>>4), dt_byte);
      insn.Op2.offb = (uchar)insn.size;
      if ( !op_ds(insn.Op2, rDR0+(b1&15), insn.get_next_word(), dt_byte) )
        return 0;
      break;

    case 0x69:          // mov WRj, @DRk+dis
      b1 = insn.get_next_byte();
      op_rd(insn.Op1, rWR0+(b1>>4), dt_word);
      insn.Op2.offb = (uchar)insn.size;
      if ( !op_ds(insn.Op2, rDR0+(b1&15), insn.get_next_word(), dt_word) )
        return 0;
      break;

    case 0x19:          // mov @WRj+dis, Rm
      b1 = insn.get_next_byte();
      insn.Op1.offb = (uchar)insn.size;
      op_ds(insn.Op1, rWR0+(b1&15), insn.get_next_word(), dt_byte);
      op_rd(insn.Op2, rR0 +(b1>>4), dt_byte);
      break;

    case 0x59:          // mov @WRj+dis, WRk
      b1 = insn.get_next_byte();
      insn.Op1.offb = (uchar)insn.size;
      op_ds(insn.Op1, rWR0+(b1&15), insn.get_next_word(), dt_word);
      op_rd(insn.Op2, rWR0+(b1>>4), dt_word);
      break;

    case 0x39:          // mov @DRj+dis, Rm
      b1 = insn.get_next_byte();
      insn.Op1.offb = (uchar)insn.size;
      if ( !op_ds(insn.Op1, rDR0+(b1&15), insn.get_next_word(), dt_byte) )
        return 0;
      op_rd(insn.Op2, rR0 +(b1>>4), dt_byte);
      break;

    case 0x79:          // mov @DRk+dis, WRj
      b1 = insn.get_next_byte();
      insn.Op1.offb = (uchar)insn.size;
      if ( !op_ds(insn.Op1, rDR0+(b1&15), insn.get_next_word(), dt_word) )
        return 0;
      op_rd(insn.Op2, rWR0+(b1>>4), dt_word);
      break;

    case 0x0A:          // movz WRj, Rm
    case 0x1A:          // movs WRj, Rm
      b1 = insn.get_next_byte();
      op_rd(insn.Op1, rWR0+(b1>>4), dt_word);
      op_rd(insn.Op2, rR0 +(b1&15), dt_byte);
      break;

    case 0x0B:          // 1000 mov WRj, @WRj
                        // 1010 mov WRj, @DRk
      {
        b1 = insn.get_next_byte();
        int ri;
        switch ( b1 & 15 )
        {
          case 0x8: ri = rWR0; break;
          case 0xA: ri = rDR0; break;
          case 0x9: return 0;
          case 0xB: return 0;
          default:  return make_short(insn, I51_inc, b1);
        }
        b2 = insn.get_next_byte();
        if ( b2 & 15 )
          return 0;
        op_rd(insn.Op1, rWR0+(b2>>4), dt_word);
        if ( !op_ph(insn.Op2, ri + (b1>>4), dt_word) )
          return 0;
      }
      break;

    case 0x1B:          // 1000 mov @WRj, WRj
                        // 1010 mov @DRk, WRj
      {
        b1 = insn.get_next_byte();
        int ri;
        switch ( b1 & 15 )
        {
          case 0x8: ri = rWR0; break;
          case 0xA: ri = rDR0; break;
          case 0x9: return 0;
          case 0xB: return 0;
          default:  return make_short(insn, I51_dec, b1);
        }
        b2 = insn.get_next_byte();
        if ( b2 & 15 )
          return 0;
        if ( !op_ph(insn.Op1, ri + (b1>>4), dt_word) )
          return 0;
        op_rd(insn.Op2, rWR0+(b2>>4), dt_word);
      }
      break;

    case 0x7A:
      {
        b1 = insn.get_next_byte();
        switch ( b1&15 )
        {
          case 9:                 // 1001 mov @WRj, Rm
          case 0xB:               // 1011 mov @DRk, Rm
            b2 = insn.get_next_byte();
            if ( b2 & 15 )
              return 0;
            if ( !op_ph(insn.Op1, ((b1&2) ? rDR0 : rWR0) + (b1>>4), dt_byte) )
              return 0;
            op_rd(insn.Op2, rR0+(b2>>4), dt_byte);
            break;
          case 0xC:               // movh DRk, #data16
            insn.itype = I51_movh;
            if ( !op_rd(insn.Op1, rDR0+(b1>>4), dt_dword) )
              return 0;
            insn.Op2.offb = (uchar)insn.size;
            op_im(insn.Op2, insn.get_next_word(), dt_word);
            break;
          default:
            goto CONT;
        }
        break;
CONT:
        uval_t addr = (b1&2) ? insn.get_next_word() : insn.get_next_byte();
        switch ( b1&15 )
        {
          case 0x1:               // mov dir8, Rm
          case 0x3:               // mov dir16, Rm
            op_mm(insn.Op1, addr, dt_byte);
            op_rd(insn.Op2, rR0+(b1>>4), dt_byte);
            break;
          case 0x5:               // mov dir8, WRj
          case 0x7:               // mov dir16, WRj
            op_mm(insn.Op1, addr, dt_word);
            op_rd(insn.Op2, rWR0+(b1>>4), dt_word);
            break;
          case 0xD:               // mov dir8, DRj
          case 0xF:               // mov dir16, DRj
            op_mm(insn.Op1, addr, dt_dword);
            if ( !op_rd(insn.Op2, rDR0+(b1>>4), dt_dword) )
              return 0;
            break;
          default: return 0;
        }
      }
      break;

    case 0x89:          // ljmp  @WRj or @DRj
    case 0x99:          // lcall @WRj or @DRj
      {
        int r;
        uchar dt;
        b1 = insn.get_next_byte();
        switch ( b1 & 15 )
        {
          case 4:
            r = rWR0;
            dt = dt_word;
            break;
          case 8:
            r = rDR0;
            dt = dt_dword;
            insn.itype = (insn.itype == I51_ljmp) ? I51_ejmp : I51_ecall;
            break;
          default:
            return 0;
        }
        if ( !op_ph(insn.Op1, r+(b1>>4), dt) )
          return 0;
      }
      break;

    case 0x8A:          // ejmp  addr24
    case 0x9A:          // ecall addr24
      op_near(insn.Op1, get_next_24bits(insn));
      break;

    case 0xAA:          // eret
    case 0xB9:          // trap
      break;

    case 0xCA:          // push
    case 0xDA:          // pop
      b1 = insn.get_next_byte();
      switch ( b1 & 15 )
      {
        case 0x1:                                       // mov DRk, PC
          if ( code != 0xCA )
            return 0;
          insn.itype = I51_mov;
          if ( !op_rd(insn.Op1, rDR0+(b1>>4), dt_dword) )
            return 0;
          op_rd(insn.Op2, rPC, dt_dword);
          break;
        case 0x2:                                       // #data8
          insn.Op1.offb = (uchar)insn.size;
          op_im(insn.Op1, insn.get_next_byte(), dt_byte);
          break;
        case 0x6:                                       // #data16
          insn.Op1.offb = (uchar)insn.size;
          op_im(insn.Op1, insn.get_next_word(), dt_word);
          break;
        case 0x8:                                       // Rm
          op_rd(insn.Op1, rR0+(b1>>4), dt_byte);
          break;
        case 0x9:                                       // WRj
          op_rd(insn.Op1, rWR0+(b1>>4), dt_word);
          break;
        case 0xB:                                       // DRj
          if ( !op_rd(insn.Op1, rDR0+(b1>>4), dt_dword) )
            return 0;
          break;
        default:
          return 0;
      }
      break;

    case 0xA9:          // bit instructions
      {
        static const uchar subtypes[] =
        {
          I51_null, I51_jbc,  I51_jb,   I51_jnb,
          I51_null, I51_null, I51_null, I51_orl,
          I51_anl,  I51_mov,  I51_mov,  I51_cpl,
          I51_clr,  I51_setb, I51_orl,  I51_anl
        };
        b1 = insn.get_next_byte();
        if ( b1 & 8 )
          return 0;
        insn.itype = subtypes[ b1 >> 4];
        if ( insn.itype == I51_null )
          return 0;
        insn.Op1.type = o_bit251;
        insn.Op1.dtype = dt_byte;
        insn.Op1.b251_bit = b1 & 7;
        insn.Op1.addr = insn.get_next_byte();
        insn.Op1.b251_bitneg = 0;
        switch ( b1 >> 4 )
        {
          case 0x1:             // jbc bit, rel
          case 0x2:             // jb  bit, rel
          case 0x3:             // jnb bit, rel
            {
              signed char rel = insn.get_next_byte();
              op_near(insn.Op2, truncate(insn.ip + insn.size + rel));
            }
            break;
          case 0xE:             // orl cy, /bit
          case 0xF:             // anl cy, /bit
            insn.Op1.b251_bitneg = 1;
            [[fallthrough]];
          case 0x7:             // orl cy, bit
          case 0x8:             // anl cy, bit
          case 0xA:             // mov cy, bit
            insn.Op2 = insn.Op1;
            opC(insn.Op1);
            break;
          case 0x9:             // mov bit, cy
            opC(insn.Op2);
            break;
          case 0xB:             // cpl  bit
          case 0xC:             // clr  bit
          case 0xD:             // setb bit
            break;
        }
      }
      break;

    case 0x0E:          // sra
    case 0x1E:          // srl
    case 0x3E:          // sll
      b1 = insn.get_next_byte();
      switch ( b1 & 15 )
      {
        case 0:
          op_rd(insn.Op1, rR0 +(b1>>4), dt_byte);
          break;
        case 4:
          op_rd(insn.Op1, rWR0+(b1>>4), dt_word);
          break;
        default:
          return 0;
      }
      break;

    case 0x2C:          // add Rm, Rm
    case 0x4C:          // orl Rm, Rm
    case 0x5C:          // anl Rm, Rm
    case 0x6C:          // xrl Rm, Rm
    case 0x7C:          // mov Rm, Rm
    case 0x8C:          // div Rm, Rm
    case 0x9C:          // sub Rm, Rm
    case 0xAC:          // mul Rm, Rm
    case 0xBC:          // cmp Rm, Rm
      b1 = insn.get_next_byte();
      op_rd(insn.Op1, rR0+(b1>>4), dt_byte);
      op_rd(insn.Op2, rR0+(b1&15), dt_byte);
      break;

    case 0x2D:          // add WRj, WRj
    case 0x4D:          // orl WRj, WRj
    case 0x5D:          // anl WRj, WRj
    case 0x6D:          // xrl WRj, WRj
    case 0x7D:          // mov WRj, WRj
    case 0x8D:          // div WRj, WRj
    case 0x9D:          // sub WRj, WRj
    case 0xAD:          // mul WRj, WRj
    case 0xBD:          // cmp WRj, WRj
      b1 = insn.get_next_byte();
      op_rd(insn.Op1, rWR0+(b1>>4), dt_word);
      op_rd(insn.Op2, rWR0+(b1&15), dt_word);
      break;

    case 0x2F:          // add DRj, DRj
    case 0x7F:          // mov DRj, DRj
    case 0x9F:          // sub DRj, DRj
    case 0xBF:          // cmp DRj, DRj
      b1 = insn.get_next_byte();
      if ( !op_rd(insn.Op1, rDR0+(b1>>4), dt_dword) )
        return 0;
      if ( !op_rd(insn.Op2, rDR0+(b1&15), dt_dword) )
        return 0;
      break;

    case 0x4E:          // orl reg, op2
    case 0x5E:          // anl reg, op2
    case 0x6E:          // xrl reg, op2
      oax = 1;  // orl, anl, xrl
      [[fallthrough]];
    case 0x2E:          // add reg, op2
    case 0x7E:          // mov reg, op2
    case 0x8E:          // div reg, op2
    case 0x9E:          // sub reg, op2
    case 0xAE:          // mul reg, op2
    case 0xBE:          // cmp reg, op2
      b1 = insn.get_next_byte();
      switch ( b1 & 15 )
      {
        case 0x0:                                       // Rm, #8
          op_rd(insn.Op1, rR0+(b1>>4), dt_byte);
          insn.Op2.offb = (uchar)insn.size;
          op_im(insn.Op2, insn.get_next_byte(), dt_byte);
          break;
        case 0x4:                                       // WRj, #16
          op_rd(insn.Op1, rWR0+(b1>>4), dt_word);
          insn.Op2.offb = (uchar)insn.size;
          op_im(insn.Op2, insn.get_next_word(), dt_word);
          break;
        case 0x8:                                       // DRk, #16
          if ( oax )
            return 0;
          if ( !op_rd(insn.Op1, rDR0+(b1>>4), dt_dword) )
            return 0;
          insn.Op2.offb = (uchar)insn.size;
          op_im(insn.Op2, insn.get_next_word(), dt_word);
          break;
        case 0xC:                                       // DRk, #(1)16
          if ( oax )
            return 0;
          if ( !op_rd(insn.Op1, rDR0+(b1>>4), dt_dword) )
            return 0;
          insn.Op2.offb = (uchar)insn.size;
          op_im(insn.Op2, insn.get_next_word(), dt_word);
          insn.auxpref |= aux_1ext;
          break;
        case 0x1:                                       // Rm, dir8
          op_rd(insn.Op1, rR0+(b1>>4), dt_byte);
          op_mm(insn.Op2, insn.get_next_byte(), dt_byte);
          break;
        case 0x5:                                       // WRj, dir8
          op_rd(insn.Op1, rWR0+(b1>>4), dt_word);
          op_mm(insn.Op2, insn.get_next_byte(), dt_word);
          break;
        case 0xD:                                       // DRk, dir8
          if ( oax )
            return 0;
          if ( !op_rd(insn.Op1, rDR0+(b1>>4), dt_dword) )
            return 0;
          op_mm(insn.Op2, insn.get_next_byte(), dt_word);
          break;
        case 0x3:                                       // Rm, dir16
          op_rd(insn.Op1, rR0+(b1>>4), dt_byte);
          op_mm(insn.Op2, insn.get_next_word(), dt_byte);
          break;
        case 0x7:                                       // WRj, dir16
          op_rd(insn.Op1, rWR0+(b1>>4), dt_word);
          op_mm(insn.Op2, insn.get_next_word(), dt_word);
          break;
        case 0xF:                                       // DRk, dir16
          if ( code != 0x7E )
            return 0;         // only mov works
          if ( !op_rd(insn.Op1, rDR0+(b1>>4), dt_dword) )
            return 0;
          op_mm(insn.Op2, insn.get_next_word(), dt_word);
          break;
        case 0x9:                                       // Rm, @WRj
          b2 = insn.get_next_byte();
          if ( b2 & 15 )
            return 0;
          op_rd(insn.Op1, rR0 +(b2>>4), dt_byte);
          op_ph(insn.Op2, rWR0+(b1>>4), dt_byte);
          break;
        case 0xB:                                       // Rm, @DRk
          b2 = insn.get_next_byte();
          if ( b2 & 15 )
            return 0;
          op_rd(insn.Op1, rR0 +(b2>>4), dt_byte);
          if ( !op_ph(insn.Op2, rDR0+(b1>>4), dt_byte) )
            return 0;
          break;
        default:
          return 0;
      }
      break;

    default:
      error("%a: internal ana_extended() error, code=%x", insn.ea, code);
  }

  return insn.size;
}

//----------------------------------------------------------------------
// analyze an basic instruction
int i51_t::ana_basic(insn_t &insn)
{
  ushort code = insn.get_next_byte();
  bool mx_a5 = false;
  if ( code == 0xA5 && ptype == prc_51mx )
  {
    code = insn.get_next_byte();
    mx_a5 = true;
  }

  ushort nibble0 = (code & 0xF);
  ushort nibble1 = (code >> 4);
  char off;
  if ( mx_a5 )
  {
    if ( nibble1 == 0x6 && nibble0 >= 0x8 ) // ADD PRi,#data2 (0 1 1 0 1 i d1 d2)
    {
      insn.itype = I51_add;
      op_rd(insn.Op1, (nibble0 & 4) == 0 ? rPR0 : rPR1, dt_dword);
      int val = nibble0 & 3;
      op_im(insn.Op2, val == 0 ? 4 : val, dt_byte);
      return insn.size;
    }
    if ( nibble1 == 0x4 && nibble0 >= 0x8 ) // EMOVE A, @PRi+#data2
    {
      insn.itype = I51_emov;
      opAcc(insn.Op1);
      int val = nibble0 & 3;
      op_ph(insn.Op2, (nibble0 & 4) == 0 ? fPr0 : fPr1, dt_dword, val == 0 ? 4 : val);
      return insn.size;
    }
    if ( nibble1 == 0x5 && nibble0 >= 0x8 ) // EMOVE @PRi+#data2, A
    {
      insn.itype = I51_emov;
      int val = nibble0 & 3;
      op_ph(insn.Op1, (nibble0 & 4) == 0 ? fPr0 : fPr1, dt_dword, val == 0 ? 4 : val);
      opAcc(insn.Op2);
      return insn.size;
    }
  }
  if ( nibble0 < 4 )              // complex coding, misc instructions
  {
    switch ( nibble0 )
    {
      case 0:
        {
          static const uchar misc0[16] =
          {
            I51_nop, I51_jbc, I51_jb,  I51_jnb,
            I51_jc,  I51_jnc, I51_jz,  I51_jnz,
            I51_sjmp,I51_mov, I51_orl, I51_anl,
            I51_push,I51_pop, I51_movx,I51_movx
          };
          insn.itype = misc0[nibble1];
        }
        switch ( nibble1 )
        {
          case 0x1: case 0x2: case 0x3: // jbc, jb, jnb
            insn.Op1.type  = o_bit;
            insn.Op1.reg = insn.get_next_byte();
            insn.Op2.type = o_near;
            off = insn.get_next_byte();
            insn.Op2.addr = truncate(insn.ip + insn.size + off); // signed addition
            insn.Op2.dtype = dt_word;
            break;
          case 0x4: case 0x5: case 0x6: case 0x7: case 0x8: // jc, jnc, jz, jnz, sjmp
            insn.Op1.type = o_near;
            off = insn.get_next_byte();
            insn.Op1.addr = truncate(insn.ip + insn.size + off); // signed addition
            insn.Op1.dtype = dt_word;
            break;
          case 0x9: // mov
            insn.Op1.type = o_reg;
            insn.Op1.reg = mx_a5 ? rEptr : rDptr;
            insn.Op1.dtype = mx_a5 ? dt_dword : dt_word;
            insn.Op2.type  = o_imm;
            insn.Op2.offb  = (uchar)insn.size;
            if ( mx_a5 )
            {
              insn.Op2.value = (((ea_t)insn.get_next_word()) << 8) + insn.get_next_byte();
              insn.Op2.dtype = dt_dword;
            }
            else
            {
              insn.Op2.value = insn.get_next_word();
              insn.Op2.dtype = dt_word;
            }
            break;
          case 0xA: case 0xB: // orl, anl
            opC(insn.Op1);
            insn.Op2.type = o_bitnot;
            insn.Op2.reg = insn.get_next_byte();
            break;
          case 0xC: case 0xD: // push, pop
            insn.Op1.type = o_mem;
            insn.Op1.addr = insn.get_next_byte();
            break;
          case 0xE: // movx
            opAcc(insn.Op1);
            insn.Op2.type = o_phrase;
            insn.Op2.phrase = mx_a5 ? fEptr : fDptr;
            break;
          case 0xF: // movx
            opAcc(insn.Op2);
            insn.Op1.type = o_phrase;
            insn.Op1.phrase = mx_a5 ? fEptr : fDptr;
            break;
        }
        break;
      case 1: // acall, ajmp
        {
          ushort lowbits = insn.get_next_byte();
          insn.Op1.type = o_near;
          insn.Op1.addr = truncate((code&0xE0)<<3) + lowbits + ((insn.ip+insn.size) & ~0x7FF);
          insn.Op1.dtype = dt_word;
          insn.itype = (nibble1 & 1) ? I51_acall : I51_ajmp;
        }
        break;
      case 2:
        {
          static const uchar misc2[16] =
          {
            I51_ljmp,I51_lcall,I51_ret,I51_reti,
            I51_orl, I51_anl, I51_xrl, I51_orl,
            I51_anl, I51_mov, I51_mov, I51_cpl,
            I51_clr, I51_setb,I51_movx,I51_movx
          };
          insn.itype = misc2[nibble1];
        }
        switch ( nibble1 )
        {
          case 0x0: case 0x1: // ljump (ejmp), lcall (ecall)
            insn.Op1.type = o_near;
            if ( mx_a5 ) // ecall
            {
              insn.itype = nibble1 == 0 ? I51_ejmp : I51_ecall;
              insn.Op1.addr = insn.get_next_word();
              insn.Op1.addr = (insn.Op1.addr<<8) + insn.get_next_byte();
              if ( insn.Op1.addr >= 0x800000 )
                insn.Op1.addr -= 0x800000;
            }
            else
            {
              insn.Op1.addr = insn.get_next_word();
            }
            insn.Op1.addr|= (insn.ip+insn.size) & ~0xFFFF;
            insn.Op1.dtype = mx_a5 ? dt_dword : dt_word;
            break;
          case 0x2: // ret (eret);
            if ( mx_a5 )
              insn.itype = I51_eret;
            break;
          case 0x4: case 0x5: case 0x6: // orl, anl, xrl,
            insn.Op1.type = o_mem;
            insn.Op1.addr = insn.get_next_byte();
            opAcc(insn.Op2);
            break;
          case 0x7: case 0x8: case 0xA: // orl, anl, mov
            opC(insn.Op1);
            insn.Op2.type = o_bit;
            insn.Op2.reg = insn.get_next_byte();
            break;
          case 0x9: // mov
            opC(insn.Op2);
            [[fallthrough]];
          case 0xB: case 0xC: case 0xD: // cpl, clr, setb
            insn.Op1.type = o_bit;
            insn.Op1.reg = insn.get_next_byte();
            break;
          case 0xE: // movx
            opAcc(insn.Op1);
            insn.Op2.type = o_phrase;
            insn.Op2.phrase = fR0;
            break;
          case 0xF: // movx
            insn.Op1.type = o_phrase;
            insn.Op1.phrase = fR0;
            opAcc(insn.Op2);
            break;
        }
        break;
      case 3:
        {
          static const uchar misc3[16] =
          {
            I51_rr,  I51_rrc, I51_rl,  I51_rlc,
            I51_orl, I51_anl, I51_xrl, I51_jmp,
            I51_movc,I51_movc,I51_inc, I51_cpl,
            I51_clr, I51_setb,I51_movx,I51_movx
          };
          insn.itype = misc3[nibble1];
        }
        switch ( nibble1 )
        {
          case 0x0: case 0x1: case 0x2: case 0x3: // rr, rrc, rl, rlc
            opAcc(insn.Op1);
            break;
          case 0x4: case 0x5: case 0x6: // orl, anl, xrl
            insn.Op1.type = o_mem;
            insn.Op1.addr = insn.get_next_byte();
            insn.Op2.offb  = (uchar)insn.size;
            insn.Op2.type  = o_imm;
            insn.Op2.value = insn.get_next_byte();
            break;
          case 0x7: // jmp
            insn.Op1.type = o_phrase;
            insn.Op1.phrase = mx_a5 ? fAeptr : fAdptr;
            break;
          case 0x8: case 0x9: // movc
            opAcc(insn.Op1);
            insn.Op2.type = o_phrase;
            insn.Op2.phrase = nibble1 == 0x8 ? fApc : (mx_a5 ? fAeptr : fAdptr);
            break;
          case 0xA: // inc
            insn.Op1.type = o_reg;
            insn.Op1.reg = mx_a5 ? rEptr : rDptr;
            insn.Op1.dtype = mx_a5 ? dt_dword : dt_word;
            break;
          case 0xB: case 0xC: case 0xD: // cpl, clr, setb
            opC(insn.Op1);
            break;
          case 0xE: // movx
            opAcc(insn.Op1);
            insn.Op2.type = o_phrase;
            insn.Op2.phrase = fR1;
            break;
          case 0xF: // movx
            insn.Op1.type = o_phrase;
            insn.Op1.phrase = fR1;
            opAcc(insn.Op2);
            break;
        }
        break;
    }
  }
  else
  {         // i.e. nibble0 >= 4
    static const uchar regulars[16] =
    {
      I51_inc, I51_dec, I51_add, I51_addc,
      I51_orl, I51_anl, I51_xrl, I51_mov,
      I51_mov, I51_subb,I51_mov, I51_cjne,
      I51_xch, I51_djnz,I51_mov, I51_mov
    };
    insn.itype = regulars[nibble1];
    switch ( nibble1 )
    {
      case 0x00: case 0x01:     // inc, dec
        operand1(insn, nibble0);
        break;
      case 0x0C:                // xch
        if ( nibble0 == 4 )
        {
          insn.itype = I51_swap;
          opAcc(insn.Op1);
          break;
        }
        // fallthrough
      case 0x02: case 0x03: case 0x04: // add, addc, orl
      case 0x05: case 0x06: case 0x09: // anl, xrl, subb
        operand2(insn, nibble0);
        opAcc(insn.Op1);
        break;
      case 0x07:                // mov
        operand1(insn, nibble0);
        insn.Op2.offb = (uchar)insn.size;
        insn.Op2.type = o_imm;
        insn.Op2.value = insn.get_next_byte();
        break;
      case 0x08:                // mov
        if ( nibble0 == 4 )
        {
          insn.itype = I51_div;
          insn.Op1.type = o_reg;
          insn.Op1.reg = rAB;
          break;
        }
        operand2(insn, nibble0);
        insn.Op1.type = o_mem;
        insn.Op1.addr = insn.get_next_byte();
        break;
      case 0x0A:                // mov
        if ( nibble0 == 4 )
        {
          insn.itype = I51_mul;
          insn.Op1.type = o_reg;
          insn.Op1.reg = rAB;
          break;
        }
        if ( nibble0 == 5 )
          return 0;   // mov to imm - no sense (0xA5)
        operand1(insn, nibble0);
        insn.Op2.type = o_mem;
        insn.Op2.addr = insn.get_next_byte();
        break;
      case 0x0B:                // cjne
        if ( nibble0 == 5 )
        {
          opAcc(insn.Op1);
          insn.Op2.type = o_mem;
          insn.Op2.addr = insn.get_next_byte();
        }
        else
        {
          operand1(insn, nibble0);
          insn.Op2.offb  = (uchar)insn.size;
          insn.Op2.type  = o_imm;
          insn.Op2.value = insn.get_next_byte();
        }
        insn.Op3.type = o_near;
        off = insn.get_next_byte();
        insn.Op3.addr = truncate(insn.ip + insn.size + off);  // signed addition
        insn.Op3.dtype = dt_word;
        break;
      case 0x0D:                // djnz
        switch ( nibble0 )
        {
          case 4:
            insn.itype = I51_da;
            opAcc(insn.Op1);
            break;
          case 6: case 7:
            insn.itype = I51_xchd;
            opAcc(insn.Op1);
            operand2(insn, nibble0);
            break;
          default:
            operand1(insn, nibble0);
            off = insn.get_next_byte();
            insn.Op2.type = o_near;
            insn.Op2.addr = truncate(insn.ip + insn.size + off); // signed addition
            insn.Op2.dtype = dt_word;
            break;
        }
        break;
      case 0x0E:                // mov
        opAcc(insn.Op1);
        if ( nibble0 == 4 )
        {
          insn.itype = I51_clr;
          break;
        }
        operand2(insn, nibble0);
        break;
      case 0x0F:                // mov
        if ( nibble0 == 4 )
        {
          insn.itype = I51_cpl;
          opAcc(insn.Op1);
          break;
        }
        operand1(insn, nibble0);
        insn.Op2.type = o_reg;
        insn.Op2.reg = rAcc;
        break;
    }
  }
  return insn.size;
}

//----------------------------------------------------------------------
// analyze an instruction
int i51_t::ana(insn_t *_insn)
{
  insn_t &insn = *_insn;
  insn.Op1.dtype = dt_byte;
  insn.Op2.dtype = dt_byte;
  insn.Op3.dtype = dt_byte;

  uchar code = get_byte(insn.ea);
  switch ( ptype )
  {
    case prc_51:
    case prc_51mx:
      return ana_basic(insn);

    case prc_251_src:
    case prc_930_src:
      if ( code == 0xA5 )
      {
        insn.size++;             // skip A5
        code = get_byte(insn.ea+1);
        if ( (code & 15) < 6 )
          return 0;
        return ana_basic(insn);
      }
      if ( (code & 15) < 6 )
        return ana_basic(insn);
      return ana_extended(insn);

    case prc_251_bin:
    case prc_930_bin:
      if ( code == 0xA5 )
      {
        insn.size++;             // skip A5
        return ana_extended(insn);
      }
      return ana_basic(insn);
  }
  return 0;   //lint !e527 statement is unreachable
}
