Files
SDD af7d3565a5 dis34: print long relative JR as jr and surface B-file SP as b15
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).
2026-10-18 00:54:34 +03:00

108 lines
3.9 KiB
Python
Executable File

#!/usr/bin/env python3
"""Compare two dis34 builds over the whole ROM and classify every difference.
Purpose: prove the two `dis34` fixes are the *only* changes to its output.
* long-relative JR fix: the line must change from ``ja<cc>`` to ``jr<cc>``
and carry exactly two opcode words, the first being ``0xC?00``;
* B-file SP fix: the mnemonic must be unchanged, every ``sp`` operand must
become ``b15``, and the encoding must be able to name B15: bit 4 set, or
the cross-file MOVE Rs,Rd whose destination is the other file.
Usage: verify_dis.py OLD_DIS34 NEW_DIS34 [ROM]
Chunks are disassembled with 16 zero bytes of padding so a multi-word
instruction at the end of a chunk cannot abort the run. The padding is
identical for both builds and its decoded lines are still compared.
"""
import os
import re
import subprocess
import sys
HEXWORD = re.compile(r"\b[0-9a-fA-F]{4}\b")
def disassemble(exe, chunk, base, work):
part = os.path.join(work, "verify_chunk.bin")
with open(part, "wb") as f:
f.write(chunk + b"\0" * 16)
p = subprocess.run([exe, "-b", "%X" % base, "-o", part],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
check=True)
return p.stdout.decode("latin-1").splitlines()
def words(line):
code, _, comment = line.partition(";")
return HEXWORD.findall(comment)
def can_name_b15(w0):
"""True if the instruction's register field can select B-file reg 15.
Bit 4 (the R bit) normally selects the file. MOVE Rs,Rd cross-file
(op 0x4E00) is the exception: its *destination* is the other file
(dis34 prints ``regname(rd, rf ^ 1)``), so bit 4 = 0 still means B15
for the destination.
"""
op = int(w0, 16)
return bool(op & 0x10) or (op & 0xfe00) == 0x4e00
def main():
old_exe, new_exe = sys.argv[1], sys.argv[2]
rom_path = sys.argv[3] if len(sys.argv) > 3 else "vt1200_v22.bin"
work = os.path.dirname(os.path.abspath(__file__)) + "/work"
os.makedirs(work, exist_ok=True)
rom = open(rom_path, "rb").read()
chunk_size = 32768
base0 = 0x3F200000
n_jr = n_sp = 0
errors = []
for start in range(0, len(rom), chunk_size):
chunk = rom[start:start + chunk_size]
base = base0 + start * 8
old = disassemble(old_exe, chunk, base, work)
new = disassemble(new_exe, chunk, base, work)
if len(old) != len(new):
errors.append("chunk %#x: line count %d != %d" %
(start, len(old), len(new)))
continue
for i, (a, b) in enumerate(zip(old, new)):
if a == b:
continue
w = words(a)
code_a = a.partition(";")[0].strip()
code_b = b.partition(";")[0].strip()
tok_a = code_a.split()[0].lower() if code_a.split() else ""
tok_b = code_b.split()[0].lower() if code_b.split() else ""
ja = re.fullmatch(r"ja([a-z]+)", tok_a)
if (ja and tok_b == "jr" + ja.group(1) and len(w) == 2
and re.fullmatch(r"c[0-9a-f]00", w[0].lower())):
n_jr += 1
continue
# SP fix: same mnemonic, only sp -> b15, R bit set
if (tok_a == tok_b and "sp" in code_a and "b15" in code_b
and code_a.replace("sp", "b15") == code_b
and w and can_name_b15(w[0])):
n_sp += 1
continue
errors.append("chunk %#x line %d:\n - %s\n + %s" %
(start, i + 1, a, b))
print("long-relative JA->JR changes: %d" % n_jr)
print("B-file SP sp->b15 changes: %d" % n_sp)
if errors:
print("UNEXPECTED changes: %d" % len(errors))
for e in errors[:10]:
print(e)
return 1
print("all differences are the two intended categories")
return 0
if __name__ == "__main__":
sys.exit(main())