/* asm_adsp219x.c -- Full Radare2 arch plugin for Analog Devices ADSP-219x
   Copyright (C) 2026 Dr. Christian Giessen

   This file implements the complete ADSP-219x Instruction Set (Types 1-37).  */

#include <r_arch.h>

/* --- Tables from Chapter 9 --- */

static const char *cond_str[] =
{ "EQ", "NE", "GT", "LE", "LT", "GE", "AV", "NOT AV", "AC", "NOT AC", "SWCOND", "NOT SWCOND", "MV", "NOT MV", "NOT CE", "TRUE" };

static const char *reg0[] =
{ "AX0", "AX1", "MX0", "MX1", "AY0", "AY1", "MY0", "MY1", "MR2", "SR2", "AR", "SI", "MR1", "SR1", "MR0", "SR0" };

static const char *reg1[] =
{ "I0", "I1", "I2", "I3", "M0", "M1", "M2", "M3", "L0", "L1", "L2", "L3", "IMASK", "IRPTL", "ICNTL", "CNTR" };

static const char *reg2[] =
{ "I4", "I5", "I6", "I7", "M4", "M5", "M6", "M7", "L4", "L5", "L6", "L7", "STACKA", "LPCSTACKA", "RES", "RES" };

static const char *reg3[] =
{ "ASTAT", "MSTAT", "SSTAT", "LPSTACKP", "CCODE", "SE", "SB", "PX", "DMPG1", "DMPG2", "IOPG", "IJPG", "RES", "RES", "RES", "STACKP" };

static const char *amf_mac[] =
{ "NOP", "X*Y (RND)", "MR+X*Y (RND)", "MR-X*Y (RND)", "X*Y (SS)", "X*Y (SU)", "X*Y (US)", "X*Y (UU)",
  "MR+X*Y (SS)", "MR+X*Y (SU)", "MR+X*Y (US)", "MR+X*Y (UU)", "MR-X*Y (SS)", "MR-X*Y (SU)", "MR-X*Y (US)", "MR-X*Y (UU)" };

static const char *amf_alu[] =
{ "Y", "Y+1", "X+Y+C", "X+Y", "NOT Y", "-Y", "X-Y+C-1", "X-Y", "Y-1", "Y-X", "Y-X+C-1", "NOT X", "X AND Y", "X OR Y", "X XOR Y", "ABS X" };

static const char *sf_names[] =
{ "LSHIFT (HI)", "LSHIFT (HI, OR)", "LSHIFT (LO)", "LSHIFT (LO, OR)", "ASHIFT (HI)", "ASHIFT (HI, OR)", "ASHIFT (LO)", "ASHIFT (LO, OR)",
  "NORM (HI)", "NORM (HI, OR)", "NORM (LO)", "NORM (LO, OR)", "EXP (HI)", "EXP (HIX)", "EXP (LO)", "Derive Block Exponent" };

static const char *xop_alu[] = { "AX0", "AX1", "AR", "MR0", "MR1", "MR2", "SR0", "SR1" };
static const char *xop_mac[] = { "MX0", "MX1", "AR", "MR0", "MR1", "MR2", "SR0", "SR1" };
static const char *xop_shift[] = { "SI", "SR2", "AR", "MR0", "MR1", "MR2", "SR0", "SR1" };
static const char *yop_alu[] = { "AY0", "AY1", "AF", "0" };
static const char *yop_mac[] = { "MY0", "MY1", "SR1", "0" };

/* --- Helpers --- */

static const char *
get_reg (int gp, int idx)
{
  switch (gp & 0x3)
    {
    case 0: return reg0[idx & 0xF];
    case 1: return reg1[idx & 0xF];
    case 2: return reg2[idx & 0xF];
    case 3: return reg3[idx & 0xF];
    }
  return "??";
}

/* --- Decoder --- */

static bool
decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
{
  ut32 ins, ins2 = 0;
  const ut8 *b = op->bytes;
  int avail = op->size;
  if (avail < 3) return false;

  ins = ((ut32) b[0] << 16) | ((ut32) b[1] << 8) | (ut32) b[2];
  if (avail >= 6)
    ins2 = ((ut32) b[3] << 16) | ((ut32) b[4] << 8) | (ut32) b[5];
  op->size = 3;
  op->type = R_ANAL_OP_TYPE_UNK;
  if (!(mask & R_ARCH_OP_MASK_DISASM)) return true;

  /* Priority check: High bits */
  ut32 b23_22 = (ins >> 22) & 0x3;
  ut32 b21_20 = (ins >> 20) & 0x3;

  /* Type 30/31: NOP / IDLE */
  if ((ins >> 16) == 0x00)
    {
      if (ins == 0) { op->mnemonic = strdup ("NOP"); op->type = R_ANAL_OP_TYPE_NOP; }
      else if ((ins >> 8) == 0x02) { op->mnemonic = r_str_newf ("IDLE 0x%X", ins & 0xFF); op->type = R_ANAL_OP_TYPE_TRAP; }
      return true;
    }

  /* Type 1: Compute | DM | PM (11xxxx) */
  /* PD=bits21-20, DD=bits19-18, AMF=bits17-13, YOP=bits12-11,
     XOP=bits10-8, PMI=bits7-6, PMM=bits5-4, DMI=bits3-2, DMM=bits1-0 */
  if (b23_22 == 3)
    {
      ut32 amf = (ins >> 13) & 0x1F;
      const char *f = (amf < 16) ? amf_mac[amf] : amf_alu[amf - 16];
      int dmi = (ins >> 2) & 3, dmm = ins & 3;
      int pmi = (ins >> 6) & 3, pmm = (ins >> 4) & 3;
      int dd = (ins >> 18) & 3, pd = (ins >> 20) & 3;
      op->mnemonic = r_str_newf ("%s, %s=DM(I%d+=M%d), %s=PM(I%d+=M%d)",
                                 f, reg0[dd], dmi, dmm,
                                 reg0[pd + 4], pmi + 4, pmm + 4);
      return true;
    }

  /* Type 3: Direct Memory (10xxxx)
     bit21: 1=Ireg/Mreg, 0=Dreg.  D=bit20: 0=read, 1=write.
     Addr16=bits19-4.  Reg=bits3-0. DM only (no PM). */
  if (b23_22 == 2)
    {
      ut32 d = (ins >> 20) & 1;
      ut32 addr = (ins >> 4) & 0xFFFF;
      ut32 reg = ins & 0xF;
      const char *rname = ((ins >> 21) & 1)
        ? reg1[reg] : reg0[reg];
      if (d)
        op->mnemonic = r_str_newf ("DM(0x%04X) = %s",
                                   addr, rname);
      else
        op->mnemonic = r_str_newf ("%s = DM(0x%04X)",
                                   rname, addr);
      return true;
    }

  /* Type 4: Compute | DM/PM Postmodify (011xxx)
     G=bit20 (0=DM/DAG1, 1=PM/DAG2), D=bit19 (0=read, 1=write),
     Z=bit18, AMF=bits17-13, YOP=bits12-11, XOP=bits10-8,
     DREG=bits7-4, I=bits3-2, M=bits1-0. */
  if ((ins >> 21) == 0x3)
    {
      ut32 g = (ins >> 20) & 1;
      ut32 d = (ins >> 19) & 1;
      ut32 amf = (ins >> 13) & 0x1F;
      const char *f = (amf < 16) ? amf_mac[amf] : amf_alu[amf - 16];
      ut32 dreg = (ins >> 4) & 0xF;
      ut32 ireg = ((ins >> 2) & 3) | (g ? 4 : 0);
      ut32 mreg = (ins & 3) | (g ? 4 : 0);
      char mem = g ? 'P' : 'D';
      if (d)
        op->mnemonic = r_str_newf ("%s, %cM(I%d += M%d) = %s",
                                   f, mem, ireg, mreg,
                                   reg0[dreg]);
      else
        op->mnemonic = r_str_newf ("%s, %s = %cM(I%d += M%d)",
                                   f, reg0[dreg], mem,
                                   ireg, mreg);
      return true;
    }

  /* Type 9/9a: Compute (bits 23-19 = 00100)
     Z=bit18, AMF=bits17-13.
     Five sub-encodings distinguished by lower bits:
       9a register-file: bits5-4 = 10
       9  standard:      bits7-4 = 0000, bits12-11 != 11
       9  YOP=0:         bits7-4 = 0000, bits12-11 = 11
       9  MAC square:    bits12-11 = 00, bit4 = 1, bits7-5 = 000
       9  constant YOP:  otherwise */
  if ((ins >> 19) == 0x04)
    {
      ut32 amf = (ins >> 13) & 0x1F;
      ut32 xop_i = (ins >> 8) & 0x7;
      const char *f;
      const char *dst;
      const char *x;

      /* Select AMF mnemonic and XOP table based on operation */
      if (amf < 16)
        {
          f = amf_mac[amf];
          dst = "MR";
          x = xop_mac[xop_i];
        }
      else
        {
          f = amf_alu[amf - 16];
          dst = "AR";
          x = xop_alu[xop_i];
        }

      /* Type 9a: register-file form (bits5-4 = 10)
         XREG=bits11-8 (4 bits), YREG=bits3-0 (4 bits),
         both index into reg0[]. */
      if (((ins >> 4) & 0x3) == 0x2)
        {
          ut32 xreg = (ins >> 8) & 0xF;
          ut32 yreg = ins & 0xF;
          op->mnemonic = r_str_newf ("%s = %s(%s, %s)",
                                     dst, f, reg0[xreg],
                                     reg0[yreg]);
          return true;
        }

      ut32 cond = ins & 0xF;
      ut32 yop_i = (ins >> 11) & 0x3;
      const char *cp = (cond == 15) ? "" : "IF ";
      const char *cs = (cond == 15) ? "" : cond_str[cond];
      const char *sp = (cond == 15) ? "" : " ";

      /* MAC squaring: bits12-11 = 00, bit4 = 1, bits7-5 = 000 */
      if (yop_i == 0 && ((ins >> 4) & 0xF) == 0x1)
        {
          op->mnemonic = r_str_newf ("%s%s%s%s = %s(%s^2)",
                                     cp, cs, sp, dst, f, x);
          return true;
        }

      /* YOP=0 form: bits12-11 = 11, bits7-4 = 0000 */
      if (yop_i == 3 && ((ins >> 4) & 0xF) == 0)
        {
          op->mnemonic = r_str_newf ("%s%s%s%s = %s(%s, 0)",
                                     cp, cs, sp, dst, f, x);
          return true;
        }

      /* Standard form: bits7-4 = 0000 */
      if (((ins >> 4) & 0xF) == 0)
        {
          const char *y = (amf < 16) ? yop_mac[yop_i]
                                     : yop_alu[yop_i];
          op->mnemonic = r_str_newf ("%s%s%s%s = %s(%s, %s)",
                                     cp, cs, sp, dst, f, x, y);
          return true;
        }

      /* Constant YOP form: YY=bits12-11, CC=bits7-6, BO=bits5-4.
         Only BO=01 and BO=11 are valid encodings.
         Index = (YY<<2)|CC.  BO=01: val = 1<<idx (idx<15)
         or -32768 (idx=15).  BO=11: val = -(1<<idx)-1
         or +32767 (idx=15). */
      {
        ut32 bo = (ins >> 4) & 0x3;
        if (bo == 1 || bo == 3)
          {
            ut32 yy = (ins >> 11) & 0x3;
            ut32 cc_bits = (ins >> 6) & 0x3;
            ut32 idx = (yy << 2) | cc_bits;
            int32_t val;
            if (idx == 15)
              val = (bo == 3) ? 32767 : -32768;
            else if (bo == 3)
              val = -((int32_t)(1 << idx)) - 1;
            else
              val = (int32_t)(1 << idx);
            op->mnemonic = r_str_newf (
              "%s%s%s%s = %s(%s, %d)",
              cp, cs, sp, dst, f, x, val);
            return true;
          }
        /* BO=00 or BO=10 with non-zero lower bits:
           not a valid Type 9 encoding, fall through. */
      }
    }

  /* Type 8: Compute | Dreg1 <- Dreg2 (00101Z)
     Z=bit18, AMF=bits17-13, YOP=bits12-11,
     XOP=bits10-8, DDREG=bits7-4, SDREG=bits3-0.
     NONE pattern: bits7-0 = 10101010 = 0xAA. */
  if ((ins >> 19) == 0x05)
    {
      ut32 amf = (ins >> 13) & 0x1F;
      ut32 yop_i = (ins >> 11) & 0x3;
      ut32 xop_i = (ins >> 8) & 0x7;
      ut32 ddreg = (ins >> 4) & 0xF;
      ut32 sdreg = ins & 0xF;
      const char *f;
      const char *x;
      const char *y;
      /* AMF < 16 = MAC op, AMF >= 16 = ALU op.
         XOP/YOP tables follow the operation type, not Z. */
      if (amf < 16)
        {
          f = amf_mac[amf];
          x = xop_mac[xop_i];
          y = yop_mac[yop_i];
        }
      else
        {
          f = amf_alu[amf - 16];
          x = xop_alu[xop_i];
          y = yop_alu[yop_i];
        }
      /* NONE = ALU/MAC: bits 7-0 == 0xAA (10101010) */
      if ((ins & 0xFF) == 0xAA)
        op->mnemonic = r_str_newf ("NONE = %s(%s, %s)",
                                   f, x, y);
      else
        op->mnemonic = r_str_newf ("%s(%s, %s), %s = %s",
                                   f, x, y,
                                   reg0[ddreg], reg0[sdreg]);
      return true;
    }

  /* Type 6/7/IO/System (010xxx / 011xxx) */
  if (b23_22 == 1)
    {
      if (b21_20 == 0) { op->mnemonic = r_str_newf ("%s = 0x%04X", reg0[ins&0xF], (ins>>4)&0xFFFF); return true; } /* Type 6 */
      if (b21_20 == 1) { op->mnemonic = r_str_newf ("%s = 0x%04X", reg1[ins&0xF], (ins>>4)&0xFFFF); return true; } /* Type 7 */
      /* Type 34 and 35 are in the b23_22==0 block below */
    }

  /* Type 8/9/10/11/17... (00xxxx) */
  if (b23_22 == 0)
    {
      if (b21_20 == 3) { op->mnemonic = r_str_newf ("%s = 0x%04X", reg2[ins&0xF], (ins>>4)&0xFFFF); return true; } /* Type 7 (Reg2) */

      /* Type 36: Long Jump/Call (2-word, bits 23-16 = 00000101) */
      if ((ins >> 16) == 0x05)
        {
          if (avail < 6)
            {
              op->mnemonic = r_str_newf ("trunc L%s 0x%06X",
                ((ins >> 12) & 1) ? "CALL" : "JUMP", ins);
              return true;
            }
          ut32 s_bit = (ins >> 12) & 1;
          ut32 cond = ins & 0xF;
          ut32 addr_hi = (ins >> 4) & 0xFF;
          ut32 addr_lo = ins2 & 0xFFFFFF;
          ut32 addr = (addr_hi << 16) | (addr_lo & 0xFFFF);
          /* 24-bit absolute address for long jump/call */
          op->size = 6;
          if (cond == 15)
            op->mnemonic = r_str_newf ("%s 0x%06X",
              s_bit ? "LCALL" : "LJUMP", addr);
          else
            op->mnemonic = r_str_newf ("IF %s %s 0x%06X",
              cond_str[cond],
              s_bit ? "LCALL" : "LJUMP", addr);
          op->type = s_bit ? R_ANAL_OP_TYPE_CALL
                          : R_ANAL_OP_TYPE_JMP;
          op->jump = addr;
          return true;
        }

      /* Type 22: DM/PM = Data16 (2-word, bits 23-13 = 00000011110) */
      if ((ins >> 13) == 0x1E && !((ins >> 12) & 1))
        {
          if (avail < 6)
            {
              op->mnemonic = r_str_newf ("trunc DM_IMM16 0x%06X",
                                         ins);
              return true;
            }
          ut32 g = (ins >> 12) & 1;
          ut32 ireg = (ins >> 2) & 0x3;
          ut32 mreg = ins & 0x3;
          ut32 data_lo = (ins >> 4) & 0xFF;
          ut32 data_hi = (ins2 >> 16) & 0xFF;
          ut32 data = (data_hi << 8) | data_lo;
          int base = g ? 4 : 0;
          op->size = 6;
          op->mnemonic = r_str_newf ("%cM(I%d += M%d) = 0x%04X",
            g ? 'P' : 'D', ireg + base, mreg + base, data);
          return true;
        }

      /* Type 22a: PM = Data24 (2-word, bits 23-13 = 00000011111) */
      if ((ins >> 13) == 0x1F)
        {
          if (avail < 6)
            {
              op->mnemonic = r_str_newf ("trunc PM_IMM24 0x%06X",
                                         ins);
              return true;
            }
          ut32 g = (ins >> 12) & 1;
          ut32 ireg = (ins >> 2) & 0x3;
          ut32 mreg = ins & 0x3;
          ut32 data_mid = (ins >> 4) & 0xFF;
          ut32 data_hi = (ins2 >> 16) & 0xFF;
          ut32 data_lo = (ins2 >> 8) & 0xFF;
          ut32 data = (data_hi << 16) | (data_mid << 8) | data_lo;
          int base = g ? 4 : 0;
          op->size = 6;
          op->mnemonic = r_str_newf ("PM(I%d += M%d) = 0x%06X:24",
            ireg + base, mreg + base, data);
          return true;
        }
      /* Type 10a: bits 23-18 = 000111 (unconditional 16-bit rel) */
      if ((ins >> 18) == 0x07)
        {
          /* addr = bits 17-4 (14 bits) | bits 1-0 (2 MSBs) */
          ut32 rel = ((ins >> 4) & 0x3FFF) | ((ins & 0x3) << 14);
          ut32 b_bit = (ins >> 3) & 1;
          ut32 s_bit = (ins >> 2) & 1;
          /* Sign-extend 16-bit relative offset */
          int32_t srel = (int32_t)(int16_t) rel;
          ut64 target = op->addr + (srel * 3);
          op->mnemonic = r_str_newf ("%s 0x%06" PFMT64x "%s",
                                     s_bit ? "CALL" : "JUMP",
                                     target,
                                     b_bit ? " (DB)" : "");
          op->type = s_bit ? R_ANAL_OP_TYPE_CALL
                          : R_ANAL_OP_TYPE_JMP;
          op->jump = target;
          return true;
        }
      /* Type 10: bits 23-19 = 00011, bit18 = 0 (conditional 13-bit rel) */
      if ((ins >> 19) == 0x03 && !((ins >> 18) & 1))
        {
          ut32 b_bit = (ins >> 17) & 1;
          ut32 rel = (ins >> 4) & 0x1FFF;
          ut32 cond = ins & 0xF;
          /* Sign-extend 13-bit relative offset */
          int32_t srel = (rel & 0x1000)
            ? (int32_t)(rel | 0xFFFFE000) : (int32_t) rel;
          ut64 target = op->addr + (srel * 3);
          if (cond == 15)
            op->mnemonic = r_str_newf ("JUMP 0x%06" PFMT64x "%s",
                                       target,
                                       b_bit ? " (DB)" : "");
          else
            op->mnemonic = r_str_newf ("IF %s JUMP 0x%06" PFMT64x "%s",
                                       cond_str[cond], target,
                                       b_bit ? " (DB)" : "");
          op->type = R_ANAL_OP_TYPE_CJMP;
          op->jump = target;
          return true;
        }
      /* Type 17: Reg = Reg (bits 23-16 = 00001101) */
      if ((ins >> 16) == 0x0D)
        {
          op->mnemonic = r_str_newf ("%s = %s",
            get_reg ((ins >> 10) & 3, (ins >> 4) & 0xF),
            get_reg ((ins >> 8) & 3, ins & 0xF));
          return true;
        }

      /* Type 20: RTS/RTI (bits 23-16 = 00001010) */
      if ((ins >> 16) == 0x0A)
        {
          ut32 b_bit = (ins >> 15) & 1;
          ut32 t_bit = (ins >> 14) & 1;
          ut32 cond = (ins >> 4) & 0xF;
          const char *ret = t_bit ? "RTI" : "RTS";
          if (cond == 15)
            op->mnemonic = r_str_newf ("%s%s", ret,
                                       b_bit ? " (DB)" : "");
          else
            op->mnemonic = r_str_newf ("IF %s %s%s",
                                       cond_str[cond], ret,
                                       b_bit ? " (DB)" : "");
          op->type = R_ANAL_OP_TYPE_RET;
          return true;
        }

      /* Type 12: Shift | DM/PM (bits 23-17 = 0001001) */
      if ((ins >> 17) == 0x09)
        {
          ut32 g = (ins >> 16) & 1;
          ut32 sf = (ins >> 13) & 0x7;
          ut32 d = (ins >> 12) & 1;
          ut32 xop_i = (ins >> 9) & 0x7;
          ut32 dreg = (ins >> 4) & 0xF;
          ut32 ireg = (ins >> 2) & 0x3;
          ut32 mreg = ins & 0x3;
          int base = g ? 4 : 0;
          if (d)
            op->mnemonic = r_str_newf ("%s %s, %cM(I%d += M%d) = %s",
              sf_names[sf], xop_shift[xop_i],
              g ? 'P' : 'D', ireg + base, mreg + base,
              reg0[dreg]);
          else
            op->mnemonic = r_str_newf ("%s %s, %s = %cM(I%d += M%d)",
              sf_names[sf], xop_shift[xop_i],
              reg0[dreg],
              g ? 'P' : 'D', ireg + base, mreg + base);
          return true;
        }

      /* Type 14: Shift | Dreg move (bits 23-16 = 00010100) */
      if ((ins >> 16) == 0x14)
        {
          ut32 sf = (ins >> 12) & 0x7;
          ut32 xreg = (ins >> 9) & 0x7;
          ut32 ddreg = (ins >> 4) & 0xF;
          ut32 sdreg = ins & 0xF;
          op->mnemonic = r_str_newf ("%s %s, %s = %s",
            sf_names[sf], xop_shift[xreg],
            reg0[ddreg], reg0[sdreg]);
          return true;
        }

      /* Type 16: Conditional Shift (bits 23-16 = 00001110) */
      if ((ins >> 16) == 0x0E)
        {
          ut32 sf = (ins >> 12) & 0xF;
          ut32 xreg = (ins >> 8) & 0x7;
          ut32 cond = ins & 0xF;
          if (cond == 15)
            op->mnemonic = r_str_newf ("SR = %s %s",
              sf_names[sf], xop_shift[xreg]);
          else
            op->mnemonic = r_str_newf ("IF %s SR = %s %s",
              cond_str[cond], sf_names[sf],
              xop_shift[xreg]);
          return true;
        }

      /* Type 11: DO UNTIL (bits 23-16 = 00010110, 12-bit rel) */
      if ((ins >> 16) == 0x16)
        {
          ut32 rel = (ins >> 4) & 0xFFF;
          ut32 term = ins & 0xF;
          int32_t srel = (rel & 0x800)
            ? (int32_t)(rel | 0xFFFFF000) : (int32_t) rel;
          ut64 target = op->addr + (srel * 3);
          op->mnemonic = r_str_newf (
            "DO 0x%06" PFMT64x " UNTIL %s",
            target, cond_str[term]);
          return true;
        }

      /* Type 15: Shift Imm (bits 23-16 = 00001111) */
      if ((ins >> 16) == 0x0F)
        {
          ut32 sf = (ins >> 12) & 0xF;
          ut32 xreg = (ins >> 8) & 0xF;
          int8_t exp = (int8_t)(ins & 0xFF);
          op->mnemonic = r_str_newf ("SR = %s %s BY %d",
                                     sf_names[sf], reg0[xreg],
                                     exp);
          return true;
        }

      /* Type 18: Mode Control (bits 23-16 = 00001100) */
      if ((ins >> 16) == 0x0C)
        {
          static const char *mbits[] =
            { "INT", "SD", "SR", "BR", "OL", "AS", "MM", "TI" };
          char buf[128];
          int pos = 0, i;
          for (i = 0; i < 8; i++)
            {
              ut32 v = (ins >> (i * 2)) & 0x3;
              if (v == 2)
                pos += snprintf (buf + pos,
                  sizeof (buf) - pos, "%sDIS %s",
                  pos ? ", " : "", mbits[i]);
              else if (v == 3)
                pos += snprintf (buf + pos,
                  sizeof (buf) - pos, "%sENA %s",
                  pos ? ", " : "", mbits[i]);
            }
          op->mnemonic = pos ? strdup (buf)
                             : strdup ("MODE NOP");
          return true;
        }

      /* Type 25: SAT MR/SR (bits 23-14 = 0000001100) */
      if ((ins >> 14) == 0x0C)
        {
          ut32 r = (ins >> 13) & 1;
          op->mnemonic = r_str_newf ("SAT %s",
                                     r ? "SR" : "MR");
          return true;
        }

      /* Type 33: Reg3 = Data12 (bits 23-16 = 00010000) */
      if ((ins >> 16) == 0x10)
        {
          ut32 data = (ins >> 4) & 0xFFF;
          ut32 reg = ins & 0xF;
          op->mnemonic = r_str_newf ("%s = 0x%03X",
                                     reg3[reg], data);
          return true;
        }

      /* Type 19: Indirect Jump/Call (bits 23-16 = 00001011) */
      if ((ins >> 16) == 0x0B)
        {
          ut32 b_bit = (ins >> 15) & 1;
          ut32 s_bit = (ins >> 14) & 1;
          ut32 g = (ins >> 13) & 1;
          ut32 cond = (ins >> 4) & 0xF;
          ut32 ireg = ins & 0x3;
          int base = g ? 4 : 0;
          if (cond == 15)
            op->mnemonic = r_str_newf ("%s (I%d)%s",
              s_bit ? "CALL" : "JUMP",
              ireg + base,
              b_bit ? " (DB)" : "");
          else
            op->mnemonic = r_str_newf ("IF %s %s (I%d)%s",
              cond_str[cond],
              s_bit ? "CALL" : "JUMP",
              ireg + base,
              b_bit ? " (DB)" : "");
          op->type = s_bit ? R_ANAL_OP_TYPE_CALL
                          : R_ANAL_OP_TYPE_JMP;
          return true;
        }

      /* Type 21: MODIFY (bits 23-16 = 00000001, bit15=1) */
      if ((ins >> 16) == 0x01 && ((ins >> 15) & 1))
        {
          ut32 g = (ins >> 12) & 1;
          ut32 ireg = ins & 0x3;
          ut32 mreg = (ins >> 2) & 0x3;
          int base = g ? 4 : 0;
          op->mnemonic = r_str_newf ("MODIFY(I%d += M%d)",
                                     ireg + base, mreg + base);
          return true;
        }

      /* Type 23: DIVQ (bits 23-12 = 000000111101) */
      if ((ins >> 12) == 0x03D)
        {
          ut32 xop_i = (ins >> 8) & 0x7;
          op->mnemonic = r_str_newf ("DIVQ %s",
                                     xop_alu[xop_i]);
          return true;
        }

      /* Type 24: DIVS (bits 23-13 = 00000011100) */
      if ((ins >> 13) == 0x1C)
        {
          ut32 yop_i = (ins >> 11) & 0x3;
          ut32 xop_i = (ins >> 8) & 0x7;
          op->mnemonic = r_str_newf ("DIVS %s, %s",
                                     yop_alu[yop_i],
                                     xop_alu[xop_i]);
          return true;
        }

      /* Type 26: Push/Pop/Cache (bits 23-16 = 00001000) */
      if ((ins >> 16) == 0x08)
        {
          ut32 cf = (ins >> 7) & 1;
          ut32 ppp = (ins >> 4) & 0x7;
          ut32 lpp = (ins >> 2) & 0x3;
          ut32 spp = ins & 0x3;
          char buf[64];
          int pos = 0;
          if (cf)
            pos += snprintf (buf + pos,
              sizeof (buf) - pos, "FLUSH CACHE");
          if (ppp == 1)
            pos += snprintf (buf + pos,
              sizeof (buf) - pos, "%sPUSH PC",
              pos ? ", " : "");
          else if (ppp == 2)
            pos += snprintf (buf + pos,
              sizeof (buf) - pos, "%sPOP PC",
              pos ? ", " : "");
          if (lpp == 1)
            pos += snprintf (buf + pos,
              sizeof (buf) - pos, "%sPUSH LOOP",
              pos ? ", " : "");
          else if (lpp == 2)
            pos += snprintf (buf + pos,
              sizeof (buf) - pos, "%sPOP LOOP",
              pos ? ", " : "");
          if (spp == 1)
            pos += snprintf (buf + pos,
              sizeof (buf) - pos, "%sPUSH STS",
              pos ? ", " : "");
          else if (spp == 2)
            pos += snprintf (buf + pos,
              sizeof (buf) - pos, "%sPOP STS",
              pos ? ", " : "");
          op->mnemonic = pos ? strdup (buf)
                             : strdup ("STACK NOP");
          return true;
        }

      /* Type 29: Dreg <-> DM imm modify (bits 23-18 = 000010)
         Always DM. G=bit13 selects DAG group (I0-3 vs I4-7),
         not the memory bus.  U=bit16 (post/pre), D=bit12 (r/w),
         DRU=bits15-14, DRL=bits1-0, DREG=(DRU<<2)|DRL,
         MOD=bits11-4 (signed), I=bits3-2. */
      if ((ins >> 18) == 0x02)
        {
          ut32 u = (ins >> 16) & 1;
          ut32 dru = (ins >> 14) & 0x3;
          ut32 g = (ins >> 13) & 1;
          ut32 d = (ins >> 12) & 1;
          ut32 mod = (ins >> 4) & 0xFF;
          ut32 ireg = (ins >> 2) & 0x3;
          ut32 drl = ins & 0x3;
          ut32 dreg = (dru << 2) | drl;
          int8_t smod = (int8_t) mod;
          int base = g ? 4 : 0;
          const char *op_str = u ? "+=" : "+";
          if (d)
            op->mnemonic = r_str_newf ("DM(I%d %s %d) = %s",
              ireg + base, op_str, smod,
              reg0[dreg]);
          else
            op->mnemonic = r_str_newf ("%s = DM(I%d %s %d)",
              reg0[dreg], ireg + base, op_str,
              smod);
          return true;
        }

      /* Type 32: Any Reg <-> DM/PM (bits 23-17 = 0001010, bit11=0) */
      if ((ins >> 17) == 0x0A && !((ins >> 11) & 1))
        {
          ut32 ms = (ins >> 15) & 1;
          ut32 u_bit = (ins >> 14) & 1;
          ut32 g = (ins >> 13) & 1;
          ut32 d = (ins >> 12) & 1;
          ut32 rgp = (ins >> 8) & 0x3;
          ut32 reg = (ins >> 4) & 0xF;
          ut32 ireg = (ins >> 2) & 0x3;
          ut32 mreg = ins & 0x3;
          int base = g ? 4 : 0;
          /* MS selects bus (0=DM 16-bit, 1=PM 24-bit).
             G selects DAG group only. */
          const char *mem = ms ? "PM" : "DM";
          const char *mod = u_bit ? "+=" : "+";
          const char *rname = get_reg (rgp, reg);
          if (d)
            op->mnemonic = r_str_newf ("%s(I%d %s M%d) = %s",
              mem, ireg + base, mod, mreg + base,
              rname);
          else
            op->mnemonic = r_str_newf ("%s = %s(I%d %s M%d)",
              rname, mem, ireg + base, mod,
              mreg + base);
          return true;
        }

      /* Type 32a: DAG reg store + register transfer.
         bits23-17=0001010, bit15=0, bits12-11=11, bit10=0.
         U=bit14, G=bit13, RGP=bits9-8, DAG_REG=bits7-4,
         I=bits3-2, M=bits1-0.
         Syntax: DM(Ireg1 op Mreg1) = DAGreg, DAGreg = Ireg1 */
      if ((ins >> 17) == 0x0A
          && !((ins >> 15) & 1)
          && ((ins >> 11) & 0x3) == 0x3
          && !((ins >> 10) & 1))
        {
          ut32 u_bit = (ins >> 14) & 1;
          ut32 g = (ins >> 13) & 1;
          ut32 rgp = (ins >> 8) & 0x3;
          ut32 dag_reg = (ins >> 4) & 0xF;
          ut32 ireg = (ins >> 2) & 0x3;
          ut32 mreg = ins & 0x3;
          int base = g ? 4 : 0;
          const char *mod = u_bit ? "+=" : "+";
          const char *rname = get_reg (rgp, dag_reg);
          op->mnemonic = r_str_newf (
            "DM(I%d %s M%d) = %s, %s = I%d",
            ireg + base, mod, mreg + base,
            rname, rname, ireg + base);
          return true;
        }

      /* Type 21a: MODIFY imm (bits 23-16 = 00000001, bit15=0) */
      if ((ins >> 16) == 0x01 && !((ins >> 15) & 1))
        {
          ut32 g = (ins >> 12) & 1;
          ut32 ireg = ins & 0x3;
          int8_t mod = (int8_t)((ins >> 4) & 0xFF);
          int base = g ? 4 : 0;
          op->mnemonic = r_str_newf ("MODIFY(I%d += %d)",
                                     ireg + base, mod);
          return true;
        }

      /* Type 34: IO reg (bits 23-16 = 00000110, bit15=1) */
      if ((ins >> 16) == 0x06 && ((ins >> 15) & 1))
        {
          ut32 d = (ins >> 12) & 1;
          ut32 addr_hi = (ins >> 13) & 0x3;
          ut32 addr = (addr_hi << 8) | ((ins >> 4) & 0xFF);
          ut32 dreg = ins & 0xF;
          if (d)
            op->mnemonic = r_str_newf ("IO(0x%03X) = %s",
                                       addr, reg0[dreg]);
          else
            op->mnemonic = r_str_newf ("%s = IO(0x%03X)",
                                       reg0[dreg], addr);
          return true;
        }
      /* Type 35: Sys ctrl reg (bits 23-16 = 00000110, bit15=0) */
      if ((ins >> 16) == 0x06 && !((ins >> 15) & 1))
        {
          ut32 d = (ins >> 12) & 1;
          ut32 addr = (ins >> 4) & 0xFF;
          ut32 dreg = ins & 0xF;
          if (d)
            op->mnemonic = r_str_newf ("REG(0x%02X) = %s",
                                       addr, reg0[dreg]);
          else
            op->mnemonic = r_str_newf ("%s = REG(0x%02X)",
                                       reg0[dreg], addr);
          return true;
        }

      /* Type 37: SETINT/CLRINT (bits 23-15 = 000001110) */
      if ((ins >> 15) == 0x0E)
        {
          ut32 c = (ins >> 5) & 1;
          ut32 bit = ins & 0xF;
          op->mnemonic = r_str_newf ("%s %d",
            c ? "CLRINT" : "SETINT", bit);
          return true;
        }
    }

  op->mnemonic = r_str_newf ("unk 0x%06X", ins);
  return true;
}

static int archinfo (RArchSession *s, ut32 q)
{
  switch (q) {
  case R_ARCH_INFO_CODE_ALIGN: return 3;
  case R_ARCH_INFO_MINOP_SIZE: case R_ARCH_INFO_MAXOP_SIZE: return 3;
  default: return -1;
  }
}

const RArchPlugin r_arch_plugin_adsp219x = {
  .meta = { .name = "adsp219x", .author = "Dr. Christian Giessen", .desc = "ADSP-219x Master Plugin", .license = "LGPL-3.0-only" },
  .arch = "adsp219x", .bits = R_SYS_BITS_PACK(24), .endian = R_SYS_ENDIAN_BIG, .info = archinfo, .decode = (RArchPluginDecodeCallback)decode,
};

#ifndef R2_PLUGIN_INCORE
R_API RLibStruct radare_plugin = { .type = R_LIB_TYPE_ARCH, .data = (void *)&r_arch_plugin_adsp219x, .version = R2_VERSION };
#endif
