#!/usr/bin/env python3 """Cross-check a project's prg.s instruction boundaries against a Mesen CDL file. The CDL is the oracle. Mesen marks every byte the CPU touched as part of the instruction stream (opcode *and* its inline operand bytes) with the Code bit, and every byte the CPU read or wrote as data with the Data bit. So: * a byte the CDL calls Data must not start an instruction in prg.s; * a byte the CDL calls Code must be covered by some instruction in prg.s -- either its opcode or one of its inline operand bytes; * bytes the CDL never observed are free (unexecuted code or plain data). Usage: cdl_check.py [base] """ import re import sys from pathlib import Path AUDIT = re.compile(r";\s*CPU:\$([0-9A-Fa-f]{4})\s+PRG:([0-9A-Fa-f]{4})\s+((?:[0-9A-Fa-f]{2} )*[0-9A-Fa-f]{2})") DATAOP = re.compile(r"^\s*\.(byte|word|incbin|res|fill)") def main(): src = Path(sys.argv[1]).read_text().splitlines() cdl = Path(sys.argv[2]).read_bytes() base = int(sys.argv[3], 16) if len(sys.argv) > 3 else 0x8000 n = 32768 body = cdl[9:9 + n] if cdl[:5] == b"CDLv2" else cdl[-n:] starts = set() covered = set() for ln in src: m = AUDIT.search(ln) if not m: continue cpu = int(m.group(1), 16) raw = bytes.fromhex(m.group(3).replace(" ", "")) if DATAOP.match(ln): for k in range(len(raw)): covered.add(cpu + k) else: starts.add(cpu) for k in range(len(raw)): covered.add(cpu + k) bad_code = [] bad_data = [] code_bytes = data_bytes = 0 for off in range(n): b = body[off] cpu = base + off if b & 1: code_bytes += 1 if cpu not in covered: bad_code.append(cpu) elif b & 2: data_bytes += 1 if cpu in starts: bad_data.append(cpu) print(f"CDL: code={code_bytes} data={data_bytes} unknown={n - code_bytes - data_bytes}") print(f"prg.s: {len(starts)} instruction starts, {len(covered)} bytes covered") print(f"CDL-Code bytes not covered by prg.s: {len(bad_code)}") for a in bad_code[:40]: print(f" ${a:04X}") if len(bad_code) > 40: print(f" ... and {len(bad_code) - 40} more") print(f"CDL-Data bytes that start an instruction in prg.s: {len(bad_data)}") for a in bad_data[:40]: print(f" ${a:04X}") if len(bad_data) > 40: print(f" ... and {len(bad_data) - 40} more") return 0 if not bad_code and not bad_data else 1 if __name__ == "__main__": sys.exit(main())