
#include "fr.hpp"

// distinct sizes :
//lint -esym(749, S_11) not referenced
enum
{
  S_0,
  S_4,        // 4 bits
  S_5,        // 5 bits
  S_8,        // 8 bits
  S_11,       // 11 bits
  S_12,       // 12 bits
  S_16        // 16 bits
};

// bits numbers for sizes :
const int bits[] =
{
  0,
  4,
  5,
  8,
  11,
  12,
  16
};

// masks for sizes :
const int masks[] =
{
  0x0000,
  0x000F,
  0x001F,
  0x00FF,
  0x07FF,
  0x0FFF,
  0xFFFF
};

const char dtypes[] =
{
  0,
  dt_byte,
  dt_byte,
  dt_byte,
  dt_word,
  dt_word,
  dt_word
};

// distinct operands :
enum
{
  O_null,         // null opcode
  O_gr,           // general register                         Ri
  O_gri,          // general register indirect                @Ri
  O_grip,         // general register indirect post-increment @Ri+
  O_r13_gr_i,     // indirect r13 + general register          @(R13, Ri)
  O_r14_imm8_i,   // indirect r14 + 8 bits immediate value    @(R14, imm)
  O_r15_imm4_i,   // indirect r15 + 4 bits immediate value    @(R15, imm)
  O_r15ip,        // indirect r15 post-increment              @R15+
  O_r15im,        // indirect r15 pre-decrement               @-R15
  O_r13,          // register r13                             R13
  O_r13ip,        // indirect r13 post-increment              @R13+
  O_dr,           // dedicated register                       Rs
  O_ps,           // program status register (PS)             PS
  O_imm,          // immediate value                          #i
  O_diri,         // indirect direct value                    @i
  O_rel,          // relative value                           label5
  O_reglist       // register list                            (R0, R1, R2, ...)
};

static int invert_word(int word)
{
  int new_word = 0;

  new_word |= (word & 0x000F) >> 0;
  new_word <<= 4;
  new_word |= (word & 0x00F0) >> 4;
  new_word <<= 4;
  new_word |= (word & 0x0F00) >> 8;
  new_word <<= 4;
  new_word |= (word & 0xF000) >> 12;

  return new_word;
}

// structure of an opcode :
struct opcode_t
{
  int itype;
  int opcode;
  int opcode_size;

  int op1;
  int op1_size;
  int op2;
  int op2_size;

#define I_SWAPOPS          0x00000100      // swap operands
#define I_DSHOT            0x00000200      // delay shot
#define I_ADDR_R           OP_ADDR_R
#define I_ADDR_W           OP_ADDR_W
#define I_IMM_2            0x00001000      // imm = imm * 2
#define I_IMM_4            0x00002000      // imm = imm * 4
  int flags;

  inline bool swap_ops(void) const { return (flags & I_SWAPOPS) != 0; }
  inline bool delay_shot(void) const { return (flags & I_DSHOT) != 0; }
  inline bool implied(void) const { return op1 == O_null && op2 == O_null; }

  int size(void) const
  {
    int n = bits[opcode_size];
    if ( op1 != O_null )
      n += bits[op1_size];
    if ( op2 != O_null )
      n += bits[op2_size];
    return n;
  }

  static void check(void);
  static const struct opcode_t *find(insn_t &insn, int *_data);
};

// FR opcodes :
static const struct opcode_t opcodes[] =
{
  { fr_add,       0xA6,       S_8,    O_gr,           S_4,    O_gr,       S_4,        0         },
  { fr_add,       0xA4,       S_8,    O_imm,          S_4,    O_gr,       S_4,        0         },
  { fr_add2,      0xA5,       S_8,    O_imm,          S_4,    O_gr,       S_4,        0         },
  { fr_addc,      0xA7,       S_8,    O_gr,           S_4,    O_gr,       S_4,        0         },
  { fr_addn,      0xA2,       S_8,    O_gr,           S_4,    O_gr,       S_4,        0         },
  { fr_addn,      0xA0,       S_8,    O_imm,          S_4,    O_gr,       S_4,        0         },
  { fr_addn2,     0xA1,       S_8,    O_imm,          S_4,    O_gr,       S_4,        0         },
  { fr_sub,       0xAC,       S_8,    O_gr,           S_4,    O_gr,       S_4,        0         },
  { fr_subc,      0xAD,       S_8,    O_gr,           S_4,    O_gr,       S_4,        0         },
  { fr_subn,      0xAE,       S_8,    O_gr,           S_4,    O_gr,       S_4,        0         },
  { fr_cmp,       0xAA,       S_8,    O_gr,           S_4,    O_gr,       S_4,        0         },
  { fr_cmp,       0xA8,       S_8,    O_imm,          S_4,    O_gr,       S_4,        0         },
  { fr_cmp2,      0xA9,       S_8,    O_imm,          S_4,    O_gr,       S_4,        0         },
  { fr_and,       0x82,       S_8,    O_gr,           S_4,    O_gr,       S_4,        0         },
  { fr_and,       0x84,       S_8,    O_gr,           S_4,    O_gri,      S_4,        0         },
  { fr_andh,      0x85,       S_8,    O_gr,           S_4,    O_gri,      S_4,        0         },
  { fr_andb,      0x86,       S_8,    O_gr,           S_4,    O_gri,      S_4,        0         },
  { fr_or,        0x92,       S_8,    O_gr,           S_4,    O_gr,       S_4,        0         },
  { fr_or,        0x94,       S_8,    O_gr,           S_4,    O_gri,      S_4,        0         },
  { fr_orh,       0x95,       S_8,    O_gr,           S_4,    O_gri,      S_4,        0         },
  { fr_orb,       0x96,       S_8,    O_gr,           S_4,    O_gri,      S_4,        0         },
  { fr_eor,       0x9A,       S_8,    O_gr,           S_4,    O_gr,       S_4,        0         },
  { fr_eor,       0x9C,       S_8,    O_gr,           S_4,    O_gri,      S_4,        0         },
  { fr_eorh,      0x9D,       S_8,    O_gr,           S_4,    O_gri,      S_4,        0         },
  { fr_eorb,      0x9E,       S_8,    O_gr,           S_4,    O_gri,      S_4,        0         },
  { fr_bandl,     0x80,       S_8,    O_imm,          S_4,    O_gri,      S_4,        0         },
  { fr_bandh,     0x81,       S_8,    O_imm,          S_4,    O_gri,      S_4,        0         },
  { fr_borl,      0x90,       S_8,    O_imm,          S_4,    O_gri,      S_4,        0         },
  { fr_borh,      0x91,       S_8,    O_imm,          S_4,    O_gri,      S_4,        0         },
  { fr_beorl,     0x98,       S_8,    O_imm,          S_4,    O_gri,      S_4,        0         },
  { fr_beorh,     0x99,       S_8,    O_imm,          S_4,    O_gri,      S_4,        0         },
  { fr_btstl,     0x88,       S_8,    O_imm,          S_4,    O_gri,      S_4,        0         },
  { fr_btsth,     0x89,       S_8,    O_imm,          S_4,    O_gri,      S_4,        0         },
  { fr_mul,       0xAF,       S_8,    O_gr,           S_4,    O_gr,       S_4,        0         },
  { fr_mulu,      0xAB,       S_8,    O_gr,           S_4,    O_gr,       S_4,        0         },
  { fr_mulh,      0xBF,       S_8,    O_gr,           S_4,    O_gr,       S_4,        0         },
  { fr_muluh,     0xBB,       S_8,    O_gr,           S_4,    O_gr,       S_4,        0         },
  { fr_div0s,     0x974,      S_12,   O_gr,           S_4,    O_null,     0,          0         },
  { fr_div0u,     0x975,      S_12,   O_gr,           S_4,    O_null,     0,          0         },
  { fr_div1,      0x976,      S_12,   O_gr,           S_4,    O_null,     0,          0         },
  { fr_div2,      0x977,      S_12,   O_gr,           S_4,    O_null,     0,          0         },
  { fr_div3,      0x9F60,     S_16,   O_null,         0,      O_null,     0,          0         },
  { fr_div4s,     0x9F70,     S_16,   O_null,         0,      O_null,     0,          0         },
  { fr_lsl,       0xB6,       S_8,    O_gr,           S_4,    O_gr,       S_4,        0         },
  { fr_lsl,       0xB4,       S_8,    O_imm,          S_4,    O_gr,       S_4,        0         },
  { fr_lsl2,      0xB5,       S_8,    O_imm,          S_4,    O_gr,       S_4,        0         },
  { fr_lsr,       0xB2,       S_8,    O_gr,           S_4,    O_gr,       S_4,        0         },
  { fr_lsr,       0xB0,       S_8,    O_imm,          S_4,    O_gr,       S_4,        0         },
  { fr_lsr2,      0xB1,       S_8,    O_imm,          S_4,    O_gr,       S_4,        0         },
  { fr_asr,       0xBA,       S_8,    O_gr,           S_4,    O_gr,       S_4,        0         },
  { fr_asr,       0xB8,       S_8,    O_imm,          S_4,    O_gr,       S_4,        0         },
  { fr_asr2,      0xB9,       S_8,    O_imm,          S_4,    O_gr,       S_4,        0         },
  // fr_ldi_32 not here (considered as special)
  // fr_ldi_20 not here (considered as special)
  { fr_ldi_8,     0x0C,       S_4,    O_imm,          S_8,    O_gr,       S_4,        0         },
  { fr_ld,        0x04,       S_8,    O_gri,          S_4,    O_gr,       S_4,        0         },
  { fr_ld,        0x00,       S_8,    O_r13_gr_i,     S_4,    O_gr,       S_4,        0         },
  { fr_ld,        0x02,       S_4,    O_r14_imm8_i,   S_8,    O_gr,       S_4,        I_IMM_4   },
  { fr_ld,        0x03,       S_8,    O_r15_imm4_i,   S_4,    O_gr,       S_4,        I_IMM_4   },
  { fr_ld,        0x70,       S_12,   O_r15ip,        S_0,    O_gr,       S_4,        0         },
  { fr_ld,        0x78,       S_12,   O_r15ip,        S_0,    O_dr,       S_4,        0         },
  { fr_ld,        0x790,      S_16,   O_r15ip,        S_0,    O_ps,       S_0,        0         },
  { fr_lduh,      0x05,       S_8,    O_gri,          S_4,    O_gr,       S_4,        0         },
  { fr_lduh,      0x01,       S_8,    O_r13_gr_i,     S_4,    O_gr,       S_4,        0         },
  { fr_lduh,      0x04,       S_4,    O_r14_imm8_i,   S_8,    O_gr,       S_4,        I_IMM_2   },
  { fr_ldub,      0x06,       S_8,    O_gri,          S_4,    O_gr,       S_4,        0         },
  { fr_ldub,      0x02,       S_8,    O_r13_gr_i,     S_4,    O_gr,       S_4,        0         },
  { fr_ldub,      0x06,       S_4,    O_r14_imm8_i,   S_8,    O_gr,       S_4,        0         },
  { fr_st,        0x14,       S_8,    O_gri,          S_4,    O_gr,       S_4,        I_SWAPOPS },
  { fr_st,        0x10,       S_8,    O_r13_gr_i,     S_4,    O_gr,       S_4,        I_SWAPOPS },
  { fr_st,        0x03,       S_4,    O_r14_imm8_i,   S_8,    O_gr,       S_4,        I_SWAPOPS|I_IMM_4 },
  { fr_st,        0x13,       S_8,    O_r15_imm4_i,   S_4,    O_gr,       S_4,        I_SWAPOPS|I_IMM_4 },
  { fr_st,        0x170,      S_12,   O_gr,           S_4,    O_r15im,    S_0,        0         },
  { fr_st,        0x178,      S_12,   O_dr,           S_4,    O_r15im,    S_0,        0         },
  { fr_st,        0x1790,     S_16,   O_ps,           S_0,    O_r15im,    S_0,        0         },
  { fr_sth,       0x15,       S_8,    O_gri,          S_4,    O_gr,       S_4,        I_SWAPOPS },
  { fr_sth,       0x11,       S_8,    O_r13_gr_i,     S_4,    O_gr,       S_4,        I_SWAPOPS },
  { fr_sth,       0x05,       S_4,    O_r14_imm8_i,   S_8,    O_gr,       S_4,        I_SWAPOPS|I_IMM_2 },
  { fr_stb,       0x16,       S_8,    O_gri,          S_4,    O_gr,       S_4,        I_SWAPOPS },
  { fr_stb,       0x12,       S_8,    O_r13_gr_i,     S_4,    O_gr,       S_4,        I_SWAPOPS },
  { fr_stb,       0x07,       S_4,    O_r14_imm8_i,   S_8,    O_gr,       S_4,        I_SWAPOPS },
  { fr_mov,       0x8B,       S_8,    O_gr,           S_4,    O_gr,       S_4,        0         },
  { fr_mov,       0xB7,       S_8,    O_dr,           S_4,    O_gr,       S_4,        0         },
  { fr_mov,       0x171,      S_12,   O_ps,           S_0,    O_gr,       S_4,        0         },
  { fr_mov,       0xB3,       S_8,    O_dr,           S_4,    O_gr,       S_4,        I_SWAPOPS },
  { fr_mov,       0x71,       S_12,   O_gr,           S_4,    O_ps,       S_0,        0         },
  { fr_jmp,       0x970,      S_12,   O_gri,          S_4,    O_null,     0,          0         },
  { fr_call,      0x971,      S_12,   O_gri,          S_4,    O_null,     0,          0         },
  { fr_ret,       0x9720,     S_16,   O_null,         0,      O_null,     0,          0         },
  { fr_int,       0x1F,       S_8,    O_imm,          S_8,    O_null,     0,          0         },
  { fr_inte,      0x9F30,     S_16,   O_null,         0,      O_null,     0,          0         },
  { fr_reti,      0x9730,     S_16,   O_null,         0,      O_null,     0,          0         },
  { fr_bra,       0xE0,       S_8,    O_rel,          S_8,    O_null,     0,          0         },
  { fr_bno,       0xE1,       S_8,    O_rel,          S_8,    O_null,     0,          0         },
  { fr_beq,       0xE2,       S_8,    O_rel,          S_8,    O_null,     0,          0         },
  { fr_bne,       0xE3,       S_8,    O_rel,          S_8,    O_null,     0,          0         },
  { fr_bc,        0xE4,       S_8,    O_rel,          S_8,    O_null,     0,          0         },
  { fr_bnc,       0xE5,       S_8,    O_rel,          S_8,    O_null,     0,          0         },
  { fr_bn,        0xE6,       S_8,    O_rel,          S_8,    O_null,     0,          0         },
  { fr_bp,        0xE7,       S_8,    O_rel,          S_8,    O_null,     0,          0         },
  { fr_bv,        0xE8,       S_8,    O_rel,          S_8,    O_null,     0,          0         },
  { fr_bnv,       0xE9,       S_8,    O_rel,          S_8,    O_null,     0,          0         },
  { fr_blt,       0xEA,       S_8,    O_rel,          S_8,    O_null,     0,          0         },
  { fr_bge,       0xEB,       S_8,    O_rel,          S_8,    O_null,     0,          0         },
  { fr_ble,       0xEC,       S_8,    O_rel,          S_8,    O_null,     0,          0         },
  { fr_bgt,       0xED,       S_8,    O_rel,          S_8,    O_null,     0,          0         },
  { fr_bls,       0xEE,       S_8,    O_rel,          S_8,    O_null,     0,          0         },
  { fr_bhi,       0xEF,       S_8,    O_rel,          S_8,    O_null,     0,          0         },
  { fr_jmp,       0x9F0,      S_12,   O_gri,          S_4,    O_null,     0,          I_DSHOT   },
  { fr_call,      0x9F1,      S_12,   O_gri,          S_4,    O_null,     0,          I_DSHOT   },
  { fr_ret,       0x9F20,     S_16,   O_null,         0,      O_null,     0,          I_DSHOT   },
  { fr_bra,       0xF0,       S_8,    O_rel,          S_8,    O_null,     0,          I_DSHOT   },
  { fr_bno,       0xF1,       S_8,    O_rel,          S_8,    O_null,     0,          I_DSHOT   },
  { fr_beq,       0xF2,       S_8,    O_rel,          S_8,    O_null,     0,          I_DSHOT   },
  { fr_bne,       0xF3,       S_8,    O_rel,          S_8,    O_null,     0,          I_DSHOT   },
  { fr_bc,        0xF4,       S_8,    O_rel,          S_8,    O_null,     0,          I_DSHOT   },
  { fr_bnc,       0xF5,       S_8,    O_rel,          S_8,    O_null,     0,          I_DSHOT   },
  { fr_bn,        0xF6,       S_8,    O_rel,          S_8,    O_null,     0,          I_DSHOT   },
  { fr_bp,        0xF7,       S_8,    O_rel,          S_8,    O_null,     0,          I_DSHOT   },
  { fr_bv,        0xF8,       S_8,    O_rel,          S_8,    O_null,     0,          I_DSHOT   },
  { fr_bnv,       0xF9,       S_8,    O_rel,          S_8,    O_null,     0,          I_DSHOT   },
  { fr_blt,       0xFA,       S_8,    O_rel,          S_8,    O_null,     0,          I_DSHOT   },
  { fr_bge,       0xFB,       S_8,    O_rel,          S_8,    O_null,     0,          I_DSHOT   },
  { fr_ble,       0xFC,       S_8,    O_rel,          S_8,    O_null,     0,          I_DSHOT   },
  { fr_bgt,       0xFD,       S_8,    O_rel,          S_8,    O_null,     0,          I_DSHOT   },
  { fr_bls,       0xFE,       S_8,    O_rel,          S_8,    O_null,     0,          I_DSHOT   },
  { fr_bhi,       0xFF,       S_8,    O_rel,          S_8,    O_null,     0,          I_DSHOT   },
  { fr_dmov,      0x08,       S_8,    O_diri,         S_8,    O_r13,      S_0,        I_ADDR_R  },
  { fr_dmov,      0x18,       S_8,    O_r13,          S_0,    O_diri,     S_8,        I_ADDR_W  },
  { fr_dmov,      0x0C,       S_8,    O_diri,         S_8,    O_r13ip,    S_0,        I_ADDR_R  },
  { fr_dmov,      0x1C,       S_8,    O_r13ip,        S_0,    O_diri,     S_8,        I_ADDR_W  },
  { fr_dmov,      0x0B,       S_8,    O_diri,         S_8,    O_r15im,    S_0,        I_ADDR_R  },
  { fr_dmov,      0x1B,       S_8,    O_r15ip,        S_0,    O_diri,     S_8,        I_ADDR_W  },
  { fr_dmovh,     0x09,       S_8,    O_diri,         S_8,    O_r13,      S_0,        I_ADDR_R  },
  { fr_dmovh,     0x19,       S_8,    O_r13,          S_0,    O_diri,     S_8,        I_ADDR_W  },
  { fr_dmovh,     0x0D,       S_8,    O_diri,         S_8,    O_r13ip,    S_0,        I_ADDR_R  },
  { fr_dmovh,     0x1D,       S_8,    O_r13ip,        S_0,    O_diri,     S_8,        I_ADDR_W  },
  { fr_dmovb,     0x0A,       S_8,    O_diri,         S_8,    O_r13,      S_0,        I_ADDR_R  },
  { fr_dmovb,     0x1A,       S_8,    O_r13,          S_0,    O_diri,     S_8,        I_ADDR_W  },
  { fr_dmovb,     0x0E,       S_8,    O_diri,         S_8,    O_r13ip,    S_0,        I_ADDR_R  },
  { fr_dmovb,     0x1E,       S_8,    O_r13ip,        S_0,    O_diri,     S_8,        I_ADDR_W  },
  { fr_ldres,     0xBC,       S_8,    O_imm,          S_4,    O_grip,     S_4,        I_SWAPOPS },
  { fr_stres,     0xBD,       S_8,    O_imm,          S_4,    O_grip,     S_4,        0         },
  // fr_copop not here (considered as special)
  // fr_copld not here (considered as special)
  // fr_copst not here (considered as special)
  // fr_copsv not here (considered as special)
  { fr_nop,       0x9FA0,     S_16,   O_null,         0,      O_null,     0,          0         },
  { fr_andccr,    0x83,       S_8,    O_imm,          S_8,    O_null,     0,          0         },
  { fr_orccr,     0x93,       S_8,    O_imm,          S_8,    O_null,     0,          0         },
  { fr_stilm,     0x87,       S_8,    O_imm,          S_8,    O_null,     0,          0         },
  { fr_addsp,     0xA3,       S_8,    O_imm,          S_8,    O_null,     0,          0         },
  { fr_extsb,     0x978,      S_12,   O_gr,           S_4,    O_null,     0,          0         },
  { fr_extub,     0x979,      S_12,   O_gr,           S_4,    O_null,     0,          0         },
  { fr_extsh,     0x97A,      S_12,   O_gr,           S_4,    O_null,     0,          0         },
  { fr_extuh,     0x97B,      S_12,   O_gr,           S_4,    O_null,     0,          0         },
  { fr_ldm0,      0x8C,       S_8,    O_reglist,      S_8,    O_null,     0,          0         },
  { fr_ldm1,      0x8D,       S_8,    O_reglist,      S_8,    O_null,     0,          0         },
  { fr_stm0,      0x8E,       S_8,    O_reglist,      S_8,    O_null,     0,          0         },
  { fr_stm1,      0x8F,       S_8,    O_reglist,      S_8,    O_null,     0,          0         },
  { fr_enter,     0x0F,       S_8,    O_imm,          S_8,    O_null,     0,          I_IMM_4   },
  { fr_leave,     0x9F90,     S_16,   O_null,         0,      O_null,     0,          0         },
  { fr_xchb,      0x8A,       S_8,    O_gri,          S_4,    O_gr,       S_4,        0         }
};

void opcode_t::check(void)
{
  for ( int i = 0; i < qnumber(opcodes); i++ )
  {
    int n = opcodes[i].size();
//  if ( n != 16 && n != 32 )
//      msg("instruction n%d (%d) : size %d\n", i, opcodes[i].insn, n);
    QASSERT(10001, n == 16 || n == 32);
  }
}

const struct opcode_t * opcode_t::find(insn_t &insn, int *_data)
{
  QASSERT(10002, _data != nullptr);

  int data = (*_data << 8) | get_byte(insn.ip + insn.size);
  for ( int i = 0; i < qnumber(opcodes); i++ )
  {
    int mask;
    int shift;
    switch ( opcodes[i].opcode_size )
    {
      case S_4:  mask = 0xF000; shift = 12; break;
      case S_5:  mask = 0xF100; shift = 11; break;
      case S_8:  mask = 0xFF00; shift = 8;  break;
      case S_12: mask = 0xFFF0; shift = 4;  break;
      case S_16: mask = 0xFFFF; shift = 0;  break;
      default:   INTERR(10012);
    }
    if ( ((data & mask) >> shift) != opcodes[i].opcode )
      continue;

    insn.size++;
    *_data = invert_word(data);
    return &opcodes[i];
  }
  return nullptr;
}

// get general register.
static int get_gr(const int num)
{
  QASSERT(10003, num >= 0 && num <= 15);
  return num;
}

// get coprocessor register.
static int get_cr(const int num)
{
  QASSERT(10004, num >= 0 && num <= 15);
  return num + 16;
}

// get dedicated register.
static int get_dr(int num)
{
  static const int drs[] =
  {
    rTBR,
    rRP,
    rSSP,
    rUSP,
    rMDH,
    rMDL,
    rReserved6,
    rReserved7,
    rReserved8,
    rReserved9,
    rReserved10,
    rReserved11,
    rReserved12,
    rReserved13,
    rReserved14,
    rReserved15
  };
  QASSERT(10005, num >= 0 && num <= 15);
  return drs[num];
}

// fill an operand as a register.
static void set_reg(op_t &op, int reg, char d_typ)
{
  op.type = o_reg;
  op.reg = (uint16)reg;
  op.dtype = d_typ;
}

// fill an operand as an immediate value.
static void set_imm(op_t &op, int imm, char d_typ)
{
  op.type = o_imm;
  switch ( d_typ )
  {
    case dt_byte:  op.value = (char) imm; break;
    case dt_word:  op.value = (short) imm; break;
    case dt_dword: op.value = imm; break;
    default:       INTERR(10013);
  }
  op.dtype = d_typ;
}

// fill an operand as a phrase.
static void set_phrase(op_t &op, int type, int val, char d_typ)
{
  switch ( type )
  {
    case fIGR:       // indirect general register
    case fIGRP:      // indirect general register with post-increment
    case fIGRM:      // indirect general register with pre-decrement
    case fR13RI:     // indirect displacement between R13 and a general register
      op.reg = (uint16)val;
      break;

    case fIRA:       // indirect relative address
      op.addr = val;
      break;

    default:
      INTERR(10014);
  }
  op.type = o_phrase;
  op.specflag2 = (char)type;
  op.dtype = d_typ;
}

// fill an operand as a relative address.
static void set_rel(const insn_t &insn, op_t &op, int addr, char d_typ)
{
  op.type = o_near;
  int raddr;
  switch ( d_typ ) /* ugly but functional */
  {
    case dt_byte:
      raddr = ((signed char) addr);
      break;

    case dt_word:
      raddr = ((signed short) addr);
      break;

    default:
      INTERR(10015);
  }
  op.addr = insn.ip + 2 + (raddr * 2);
#if defined(__DEBUG__)
  msg("0x%a = 0x%a + 2 + ((signed) 0x%X) * 2)\n", op.addr, insn.ip, addr);
#endif /* __DEBUG__ */
  op.dtype = dt_code;
}

// fill an operand as a reglist
static void set_reglist(op_t &op, int list)
{
  op.type = o_reglist;
  op.value = list;
  op.dtype = dt_byte;  // list is coded in a byte
}

static void set_displ(op_t &op, int reg, int imm, int flag, int local_flag)
{
  op.type = o_displ;
  if ( reg != -1 )
    op.reg = (uint16)get_gr(reg);
  if ( imm != -1 )
  {
    int mul = 1;
    if ( local_flag & I_IMM_2 )
      mul = 2;
    if ( local_flag & I_IMM_4 )
      mul = 4;
    op.value = ((unsigned) imm) * mul;
  }
  op.dtype = dt_byte;
  op.specflag1 |= flag;
}

// swap 2 opcodes (o1 <=> o2).
static void swap_ops(op_t &o1, op_t &o2)
{
  QASSERT(10006, o1.type != o_void && o2.type != o_void);
  op_t tmp = o1;
  o1 = o2;
  o2 = tmp;
}

static void adjust_data(int size, int *data)
{
  QASSERT(10007, data != nullptr);
  int new_data = *data >> bits[size];
  *data = new_data;
}

/*
static void prepare_data(int size, int *data) {
    QASSERT(10008, data != nullptr);
    int new_data = 0;
    switch ( size ) {
        case S_0:
        case S_4:
            new_data = *data;
            break;

        case S_5:
        case S_8:
            new_data |= (*data & 0x00F0) >> 4;
            new_data |= (*data & 0x000F) << 4;
            break;

        case S_11:
        case S_12:
            new_data |= (*data & 0x0F00) >> 8;
            new_data |= (*data & 0x00F0) >> 0;
            new_data |= (*data & 0x000F) << 8;
            break;

        case S_16:
            new_data |= (*data & 0xF000) >> 12;
            new_data |= (*data & 0x0F00) >> 4;
            new_data |= (*data & 0x00F0) << 4;
            new_data |= (*data & 0x000F) << 12;
            break;
    }
    *data = new_data;
}*/

#define SWAP_IF_BYTE(data)          \
    do                              \
    {                               \
      if ( operand_size == S_8 )    \
      {                             \
        int h = (data & 0x0F) << 4; \
        int l = (data & 0xF0) >> 4; \
        data = h | l;               \
      }                             \
    }                               \
    while ( 0 )

//
// defines some shortcuts.
//

#define __set_gr(op, reg)               set_reg(op, reg, dt_byte)
#define set_gr(op, reg)                 __set_gr(op, get_gr(reg))
#define __set_dr(op, reg)               set_reg(op, reg, dt_word)
#define set_dr(op, reg)                 __set_dr(op, get_dr(reg))
#define __set_cr(op, reg)               set_reg(op, reg, dt_word)
#define set_cr(op, reg)                 __set_cr(op, get_cr(reg))

#define set_gri(insn, op, reg)          set_phrase(op, fIGR, get_gr(reg), dt_byte)
#define set_grip(insn, op, reg)         set_phrase(op, fIGRP, get_gr(reg), dt_byte)
#define set_grim(insn, op, reg)         set_phrase(op, fIGRM, get_gr(reg), dt_byte)
#define set_diri(insn, op, addr)        set_phrase(op, fIRA, addr, dt_word)
#define set_r13_gr_i(insn, op, reg)     set_phrase(op, fR13RI, get_gr(reg), dt_byte)
#define fill_op1(insn, data, opc)       fill_op(insn, data, insn.Op1, opc->op1, opc->op1_size, opc->flags)
#define fill_op2(insn, data, opc)       fill_op(insn, data, insn.Op2, opc->op2, opc->op2_size, opc->flags)
//#define set_displ_gr(op, gr, f1)        set_displ(op, gr, -1, f1, 0)
#define set_displ_imm(op, imm, f1, f2)  set_displ(op, -1, imm, f1, f2)

static void fill_op(const insn_t &insn, int data, op_t &op, int operand, int operand_size, int flags)
{
  data &= masks[operand_size];
  //prepare_data(operand_size, &data);
  switch ( operand )
  {
    case O_gr:           // general register                         Ri
      QASSERT(10009, operand_size == S_4);
      set_gr(op, data);
      break;

    case O_gri:          // general register indirect                @Ri
      QASSERT(10010, operand_size == S_4);
      set_gri(insn, op, data);
      break;

    case O_grip:          // general register indirect                @Ri
      QASSERT(10011, operand_size == S_4);
      set_grip(insn, op, data);
      break;

    case O_r13_gr_i:     // indirect r13 + general register          @(R13, Ri)
      set_r13_gr_i(insn, op, data);
      break;

    case O_r14_imm8_i:   // indirect r14 + 8 bits immediate value    @(R14, imm)
      SWAP_IF_BYTE(data);
      set_displ_imm(op, data, OP_DISPL_IMM_R14, flags);
      break;

    case O_r15_imm4_i:   // indirect r15 + 4 bits immediate value    @(R15, imm)
      SWAP_IF_BYTE(data);
      set_displ_imm(op, data, OP_DISPL_IMM_R15, flags);
      break;

    case O_r15ip:        // indirect r15 post-increment              @R15+
      set_grip(insn, op, rR15);
      break;

    case O_r15im:        // indirect r15 pre-decrement               @-R15
      set_grim(insn, op, rR15);
      break;

    case O_r13:          // register r13                             R13
      __set_gr(op, rR13);
      break;

    case O_r13ip:        // indirect r13 post-increment              @R13+
      set_grip(insn, op, rR13);
      break;

    case O_dr:           // dedicated register                       Rs
      set_dr(op, data);
      break;

    case O_ps:           // program status register (PS)             PS
      __set_dr(op, rPS);
      break;

    case O_imm:          // immediate value                          #i
      SWAP_IF_BYTE(data);
      if ( insn.itype == fr_enter )
        data *= 4;
      if ( insn.itype == fr_addsp )
        data = ((signed) data) * 4;
      set_imm(op, data, dtypes[operand_size]);
      break;

    case O_diri:         // indirect direct value                    @i
      SWAP_IF_BYTE(data);
      if ( insn.itype == fr_dmov )
        data *= 4;
      if ( insn.itype == fr_dmovh )
        data *= 2;
      set_diri(insn, op, data);
      op.specflag1 |= flags;
      break;

    case O_rel:          // relative value                           label5
      SWAP_IF_BYTE(data);
      set_rel(insn, op, data, dtypes[operand_size]);
      break;

    case O_reglist:      // register list                            (R0, R1, R2, ...)
      SWAP_IF_BYTE(data);
      set_reglist(op, data);
      break;

    case O_null:         // null opcode
      INTERR(10016);
  }
}

// analyze a "common" instruction (those which are listed in the opcodes[] array).
static bool ana_common(insn_t &insn, int data)
{
  const struct opcode_t *op = opcode_t::find(insn, &data);
  if ( op == nullptr )
    return false;

  // fill instruction type
  insn.itype = (uint16)op->itype;

  // if instruction is implied, our job is finished!
  if ( op->implied() )
    goto ana_finished;

  adjust_data(op->opcode_size, &data);

  // fill operand 1
  if ( op->op1 != O_null )
  {
    fill_op1(insn, data, op);
    adjust_data(op->op1_size, &data);
  }

  // fill operand 2
  if ( op->op2 != O_null )
  {
    fill_op2(insn, data, op);
    adjust_data(op->op2_size, &data);
  }

  // swap opcodes if needed
  if ( op->swap_ops() )
    swap_ops(insn.Op1, insn.Op2);

ana_finished:
  insn.auxpref = 0;

  // is insn delay shot ?
  if ( op->delay_shot() )
    insn.auxpref |= INSN_DELAY_SHOT;

  return true;
}

// analyze a "special" instruction (those which are NOT listed in the opcodes[] array).
static bool ana_special(insn_t &insn, int data)
{
  // detect ldi:20 instructions
  if ( data == 0x9B )
  {
    insn.itype = fr_ldi_20;
    data = (data << 8) | insn.get_next_byte();
    set_gr(insn.Op2, data & 0x000F);
    set_imm(insn.Op1, insn.get_next_word() | ((data & 0x00F0) << 12), dt_dword);
    return true;
  }

  data = (data << 8) | get_byte(insn.ea + insn.size);

  // detect ldi:32 instructions
  if ( (data & 0xFFF0) == 0x9F80 )
  {
    insn.size++;
    insn.itype = fr_ldi_32;
    set_gr(insn.Op2, data & 0x000F);
    set_imm(insn.Op1, insn.get_next_dword(), dt_dword);
    return true;
  }

  // detect call [rel] instructions
  int tmp = (data & 0xF800) >> 11;
  if ( tmp == 0x1A || tmp == 0x1B )
  {
    insn.itype = fr_call;
    insn.size++;
    // extend sign
    if ( data & 0x400 )
      data |= ~0x07FF;
    else
      data &= 0x07FF;
    set_rel(insn, insn.Op1, data, dt_word);
    if ( tmp == 0x1B )
      insn.auxpref |= INSN_DELAY_SHOT;
    return true;
  }

  // detect copop/copld/copst/copsv instructions
  if ( ((data & 0xFF00) >> 8) == 0x9F )
  {
    int word = get_word(insn.ea + insn.size + 1);
    insn.itype = fr_null;
    switch ( (data & 0x00F0) >> 4 )
    {
      // copop
      case 0xC:
        insn.itype = fr_copop;
        set_cr(insn.Op3, (word & 0x00F0) >> 4);
        set_cr(insn.Op4, word & 0x000F);
        break;

      // copld
      case 0xD:
        insn.itype = fr_copld;
        set_gr(insn.Op3, (word & 0x00F0) >> 4);
        set_cr(insn.Op4, word & 0x000F);
        break;

      // copst
      case 0xE:
        insn.itype = fr_copst;
        set_cr(insn.Op3, (word & 0x00F0) >> 4);
        set_gr(insn.Op4, word & 0x000F);
        break;

      // copsv
      case 0xF:
        insn.itype = fr_copsv;
        set_cr(insn.Op3, (word & 0x00F0) >> 4);
        set_gr(insn.Op4, word & 0x000F);
        break;
    }
    if ( insn.itype != fr_null )
    {
      set_imm(insn.Op1, data & 0x000F, dt_byte);
      set_imm(insn.Op2, (word & 0xFF00) >> 8, dt_byte);
      insn.size += 3;
      return true;
    }
  }

  return false;
}

// analyze an instruction.
int idaapi ana(insn_t *_insn)
{
  insn_t &insn = *_insn;

#if defined(__DEBUG__)
  opcode_t::check();
#endif /* __DEBUG__ */

  int byte = insn.get_next_byte();

  bool ok = ana_special(insn, byte);
  if ( !ok )
    ok = ana_common(insn, byte);

  return ok ? insn.size : 0;
}
