Skip to content

Add missing TI TMS320C6x archtiectures - C62x, C66x, C67x.#6603

Draft
notxvilka wants to merge 8 commits into
devfrom
asan-tms320-c6x
Draft

Add missing TI TMS320C6x archtiectures - C62x, C66x, C67x.#6603
notxvilka wants to merge 8 commits into
devfrom
asan-tms320-c6x

Conversation

@notxvilka

Copy link
Copy Markdown
Contributor

Your checklist for this pull request

  • I've read the guidelines for contributing to this repository.
  • I made sure to follow the project's coding style.
  • I've documented every RZ_API function and struct this PR changes.
  • I've added tests that prove my changes are effective (required for changes to RZ_API).
  • I've updated the Rizin book with the relevant information (if needed).
  • I've used AI tools to generate fully or partially these code changes and I'm sure the changes are not copyrighted by somebody else.

Detailed description

We had the TMS320C64 support via Capstone, but the capstone implementation is incomplete, also by creating a sharing infrastructure like TMS320C5x series already does and reuse some of the already existing TMS320 infrastructure in Rizin, we can reuse common code (and uplifting to RzIL) between all of the TMS320C6x architectures.

Test plan

CI is green

Closing issues

Closes rizinorg/ideas#64

XVilka added 8 commits July 5, 2026 23:36
TI C6000 relocatable COFF objects carry symbol relocations that Rizin
ignored: the relocation path keyed on the PE machine word and read fixed
10-byte entries, but TI COFF2 records its target in a separate field and
uses 12-byte entries. MVKL/MVKH address halves and data pointers were
left at zero, so nothing referencing a symbol resolved.

TI encodes each relocation as an RPN expression evaluated by a small
stack machine (SPRAAO8): RE_PUSH* load a symbol value or a constant, the
arithmetic operators fold the two top stack entries, and the RE_*STFLD
operators store the result into the bit field the reloc's symbol-index
word describes. Evaluate that per section, together with the classic
direct RELBYTE/WORD/LONG data relocations, and emit one patch per store
so both the reloc list and the patched buffer show the resolved address.

Co-authored-by agent: Claude/claude-opus-4-8
ELF EM_TI_C6000 objects were falling through to x86; map the machine to
the tms320 arch and default the cpu to c674x (the EABI does not record
the variant, so use the unified C64x+/C67x+ superset the ABI targets).

Convert the full EABI relocation set: the data relocations (ABS8/16/32)
resolve to a flat symbol value, the instruction-field relocations (the
MVKL/MVKH ABS_L16/H16 halves and the static-base SBR_* and PC-relative
PCR_* displacements) patch a bit field inside a 32-bit opcode and so
carry their width but no directly resolvable value, and the
framing/attribute entries (ALIGN/FPHEAD/NOCMP) patch nothing. A C6000
object that previously resolved no relocations now resolves its full set
with symbol names.

Co-authored-by agent: Claude/claude-opus-4-8
The C64x support was a thin Capstone wrapper: it only covered C64x,
carried no analysis of its own, and could not grow to the rest of the
C6000 line. Replace it with one native engine for the whole VLIW family
(c62x/c64x/c64x+/c67x/c674x/c66x), structured like the c54x/c55 engines
so RzIL and pseudo output can be layered on later.

The engine is a pure decoder feeding stateless consumers: decode.c
produces a C6xInsn (unit, operands, predicate, parallel bit, op-type) and
format.c / analysis.c render text and fill RzAnalysisOp from it. A single
opcode table drives every cpu; floating-point and SIMD rows are gated by
per-cpu feature flags, so mpysp and the C64x+ integer extensions (mvd,
mpyli, mpy32u, andn, ...) decode only where they exist. Double-precision
and 40-bit long results render as register pairs (a1:a0).

Real C64x+/C674x code is dominated by compact 16-bit instructions: a
fetch packet may end in a header word (.fphead) whose layout field
repacks the other seven words as pairs of 16-bit opcodes and supplies the
register set, saturation, and parallel bit. A fixed 32-bit decoder
misaligns the bulk of such code, so the decoder reads the header ahead of
the packet and sizes each slot as two or four bytes. Every compact format
the corpus exercises is expanded: nop/mv/mvk, the .L ALU and
compare-against-constant forms, the .D word/byte/half and doubleword
loads/stores in all addressing modes (including the B15 stack forms), the
.S shift/field/mvc/mvk forms, and the branches, all carrying the
packet-relative displacement.

Control flow the Capstone path never produced: B/BNOP displacements to
jmp/cjmp with resolved targets, B to a register to rjmp, the BDEC/BPOS
loop back-edges, and the ABI return (B .S2 B3) to ret -- in both the
32-bit and compact forms -- so function boundaries and the
software-pipelined loop fall out of the graph. CALLP is recognised as a
call: it is encoded as an otherwise-meaningless unconditional branch with
the z bit set (creg == 0, bits 31:28 == 0001), so a real predicated
branch there is unaffected. Branch offsets are relative to the
fetch-packet base (PCE1), and BNOP is half-word-scaled so it can reach
compact slots; the compact branch encodings are always BNOP, even with a
zero NOP count.

The whole set is validated byte-exact against TI dis6x (c6000 v7.4.4, run
under Wine): all eight emulateme objects (every variant, COFF and ELF,
ccsv5 and ccsv7) and 9480 unique instruction words (6047 C64x, 1381 C67x,
2052 C674x) disassemble with zero invalids and zero mismatches. That
includes the assembler idioms dis6x prints and the underlying decode
already implied -- MV (or/add against 0), NEG, NOT (xor -1), ZERO
(a register subtracted from itself), and the negative-constant SUB spelled
as an add -- now rendered the same way on the .L, .S, and .D units, whose
differing operand orders the earlier code got wrong for the .D forms. The
sweeps also corrected a scrambled .S shift table and a set of 40-bit
(slong) SUB/ADD forms whose pair destination had been read as a single.

The register profile gains the C6000 EABI stack/frame roles (B15 is SP,
A15 the frame pointer), the CSR status alias, and the corrected
generic-argument mapping (the fourth argument register is B6, not a second
A6). Analysis fills op->family (FPU for the floating-point ops), op->val
for an immediate operand, and op->stackptr for an add/sub writing the
stack pointer, so prologue/epilogue stack motion is visible.

The Capstone backend stays reachable as the c64x-capstone cpu for the
pseudo output the native engine lacks; the tests asserting
Capstone-specific opex are pointed at it, while the shared register
profile is exercised through native c64x.

The C64x+/C66x packed-SIMD set is filled in across .M, .S, and .L: the
packed multiplies and dot products, GMPY4, ROTL, the XPND/SHFL/DEAL/BITC/
BITR shuffles, MPY32(U/SU/US), the packed add/sub/min/max and pack/unpack
forms, and MPY(SP2)DP -- derived from the TI asm6x assembler and checked
byte-exact against dis6x, cross-path forms included. The C66x complex
and matrix multiplies (CMPY*, GMPY, XORMPY, DDOTP*, the 4H dot products)
decode in the z=1/creg=0 corner of the same space -- always
unconditional, which is exactly what tells them apart from the C64x+ op
sharing each slot -- gated so CMPY is invalid on C64x/C67x and DOTP4H on
C674x. The 128-bit C66x quad/matrix multiplies (QMPY32, QMPYSP,
QSMPY32R1, DMPYSP, CMATMPY/CMATMPYR1) decode too, via a new register-
quad operand (a7:a6:a5:a4). Analysis (RzAnalysisOp) covers every added
instruction: the packed and complex multiplies are MUL (MPYSPDP/
MPYSP2DP in the FPU family), packed add/sub/compare/shift map to their
integer types, SPLOOP is NULL and IDLE is NOP; predication is keyed on
creg alone so the unconditional z=1 ops are not mistaken for CJMPs. The software-
pipelined loop-buffer ops (SPLOOP/SPLOOPD/SPMASK) and IDLE decode too. The
register profile is narrowed per cpu: C62x/C67x expose 16 registers per
side and no C64x+ control-register file, C64x and later the full 32 plus
that file. Finally the || parallel prefix follows the TI convention -- it
marks an instruction that runs in parallel with the *previous* one,
carried across the stateless decode calls by a one-word look-back that a
fetch-packet header passes through transparently.

Co-authored-by agent: Claude/claude-opus-4-8
Lift the decoded C6xInsn to RzIL through op->il_op, with a per-cpu IL
config: a 32-bit PC bound as pce1 (the name the c6x profile uses), byte-
addressed memory, the 16- or 32-register general set matching the profile
(c62x/c67x expose only a0-a15/b0-b15), and the control registers MVC
reaches -- the set common to every C6000 plus the C64x+ extension bound
only on the wide variants, so IL-VM setup never references a register the
profile omits. The scalar core is covered: the MV/MVK/MVKL/MVKH/MVD moves
and MVC control-register moves; the register and immediate ALU (ADD, SUB,
ADDK, AND, ANDN, OR, XOR, NEG, NOT, ABS with INT_MIN saturation); the
shifts (SHL, SHR arithmetic, SHRU logical); the 16x16 multiplies (MPY,
MPYH), the two-way dot products (DOTP2 = lo*lo + hi*hi, DOTPN2 = hi*hi -
lo*lo), and the four-way byte dot products (DOTPU4 unsigned, DOTPSU4
signed x unsigned); the single- and double-precision float arithmetic
(ADDSP/SUBSP/MPYSP and ADDDP/SUBDP/MPYDP over register pairs) and the
int-to-float and single-to-int conversions (INTSP, SPINT round to
nearest, INTDP, SPDP), lifted through the RzIL floating-point ops as
IEEE-754 binary32/binary64 rounding to nearest even (SPINT's decode is
added alongside in the disassembler commit); the packed add/sub (ADD2/SUB2 over two halfwords, ADD4/SUB4 over
four bytes, each lane independent) and their signed-saturating forms
(SADD2/SSUB2, each lane clamped to the signed 16-bit range); the packed
min/max (MIN2/MAX2 signed halfwords, MINU4/MAXU4 unsigned bytes) and
packed absolute value (ABS2, per halfword with 0x8000 -> 0x7fff
saturation); the halfword packs (PACK2/PACKH2/PACKLH2/PACKHL2), the signed
32->16 saturating pack (SPACK2), and the byte unpacks (UNPKLU4/UNPKHU4);
the scalar compares (CMPEQ, CMPGT/CMPGTU, CMPLT/CMPLTU, result 1 or 0) and
the packed compares (CMPEQ2/CMPGT2 halfwords, CMPEQ4/CMPGTU4 bytes, one
result bit per lane); field extracts (EXT, EXTU) and ZERO; the
byte/half/word loads and stores and the 64-bit doubleword pair loads and
stores (LDDW/STDW and the non-aligned LDNDW/STNDW, whose even register
holds the low word at the lower address); and the control transfers -- the
B/BNOP branches (register, control-register or PC-relative target), the
protected call CALLP (which also records the return address), and the
PC-relative address computation ADDKPC. All memory forms cover every
addressing mode, including pre/post base write-back. MVC is a plain
register copy, so status-register side effects are not modelled. A
predicated instruction wraps its effect in a BRANCH on the creg register
with the z sense, which for a branch yields a conditional jump.

Every lifted form is checked in the c6x asm tests, where rz-asm -I both
renders and validates the IL, matching the rest of tms320.

One VLIW trait bounds emulation, deliberately for this pass: instructions
in one execute packet read the register file as of the packet start (all
reads before all writes), which the sequential RzIL VM does not model as a
parallel register swap. Branches carry five delay slots; as in the other
delay-slot lifters, the IL emits the transfer directly, so the sequential
VM takes a branch at once rather than after five execute packets -- but
the branch target, and CALLP's return address, are exact. The
cont/parallel flags and op->delay carry what a packet- and delay-slot-
aware pass would need. The byte-saturating ops, the packed shifts, the
saturating/complex multiplies, and the float-to-int conversions
(decoder-gated) are not yet lifted.

Co-authored-by agent: Claude/claude-opus-4-8
The native c6x engine now decodes, analyzes, and lifts the whole C64x/
C64x+ instruction set (verified byte-exact against the assembler and with
zero invalids over the emulateme corpus), so the optional Capstone-based
c64x path is redundant. Drop the `c64x-capstone` cpu, its wrapper, and
the plugin plumbing; C64x is now native-only.

Co-authored-by agent: Claude/claude-opus-4-8
Restore the aoma/aom mnemonic listing that the Capstone path used to
provide, now driven by the native decoder. c6x_mnemonics() gathers the
printed mnemonic of every decode-table row plus the forms the decoder
emits directly (loads, stores, moves, branches, the nop/idle family),
sorted and de-duplicated. The asm plugin mnemonics callback serves it for
the native c6x cpus and returns nothing for the C5000/C54x front-ends,
which expose no such table.

Co-authored-by agent: Claude/claude-opus-4-8
The shared cc-tms320-32 sdb only carried the C5000 convention, so C6000
functions fell back to it (ar0-ar3), registers the C6000 file does not
have. Add the C6000 EABI (SPRAB89) convention -- arguments in
A4/B4/A6/B6/A8/B8/A10/B10/A12/B12, return in A4 -- and point the analysis
default at it for the c6x cpus, restoring c55x when a C5000 cpu is
selected. Together with the wider reg-profile-to-cc derivation this makes
arcc report the full ten-argument convention.

Co-authored-by agent: Claude/claude-opus-4-8
Add a module README describing the current state of the c6x native
disassembler, analysis, and RzIL uplift: the decoder/consumer split,
the VLIW packet and compact-fetch-packet handling, the decoded
instruction coverage across the family, and what the RzIL lifter models.

Co-authored-by agent: Claude/claude-opus-4-8
@github-actions github-actions Bot added rz-test RzBin RzAnalysis ELF RZIL TMS320 AI/LLM Partially or fully AI generated. Critical thinking is advised! labels Jul 5, 2026
}
RzILOpPure *off = reg_off
? (scale > 1 ? MUL(VARG(c6x_reg_name(m->base_side, m->off_reg)), U32(scale)) : VARG(c6x_reg_name(m->base_side, m->off_reg)))
: U32(m->off_cst * scale);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI/LLM Partially or fully AI generated. Critical thinking is advised! ELF rz-test RzAnalysis RzBin RZIL TMS320

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Texas Instruments TMS320C67x architecture

3 participants