The 16-bit-displacement jump (low byte 0x00, two words) is JRcc, not the three-word JAcc (low byte 0x80). Printing it as ja made linear disassembly report the wrong control flow and made every such branch grow by a word when reassembled. Register 15 is the stack pointer in either file: 0x0F is A15 and 0x1F is B15. The disassembler printed both as sp, hiding B-file stack-pointer code. Print b15 when the R bit selects the B file. Validated with the 1987 TI GSPA/GSPLNK/GSPROM round trip: the 32 KB region at bit 3F208000 still reassembles byte-for-byte (md5 43adb6669571041985f3e26fd9ff7cb9), as do the 4 KB and 16 KB spans and the 0x8000/3F240000 region. The converter no longer needs its ja->jr or sp->b15 rewrites (5 fixups -> 3); a whole-ROM diff contains only those two output categories. The ROM is unchanged (b46a3e442ccb86134d2356bd70d22fd2).
Round-trip validation for the two dis34 fixes
This directory proves the two corrections in tools/smart34010/dis34.c
against the 1987 TI TMS34010 toolchain (GSPA/GSPLNK/GSPROM under
DOSBox), using the same method as reference/tms34010-sdk.md. The ROM is
only read; its md5 must stay b46a3e442ccb86134d2356bd70d22fd2.
The encodings (from docs/TMS34010-users-guide.txt)
Long relative jump. The Program Control summary (PDF p. A-16) gives:
JAcc Address Jump Absolute Conditional 3 3,4 1 100 code 1000 0000
JRcc Address Jump Relative Conditional 2 3,2 1100 code 0000 0000
JRcc Address Jump Relative Conditional - Short 2,1 1 100 code xxxx xxxx
The ±32K-word JRcc description (p. 12-96) says:
The assembler calculates the offset as (Address - PC')/16 and inserts the resulting offset into the second instruction word of the opcode. The range for this form of the JRcc instruction is -32,768 to +32,767 words.
So low byte 0x00 + a 16-bit displacement is JRcc, not JAcc; only low
byte 0x80 + a 32-bit address is JAcc.
B-file SP. Section 5.1.3 (p. 5-8):
The TMS34010 contains only a single SP. However, this SP can be addressed as a member of either register file, as register A15 or register B15.
and the MMFM description (p. 12-109):
The R bit (bit 4) in the first word indicates which register file is affected.
0x0F is A15 (sp); 0x1F is B15 (b15). GSPA accepts b15 and rejects
a15.
Fixups required by the converter, before and after
reference/tms34010-sdk.md lists five fixups for dis34 output. The old
converter (harness/sdk-scratch/dis2as34.py) implemented all five:
| # | fixup | before | after |
|---|---|---|---|
| 1 | hex constants (0x220 → 0220h) |
needed | needed |
| 2 | MOVI/ADDI/CMPI/SUBI short vs long (,W / ,L) |
needed | needed |
| 3 | B-file SP (rl …, sp → b15) |
needed | gone — dis34 prints b15 |
| 4 | long relative ja<cc> → jr<cc> (2 words) |
needed | gone — dis34 prints jr<cc> |
| 5 | branch target not an instruction boundary → .word |
needed | needed |
The shipped converter here (dis2as34.py) drops 3 and 4, so its fixup list is
5 → 3. The JR one is the expected disappearance; the SP one disappears
as a direct consequence of surfacing B15. (harness/sdk-scratch/ is left
untouched as the record of the pre-fix method.)
Results (new dis34 + dis2as34.py with fixups 3 and 4 removed)
| region (file offset) | base (bit) | size | differing | md5 |
|---|---|---|---|---|
0x1000 |
3F208000 |
4 KB | 0 | a4018fed12e062658bc81957f149265b |
0x1000 |
3F208000 |
16 KB | 0 | 0a2e679bc864d52d32c59740142c5b96 |
0x1000 |
3F208000 |
32 KB | 0 | 43adb6669571041985f3e26fd9ff7cb9 |
0x8000 |
3F240000 |
4 KB | 0 | 438e743431d56e0c12ededce1c36952a |
The same 32 KB region disassembled by the old dis34 and fed through the
converter with the removed rewrites fails (assembled 32912 bytes, original 32768, 27247 differing): the long relative branches grow by a word and the
rl … sp destination is encoded as A15. With the fixed dis34 the converter
source is equivalent to the old one: only 3 lines change sp to b15 and
2 differ in whitespace (rl 09h,b15 vs rl 09h, b15); assembly is
byte-identical.
Proof that the fixes change nothing else
verify_dis.py disassembles the whole ROM in 32 KB chunks with both a control
binary and the fixed binary and classifies every changed line:
$ ./verify_dis.py work/dis34.orig work/dis34.sponly ../../vt1200_v22.bin
long-relative JA->JR changes: 0
B-file SP sp->b15 changes: 2623
all differences are the two intended categories
$ ./verify_dis.py work/dis34.orig ../../tools/smart34010/dis34 ../../vt1200_v22.bin
long-relative JA->JR changes: 6235
B-file SP sp->b15 changes: 2623
all differences are the two intended categories
The SP-only comparison is the additivity check: zero mnemonic changes, and
every change is exactly an sp operand becoming b15 on an instruction whose
encoding can name B15 (bit 4 set, or the cross-file MOVE Rs,Rd dest). The
full-fix comparison adds only the JA→JR mnemonic changes on 2-word
0xC?00 instructions. Labels and line counts are unchanged (labels do not
depend on either fix).
Reproducing
cd harness/sdk-roundtrip
./prepare_work.sh # builds work/dis34.orig and dis34.sponly
./roundtrip.sh 0x1000 32768 3F208000 # prints BYTE-EXACT + md5
DIS34=$PWD/work/dis34.orig ./roundtrip.sh 0x1000 32768 3F208000 # expected FAIL
./verify_dis.py work/dis34.orig work/dis34.sponly ../../vt1200_v22.bin
./verify_dis.py work/dis34.orig ../../tools/smart34010/dis34 ../../vt1200_v22.bin
work/ also holds the pre-fix source/binary (dis34.orig*) and an SP-only
binary (dis34.sponly*) used for the isolation checks. The SDK image is
extracted from tools/tms34010-sdk/ into work/ on first use.
Cached disassemblies elsewhere (reference/dis34-a-v22.txt.gz,
harness/dis/*.txt) were produced by the buggy binary and still show
ja<cc> for the 2-word relative jumps; regenerate them with the fixed
dis34 before trusting their control flow.