#!/usr/bin/env python3 """ Parse an SDCC .map file produced by sdldz80 and verify that every named bank fits inside its 16 KB window. The Sprinter toolchain expects each `_BANKn` area (with n >= 1) to occupy at most 16384 bytes — that is the size of CPU window 3 where banked code runs at execution time. The SDCC linker itself does not enforce this limit, so we catch it post-link. We also surface the HOME budget: anything left between the end of _CODE and the start of window 2 (0x8000) is leftover space for adding code/data without banking. Usage: check_banks.py Exits non-zero with a clear message if any bank is over its limit. """ import re import sys BANK_LIMIT = 16 * 1024 # HOME upper bound depends on layout: # HUGE: CODE at 0x4100 (W1), HOME may spill into W2 → ceiling 0xC000 # BIG: CODE at 0x8100 (W2), HOME stays in W2 → ceiling 0xC000 # tiny/small: same ceiling — anything in W3 is bank territory. # We pick the ceiling per-image based on where _CODE lives, so we don't # falsely flag W2-resident code as "spilled into stack/heap". HOME_CEILING = 0xC000 def parse_map(path): """ Returns a dict {area_name: (addr, size)} for area lines like: _CODE 00004100 00000313 = ... """ line_re = re.compile(r"^\s*(_\w+)\s+([0-9A-Fa-f]{8})\s+([0-9A-Fa-f]{8})\s*=") areas = {} with open(path) as f: for ln in f: m = line_re.match(ln) if not m: continue name = m.group(1) addr = int(m.group(2), 16) size = int(m.group(3), 16) areas[name] = (addr, size) return areas def main(): if len(sys.argv) != 2: sys.exit("usage: check_banks.py ") areas = parse_map(sys.argv[1]) fails = [] bank_names = sorted( n for n in areas if re.fullmatch(r"_BANK\d+", n) ) for name in bank_names: addr, size = areas[name] pct = size * 100.0 / BANK_LIMIT marker = "OK" if size > BANK_LIMIT: marker = "OVERFLOW" fails.append((name, size)) print(f" {name:<12} @ 0x{addr:08X} size {size:>5} / {BANK_LIMIT} ({pct:5.1f}%) {marker}") # HOME budget — _CODE can land in W1 (huge/small) or W2 (big/tiny). if "_CODE" in areas: addr, size = areas["_CODE"] end = addr + size budget_remaining = HOME_CEILING - end which_window = "W2 (0x8000-0xBFFF)" if addr >= 0x8000 else "HOME (0x4100-0xBFFF)" print(f" _CODE @ 0x{addr:08X} size {size:>5} → ends at 0x{end:04X}, " f"{which_window} has {budget_remaining} bytes free before 0x{HOME_CEILING:04X}") if budget_remaining < 0: print(f" ERROR: _CODE extends past 0x{HOME_CEILING:04X} (stack/heap territory)") fails.append(("_CODE", size)) if fails: print() for name, size in fails: print(f" {name} too big: {size} bytes (limit {BANK_LIMIT})") sys.exit(1) if __name__ == "__main__": main()