Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions tms320/c2x/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# TMS320C2x (legacy) test fixtures

Fixtures for Rizin's legacy single-accumulator TMS320C2x support
(`asm.arch=tms320`, `analysis.cpu=c2x`; COFF autodetect via target id
`0x0092`).

## Why these are hand-built

Unlike the other TMS320 targets in this directory (`c54x`, `c55x`, `c28x`,
`c6x` — all compiled from `emulateme_nostd.c` by the TI CGT tools, see
`../compilation_cmds_ccsv5.txt`), the legacy C2x has **no available
assembler**:

* The TI **C2000** Code Generation Tools (`cl2000`/`asm2000`) target **C28x**,
not the legacy single-accumulator C2x — a different instruction set.
* **binutils** ships `tic54x`, `tic4x` and `tic30` GAS targets, but nothing
for the legacy C2x/C25.

So C2x machine code is produced by hand from the published instruction
encodings (the opcode bit patterns match MAME's tested `tms32025` disassembler).
The disassembly/IL expectations live in the Rizin tree, not here:

* `test/db/asm/tms320_c2x_16` — disassembly + RzIL vectors (`d "..." <hex>`).
* `test/db/analysis/tms320.c2x_16` — op classification, reg profile, COFF open.

## Files

* `hello_c2x.ticoff2.coff` — a minimal TI COFF2 object (`nop; zac; lack #1;
ret`) with file magic `0x00C2` and target id `0x0092` (the first-generation
fixed-point id shared by the C1x/C2x/C5x tools), used by the COFF-open test.
* `make_c2x_coff.py` — regenerates the COFF object:
`python3 make_c2x_coff.py hello_c2x.ticoff2.coff`

## Verification status

These fixtures were prepared against the encoding reference and the COFF format,
but had not been round-tripped through a built `rz-asm`/loader at authoring
time. The COFF `target_id 0x0092` mapping and the F8..FF branch opcode tail in
particular should be confirmed against real C2x objects before being relied on.

## TMS320C5x

The C5x (C50/C51/C53) is an upward-compatible superset of the C2x and shares
the legacy first-generation COFF target id (`0x0092`), so the same fixture also
exercises the `c5x` CPU when opened with `-c c5x`:

```
rizin -a tms320 -c c5x hello_c2x.ticoff2.coff
```

Rizin decodes the C5x via the shared C2x core (which covers the C2x-compatible
instruction set that forms the bulk of real C5x code) and exposes the full C5x
register model (ACCB, PMST, TREG1/2, the circular-buffer pointers, BMAR, ...).
Real C5x objects (e.g. TMS320C5x DSK `.obj`/`.out` images) open and report
`arch=tms320, cpu=c2x, bits=16` and can be re-opened as `c5x`.
Binary file added tms320/c2x/hello_c2x.ticoff2.coff
Binary file not shown.
63 changes: 63 additions & 0 deletions tms320/c2x/make_c2x_coff.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2026 RizinOrg <info@rizin.re>
# SPDX-License-Identifier: LGPL-3.0-only
#
# Generate a minimal TI COFF2 object for the legacy TMS320C2x. No assembler
# targets the legacy C2x (neither the TI C2000 tools, which emit C28x, nor
# binutils), so this hand-assembles a tiny .text section of known C2x words and
# wraps it in a TI COFF2 container with target_id 0x0092 (the first-generation
# fixed-point id, shared by the C1x/C2x/C5x tools) so Rizin's COFF loader
# autodetects arch=tms320, cpu=c2x. Headers are little-endian (TI COFF
# convention); the C2x instruction words in .text are MSB-first.
import struct, sys

# .text : nop ; zac ; lack #1 ; ret (MSB-first 16-bit words)
code_words = [0x5500, 0xCA00, 0xCA01, 0xCE26]
text = b"".join(struct.pack(">H", w) for w in code_words) # big-endian words

F_MAGIC = 0x00C2 # TI COFF version 2
TARGET_ID = 0x0092 # TMS320C1x/C2x/C5x (first-gen fixed point)
OPTHDR_SIZE = 28
SCNHDR_SIZE = 40
HDR_SIZE = 20

# layout: file hdr(20) + target_id(2) + opt hdr(28) + 1 scn hdr(40) + data
data_off = HDR_SIZE + 2 + OPTHDR_SIZE + SCNHDR_SIZE

# file header (little-endian)
fh = struct.pack("<H", F_MAGIC) # f_magic
fh += struct.pack("<H", 1) # f_nscns
fh += struct.pack("<I", 0) # f_timdat
fh += struct.pack("<I", 0) # f_symptr (no symbol table)
fh += struct.pack("<I", 0) # f_nsyms
fh += struct.pack("<H", OPTHDR_SIZE) # f_opthdr
fh += struct.pack("<H", 0x0002) # f_flags = F_EXEC
fh += struct.pack("<H", TARGET_ID) # TI target id (read right after the header)

# optional header (little-endian)
oh = struct.pack("<H", 0x0108) # magic (TI executable)
oh += struct.pack("<H", 0) # vstamp
oh += struct.pack("<I", len(text)) # tsize
oh += struct.pack("<I", 0) # dsize
oh += struct.pack("<I", 0) # bsize
oh += struct.pack("<I", 0) # entry
oh += struct.pack("<I", 0) # text_start
oh += struct.pack("<I", 0) # data_start

# section header (little-endian)
sh = b".text\x00\x00\x00" # s_name[8]
sh += struct.pack("<I", 0) # s_paddr
sh += struct.pack("<I", 0) # s_vaddr
sh += struct.pack("<I", len(text)) # s_size (bytes)
sh += struct.pack("<I", data_off) # s_scnptr
sh += struct.pack("<I", 0) # s_relptr
sh += struct.pack("<I", 0) # s_lnnoptr
sh += struct.pack("<H", 0) # s_nreloc
sh += struct.pack("<H", 0) # s_nlnno
sh += struct.pack("<I", 0x00000020) # s_flags = STYP_TEXT

blob = fh + oh + sh + text
assert len(fh) == HDR_SIZE + 2 and len(oh) == OPTHDR_SIZE and len(sh) == SCNHDR_SIZE
out = sys.argv[1] if len(sys.argv) > 1 else "hello_c2x.ticoff2.coff"
open(out, "wb").write(blob)
print("wrote %s (%d bytes), text=%d bytes" % (out, len(blob), len(text)))
Binary file added tms320/c2x_legacy/01_arithmetic.bin
Binary file not shown.
Binary file added tms320/c2x_legacy/02_logic_shifts.bin
Binary file not shown.
Binary file added tms320/c2x_legacy/03_branches_calls.bin
Binary file not shown.
Binary file added tms320/c2x_legacy/04_fir_filter.bin
Binary file not shown.
Binary file added tms320/c2x_legacy/05_status_data.bin
Binary file not shown.
Binary file added tms320/c2x_legacy/10_full_program.bin
Binary file not shown.
Binary file added tms320/c2x_legacy/emulateme.bin
Binary file not shown.
68 changes: 68 additions & 0 deletions tms320/c2x_legacy/src/01_arithmetic.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
; SPDX-FileCopyrightText: 2026 RizinOrg <info@rizin.re>
; SPDX-License-Identifier: LGPL-3.0-only
;
; 01_arithmetic.asm -- TMS320C2x arithmetic and load/store coverage.
;
; Ported in spirit from the C54x coff1/01_arithmetic.s, re-expressed for the
; single-accumulator C2x: ADD/SUB with shifts and the H/S/T/C variants, the
; ADDK/SUBK/ADLK/SBLK/LALK immediates, ABS/NEG, the LT/MPY/PAC product chain
; and direct + indirect (post-modify) data addressing.
;
; Computes ((5 + 3) << 1) - |0 - 7| + (6 * 7) and leaves it in a scratch word,
; touching every arithmetic addressing form on the way. Data lives on page 4
; (word base 0x200) to stay clear of the program image.
;
.org 0
ldpk 4 ; DP = 4 -> data page base word 0x200

; seed a few data words
lalk 7, 0
sacl 0 ; mem[0x200] = 7
lalk 6, 0
sacl 1 ; mem[0x201] = 6
lalk 0x55, 0
sacl 2 ; mem[0x202] = 0x55

; accumulator immediates and shifts
lack 5
addk 3 ; ACC = 8
sfl ; ACC = 16
sacl 3 ; mem[0x203] = 16

; subtract with |.| via ABS
zac
sub 0 ; ACC = -7
abs ; ACC = 7
neg ; ACC = -7
abs ; ACC = 7
sacl 4

; long immediates
lalk 0x1234, 0
adlk 0x0100, 0 ; ACC = 0x1334
sblk 0x0034, 0 ; ACC = 0x1300
andk 0x0F00, 0 ; ACC = 0x0300
sacl 5

; product chain: P = 6 * 7, accumulate
lt 1 ; T = 6
mpy 0 ; P = 6 * 7 = 42
pac ; ACC = 42
apac ; ACC = 84
spac ; ACC = 42
sacl 6

; indirect addressing with post-increment
larp 2
lrlk 2, 0x210
lack 0x11
sacl *+, 0 ; mem[0x210] = 0x11 ; AR2 -> 0x211
lack 0x22
sacl *, 0 ; mem[0x211] = 0x22
lrlk 2, 0x210
zac
add *+, 0 ; ACC += 0x11
add *, 0 ; ACC += 0x22 -> ACC = 0x33
sacl 7

idle
53 changes: 53 additions & 0 deletions tms320/c2x_legacy/src/02_logic_shifts.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
; SPDX-FileCopyrightText: 2026 RizinOrg <info@rizin.re>
; SPDX-License-Identifier: LGPL-3.0-only
;
; 02_logic_shifts.asm -- TMS320C2x logical and shift/rotate coverage.
;
; AND/OR/XOR with memory, the ANDK/ORK/XORK long-immediate forms, the
; SFL/SFR/ROL/ROR shifters (carry in/out), CMPL, and the T-controlled shifts
; LACT/ADDT. Builds a value through a sequence of bit operations.
;
.org 0
ldpk 4

lalk 0x0F0F, 0
sacl 0 ; mem = 0x0F0F
lalk 0x00FF, 0
sacl 1
lalk 0x3300, 0
sacl 2

lalk 0xF0F0, 0
and 0 ; ACC = 0xF0F0 & 0x0F0F = 0
or 2 ; ACC = 0x3300
xor 0 ; ACC = 0x3300 ^ 0x0F0F = 0x3C0F
sacl 3

; immediate logicals
lack 0
ork 0xFF00, 0 ; ACC = 0xFF00
andk 0x0FF0, 0 ; ACC = 0x0F00
xork 0x00F0, 0 ; ACC = 0x0FF0
cmpl ; ACC = ~0x0FF0 = 0xFFFFF00F
sacl 4

; shifts and rotates through carry
sc ; C = 1
lack 1
rol ; ACC = 3 (carry into bit0), C = 0
rol ; ACC = 6
sfl ; ACC = 12
sfr ; ACC = 6
ror ; rotate right through carry
sacl 5

; T-controlled shift (LACT shifts by T[3:0])
lalk 4, 0
sacl 6
lt 6 ; T = 4
lalk 0x0003, 0
sacl 7
lact 7 ; ACC = mem[7] << (T & 0xf) = 3 << 4 = 0x30
sacl 8

idle
56 changes: 56 additions & 0 deletions tms320/c2x_legacy/src/03_branches_calls.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
; SPDX-FileCopyrightText: 2026 RizinOrg <info@rizin.re>
; SPDX-License-Identifier: LGPL-3.0-only
;
; 03_branches_calls.asm -- TMS320C2x control-flow coverage.
;
; The conditional-branch set (BZ/BNZ/BGZ/BLEZ/BGEZ/BLZ/BV/BNV/BC/BNC), an
; unconditional B, and a CALL/RET pair exercising the (synthetic) stack. The
; program walks a short decision tree and a subroutine, ending with a known ACC.
;
.org 0
ldpk 4

; subroutine call: add5 adds 5 to ACC
lack 10
call add5 ; ACC = 15
sacl 0

; conditional branch on sign
zac
sub 0 ; ACC = -15 (mem[0] = 15)
blz neg_path ; taken (ACC < 0)
lack 0xEE ; (skipped)
b done_sign
neg_path:
lack 0x42 ; ACC = 0x42
done_sign:
sacl 1

; BZ / BNZ
zac
bz was_zero ; taken
lack 0xFF
was_zero:
lack 0x7
bnz nonzero ; taken
lack 0xFF
nonzero:
sacl 2

; carry-based branch
lalk 0xFFFF, 1 ; ACC = 0xFFFE
addk 2 ; overflow into carry; ACC = 0x10000
bc had_carry ; taken
lack 0xAA
b after_c
had_carry:
lack 0x5A
after_c:
sacl 3

idle

; add5: ACC += 5 ; return
add5:
addk 5
ret
76 changes: 76 additions & 0 deletions tms320/c2x_legacy/src/04_fir_filter.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
; SPDX-FileCopyrightText: 2026 RizinOrg <info@rizin.re>
; SPDX-License-Identifier: LGPL-3.0-only
;
; 04_fir_filter.asm -- TMS320C2x FIR / dot-product kernel.
;
; The canonical DSP MAC chain on the single-accumulator C2x: an 8-tap dot
; product y = sum(h[i] * x[i]) computed with the LT / MPY / LTA / APAC idiom and
; indirect post-increment addressing through AR2 (samples) and AR3 (coeffs).
;
; x = {1,2,3,4,5,6,7,8}, h = {8,7,6,5,4,3,2,1}
; y = 8+14+18+20+20+18+14+8 = 120 = 0x78
;
.org 0
ldpk 6 ; data page base word 0x300

; samples x[0..7] at 0x300..0x307
lack 1
sacl 0
lack 2
sacl 1
lack 3
sacl 2
lack 4
sacl 3
lack 5
sacl 4
lack 6
sacl 5
lack 7
sacl 6
lack 8
sacl 7
; coeffs h[0..7] at 0x308..0x30F
lack 8
sacl 8
lack 7
sacl 9
lack 6
sacl 10
lack 5
sacl 11
lack 4
sacl 12
lack 3
sacl 13
lack 2
sacl 14
lack 1
sacl 15

; dot product
larp 2
lrlk 2, 0x300 ; AR2 -> x[]
lrlk 3, 0x308 ; AR3 -> h[]
zac
lt *+, 3 ; T = x0 ; ARP -> AR3
mpy *+, 2 ; P = x0*h0 ; ARP -> AR2
lta *+, 3 ; ACC += P ; T = x1 ; ARP -> AR3
mpy *+, 2 ; P = x1*h1
lta *+, 3
mpy *+, 2
lta *+, 3
mpy *+, 2
lta *+, 3
mpy *+, 2
lta *+, 3
mpy *+, 2
lta *+, 3
mpy *+, 2
lta *+, 3
mpy *+, 2
apac ; final accumulate -> ACC = 120
ldpk 6
sacl 16 ; store result word

idle
Loading
Loading