Files
rk3588-ddr-analysis/simulation/mmio_regions.py
T
test0r 46155bbe91 simulation: tripwire + PC-bucketed diff + bitflip sweep
Ship the new simulation & verification stack under simulation/:

- mmio_regions.py — address → region classifier (DDRCTL, DDRPHY,
  OTP, SRAM, …). Shared by every other tool so trace output is
  scannable without memorising the memory map.
- sim_tripwire.py — Bin-style per-access capture. Records
  (seq, insn_tick, pc, addr, size, rw, val, region, fn_name) per
  MMIO access. PCResolver bisects the vendor funs table parsed
  from ddr_conservative_asm.s.
- tripwire_diff.py — PC-bucketed difflib.SequenceMatcher diff of
  two tripwire CSVs. Buckets by fn_name so bitflip-induced control
  flow divergence doesn't cascade noise.
- training_sim.py — DDR training simulator with --mode pass and
  --mode bitflip (flip first N reads per training status, exercise
  retry paths). BITFLIP_ONLY env var narrows to a single addr for
  the sweep.
- bitflip_sweep.py — Flip each of 23 training-status addresses
  one-at-a-time and tabulate retry convergence. Surfaces which
  function(s) react to a transient fault by writing different
  downstream register values.

Plus:

- mmio_diff.py updated: region-tagged divergence output,
  --show-regions histogram, --tripwire-out-{vendor,rebuilt} CSV
  capture, --capture-stack-writes for stack-allocated buffer diffs.
- debug_probes/tp_slot_{probe,writes}.py — ad-hoc Unicorn probes
  for chasing a single-slot divergence in an SRAM buffer. Kept as
  reference examples of how to extend the tripwire toolchain.

The stack found 6 silicon-hostile bugs in the rebuilt blob that
mmio_diff's write-sequence gate was structurally blind to, including
three ld-unresolved-symbol NULL derefs (case-mismatched externs,
missing DATA_SYMS) and one C-early-return-skips-shared-tail bug
where vendor's asm fell through to the tail via `b` after a `ret`.
2026-04-22 05:55:28 +02:00

122 lines
4.8 KiB
Python

#!/usr/bin/env python3
"""mmio_regions.py — address → region classifier for RK3588 DDR TPL.
Used by mmio_diff.py, blob_emu.py, call_trace.py, training_sim.py to
stamp each access with a short human-readable tag so trace output is
scannable without memorising hex ranges.
Categories (tag, short description):
DDRCTL uMCTL2 controller (per-channel ch+0x10000 window, or
global 0xFE010000)
DDRCTL:SW STAT/SWCTL/SWSTAT/PWRCTL subregion — frequently polled
DDRCTL:MR mode-register ops (MRCTRL0/MRSTAT subregion)
DDRPHY DDR PHY at 0xFE0C0000 (32 KB)
DDRPHY:TR training status subregion (+0x080/090/0B4/3CC/514/684/A24)
DDR_MEM actual DRAM content — 0x00000000..0x80000000 once trained
SRAM boot SRAM 0xFF000000..0xFF100000 (blob + globals)
CRU clock and reset 0xFD7C0000
DDR_CRU DDR PHY clock/reset (ch0..3) 0xFD800000..0xFD8C0000
SCRU secure clock/reset 0xFD8C0000
PMU_SRAM PMU SRAM 0xFF100000..0xFF110000
GRF general register file 0xFD580000
BUS_GRF bus-side GRF 0xFD5F0000
SGRF secure GRF 0xFE050000
PMU power-mgmt unit 0xFE400000
FW_DDR DDR firewall 0xFE030000
OTP OTP_NS — one-time-programmable controller 0xFECC0000
UART debug UART 0xFEB50000
OTHER unmapped / unclassified
"""
# Per-channel DDRCTL window repeats at these bases. Offset within
# a channel identifies the real sub-register.
DDRCTL_CHANNELS = (0xF7000000, 0xF8000000, 0xF9000000, 0xFA000000)
DDRCTL_GLOBAL = 0xFE010000
DDRCTL_WIN = 0x20000
DDRCTL_SUB = 0x10000 # ch+0x10000 lands inside ctrl space
# Training-status registers (the ones mmio_diff's REGION_OFF stubs).
DDRPHY_TRAINING_OFFSETS = {0x080, 0x090, 0x0B4, 0x3CC, 0x514, 0x684, 0xA24}
# DDRCTL sub-categories — offsets within the 0x10000 sub-window.
DDRCTL_SW_OFFSETS = {0x10014, 0x10180, 0x10C80, 0x10C84}
DDRCTL_MR_OFFSETS = {0x10080, 0x10090}
def classify(addr: int) -> str:
"""Return short region tag for an absolute address."""
# Emulator-only scratch stack (0x00400000..0x00500000) — not a real
# silicon region but tagged distinctly so tripwire can diff stack
# writes (e.g. param_2[] buffers fn_de40 fills).
if 0x00400000 <= addr < 0x00500000:
return "STACK"
# DDR memory (post-training)
if addr < 0x80000000:
return "DDR_MEM"
# Per-channel DDRCTL windows
for base in DDRCTL_CHANNELS:
if base <= addr < base + 0x40000:
off = addr - base
if off in DDRCTL_SW_OFFSETS:
return "DDRCTL:SW"
if off in DDRCTL_MR_OFFSETS:
return "DDRCTL:MR"
return "DDRCTL"
# Global DDRCTL (less common in TPL)
if DDRCTL_GLOBAL <= addr < DDRCTL_GLOBAL + DDRCTL_WIN:
return "DDRCTL"
# DDRPHY 0xFE0C0000..0xFE100000 (256 KB per 4 ports; training at base)
if 0xFE0C0000 <= addr < 0xFE100000:
off = addr & 0xFFF
if off in DDRPHY_TRAINING_OFFSETS:
return "DDRPHY:TR"
return "DDRPHY"
# Clock/reset
if 0xFD7C0000 <= addr < 0xFD800000: return "CRU"
if 0xFD800000 <= addr < 0xFD8C0000: return "DDR_CRU"
if 0xFD8C0000 <= addr < 0xFD8D0000: return "SCRU"
# Register files
if 0xFD580000 <= addr < 0xFD5A0000: return "GRF"
if 0xFD5F0000 <= addr < 0xFD600000: return "BUS_GRF"
if 0xFE050000 <= addr < 0xFE060000: return "SGRF"
# PMU / firewall / scrambler
if 0xFE400000 <= addr < 0xFE410000: return "PMU"
if 0xFE030000 <= addr < 0xFE040000: return "FW_DDR"
if 0xFECC0000 <= addr < 0xFECD0000: return "OTP"
# Debug UART
if 0xFEB50000 <= addr < 0xFEB60000: return "UART"
# Boot SRAM (blob + globals) and PMU SRAM
if 0xFF000000 <= addr < 0xFF100000: return "SRAM"
if 0xFF100000 <= addr < 0xFF110000: return "PMU_SRAM"
return "OTHER"
def classify_rw(addr: int, is_write: bool) -> str:
"""Direction-aware tag: 'DDRCTL:SW wr' vs 'DDRCTL:SW rd'."""
return f"{classify(addr):10s} {'wr' if is_write else 'rd'}"
if __name__ == "__main__":
import sys
# Smoke-test: classify a few known addresses
tests = [
(0xFE0C0A24, "DDRPHY:TR"), # DfiStatus
(0xFE0C0000, "DDRPHY"), # generic PHY reg
(0xF7010C80, "DDRCTL:SW"), # SWCTL ch0
(0xF7010080, "DDRCTL:MR"), # MRCTRL0 ch0
(0xF7010500, "DDRCTL"), # other DDRCTL
(0xFD7C0000, "CRU"),
(0xFD800000, "DDR_CRU"),
(0xFF001000, "SRAM"),
(0xFF100000, "PMU_SRAM"),
(0xFEB50000, "UART"),
(0x00100000, "DDR_MEM"),
]
fails = 0
for addr, want in tests:
got = classify(addr)
ok = "OK" if got == want else "FAIL"
if got != want: fails += 1
print(f" {ok} 0x{addr:08x} -> {got:12s} (want {want})")
sys.exit(1 if fails else 0)