librz/arch/tms320: add TMS320C2x disassembler and analysis

Add support for the legacy TMS320C2x (C25-class) fixed-point DSP family
to the shared C55 decode-IR engine: the C2x opcode table, operand
extractors, mnemonic and op-type tables, and the register profile, wired
into the tms320 asm and analysis plugins under cpu "c2x". The decoder
fills the shared C55 instruction representation so the common formatter
and analysis filler apply unchanged. Includes disassembly and opcode
classification tests, plus a COFF-loading test exercising the new bin
autodetect.
This commit is contained in:
Anton Kochkov 2026-06-29 21:12:39 +00:00 committed by NOT XVilka
parent 68471f8736
commit 7b36a031ca
17 changed files with 1455 additions and 20 deletions

View file

@ -0,0 +1,620 @@
// SPDX-FileCopyrightText: 2026 RizinOrg <info@rizin.re>
// SPDX-License-Identifier: LGPL-3.0-only
/**
* \file
* TMS320C2x (legacy single-accumulator fixed-point DSP, e.g. TMS320C25)
* disassembly + analysis, built on the shared C55 decode engine (c55_ir.[ch]).
*
* The C2x is a 16-bit word-addressed Harvard machine. Memory-reference
* instructions carry one addressing byte (the low 8 bits of the opcode word):
* bit 7 selects direct (0) or indirect (1) addressing.
* - Direct: bits 6-0 are a 7-bit data-page offset (dma); the effective
* address is (DP[8:0] << 7) | dma.
* - Indirect: bits 6-4 (M) select an auxiliary-register modification applied
* to the ARP-selected AR; bits 3-0 (N), when N>=8, reload ARP to
* N&7 (rendered ",arX"). M: 0=* 1=*- 2=*+ 4=*BR0- 5=*0- 6=*0+
* 7=*BR0+.
*
* Opcode bit patterns are transcribed from the public TMS320C2x instruction set
* (matching MAME's tested TMS320x25 disassembler). Words are stored MSB-first,
* so the engine's per-word byte-swap (words_le) is NOT used. mask/match operate
* on the engine's 4-byte MSB-first "head" (opcode word in head bits 31:16);
* operand extractors index the c55_pack() word (opcode word in the low 16 bits
* for a 2-byte instruction, the leading word in bits 31:16 for a 4-byte one).
*
* Validation status: written against the encoding reference and the engine
* contracts; NOT yet round-tripped through a built rz-asm or cross-checked
* against a hardware/reference disassembler. The F8..FF branch tail in
* particular (B/CALL/BANZ/BBZ/BBNZ/BIOZ opcodes) is reconstructed and should be
* verified before merge.
*/
#include "c2x.h"
// bitfield helper (c55_field is private to the engine)
static ut64 c2x_field(ut64 bits, ut8 lo, ut8 width) {
if (width >= 64) {
return bits >> lo;
}
return (bits >> lo) & (((ut64)1 << width) - 1);
}
// operand extractors
static const char *const c2x_indir_raw[8] = {
"*", "*-", "*+", "*?", "*br0-", "*0-", "*0+", "*br0+"
};
static const C55AddrMode c2x_indir_amode[8] = {
C55_AM_INDIRECT, C55_AM_POSTDEC, C55_AM_POSTINC, C55_AM_INDIRECT /* reserved */,
C55_AM_BITREV_SUB, C55_AM_POSTSUB, C55_AM_POSTADD, C55_AM_BITREV
};
static const char *const c2x_arx_raw[8] = {
"ar0", "ar1", "ar2", "ar3", "ar4", "ar5", "ar6", "ar7"
};
// Single data-memory operand: the byte at d->lo is the addressing byte. Direct
// addressing is represented structurally (C55_AM_DIRECT + 7-bit dma in disp) so
// the lifter can form its EA via c2x_ea(); indirect addressing is ARP-relative
// (no statically known AR), so it renders verbatim via raw and is opaque to IL.
RZ_IPI void c2x_x_mem(RZ_UNUSED const C55ArchDesc *a, ut64 bits, const C55OpDesc *d, C55Operand *out) {
ut8 b = (ut8)c2x_field(bits, d->lo, 8);
out->kind = C55_OP_MEM;
out->access = 16;
if (b & 0x80) {
ut8 m = (b >> 4) & 7;
out->amode = c2x_indir_amode[m];
out->reg.cls = C55_RC_AR; // ARP-selected at run time; placeholder index
out->reg.num = 0;
out->raw = c2x_indir_raw[m];
} else {
out->amode = C55_AM_DIRECT;
out->disp = (st32)(b & 0x7f);
}
}
// next-ARP nibble (N field): N>=8 reloads ARP to N&7, rendered ",arX".
RZ_IPI void c2x_x_nextarp(RZ_UNUSED const C55ArchDesc *a, ut64 bits, const C55OpDesc *d, C55Operand *out) {
ut8 n = (ut8)c2x_field(bits, d->lo, 4);
if (n & 0x8) {
out->kind = C55_OP_REG;
out->reg.cls = C55_RC_AR;
out->reg.num = n & 7;
out->width = 16;
out->raw = c2x_arx_raw[n & 7];
} else {
out->kind = C55_OP_NONE;
}
}
// shift count (T 4-bit / S 3-bit), rendered as an immediate.
RZ_IPI void c2x_x_shift(RZ_UNUSED const C55ArchDesc *a, ut64 bits, const C55OpDesc *d, C55Operand *out) {
out->kind = C55_OP_IMM;
out->imm = c2x_field(bits, d->lo, d->width);
out->width = 16;
}
// auxiliary-register number (R field) -> arX register operand.
RZ_IPI void c2x_x_reg(RZ_UNUSED const C55ArchDesc *a, ut64 bits, const C55OpDesc *d, C55Operand *out) {
out->kind = C55_OP_REG;
out->reg.cls = C55_RC_AR;
out->reg.num = (ut8)c2x_field(bits, d->lo, d->width);
out->width = 16;
out->raw = (out->reg.num < 8) ? c2x_arx_raw[out->reg.num] : NULL;
}
// immediate (D 8-bit / K small / W 16-bit / MPYK 13-bit); param 1 => signed.
RZ_IPI void c2x_x_imm(RZ_UNUSED const C55ArchDesc *a, ut64 bits, const C55OpDesc *d, C55Operand *out) {
out->kind = C55_OP_IMM;
out->imm = c2x_field(bits, d->lo, d->width);
out->width = d->width;
out->imm_signed = (d->param == 1);
}
// 16-bit absolute branch/call target carried in the trailing word.
RZ_IPI void c2x_x_branch(RZ_UNUSED const C55ArchDesc *a, ut64 bits, const C55OpDesc *d, C55Operand *out) {
out->kind = C55_OP_IMM;
out->imm = c2x_field(bits, d->lo, 16);
out->width = 16;
out->addr = true;
out->abs_target = true;
}
// decode table
#include "c2x_rowdefs.h"
static const C55InsnDef c2x_table[] = {
#include "c2x_core_rows.inc"
};
#include "c2x_rowundefs.h"
// register resolver
static const C55RegInfo c2x_reg_acc = { "acc", "acc", 32 };
static const C55RegInfo c2x_reg_t = { "t", "t", 16 };
static const C55RegInfo c2x_reg_dp = { "dp", "dp", 16 };
static const C55RegInfo c2x_reg_arp = { "arp", "arp", 16 };
static const C55RegInfo c2x_reg_p = { "p", "p", 32 };
static const C55RegInfo c2x_reg_pc = { "pc", "pc", 16 };
static const C55RegInfo c2x_reg_ar[8] = {
{ "ar0", "ar0", 16 }, { "ar1", "ar1", 16 }, { "ar2", "ar2", 16 }, { "ar3", "ar3", 16 },
{ "ar4", "ar4", 16 }, { "ar5", "ar5", 16 }, { "ar6", "ar6", 16 }, { "ar7", "ar7", 16 }
};
static const C55RegInfo c2x_reg_st[2] = { { "st0", "st0", 16 }, { "st1", "st1", 16 } };
RZ_IPI const C55RegInfo *c2x_reg_info(C55RegClass cls, ut8 num, RZ_UNUSED C55SubReg sub) {
switch (cls) {
case C55_RC_AC: return &c2x_reg_acc;
case C55_RC_T: return &c2x_reg_t;
case C55_RC_DP: return &c2x_reg_dp;
case C55_RC_ARP: return &c2x_reg_arp;
case C55_RC_AR: return num < 8 ? &c2x_reg_ar[num] : NULL;
case C55_RC_ST: return num < 2 ? &c2x_reg_st[num] : NULL;
// SPECIAL num 0 is the product register P, 1 is PC.
case C55_RC_SPECIAL: return num == 0 ? &c2x_reg_p : &c2x_reg_pc;
default: return NULL;
}
}
// id -> RzAnalysisOp type
RZ_IPI ut32 c2x_op_type(ut16 id) {
switch (id) {
case C2X_INS_NOP:
return RZ_ANALYSIS_OP_TYPE_NOP;
case C2X_INS_ADD:
case C2X_INS_ADDH:
case C2X_INS_ADDS:
case C2X_INS_ADDT:
case C2X_INS_ADDC:
case C2X_INS_ADDK:
case C2X_INS_ADLK:
case C2X_INS_APAC:
case C2X_INS_ADRK:
return RZ_ANALYSIS_OP_TYPE_ADD;
case C2X_INS_SUB:
case C2X_INS_SUBH:
case C2X_INS_SUBS:
case C2X_INS_SUBT:
case C2X_INS_SUBC:
case C2X_INS_SUBB:
case C2X_INS_SUBK:
case C2X_INS_SBLK:
case C2X_INS_SPAC:
case C2X_INS_SBRK:
case C2X_INS_NEG:
return RZ_ANALYSIS_OP_TYPE_SUB;
case C2X_INS_AND:
case C2X_INS_ANDK:
return RZ_ANALYSIS_OP_TYPE_AND;
case C2X_INS_OR:
case C2X_INS_ORK:
return RZ_ANALYSIS_OP_TYPE_OR;
case C2X_INS_XOR:
case C2X_INS_XORK:
return RZ_ANALYSIS_OP_TYPE_XOR;
case C2X_INS_CMPL:
return RZ_ANALYSIS_OP_TYPE_NOT;
case C2X_INS_SFL:
case C2X_INS_ROL:
return RZ_ANALYSIS_OP_TYPE_SHL;
case C2X_INS_SFR:
case C2X_INS_ROR:
return RZ_ANALYSIS_OP_TYPE_SHR;
case C2X_INS_MPY:
case C2X_INS_MPYK:
case C2X_INS_MPYA:
case C2X_INS_MPYS:
case C2X_INS_MPYU:
case C2X_INS_SQRA:
case C2X_INS_SQRS:
case C2X_INS_PAC:
case C2X_INS_MAC:
case C2X_INS_MACD:
return RZ_ANALYSIS_OP_TYPE_MUL;
case C2X_INS_LAC:
case C2X_INS_LACT:
case C2X_INS_LACK:
case C2X_INS_ZAC:
case C2X_INS_ZALH:
case C2X_INS_ZALS:
case C2X_INS_ZALR:
case C2X_INS_LAR:
case C2X_INS_LARK:
case C2X_INS_LRLK:
case C2X_INS_LALK:
case C2X_INS_LDP:
case C2X_INS_LDPK:
case C2X_INS_LT:
case C2X_INS_LTA:
case C2X_INS_LTD:
case C2X_INS_LTP:
case C2X_INS_LTS:
case C2X_INS_LST:
case C2X_INS_LST1:
case C2X_INS_LPH:
case C2X_INS_DMOV:
case C2X_INS_PSHD:
case C2X_INS_PUSH:
case C2X_INS_ABS:
return RZ_ANALYSIS_OP_TYPE_MOV;
case C2X_INS_SACL:
case C2X_INS_SACH:
case C2X_INS_SAR:
case C2X_INS_SST:
case C2X_INS_SST1:
case C2X_INS_SPL:
case C2X_INS_SPH:
case C2X_INS_POPD:
case C2X_INS_POP:
case C2X_INS_TBLW:
return RZ_ANALYSIS_OP_TYPE_STORE;
case C2X_INS_TBLR:
return RZ_ANALYSIS_OP_TYPE_LOAD;
case C2X_INS_IN:
case C2X_INS_OUT:
return RZ_ANALYSIS_OP_TYPE_IO;
case C2X_INS_B:
return RZ_ANALYSIS_OP_TYPE_JMP;
case C2X_INS_BACC:
return RZ_ANALYSIS_OP_TYPE_UJMP;
case C2X_INS_BV:
case C2X_INS_BGZ:
case C2X_INS_BLEZ:
case C2X_INS_BLZ:
case C2X_INS_BGEZ:
case C2X_INS_BNZ:
case C2X_INS_BZ:
case C2X_INS_BNV:
case C2X_INS_BBZ:
case C2X_INS_BBNZ:
case C2X_INS_BIOZ:
case C2X_INS_BANZ:
case C2X_INS_BC:
case C2X_INS_BNC:
return RZ_ANALYSIS_OP_TYPE_CJMP;
case C2X_INS_CALL:
return RZ_ANALYSIS_OP_TYPE_CALL;
case C2X_INS_CALA:
return RZ_ANALYSIS_OP_TYPE_UCALL;
case C2X_INS_RET:
return RZ_ANALYSIS_OP_TYPE_RET;
case C2X_INS_TRAP:
return RZ_ANALYSIS_OP_TYPE_TRAP;
default:
return RZ_ANALYSIS_OP_TYPE_NULL;
}
}
// id -> mnemonic (indexed by the C2X_INS_* id; unset ids read back "invalid")
static const char *const c2x_mnemonics[] = {
[C2X_INS_NOP] = "nop",
[C2X_INS_ADD] = "add",
[C2X_INS_ADDH] = "addh",
[C2X_INS_ADDS] = "adds",
[C2X_INS_ADDT] = "addt",
[C2X_INS_ADDC] = "addc",
[C2X_INS_SUB] = "sub",
[C2X_INS_SUBH] = "subh",
[C2X_INS_SUBS] = "subs",
[C2X_INS_SUBT] = "subt",
[C2X_INS_SUBC] = "subc",
[C2X_INS_SUBB] = "subb",
[C2X_INS_LAC] = "lac",
[C2X_INS_LACT] = "lact",
[C2X_INS_LACK] = "lack",
[C2X_INS_ZAC] = "zac",
[C2X_INS_ZALH] = "zalh",
[C2X_INS_ZALS] = "zals",
[C2X_INS_ZALR] = "zalr",
[C2X_INS_ADDK] = "addk",
[C2X_INS_SUBK] = "subk",
[C2X_INS_ABS] = "abs",
[C2X_INS_NEG] = "neg",
[C2X_INS_CMPL] = "cmpl",
[C2X_INS_SFL] = "sfl",
[C2X_INS_SFR] = "sfr",
[C2X_INS_ROL] = "rol",
[C2X_INS_ROR] = "ror",
[C2X_INS_NORM] = "norm",
[C2X_INS_SACL] = "sacl",
[C2X_INS_SACH] = "sach",
[C2X_INS_PAC] = "pac",
[C2X_INS_APAC] = "apac",
[C2X_INS_SPAC] = "spac",
[C2X_INS_LPH] = "lph",
[C2X_INS_SPL] = "spl",
[C2X_INS_SPH] = "sph",
[C2X_INS_LAR] = "lar",
[C2X_INS_SAR] = "sar",
[C2X_INS_LARK] = "lark",
[C2X_INS_LARP] = "larp",
[C2X_INS_MAR] = "mar",
[C2X_INS_LDP] = "ldp",
[C2X_INS_LDPK] = "ldpk",
[C2X_INS_ADRK] = "adrk",
[C2X_INS_SBRK] = "sbrk",
[C2X_INS_LT] = "lt",
[C2X_INS_LTA] = "lta",
[C2X_INS_LTD] = "ltd",
[C2X_INS_LTP] = "ltp",
[C2X_INS_LTS] = "lts",
[C2X_INS_MPY] = "mpy",
[C2X_INS_MPYK] = "mpyk",
[C2X_INS_MPYA] = "mpya",
[C2X_INS_MPYS] = "mpys",
[C2X_INS_MPYU] = "mpyu",
[C2X_INS_SQRA] = "sqra",
[C2X_INS_SQRS] = "sqrs",
[C2X_INS_MAC] = "mac",
[C2X_INS_MACD] = "macd",
[C2X_INS_AND] = "and",
[C2X_INS_OR] = "or",
[C2X_INS_XOR] = "xor",
[C2X_INS_ANDK] = "andk",
[C2X_INS_ORK] = "ork",
[C2X_INS_XORK] = "xork",
[C2X_INS_LST] = "lst",
[C2X_INS_LST1] = "lst1",
[C2X_INS_SST] = "sst",
[C2X_INS_SST1] = "sst1",
[C2X_INS_LALK] = "lalk",
[C2X_INS_ADLK] = "adlk",
[C2X_INS_SBLK] = "sblk",
[C2X_INS_LRLK] = "lrlk",
[C2X_INS_RPT] = "rpt",
[C2X_INS_RPTK] = "rptk",
[C2X_INS_DMOV] = "dmov",
[C2X_INS_PSHD] = "pshd",
[C2X_INS_POPD] = "popd",
[C2X_INS_PUSH] = "push",
[C2X_INS_POP] = "pop",
[C2X_INS_BITT] = "bitt",
[C2X_INS_BIT] = "bit",
[C2X_INS_TBLR] = "tblr",
[C2X_INS_TBLW] = "tblw",
[C2X_INS_BLKD] = "blkd",
[C2X_INS_BLKP] = "blkp",
[C2X_INS_IN] = "in",
[C2X_INS_OUT] = "out",
[C2X_INS_B] = "b",
[C2X_INS_BACC] = "bacc",
[C2X_INS_CALA] = "cala",
[C2X_INS_CALL] = "call",
[C2X_INS_RET] = "ret",
[C2X_INS_BANZ] = "banz",
[C2X_INS_BV] = "bv",
[C2X_INS_BGZ] = "bgz",
[C2X_INS_BLEZ] = "blez",
[C2X_INS_BLZ] = "blz",
[C2X_INS_BGEZ] = "bgez",
[C2X_INS_BNZ] = "bnz",
[C2X_INS_BZ] = "bz",
[C2X_INS_BNV] = "bnv",
[C2X_INS_BBZ] = "bbz",
[C2X_INS_BBNZ] = "bbnz",
[C2X_INS_BIOZ] = "bioz",
[C2X_INS_BC] = "bc",
[C2X_INS_BNC] = "bnc",
[C2X_INS_TRAP] = "trap",
[C2X_INS_IDLE] = "idle",
[C2X_INS_EINT] = "eint",
[C2X_INS_DINT] = "dint",
[C2X_INS_ROVM] = "rovm",
[C2X_INS_SOVM] = "sovm",
[C2X_INS_CNFD] = "cnfd",
[C2X_INS_CNFP] = "cnfp",
[C2X_INS_RSXM] = "rsxm",
[C2X_INS_SSXM] = "ssxm",
[C2X_INS_SPM] = "spm",
[C2X_INS_RXF] = "rxf",
[C2X_INS_SXF] = "sxf",
[C2X_INS_FORT] = "fort",
[C2X_INS_RC] = "rc",
[C2X_INS_SC] = "sc",
[C2X_INS_RTC] = "rtc",
[C2X_INS_STC] = "stc",
[C2X_INS_RFSM] = "rfsm",
[C2X_INS_SFSM] = "sfsm",
[C2X_INS_RHM] = "rhm",
[C2X_INS_SHM] = "shm",
[C2X_INS_RTXM] = "rtxm",
[C2X_INS_STXM] = "stxm",
[C2X_INS_CMPR] = "cmpr",
[C2X_INS_CONF] = "conf",
};
RZ_IPI const char *c2x_mnemonic(ut16 id) {
if (id < RZ_ARRAY_SIZE(c2x_mnemonics) && c2x_mnemonics[id]) {
return c2x_mnemonics[id];
}
return "invalid";
}
// descriptor + analysis entry
/**
* \brief C2x architecture descriptor for the shared C55 engine.
*
* Binds the C2x opcode table, operand extractors and consumers; the C5x
* superset reuses these for the instruction ids the two share.
*/
const C55ArchDesc c2x_arch_desc = {
.arch = C55_ARCH_C2X,
.cpu_name = "c2x",
.table = c2x_table,
.table_len = sizeof(c2x_table) / sizeof(c2x_table[0]),
.insn_len = NULL, // every row carries a fixed .len
.reg_info = c2x_reg_info,
.mnemonic = c2x_mnemonic,
.op_type = c2x_op_type,
.lift = NULL,
.mem = { .addr_unit_log2 = 0, .ptr_width = 16, .big_endian = true, .page_reg = "dp" },
.ea = NULL,
.fill_dual = NULL,
.words_le = false, // C2x words are stored MSB-first; no per-word swap
.cond_exec_prefix = false,
.parallel_prefix = false,
};
// Populate the data-flow analysis fields from the decoded operands so variable
// and argument analysis (and data-xref tracking) can inspect the instruction:
// the source/destination access values (op->src/op->dst) for register and memory
// operands, and op->ptr for direct (data-page-relative) data accesses. Shared by
// the C2x and C5x analysis paths, which use the same operand representation. The
// register/memory roles follow the load/store direction the shared filler has
// already resolved (a load's register operand is the destination; a store's
// memory operand is the destination), with the accumulator left implicit.
RZ_IPI void c2x_fill_op_access(RzAnalysis *analysis, const C55ArchDesc *a,
const C55Insn *insn, RzAnalysisOp *op) {
if (!analysis || !a || !insn || !op) {
return;
}
RzReg *reg = rz_analysis_get_reg(analysis);
// Resolve the memory role from the analysis op type: a store writes its
// memory operand, a load / accumulator-move reads it; everything else
// (arithmetic, logic, ...) reads its operands into the implicit
// accumulator. The shared filler's load/store direction targets the C54x
// operand layout, so set the read/write direction here for the C2x/C5x
// memory forms.
const bool is_store = op->type == RZ_ANALYSIS_OP_TYPE_STORE;
const bool is_load = op->type == RZ_ANALYSIS_OP_TYPE_LOAD ||
op->type == RZ_ANALYSIS_OP_TYPE_MOV;
bool has_mem = false;
size_t srci = 0;
for (ut8 i = 0; i < insn->n_ops; i++) {
const C55Operand *o = &insn->ops[i];
if (o->kind == C55_OP_MEM) {
has_mem = true;
RzAnalysisValue *v = rz_analysis_value_new();
if (!v) {
continue;
}
v->type = RZ_ANALYSIS_VAL_MEM;
v->memref = (o->access ? o->access : 16) / 8;
if (o->amode == C55_AM_DIRECT) {
// Direct addressing: the 7-bit field is the data offset within
// the current data page; expose it as the access pointer.
v->base = (ut64)(o->disp & 0x7f);
op->ptr = v->base;
op->ptrsize = v->memref;
}
// Indirect forms select the auxiliary register through ARP at run
// time, so no base register is known here; naming one would point
// every indirect access at the same AR.
if (is_store && !op->dst) {
op->dst = v;
} else if (srci < RZ_ARRAY_SIZE(op->src)) {
op->src[srci++] = v;
} else {
rz_analysis_value_free(v);
}
} else if (o->kind == C55_OP_REG && a->reg_info) {
const C55RegInfo *ri = a->reg_info(o->reg.cls, o->reg.num, o->reg.sub);
if (!ri) {
continue;
}
RzAnalysisValue *v = rz_analysis_value_new();
if (!v) {
continue;
}
v->type = RZ_ANALYSIS_VAL_REG;
v->reg = rz_reg_get(reg, ri->name, RZ_REG_TYPE_ANY);
if (is_load && !op->dst) {
op->dst = v;
} else if (srci < RZ_ARRAY_SIZE(op->src)) {
op->src[srci++] = v;
} else {
rz_analysis_value_free(v);
}
} else if (o->kind == C55_OP_IMM && srci < RZ_ARRAY_SIZE(op->src)) {
RzAnalysisValue *v = rz_analysis_value_new();
if (!v) {
continue;
}
v->type = RZ_ANALYSIS_VAL_IMM;
v->imm = (st64)o->imm;
op->src[srci++] = v;
}
}
if (has_mem) {
if (is_store) {
op->direction = RZ_ANALYSIS_OP_DIR_WRITE;
} else if (is_load || op->direction == 0) {
op->direction = RZ_ANALYSIS_OP_DIR_READ;
}
}
}
/**
* \brief Analysis entry point for the "c2x" CPU.
* \param analysis Current analysis session
* \param op Operation to fill in
* \param addr Address \p buf was read from
* \param buf Instruction bytes
* \param len Number of readable bytes in \p buf
* \param mask Which parts of \p op the caller wants filled
* \return Instruction length in bytes, or -1 on an undecodable word
*/
RZ_IPI int tms320_c2x_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr,
const ut8 *buf, int len, RzAnalysisOpMask mask) {
if (!op || !buf || len < 1) {
return 0;
}
op->addr = addr;
op->type = RZ_ANALYSIS_OP_TYPE_NULL;
C55Insn ci;
if (c55_decode(&c2x_arch_desc, buf, len, &ci)) {
c55_fill_analysis(&c2x_arch_desc, &ci, op);
c2x_fill_op_access(analysis, &c2x_arch_desc, &ci, op);
if (mask & RZ_ANALYSIS_OP_MASK_IL) {
op->il_op = c55_lift(&c2x_arch_desc, &ci, op->addr);
}
} else {
// Undecodable word: flag it and give a one-word fallback size, but
// report the failure to the caller with -1 (the common plugin convention).
op->type = RZ_ANALYSIS_OP_TYPE_ILL;
op->size = 1;
return -1;
}
return op->size;
}
/*
* Complete TMS320C2x encoding map (for extending c2x_table). Bit legend:
* A 7-bit direct dma | M 3-bit AR-modify | N 4-bit next-ARP | T 4-bit shift
* S 3-bit shift | R 3-bit AR# | P 4-bit port | D 8-bit imm | K small imm
* W 16-bit imm (next word) | C 2-bit compare | B 16-bit branch target (word)
*
* 0xxx ADD A,T / M,T,N 40xx ZALH 50xx LST 78xx SST 8xxx IN A,P
* 1xxx SUB 41xx ZALS 51xx LST1 79xx SST1 9xxx BIT A,T
* 2xxx LAC 42xx LACT 52xx LDP 7Axx POPD Axx-Bxx MPYK W
* 3xxx LAR R,A 43xx ADDC 53xx LPH 7Bxx ZALR Cxxx LARK R,D
* 38xx MPY 3Cxx LT 44xx SUBH 54xx PSHD 7Cxx SPL C8xx LDPK K
* 39xx SQRA 3Dxx LTA 45xx SUBS 55xx MAR/NOP 7Dxx SPH CAxx LACK (CA00=ZAC)
* 3Axx MPYA 3Exx LTP 46xx SUBT 56xx DMOV 7Exx ADRK CBxx RPTK
* 3Bxx MPYS 3Fxx LTD 47xx SUBC 57xx BITT 7Fxx SBRK CCxx ADDK
* 48xx ADDH 58xx TBLR CDxx SUBK
* 49xx ADDS 59xx TBLW 5Cxx MACD B,* CExx (no-op block)
* 4Axx ADDT 5Axx SQRS 5Dxx MAC B,* CFxx MPYS
* 4Bxx RPT 5Bxx LTS 5Exx BC B Dx00 LRLK R,W
* 4Cxx XOR 5Fxx BNC B Dx01 LALK W,T
* 4Dxx OR 60xx SACL S Dx02 ADLK
* 4Exx AND 68xx SACH S Exxx OUT A,P Dx03 SBLK
* 4Fxx SUBB 70xx SAR R,A Dx04 ANDK
* Dx05 ORK
* CE block (exact): CE00 EINT CE01 DINT CE02 ROVM CE03 SOVM CE04 CNFD Dx06 XORK
* CE05 CNFP CE06 RSXM CE07 SSXM CE08-B SPM K CE0C RXF CE0D SXF
* CE0E-F FORT K CE14 PAC CE15 APAC CE16 SPAC CE18 SFL CE19 SFR CE1B ABS
* CE1C PUSH CE1D POP CE1E TRAP CE1F IDLE CE20 RTXM CE21 STXM CE23 NEG
* CE24 CALA CE25 BACC CE26 RET CE27 CMPL CE30 RC CE31 SC CE32 RTC CE33 STC
* CE34 ROL CE35 ROR CE36 RFSM CE37 SFSM CE38 RHM CE39 SHM CE3C-F CONF K
* CE50-3 CMPR C CEx2 (110011101mmm0010) NORM M
* F-block branches (opcode + B word + addressing byte, bit7=1): F0 BV F1 BGZ
* F2 BLEZ F3 BLZ F4 BGEZ F5 BNZ F6 BZ F7 BNV (F8 BBZ F9 BBNZ FA BIOZ
* FB BANZ FE CALL FF B are the conventional assignments; verify the tail).
* BLKD/BLKP are the block-move forms (opcode + source-address word).
*/

View file

@ -0,0 +1,214 @@
// SPDX-FileCopyrightText: 2026 RizinOrg <info@rizin.re>
// SPDX-License-Identifier: LGPL-3.0-only
#ifndef RZ_TMS320_C2X_H
#define RZ_TMS320_C2X_H
#include "../c55_ir.h"
#include <rz_analysis.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* \file
* TMS320C2x (legacy, e.g. TMS320C25) disassembly + RzIL, plugged into the
* shared C55 decode engine. The C2x is a 16-bit word-addressed fixed-point DSP
* with a single 32-bit accumulator (ACC) plus carry, a 16-bit temporary T, a
* 32-bit product register P (with a programmable product shifter PM), eight
* 16-bit auxiliary registers AR0..7 selected by the 3-bit ARP, a 9-bit data
* page pointer DP, and ST0/ST1 status words. Program/data addresses are 16-bit.
*
* Encodings are taken from the public TMS320C2x instruction set (the opcode bit
* patterns match MAME's tested TMS320x25 disassembler). Instruction words are
* stored MSB-first (big-endian), so the engine's words_le swap is NOT used: the
* leading byte already carries the opcode field that the table matches on.
*/
/// TMS320C2x instruction identifiers (the C55Insn.id / RzAnalysisOp.id values).
enum {
C2X_INS_INVALID = 0,
C2X_INS_NOP,
// accumulator arithmetic
C2X_INS_ADD,
C2X_INS_ADDH,
C2X_INS_ADDS,
C2X_INS_ADDT,
C2X_INS_ADDC,
C2X_INS_SUB,
C2X_INS_SUBH,
C2X_INS_SUBS,
C2X_INS_SUBT,
C2X_INS_SUBC,
C2X_INS_SUBB,
C2X_INS_LAC,
C2X_INS_LACT,
C2X_INS_LACK,
C2X_INS_ZAC,
C2X_INS_ZALH,
C2X_INS_ZALS,
C2X_INS_ZALR,
C2X_INS_ADDK,
C2X_INS_SUBK,
C2X_INS_ABS,
C2X_INS_NEG,
C2X_INS_CMPL,
C2X_INS_SFL,
C2X_INS_SFR,
C2X_INS_ROL,
C2X_INS_ROR,
C2X_INS_NORM,
// store / load accumulator parts
C2X_INS_SACL,
C2X_INS_SACH,
C2X_INS_PAC,
C2X_INS_APAC,
C2X_INS_SPAC,
C2X_INS_LPH,
C2X_INS_SPL,
C2X_INS_SPH,
// auxiliary registers / pointers
C2X_INS_LAR,
C2X_INS_SAR,
C2X_INS_LARK,
C2X_INS_LARP,
C2X_INS_MAR,
C2X_INS_LDP,
C2X_INS_LDPK,
C2X_INS_ADRK,
C2X_INS_SBRK,
// T / P register and multiply
C2X_INS_LT,
C2X_INS_LTA,
C2X_INS_LTD,
C2X_INS_LTP,
C2X_INS_LTS,
C2X_INS_MPY,
C2X_INS_MPYK,
C2X_INS_MPYA,
C2X_INS_MPYS,
C2X_INS_MPYU,
C2X_INS_SQRA,
C2X_INS_SQRS,
C2X_INS_MAC,
C2X_INS_MACD,
// logical
C2X_INS_AND,
C2X_INS_OR,
C2X_INS_XOR,
C2X_INS_ANDK,
C2X_INS_ORK,
C2X_INS_XORK,
// status / long immediate
C2X_INS_LST,
C2X_INS_LST1,
C2X_INS_SST,
C2X_INS_SST1,
C2X_INS_LALK,
C2X_INS_ADLK,
C2X_INS_SBLK,
C2X_INS_LRLK,
C2X_INS_RPT,
C2X_INS_RPTK,
// memory move / table / IO
C2X_INS_DMOV,
C2X_INS_PSHD,
C2X_INS_POPD,
C2X_INS_PUSH,
C2X_INS_POP,
C2X_INS_BITT,
C2X_INS_BIT,
C2X_INS_TBLR,
C2X_INS_TBLW,
C2X_INS_BLKD,
C2X_INS_BLKP,
C2X_INS_IN,
C2X_INS_OUT,
// control flow
C2X_INS_B,
C2X_INS_BACC,
C2X_INS_CALA,
C2X_INS_CALL,
C2X_INS_RET,
C2X_INS_BANZ,
C2X_INS_BV,
C2X_INS_BGZ,
C2X_INS_BLEZ,
C2X_INS_BLZ,
C2X_INS_BGEZ,
C2X_INS_BNZ,
C2X_INS_BZ,
C2X_INS_BNV,
C2X_INS_BBZ,
C2X_INS_BBNZ,
C2X_INS_BIOZ,
C2X_INS_BC,
C2X_INS_BNC,
C2X_INS_TRAP,
C2X_INS_IDLE,
// status-bit / mode controls (CE block)
C2X_INS_EINT,
C2X_INS_DINT,
C2X_INS_ROVM,
C2X_INS_SOVM,
C2X_INS_CNFD,
C2X_INS_CNFP,
C2X_INS_RSXM,
C2X_INS_SSXM,
C2X_INS_SPM,
C2X_INS_RXF,
C2X_INS_SXF,
C2X_INS_FORT,
C2X_INS_RC,
C2X_INS_SC,
C2X_INS_RTC,
C2X_INS_STC,
C2X_INS_RFSM,
C2X_INS_SFSM,
C2X_INS_RHM,
C2X_INS_SHM,
C2X_INS_RTXM,
C2X_INS_STXM,
C2X_INS_CMPR,
C2X_INS_CONF,
};
// Legacy C2x/C5x memory model (shared by both cores). The data/program space
// is word-addressed, but the RzIL VM memory is byte-addressed, so a word
// address scales to a byte address by C2X_WORD_BYTES. C2X_MEM_ADDR_BITS is the
// width of that byte-address space (the il_config mem_key_size): 24 bits leave
// room for a 16-bit word address to scale x2 without wrapping.
#define C2X_WORD_BYTES 2
#define C2X_MEM_ADDR_BITS 24
extern const C55ArchDesc c2x_arch_desc;
// Shared decode-table pieces, reused by the C5x superset (c5x/c5x.c). The
// operand extractors are referenced by the row macros in c2x_rowdefs.h; the
// resolver/classifier/mnemonic helpers are reused (and extended) by C5x.
RZ_IPI void c2x_x_mem(const C55ArchDesc *a, ut64 bits, const C55OpDesc *d, C55Operand *out);
RZ_IPI void c2x_x_nextarp(const C55ArchDesc *a, ut64 bits, const C55OpDesc *d, C55Operand *out);
RZ_IPI void c2x_x_shift(const C55ArchDesc *a, ut64 bits, const C55OpDesc *d, C55Operand *out);
RZ_IPI void c2x_x_reg(const C55ArchDesc *a, ut64 bits, const C55OpDesc *d, C55Operand *out);
RZ_IPI void c2x_x_imm(const C55ArchDesc *a, ut64 bits, const C55OpDesc *d, C55Operand *out);
RZ_IPI void c2x_x_branch(const C55ArchDesc *a, ut64 bits, const C55OpDesc *d, C55Operand *out);
RZ_IPI const C55RegInfo *c2x_reg_info(C55RegClass cls, ut8 num, C55SubReg sub);
RZ_IPI ut32 c2x_op_type(ut16 id);
RZ_IPI const char *c2x_mnemonic(ut16 id);
RZ_IPI int tms320_c2x_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr,
const ut8 *buf, int len, RzAnalysisOpMask mask);
RZ_IPI void c2x_fill_op_access(RzAnalysis *analysis, const C55ArchDesc *a, const C55Insn *insn, RzAnalysisOp *op);
RZ_IPI RzAnalysisILConfig *tms320_c2x_il_config(RZ_NONNULL RzAnalysis *analysis);
RZ_IPI RzILOpEffect *c2x_lift(const C55Insn *insn, ut64 pc);
RZ_IPI RzILOpPure *c2x_ea(const C55ArchDesc *a, const C55Operand *m);
#ifdef __cplusplus
}
#endif
#endif /* RZ_TMS320_C2X_H */

View file

@ -0,0 +1,154 @@
// SPDX-FileCopyrightText: 2026 RizinOrg <info@rizin.re>
// SPDX-License-Identifier: LGPL-3.0-only
// clang-format off
// TMS320C2x core instruction rows (shared with the C5x superset)
// top-nibble arithmetic with 4-bit shift: ADD / SUB / LAC
{ OP(0xf080, 0x0000), .id = C2X_INS_ADD, .len = 2, .ops = { MEM(0), SHF(8, 4) } },
{ OP(0xf080, 0x0080), .id = C2X_INS_ADD, .len = 2, .ops = { MEM(0), SHF(8, 4), NARP(0) } },
{ OP(0xf080, 0x1000), .id = C2X_INS_SUB, .len = 2, .ops = { MEM(0), SHF(8, 4) } },
{ OP(0xf080, 0x1080), .id = C2X_INS_SUB, .len = 2, .ops = { MEM(0), SHF(8, 4), NARP(0) } },
{ OP(0xf080, 0x2000), .id = C2X_INS_LAC, .len = 2, .ops = { MEM(0), SHF(8, 4) } },
{ OP(0xf080, 0x2080), .id = C2X_INS_LAC, .len = 2, .ops = { MEM(0), SHF(8, 4), NARP(0) } },
// LAR / SAR: 5-bit opcode (00110rrr / 01110rrr) + R
{ OP(0xf880, 0x3000), .id = C2X_INS_LAR, .len = 2, .ops = { AR(8), MEM(0) } },
{ OP(0xf880, 0x3080), .id = C2X_INS_LAR, .len = 2, .ops = { AR(8), MEM(0), NARP(0) } },
{ OP(0xf880, 0x7000), .id = C2X_INS_SAR, .len = 2, .ops = { AR(8), MEM(0) } },
{ OP(0xf880, 0x7080), .id = C2X_INS_SAR, .len = 2, .ops = { AR(8), MEM(0), NARP(0) } },
// single-byte-opcode memory block 0x38..0x5B: multiply / T-P / logical / move
MEMOP_D(0x38, C2X_INS_MPY), MEMOP_I(0x38, C2X_INS_MPY),
MEMOP_D(0x39, C2X_INS_SQRA), MEMOP_I(0x39, C2X_INS_SQRA),
MEMOP_D(0x3a, C2X_INS_MPYA), MEMOP_I(0x3a, C2X_INS_MPYA),
MEMOP_D(0x3b, C2X_INS_MPYS), MEMOP_I(0x3b, C2X_INS_MPYS),
MEMOP_D(0x3c, C2X_INS_LT), MEMOP_I(0x3c, C2X_INS_LT),
MEMOP_D(0x3d, C2X_INS_LTA), MEMOP_I(0x3d, C2X_INS_LTA),
MEMOP_D(0x3e, C2X_INS_LTP), MEMOP_I(0x3e, C2X_INS_LTP),
MEMOP_D(0x3f, C2X_INS_LTD), MEMOP_I(0x3f, C2X_INS_LTD),
MEMOP_D(0x40, C2X_INS_ZALH), MEMOP_I(0x40, C2X_INS_ZALH),
MEMOP_D(0x41, C2X_INS_ZALS), MEMOP_I(0x41, C2X_INS_ZALS),
MEMOP_D(0x42, C2X_INS_LACT), MEMOP_I(0x42, C2X_INS_LACT),
MEMOP_D(0x43, C2X_INS_ADDC), MEMOP_I(0x43, C2X_INS_ADDC),
MEMOP_D(0x44, C2X_INS_SUBH), MEMOP_I(0x44, C2X_INS_SUBH),
MEMOP_D(0x45, C2X_INS_SUBS), MEMOP_I(0x45, C2X_INS_SUBS),
MEMOP_D(0x46, C2X_INS_SUBT), MEMOP_I(0x46, C2X_INS_SUBT),
MEMOP_D(0x47, C2X_INS_SUBC), MEMOP_I(0x47, C2X_INS_SUBC),
MEMOP_D(0x48, C2X_INS_ADDH), MEMOP_I(0x48, C2X_INS_ADDH),
MEMOP_D(0x49, C2X_INS_ADDS), MEMOP_I(0x49, C2X_INS_ADDS),
MEMOP_D(0x4a, C2X_INS_ADDT), MEMOP_I(0x4a, C2X_INS_ADDT),
MEMOP_D(0x4b, C2X_INS_RPT), MEMOP_I(0x4b, C2X_INS_RPT),
MEMOP_D(0x4c, C2X_INS_XOR), MEMOP_I(0x4c, C2X_INS_XOR),
MEMOP_D(0x4d, C2X_INS_OR), MEMOP_I(0x4d, C2X_INS_OR),
MEMOP_D(0x4e, C2X_INS_AND), MEMOP_I(0x4e, C2X_INS_AND),
MEMOP_D(0x4f, C2X_INS_SUBB), MEMOP_I(0x4f, C2X_INS_SUBB),
MEMOP_D(0x50, C2X_INS_LST), MEMOP_I(0x50, C2X_INS_LST),
MEMOP_D(0x51, C2X_INS_LST1), MEMOP_I(0x51, C2X_INS_LST1),
MEMOP_D(0x52, C2X_INS_LDP), MEMOP_I(0x52, C2X_INS_LDP),
MEMOP_D(0x53, C2X_INS_LPH), MEMOP_I(0x53, C2X_INS_LPH),
MEMOP_D(0x54, C2X_INS_PSHD), MEMOP_I(0x54, C2X_INS_PSHD),
MEMOP_D(0x56, C2X_INS_DMOV), MEMOP_I(0x56, C2X_INS_DMOV),
MEMOP_D(0x57, C2X_INS_BITT), MEMOP_I(0x57, C2X_INS_BITT),
MEMOP_D(0x58, C2X_INS_TBLR), MEMOP_I(0x58, C2X_INS_TBLR),
MEMOP_D(0x59, C2X_INS_TBLW), MEMOP_I(0x59, C2X_INS_TBLW),
MEMOP_D(0x5a, C2X_INS_SQRS), MEMOP_I(0x5a, C2X_INS_SQRS),
MEMOP_D(0x5b, C2X_INS_LTS), MEMOP_I(0x5b, C2X_INS_LTS),
// 0x5C/0x5D: multiply-accumulate with program memory (2-word, %B = pma)
PMAOP_D(0x5c, C2X_INS_MACD), PMAOP_I(0x5c, C2X_INS_MACD),
PMAOP_D(0x5d, C2X_INS_MAC), PMAOP_I(0x5d, C2X_INS_MAC),
// store accumulator low/high with 3-bit shift (%S)
{ OP(0xf880, 0x6000), .id = C2X_INS_SACL, .len = 2, .ops = { MEM(0), SHF(8, 3) } },
{ OP(0xf880, 0x6080), .id = C2X_INS_SACL, .len = 2, .ops = { MEM(0), SHF(8, 3), NARP(0) } },
{ OP(0xf880, 0x6800), .id = C2X_INS_SACH, .len = 2, .ops = { MEM(0), SHF(8, 3) } },
{ OP(0xf880, 0x6880), .id = C2X_INS_SACH, .len = 2, .ops = { MEM(0), SHF(8, 3), NARP(0) } },
// 0x78..0x7D single-byte-opcode block: store status / stack
MEMOP_D(0x78, C2X_INS_SST), MEMOP_I(0x78, C2X_INS_SST),
MEMOP_D(0x79, C2X_INS_SST1), MEMOP_I(0x79, C2X_INS_SST1),
MEMOP_D(0x7a, C2X_INS_POPD), MEMOP_I(0x7a, C2X_INS_POPD),
MEMOP_D(0x7b, C2X_INS_ZALR), MEMOP_I(0x7b, C2X_INS_ZALR),
MEMOP_D(0x7c, C2X_INS_SPL), MEMOP_I(0x7c, C2X_INS_SPL),
MEMOP_D(0x7d, C2X_INS_SPH), MEMOP_I(0x7d, C2X_INS_SPH),
// ADRK / SBRK: add/subtract short immediate (full low byte) to/from AR(ARP)
{ OP(0xff00, 0x7e00), .id = C2X_INS_ADRK, .len = 2, .ops = { IMM(0, 8, 0) } },
{ OP(0xff00, 0x7f00), .id = C2X_INS_SBRK, .len = 2, .ops = { IMM(0, 8, 0) } },
// IN / OUT: 4-bit port (%P) in bits 11-8
{ OP(0xf080, 0x8000), .id = C2X_INS_IN, .len = 2, .ops = { MEM(0), IMM(8, 4, 0) } },
{ OP(0xf080, 0x8080), .id = C2X_INS_IN, .len = 2, .ops = { MEM(0), IMM(8, 4, 0), NARP(0) } },
{ OP(0xf080, 0xe000), .id = C2X_INS_OUT, .len = 2, .ops = { MEM(0), IMM(8, 4, 0) } },
{ OP(0xf080, 0xe080), .id = C2X_INS_OUT, .len = 2, .ops = { MEM(0), IMM(8, 4, 0), NARP(0) } },
// BIT: 4-bit bit-code (%T) in bits 11-8
{ OP(0xf080, 0x9000), .id = C2X_INS_BIT, .len = 2, .ops = { MEM(0), SHF(8, 4) } },
{ OP(0xf080, 0x9080), .id = C2X_INS_BIT, .len = 2, .ops = { MEM(0), SHF(8, 4), NARP(0) } },
// immediate-operand instructions
{ OP(0xe000, 0xa000), .id = C2X_INS_MPYK, .len = 2, .ops = { IMM(0, 13, 1) } },
{ OP(0xf800, 0xc000), .id = C2X_INS_LARK, .len = 2, .ops = { AR(8), IMM(0, 8, 0) } },
{ OP(0xfe00, 0xc800), .id = C2X_INS_LDPK, .len = 2, .ops = { IMM(0, 9, 0) } },
{ FIX(0xca00, C2X_INS_ZAC) }, // LACK #0 == ZAC (specific row before the range)
{ OP(0xff00, 0xca00), .id = C2X_INS_LACK, .len = 2, .ops = { IMM(0, 8, 0) } },
{ OP(0xff00, 0xcb00), .id = C2X_INS_RPTK, .len = 2, .ops = { IMM(0, 8, 0) } },
// NORM is CEx2 with the indirect AR-modify field in bits 6:4 (always indirect).
{ OP(0xff8f, 0xce82), .id = C2X_INS_NORM, .len = 2, .ops = { MEM(0) } },
{ OP(0xff00, 0xcc00), .id = C2X_INS_ADDK, .len = 2, .ops = { IMM(0, 8, 0) } },
{ OP(0xff00, 0xcd00), .id = C2X_INS_SUBK, .len = 2, .ops = { IMM(0, 8, 0) } },
// 0xCF is MPYU (multiply unsigned). The MAME *disassembler* table mislabels
// it as a second "mpys", but the MAME execution table (and the TI ISA) run
// MPYU here; we follow the architecturally-correct mnemonic.
MEMOP_D(0xcf, C2X_INS_MPYU), MEMOP_I(0xcf, C2X_INS_MPYU),
// CE block: no-operand status / mode / control
{ FIX(0xce00, C2X_INS_EINT) }, { FIX(0xce01, C2X_INS_DINT) },
{ FIX(0xce02, C2X_INS_ROVM) }, { FIX(0xce03, C2X_INS_SOVM) },
{ FIX(0xce04, C2X_INS_CNFD) }, { FIX(0xce05, C2X_INS_CNFP) },
{ FIX(0xce06, C2X_INS_RSXM) }, { FIX(0xce07, C2X_INS_SSXM) },
{ OP(0xfffc, 0xce08), .id = C2X_INS_SPM, .len = 2, .ops = { IMM(0, 2, 0) } },
{ FIX(0xce0c, C2X_INS_RXF) }, { FIX(0xce0d, C2X_INS_SXF) },
{ FIX(0xce14, C2X_INS_PAC) }, { FIX(0xce15, C2X_INS_APAC) }, { FIX(0xce16, C2X_INS_SPAC) },
{ FIX(0xce18, C2X_INS_SFL) }, { FIX(0xce19, C2X_INS_SFR) },
{ FIX(0xce1b, C2X_INS_ABS) }, { FIX(0xce1c, C2X_INS_PUSH) }, { FIX(0xce1d, C2X_INS_POP) },
{ FIX(0xce1e, C2X_INS_TRAP) }, { FIX(0xce1f, C2X_INS_IDLE) },
{ FIX(0xce20, C2X_INS_RTXM) }, { FIX(0xce21, C2X_INS_STXM) },
{ FIX(0xce23, C2X_INS_NEG) }, { FIX(0xce24, C2X_INS_CALA) }, { FIX(0xce25, C2X_INS_BACC) },
{ FIX(0xce26, C2X_INS_RET) }, { FIX(0xce27, C2X_INS_CMPL) },
{ FIX(0xce30, C2X_INS_RC) }, { FIX(0xce31, C2X_INS_SC) },
{ FIX(0xce32, C2X_INS_RTC) }, { FIX(0xce33, C2X_INS_STC) },
{ FIX(0xce34, C2X_INS_ROL) }, { FIX(0xce35, C2X_INS_ROR) },
{ FIX(0xce36, C2X_INS_RFSM) }, { FIX(0xce37, C2X_INS_SFSM) },
{ FIX(0xce38, C2X_INS_RHM) }, { FIX(0xce39, C2X_INS_SHM) },
// CONF K (TMS320C26): two low bits select the RAM block configuration (ST1.CNF)
{ OP(0xfffc, 0xce3c), .id = C2X_INS_CONF, .len = 2, .ops = { IMM(0, 2, 0) } },
{ OP(0xfffc, 0xce50), .id = C2X_INS_CMPR, .len = 2, .ops = { IMM(0, 2, 0) } },
// D block: long-immediate (opcode word + 16-bit data word)
{ OP(0xf8ff, 0xd000), .id = C2X_INS_LRLK, .len = 4, .ops = { AR(24), IMM(0, 16, 0) } },
{ OP(0xf0ff, 0xd001), .id = C2X_INS_LALK, .len = 4, .ops = { IMM(0, 16, 0), SHF(24, 4) } },
{ OP(0xf0ff, 0xd002), .id = C2X_INS_ADLK, .len = 4, .ops = { IMM(0, 16, 0), SHF(24, 4) } },
{ OP(0xf0ff, 0xd003), .id = C2X_INS_SBLK, .len = 4, .ops = { IMM(0, 16, 0), SHF(24, 4) } },
{ OP(0xf0ff, 0xd004), .id = C2X_INS_ANDK, .len = 4, .ops = { IMM(0, 16, 0), SHF(24, 4) } },
{ OP(0xf0ff, 0xd005), .id = C2X_INS_ORK, .len = 4, .ops = { IMM(0, 16, 0), SHF(24, 4) } },
{ OP(0xf0ff, 0xd006), .id = C2X_INS_XORK, .len = 4, .ops = { IMM(0, 16, 0), SHF(24, 4) } },
// control transfer (opcode word + 16-bit target word). BC/BNC confirmed at
// 0x5E/0x5F; the F-block follows the same opcode-byte + bit7 pattern.
BROP(0x5e, C2X_INS_BC), BROP(0x5f, C2X_INS_BNC),
BROP(0xf0, C2X_INS_BV), BROP(0xf1, C2X_INS_BGZ), BROP(0xf2, C2X_INS_BLEZ),
BROP(0xf3, C2X_INS_BLZ), BROP(0xf4, C2X_INS_BGEZ), BROP(0xf5, C2X_INS_BNZ),
BROP(0xf6, C2X_INS_BZ), BROP(0xf7, C2X_INS_BNV), BROP(0xf8, C2X_INS_BBZ),
BROP(0xf9, C2X_INS_BBNZ), BROP(0xfa, C2X_INS_BIOZ), BANZOP(0xfb, C2X_INS_BANZ),
// 0xFC/0xFD: block move from program/data memory (2-word, %B = source addr)
PMAOP_D(0xfc, C2X_INS_BLKP), PMAOP_I(0xfc, C2X_INS_BLKP),
PMAOP_D(0xfd, C2X_INS_BLKD), PMAOP_I(0xfd, C2X_INS_BLKD),
BROP(0xfe, C2X_INS_CALL), BROP(0xff, C2X_INS_B),
// AR-pointer / nop (specific rows before the general MAR indirect row)
{ FIX(0x5500, C2X_INS_NOP) },
{ OP(0xfff8, 0x5588), .id = C2X_INS_LARP, .len = 2, .ops = { IMM(0, 3, 0) } },
{ OP(0xff80, 0x5580), .id = C2X_INS_MAR, .len = 2, .ops = { MEM(0), NARP(0) } },
// clang-format on

View file

@ -0,0 +1,59 @@
// SPDX-FileCopyrightText: 2026 RizinOrg <info@rizin.re>
// SPDX-License-Identifier: LGPL-3.0-only
/**
* \file
* Shared decode-table row macros for the TMS320C2x/C5x tables. Included
* immediately before a `static const C55InsnDef <cpu>_table[] = {` body and
* paired with c2x_rowundefs.h afterwards. Requires the c2x_x_* operand
* extractors (declared in c2x.h) to be visible.
*/
// head-space mask/match: the 16-bit opcode word occupies head bits 31:16.
#define OP(wmask, wmatch) .mask = ((ut32)(wmask) << 16), .match = ((ut32)(wmatch) << 16)
#define MEM(lo_) { .lo = (lo_), .width = 8, .fn = c2x_x_mem }
#define NARP(lo_) { .lo = (lo_), .width = 4, .fn = c2x_x_nextarp }
#define SHF(lo_, w_) { .lo = (lo_), .width = (w_), .fn = c2x_x_shift }
#define AR(lo_) { .lo = (lo_), .width = 3, .fn = c2x_x_reg }
#define IMM(lo_, w_, p_) { .lo = (lo_), .width = (w_), .fn = c2x_x_imm, .param = (p_) }
#define BR(lo_) { .lo = (lo_), .width = 16, .fn = c2x_x_branch }
// memory-reference opcode keyed on the whole high byte + the direct/indirect bit
#define MEMOP_D(hb, id_) \
{ \
OP(0xff80, ((ut16)(hb) << 8) | 0x0000), .id = (id_), .len = 2, .ops = { MEM(0) } \
}
#define MEMOP_I(hb, id_) \
{ \
OP(0xff80, ((ut16)(hb) << 8) | 0x0080), .id = (id_), .len = 2, .ops = { MEM(0), \
NARP(0) } \
}
#define FIX(word, id_) OP(0xffff, (word)), .id = (id_), .len = 2
#define BROP(hb, id_) \
{ \
OP(0xff80, ((ut16)(hb) << 8) | 0x0080), .id = (id_), .len = 4, .ops = { BR(0) } \
}
// BANZ also carries the indirect addressing byte (in the leading word, bits
// 31:16) that selects the loop-counter AR post-modify (*- etc.); decode it so
// the lifter can apply the auxiliary-register update.
#define BANZOP(hb, id_) \
{ \
OP(0xff80, ((ut16)(hb) << 8) | 0x0080), .id = (id_), .len = 4, .ops = { BR(0), \
MEM(16), \
NARP(16) } \
}
// 2-word instruction carrying a 16-bit memory address in the trailing word plus
// a destination addressing byte in the leading word (mac/macd/blkp/blkd). The
// leading word sits in packed bits 31:16, so MEM/NARP index from bit 16.
#define PMAOP_D(hb, id_) \
{ \
OP(0xff80, ((ut16)(hb) << 8) | 0x0000), .id = (id_), .len = 4, .ops = { IMM(0, 16, 0), \
MEM(16) } \
}
#define PMAOP_I(hb, id_) \
{ \
OP(0xff80, ((ut16)(hb) << 8) | 0x0080), .id = (id_), .len = 4, .ops = { IMM(0, 16, 0), \
MEM(16), \
NARP(16) } \
}

View file

@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: 2026 RizinOrg <info@rizin.re>
// SPDX-License-Identifier: LGPL-3.0-only
#undef OP
#undef MEM
#undef NARP
#undef SHF
#undef AR
#undef IMM
#undef BR
#undef MEMOP_D
#undef MEMOP_I
#undef FIX
#undef BROP
#undef BANZOP
#undef PMAOP_D
#undef PMAOP_I

View file

@ -25,6 +25,31 @@
// decode engine
// ---------------------------------------------------------------------------
/**
* \brief Whether \p arch encodes a parallel-execution bit in its opcode words.
* \param arch Architecture being decoded
* \return true if instructions of \p arch can carry a parallel flag
*
* The C54x, C2x and C5x are word-oriented and have no such bit, so their
* instructions never decode as the parallel form.
*/
static inline bool c55_arch_has_parallel_bit(C55Arch arch) {
return arch != C55_ARCH_C54X && arch != C55_ARCH_C2X && arch != C55_ARCH_C5X;
}
/**
* \brief Whether \p arch counts program-memory addresses in 16-bit words.
* \param arch Architecture being decoded
* \return true if a program address operand is a word index, not a byte offset
*
* The C54x/C2x/C5x fetch 16-bit words and their branch/call operands count words,
* so a target has to be doubled to land in the byte address space the analysis
* and the RzIL memory model use.
*/
static inline bool c55_arch_word_addressed(C55Arch arch) {
return arch == C55_ARCH_C2X || arch == C55_ARCH_C5X || arch == C55_ARCH_C54X;
}
// Pack the first \p n bytes (<= 8) MSB-first into a word: byte 0 is most
// significant, so a field's bit index counts from the LSB of the instruction.
static ut64 c55_pack(const ut8 *buf, int n) {
@ -148,9 +173,8 @@ bool c55_decode(const C55ArchDesc *a, const ut8 *buf, int len, C55Insn *out) {
// of the leading byte is the "||" flag rather than part of the opcode.
// A row opts into this by leaving that bit unconstrained in its mask
// (e.g. 0xfe000000), in which case the bit's value selects the parallel
// form; rows that pin the bit (0xff000000) treat it as opcode. C54x is
// word-oriented and has no such bit, so it never carries a parallel flag.
out->parallel = a->arch != C55_ARCH_C54X && !def->no_parallel && (def->mask & 0x01000000) == 0 && (head & 0x01000000) != 0;
// form; rows that pin the bit (0xff000000) treat it as opcode.
out->parallel = c55_arch_has_parallel_bit(a->arch) && !def->no_parallel && (def->mask & 0x01000000) == 0 && (head & 0x01000000) != 0;
const ut64 bits = c55_pack(buf, ilen);
if (def->alt_bit && (c55_field(bits, (ut8)(def->alt_bit - 1), 1) != 0)) {
// a variant selector beyond the 4-byte match head (e.g. firssub vs
@ -500,10 +524,10 @@ static ut64 c55_branch_target(const C55Insn *insn, ut64 pc) {
if (o->abs_target) {
// Absolute target: the operand is the destination address
// itself (24-bit program space), not a pc-relative offset.
// The C54x counts program words here, so scale the target
// Word-addressed cores count words here, so scale the target
// into the byte address space the analysis works in.
ut64 target = v & 0xffffff;
return insn->arch == C55_ARCH_C54X ? target * 2 : target;
return c55_arch_word_addressed(insn->arch) ? target * 2 : target;
}
st64 soff = o->reltarget_unsigned
? (st64)v
@ -3711,6 +3735,18 @@ static void c55_fmt_reg(RzStrBuf *sb, const C55ArchDesc *a, const C55Reg *r) {
}
static void c55_fmt_mem(RzStrBuf *sb, const C55ArchDesc *a, const C55Operand *m) {
if (a->arch == C55_ARCH_C2X || a->arch == C55_ARCH_C5X) {
// Only the direct form reaches this helper: the indirect spellings
// (*, *+, *BR0+ and the ",arX" next-ARP suffix) carry their own text in
// C55Operand.raw, which the operand loop emits before calling here.
// Assert it so a row that forgets to set raw is caught rather than
// silently rendered as a bogus data-page offset.
rz_warn_if_fail(m->amode == C55_AM_DIRECT);
// The page itself comes from DP at run time, so only the 7-bit offset
// is known statically.
rz_strbuf_appendf(sb, "0x%" PFMT32x, (ut32)m->disp & 0x7f);
return;
}
if (a->arch == C55_ARCH_C54X) {
// C54x addressing syntax differs from C55x (AR0-indexed *arN+0, circular
// *arN+%, bit-reverse *arN+0B). Render it self-contained and return; the
@ -4148,8 +4184,8 @@ char *c55_format(const C55ArchDesc *a, const C55Insn *insn) {
// with the '#' prefix (the sftl dst, #1 / #-1 forms).
rz_strbuf_appendf(&sb, "#%" PFMT64d, (st64)op->imm);
} else if (op->addr) {
if (a->arch == C55_ARCH_C54X) {
// C54x renders program/data addresses as a bare hex value
if (a->arch == C55_ARCH_C54X || a->arch == C55_ARCH_C2X || a->arch == C55_ARCH_C5X) {
// C54x/C2x render program/data addresses as a bare hex value
// (the '#' prefix is reserved for immediates).
rz_strbuf_appendf(&sb, "0x%" PFMT64x, op->imm);
} else {

View file

@ -36,6 +36,8 @@ typedef enum {
C55_ARCH_C54X = 0, ///< TMS320C54x
C55_ARCH_C55X, ///< TMS320C55x
C55_ARCH_C55XPLUS, ///< TMS320C55x+
C55_ARCH_C2X, ///< TMS320C2x (legacy single-accumulator fixed-point)
C55_ARCH_C5X, ///< TMS320C5x (second-generation fixed-point; C2x-compatible superset)
} C55Arch;
/** Register class; family-wide superset, each arch uses only its subset. */

View file

@ -321,6 +321,7 @@ arch_isa_sources = [
'isa/tms320/c55_ir.c',
'isa/tms320/c54x/c54x.c',
'isa/tms320/c54x/c54x_il.c',
'isa/tms320/c2x/c2x.c',
'isa/tms320/c55x_plus/c55plus_arch.c',
'isa/tms320/c55x/c55x_analysis.c',
'isa/tms320/c55x_plus/c55plus_analysis.c',

View file

@ -9,6 +9,7 @@
#include <tms320/c55x/c55x_analysis.h>
#include <tms320/c55x_plus/c55plus_analysis.h>
#include <tms320/c54x/c54x.h>
#include <tms320/c2x/c2x.h>
#include <tms320/c64x/c64x.h>
typedef struct tms320_ctx_t {
@ -25,6 +26,8 @@ int tms320_analysis_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr, const
return tms320_c64x_op(analysis, op, addr, buf, len, mask, context->c64x);
} else if (cpu && rz_str_casecmp(cpu, "c54x") == 0) {
return tms320_c54x_op(analysis, op, addr, buf, len, mask);
} else if (cpu && rz_str_casecmp(cpu, "c2x") == 0) {
return tms320_c2x_op(analysis, op, addr, buf, len, mask);
}
return tms320_c55x_op_byte(analysis, op, addr, buf, len, mask);
}
@ -109,6 +112,56 @@ static char *get_reg_profile(RZ_BORROW RzAnalysis *a) {
"ctr xpc .16 52 0 # Extended program counter\n"
"ctr pc .24 54 0 # Program counter\n");
}
if (cpu0 && rz_str_casecmp(cpu0, "c2x") == 0) {
// TMS320C2x: a single 32-bit accumulator ACC (with ACCL/ACCH 16-bit
// halves overlapping it), the 16-bit temporary T, the 32-bit product
// register P (PL/PH halves), eight 16-bit auxiliary registers AR0-AR7,
// the 3-bit ARP pointer (held in a 16-bit slot), the 9-bit DP data page,
// the ST0/ST1 status words and a 16-bit PC. SP is a synthetic stack
// pointer (the hardware stack is not memory-mapped). These names match
// c2x_reg_info()'s il_var bindings so the lifter resolves in the IL VM.
return rz_str_dup(
"=PC\tpc\n"
"=SP\tsp\n"
"=BP\tsp\n"
"=A0\tar0\n"
"=A1\tar1\n"
"=A2\tar2\n"
"=A3\tar3\n"
"=R0\tacc\n"
"ctr acc .32 0 0\n" // Accumulator
"gpr accl .16 0 0\n" // Accumulator low word
"gpr acch .16 2 0\n" // Accumulator high word
"ctr t .16 4 0\n" // Temporary register
"ctr p .32 6 0\n" // Product register
"gpr pl .16 6 0\n" // Product low word
"gpr ph .16 8 0\n" // Product high word
"gpr ar0 .16 10 0\n" // Auxiliary register 0
"gpr ar1 .16 12 0\n" // Auxiliary register 1
"gpr ar2 .16 14 0\n" // Auxiliary register 2
"gpr ar3 .16 16 0\n" // Auxiliary register 3
"gpr ar4 .16 18 0\n" // Auxiliary register 4
"gpr ar5 .16 20 0\n" // Auxiliary register 5
"gpr ar6 .16 22 0\n" // Auxiliary register 6
"gpr ar7 .16 24 0\n" // Auxiliary register 7
"ctr arp .16 26 0\n" // Auxiliary register pointer
"ctr dp .16 28 0\n" // Data page pointer
"ctr st0 .16 30 0\n" // Status register 0
"ctr st1 .16 32 0\n" // Status register 1
"ctr sp .16 34 0\n" // Stack pointer (synthetic)
"ctr pc .16 36 0\n" // Program counter
// status/mode bits modelled individually for the IL lifter (they
// also live inside ST0/ST1 on real silicon; ST0/ST1 are composed
// from / decomposed to these by SST/SST1/LST/LST1)
"flg c .1 38.0 0\n" // Carry
"flg ov .1 39.0 0\n" // Overflow (sticky until tested)
"flg tc .1 40.0 0\n" // Test/control bit
"gpr ovm .1 41.0 0\n" // Overflow saturation mode
"gpr sxm .1 42.0 0\n" // Sign-extension mode
"gpr pm .2 43.0 0\n" // Product shift mode
"gpr arb .16 44 0\n" // Auxiliary register pointer backup
"gpr rptc .16 46 0\n"); // Repeat counter
}
if (is_c5000(rz_analysis_get_cpu(a))) {
p =
"=PC pc\n"
@ -457,7 +510,7 @@ static RzAnalysisILConfig *tms320_il_config(RzAnalysis *analysis) {
RzAnalysisPlugin rz_analysis_plugin_tms320 = {
.name = "tms320",
.arch = "tms320",
.bits = 32,
.bits = 16 | 32,
.desc = "TMS320 DSP family code analysis plugin",
.init = tms320_analysis_init,
.fini = tms320_analysis_fini,

View file

@ -8,6 +8,7 @@
#include <tms320/c55x_plus/c55plus_arch.h>
#include <tms320/c55x/c55x_analysis.h>
#include <tms320/c54x/c54x.h>
#include <tms320/c2x/c2x.h>
#include <tms320/c64x/c64x.h>
typedef struct tms_cs_context_t {
@ -28,6 +29,8 @@ static int tms320_disassemble(const RzAsm *a, RzAsmOp *op, const ut8 *buf, int l
desc = &c55x_arch_desc;
} else if (a->cpu && !rz_str_casecmp(a->cpu, "c54x")) {
desc = &c54x_arch_desc;
} else if (a->cpu && !rz_str_casecmp(a->cpu, "c2x")) {
desc = &c2x_arch_desc;
} else {
rz_asm_op_set_asm(op, "unknown asm.cpu");
return op->size = -1;
@ -76,6 +79,7 @@ static char *tms320_mnemonics(const RzAsm *a, int id, bool json) {
static char **tms320_cpu_descriptions() {
static char *cpu_desc[] = {
"c54x", "Texas Instruments TMS320C54x DSP family",
"c2x", "Texas Instruments TMS320C2x legacy fixed-point DSP family",
"c55x", "Texas Instruments TMS320C55x DSP family",
"c55x+", "Texas Instruments TMS320C55x+ DSP family",
"c64x", "Texas Instruments TMS320C64x DSP family",
@ -87,10 +91,10 @@ static char **tms320_cpu_descriptions() {
RzAsmPlugin rz_asm_plugin_tms320 = {
.name = "tms320",
.arch = "tms320",
.cpus = "c54x,c55x,c55x+,c64x",
.desc = "Texas Instruments TMS320 DSP family (c54x,c55x,c55x+,c64x) disassembler",
.cpus = "c54x,c55x,c55x+,c2x,c64x",
.desc = "Texas Instruments TMS320 DSP family (c54x,c55x,c55x+,c2x,c64x) disassembler",
.license = "LGPL3",
.bits = 32,
.bits = 16 | 32,
.endian = RZ_SYS_ENDIAN_LITTLE | RZ_SYS_ENDIAN_BIG,
.init = tms320_init,
.fini = tms320_fini,

View file

@ -0,0 +1,19 @@
default.cc=c2x
c2x=cc
cc.c2x.arg0=ar0
cc.c2x.arg1=ar1
cc.c2x.arg2=ar2
cc.c2x.arg3=ar3
cc.c2x.argn=stack
cc.c2x.maxargs=4
cc.c2x.ret=acc
c5x=cc
cc.c5x.arg0=ar0
cc.c5x.arg1=ar1
cc.c5x.arg2=ar2
cc.c5x.arg3=ar3
cc.c5x.argn=stack
cc.c5x.maxargs=4
cc.c5x.ret=acc

View file

@ -13,6 +13,7 @@ sdb_types_files = [
'cc-sparc-32',
'cc-spc700-16',
'cc-sysz-64',
'cc-tms320-16',
'cc-tms320-32',
'cc-tricore-32',
'cc-x86-16',

View file

@ -0,0 +1,178 @@
NAME=c2x analysis: register profile (PC alias resolves)
FILE==
CMDS=<<EOF
e asm.arch=tms320
e analysis.cpu=c2x
arp~^=PC
EOF
EXPECT=<<EOF
=PC pc
EOF
RUN
NAME=c2x analysis: register profile (accumulator present)
FILE==
CMDS=<<EOF
e asm.arch=tms320
e analysis.cpu=c2x
arp~acc
EOF
EXPECT=<<EOF
=R0 acc
ctr acc .32 0 0
gpr accl .16 0 0
gpr acch .16 2 0
EOF
RUN
NAME=c2x analysis: opcode classification (batched)
FILE=malloc://64
CMDS=<<EOF
e asm.arch=tms320
e analysis.cpu=c2x
wx 5500
ao 1 @ 0~^type
ao 1 @ 0~^size
wx 0409
ao 1 @ 0~^type
wx ca05
ao 1 @ 0~^type
wx ff800040
ao 1 @ 0~^type
ao 1 @ 0~^jump
wx f6800060
ao 1 @ 0~^type
ao 1 @ 0~^jump
ao 1 @ 0~^fail
wx fe800050
ao 1 @ 0~^type
ao 1 @ 0~^jump
wx ce26
ao 1 @ 0~^type
wx ce25
ao 1 @ 0~^type
EOF
EXPECT=<<EOF
type: nop
size: 2
type: add
type: mov
type: jmp
jump: 0x00000080
type: cjmp
jump: 0x000000c0
fail: 0x00000004
type: call
jump: 0x000000a0
type: ret
type: ujmp
EOF
RUN
# Original TI COFF: the target id is the file magic. COFF2 below carries it in
# a separate field, so the loader reaches the two by different paths.
NAME=c2x bin: open original TI COFF (target id as file magic)
FILE=bins/tms320/c2x/hello_c2x.ticoff0.coff
CMDS=<<EOF
i~^arch
i~^cpu
i~^bits
i~^machine
iS~[6]
is~[6]
EOF
EXPECT=<<EOF
arch tms320
cpu c2x
bits 16
machine TMS320C1x/C2x/C5x
name
.text
lib
add
main
EOF
RUN
NAME=c2x bin: open TI COFF (target_id 0x0092 autodetects cpu c2x)
FILE=bins/tms320/c2x/hello_c2x.ticoff2.coff
CMDS=<<EOF
i~^arch
i~^cpu
i~^bits
i~^machine
EOF
EXPECT=<<EOF
arch tms320
cpu c2x
bits 16
machine TMS320C1x/C2x/C5x
EOF
RUN
NAME=c2x analysis: register argument detection (ar0/ar1 via calling convention)
FILE=malloc://64
CMDS=<<EOF
e asm.arch=tms320
e asm.bits=16
e analysis.cpu=c2x
wx 7010711120100011ce26
af
afc
afvl
EOF
EXPECT=<<EOF
c2x
arg int16_t arg1 @ ar0
arg int16_t arg2 @ ar1
EOF
RUN
NAME=c2x analysis: op fields filled (direct data pointer, access direction, register source)
FILE=malloc://64
CMDS=<<EOF
e asm.arch=tms320
e asm.bits=16
e analysis.cpu=c2x
wx 2010
ao 1 @ 0~^direction
ao 1 @ 0~^ptr
wx 6010
ao 1 @ 0~^type
ao 1 @ 0~^direction
ao 1 @ 0~^ptr
wx 7010
ao 1 @ 0~^type
ao 1 @ 0~^reg
ao 1 @ 0~^direction
EOF
EXPECT=<<EOF
direction: read
ptr: 0x00000010
type: store
direction: write
ptr: 0x00000010
type: store
reg: ar0
direction: write
EOF
RUN
# Whole-image recovery: emulateme.bin is the RzIL exerciser, whose "dbl"
# subroutine sits at word 0x4e.
NAME=c2x analysis: emulateme function recovery
FILE=bins/tms320/c2x_legacy/emulateme.bin
ARGS=-a tms320 -b 16 -e asm.cpu=c2x -e analysis.cpu=c2x
CMDS=<<EOF
aaa
afl~[0,2]
axt @ 0x9c
EOF
EXPECT=<<EOF
0x00000000 156
0x0000009c 8
fcn.00000000 0x80 [CALL] call 0x4e
EOF
RUN

75
test/db/asm/tms320_c2x_16 Normal file
View file

@ -0,0 +1,75 @@
# TMS320C2x (legacy single-accumulator fixed-point) disassembly + RzIL.
# Encodings are MSB-first 16-bit words (4-byte forms are two words).
# Vectors derive from the public C2x instruction set (MAME TMS320x25 map).
# IL is asserted only for the lifted core; indirect-addressed and
# carry/status-dependent forms decode but carry no IL yet.
# --- no-operand control / status (CE block) ---
d "nop" 5500
d "eint" ce00
d "dint" ce01
d "rovm" ce02
d "sovm" ce03
d "ssxm" ce07
d "abs" ce1b
d "push" ce1c
d "pop" ce1d
d "ret" ce26
d "cala" ce24
d "bacc" ce25
d "pac" ce14
d "apac" ce15
d "spac" ce16
# --- accumulator immediates ---
d "zac" ca00
d "lack #0x5" ca05
d "addk #0x2" cc02
d "subk #0x3" cd03
d "ldpk #0x1" c801
d "lark ar0, #0x10" c010
d "lark ar3, #0x7f" c37f
d "mpyk #0x100" a100
# --- direct (DP-relative) data-memory forms ---
d "add 0x9, #0x0" 0009
d "add 0x9, #0x4" 0409
d "sub 0x20, #0x0" 1020
d "lac 0x20, #0x0" 2020
d "sacl 0x10, #0x0" 6010
d "sach 0x10, #0x0" 6810
d "lar ar1, 0x5" 3105
d "sar ar1, 0x5" 7105
d "lt 0x9" 3c09
d "mpy 0x9" 3809
d "and 0xc" 4e0c
d "or 0xc" 4d0c
d "in 0x0, #0x1" 8100
d "out 0x0, #0x1" e100
# --- indirect addressing (ARP-relative; decode/disasm only, no IL) ---
d "add *, #0x0" 0080
d "add *+, #0x0" 00a0
d "add *-, #0x0" 0090
d "add *+, #0x0, ar2" 00aa
d "lac *br0+, #0x0" 20f0
d "sacl *0+, #0x0" 60e0
d "mar *+" 55a0
# --- long-immediate (two-word) ---
d "lrlk ar0, #0x1234" d0001234
d "lalk #0x5678, #0x0" d0015678
d "andk #0xff, #0x0" d00400ff
# --- control transfer (two-word: opcode + target) ---
d "b 0x40" ff800040
d "call 0x50" fe800050
d "bz 0x60" f6800060
d "bnz 0x70" f5800070
d "bgz 0x80" f1800080
d "blz 0x90" f3800090
# --- NORM (normalize, CEx2) ---
d "norm *" ce82
d "norm *+" cea2
d "norm *-" ce92

View file

@ -58,7 +58,7 @@ _dA__ 8 16 snes LGPL3 SuperNES CPU disassembler
_dA_I 32 64 sparc BSD Sun SPARC Capstone-based disassembler
_dA__ 16 spc700 LGPL3 Sony SPC700 (Nintendo SuperNES sound-chip) disassembler
_dA__ 32 64 sysz BSD IBM SystemZ (S/390) Capstone-based disassembler
_dA_I 32 tms320 LGPL3 Texas Instruments TMS320 DSP family (c54x,c55x,c55x+,c64x) disassembler
_dA_I 16 32 tms320 LGPL3 Texas Instruments TMS320 DSP family (c54x,c55x,c55x+,c2x,c64x) disassembler
_dA_I 32 tricore BSD Siemens TriCore Capstone-based disassembler (by billow)
_dAeI 32 v810 LGPL3 NEC V810 disassembler (by pancake)
_dAeI 32 v850 LGPL3 NEC/Renesas V850 disassembler
@ -185,6 +185,7 @@ ps PowerPC with Paired Single SIMD extension
qpx PowerPC with Quad Processing eXtensions
v9 SPARC V9: 64-bit RISC architecture specification
c54x Texas Instruments TMS320C54x DSP family
c2x Texas Instruments TMS320C2x legacy fixed-point DSP family
c55x Texas Instruments TMS320C55x DSP family
c55x+ Texas Instruments TMS320C55x+ DSP family
c64x Texas Instruments TMS320C64x DSP family

File diff suppressed because one or more lines are too long

View file

@ -663,6 +663,7 @@ TOOL=rz-asm
ARGS=-m tms320
EXPECT=<<EOF
c54x Texas Instruments TMS320C54x DSP family
c2x Texas Instruments TMS320C2x legacy fixed-point DSP family
c55x Texas Instruments TMS320C55x DSP family
c55x+ Texas Instruments TMS320C55x+ DSP family
c64x Texas Instruments TMS320C64x DSP family