Add DM cross-reference tool
tools/dm_xref.py extracts all DM address references from PM code and shows the actual DM contents at those addresses: - I-register loads with matching L-register buffer lengths - Direct DM(addr) reads and writes - CNTR loop counters - Hex, decimal, and Q15 format for DM content - Automatic detection of uninitialized (zero) regions - Configurable context window (--context N words)
This commit is contained in:
270
tools/dm_xref.py
Executable file
270
tools/dm_xref.py
Executable file
@@ -0,0 +1,270 @@
|
||||
#!/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 "<H"
|
||||
data = open(path, "rb").read()
|
||||
if len(data) % 2:
|
||||
data += b"\x00"
|
||||
return [struct.unpack_from(fmt, data, i)[0]
|
||||
for i in range(0, len(data), 2)]
|
||||
|
||||
|
||||
def signed16(v):
|
||||
return v - 0x10000 if v >= 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()
|
||||
Reference in New Issue
Block a user