#!/usr/bin/env python3 """dm_xref.py -- Extract all DM address references from PM code and show what's stored at those addresses in a DM dump. Finds: - I-register loads (I0=0x100 → DM base address) - Direct DM access (DM(0x1234) = reg / reg = DM(0x1234)) - DM immediate modify (reg = DM(I0 += offset)) - L-register loads (circular buffer lengths) - CNTR loads (loop counts) Usage: python3 dm_xref.py firmware_pm.bin firmware_dm.bin python3 dm_xref.py firmware_pm.bin firmware_dm.bin --context 16 """ import argparse import os import struct import sys REG0 = ["AX0", "AX1", "MX0", "MX1", "AY0", "AY1", "MY0", "MY1", "MR2", "SR2", "AR", "SI", "MR1", "SR1", "MR0", "SR0"] REG1 = ["I0", "I1", "I2", "I3", "M0", "M1", "M2", "M3", "L0", "L1", "L2", "L3", "IMASK", "IRPTL", "ICNTL", "STACKA"] REG2 = ["I4", "I5", "I6", "I7", "M4", "M5", "M6", "M7", "L4", "L5", "L6", "L7", "RES", "RES", "CNTR", "LPSTACKA"] def read_pm(path): data = open(path, "rb").read() words = [] for i in range(0, len(data) - 2, 3): w = (data[i] << 16) | (data[i + 1] << 8) | data[i + 2] words.append(w) return words def read_dm(path, endian="big"): fmt = ">H" if endian == "big" else "= 0x8000 else v def extract_dm_refs(pm_words): """Extract all DM address references from PM code.""" refs = [] for i, w in enumerate(pm_words): pm_addr = i * 3 b23_22 = (w >> 22) & 3 b21_20 = (w >> 20) & 3 # Type 6: Dreg = Imm16 (data constants, not DM refs) # Skip — these load values, not addresses # Type 7 REG1: I/M/L register loads if b23_22 == 1 and b21_20 == 1: val = (w >> 4) & 0xFFFF reg = REG1[w & 0xF] if reg.startswith("I"): refs.append({ "pm_addr": pm_addr, "type": "I-reg load", "reg": reg, "dm_addr": val, "desc": f"{reg} = 0x{val:04X}" }) elif reg.startswith("L"): refs.append({ "pm_addr": pm_addr, "type": "L-reg (buf len)", "reg": reg, "dm_addr": None, "value": val, "desc": f"{reg} = {val} (buffer length)" }) # Type 7 REG2: I4-I7/M4-M7/L4-L7/CNTR loads if b23_22 == 0 and b21_20 == 3: val = (w >> 4) & 0xFFFF reg = REG2[w & 0xF] if reg.startswith("I"): refs.append({ "pm_addr": pm_addr, "type": "I-reg load", "reg": reg, "dm_addr": val, "desc": f"{reg} = 0x{val:04X}" }) elif reg.startswith("L"): refs.append({ "pm_addr": pm_addr, "type": "L-reg (buf len)", "reg": reg, "dm_addr": None, "value": val, "desc": f"{reg} = {val} (buffer length)" }) elif reg == "CNTR": refs.append({ "pm_addr": pm_addr, "type": "CNTR", "reg": reg, "dm_addr": None, "value": val, "desc": f"CNTR = {val} (loop count)" }) # Type 3: Direct DM access — DM(addr16) = reg / reg = DM(addr16) if b23_22 == 2: d = (w >> 20) & 1 addr = (w >> 4) & 0xFFFF reg_idx = w & 0xF is_ireg = (w >> 21) & 1 rname = REG1[reg_idx] if is_ireg else REG0[reg_idx] direction = "write" if d else "read" refs.append({ "pm_addr": pm_addr, "type": f"DM direct {direction}", "reg": rname, "dm_addr": addr, "desc": (f"DM(0x{addr:04X}) = {rname}" if d else f"{rname} = DM(0x{addr:04X})") }) return refs def show_dm_content(dm_words, dm_addr, context=8): """Format DM content at a given address.""" if dm_addr is None or dm_addr >= len(dm_words): return " (outside DM range)" end = min(dm_addr + context, len(dm_words)) hex_vals = [] dec_vals = [] for j in range(dm_addr, end): v = dm_words[j] hex_vals.append(f"0x{v:04X}") dec_vals.append(f"{signed16(v):6d}") lines = [] lines.append(f" Hex: [{', '.join(hex_vals)}]") lines.append(f" Dec: [{', '.join(dec_vals)}]") # Check if it looks like Q15 coefficients vals = [signed16(dm_words[j]) for j in range(dm_addr, end)] if all(abs(v) <= 32767 for v in vals): q15 = [f"{v/32768:.4f}" for v in vals] lines.append(f" Q15: [{', '.join(q15)}]") # Check for all zeros if all(v == 0 for v in vals): lines.append(f" (all zeros — uninitialized)") return "\n".join(lines) def main(): parser = argparse.ArgumentParser( description="Extract DM references from PM code and" " show DM contents.") parser.add_argument("pm_file", help="PM binary (packed 3-byte)") parser.add_argument("dm_file", help="DM binary (16-bit words)") parser.add_argument("--endian", choices=["big", "little"], default="big") parser.add_argument("--context", type=int, default=8, help="Words of DM context to show" " (default: 8)") args = parser.parse_args() for f in (args.pm_file, args.dm_file): if not os.path.isfile(f): print(f"Error: {f} not found", file=sys.stderr) sys.exit(1) pm_words = read_pm(args.pm_file) dm_words = read_dm(args.dm_file, args.endian) refs = extract_dm_refs(pm_words) print(f"PM: {args.pm_file} ({len(pm_words)} instructions)") print(f"DM: {args.dm_file} ({len(dm_words)} words)") print() # Group by type ireg_loads = [r for r in refs if r["type"] == "I-reg load"] direct_access = [r for r in refs if r["type"].startswith("DM direct")] buf_lengths = [r for r in refs if r["type"] == "L-reg (buf len)"] counters = [r for r in refs if r["type"] == "CNTR"] # I-register loads → buffer base addresses if ireg_loads: print(f"=== Buffer Base Addresses" f" ({len(ireg_loads)} I-reg loads) ===") seen = {} for r in ireg_loads: key = (r["reg"], r["dm_addr"]) if key not in seen: seen[key] = r for (reg, dm_addr), r in sorted(seen.items()): print(f"\n{reg} = 0x{dm_addr:04X}" f" (PM @ 0x{r['pm_addr']:06X})") # Find matching L-register for buffer length lreg = reg.replace("I", "L") for lr in buf_lengths: if lr["reg"] == lreg: print(f" {lreg} = {lr['value']}" f" (circular buffer, {lr['value']} words)") break print(show_dm_content(dm_words, dm_addr, args.context)) print() # Direct DM accesses if direct_access: print(f"=== Direct DM Accesses" f" ({len(direct_access)} found) ===") seen_addrs = {} for r in direct_access: a = r["dm_addr"] if a not in seen_addrs: seen_addrs[a] = [] seen_addrs[a].append(r) for dm_addr in sorted(seen_addrs.keys()): ops = seen_addrs[dm_addr] descs = [r["desc"] for r in ops[:3]] more = f" +{len(ops)-3} more" if len(ops) > 3 else "" print(f"\nDM 0x{dm_addr:04X}:") for d in descs: print(f" {d}") if more: print(f" {more}") print(show_dm_content(dm_words, dm_addr, args.context)) print() # Loop counters if counters: print(f"=== Loop Counters ({len(counters)} found) ===") for r in counters: print(f" PM 0x{r['pm_addr']:06X}: {r['desc']}") print() # Summary all_dm_addrs = set() for r in refs: if r.get("dm_addr") is not None: all_dm_addrs.add(r["dm_addr"]) print(f"=== Summary ===") print(f" Unique DM addresses referenced: {len(all_dm_addrs)}") print(f" Buffer bases (I-reg): {len(ireg_loads)}") print(f" Direct DM access: {len(direct_access)}") print(f" Buffer lengths (L-reg): {len(buf_lengths)}") print(f" Loop counters (CNTR): {len(counters)}") if __name__ == "__main__": main()