Writing SBR CTRL at 0x4000_100C flips DMA data bit 0 with COUNT exactly right. Nothing in the symptom points at the map: the address is legal, the access completes, no error fires. The cause is an 8 KB DMA slot select (addr[15:13] == 0) that overlaps slots 0 and 1, so the write also lands on DMA offset 0x0C, which is KEY. Only a checker that proves no two windows overlap catches this class before silicon.
Goal: a ten-row map table that passes overlap and alignment checks.
Inputs: bases and sizes below; the engine's overlaps() and misaligned(); the APB slot selects as value and mask pairs on addr[15:0].
Steps: type the table; run the checker on the clean map; inject the 8 KB DMA window and confirm both the table check and the slot-select audit report it, and that the stray SBR write reaches the DMA KEY; restore and rerun.
Region | Base | Size | End |
|---|---|---|---|
BOOTROM | 0x0000_0000 | 16 KB | 0x0000_3FFF |
SRAM0 | 0x2000_0000 | 64 KB | 0x2000_FFFF |
SRAM1 | 0x2001_0000 | 64 KB | 0x2001_FFFF |
DMA | 0x4000_0000 | 4 KB | 0x4000_0FFF |
SBR | 0x4000_1000 | 4 KB | 0x4000_1FFF |
UART..RSTPWR | 0x4000_2000..0x4000_6000 | 4 KB each | 0x4000_6FFF |
MAP = [
("BOOTROM", 0x00000000, 0x4000),
("SRAM0", 0x20000000, 0x10000),
("SRAM1", 0x20010000, 0x10000),
("DMA", 0x40000000, 0x1000),
("SBR", 0x40001000, 0x1000),
("UART", 0x40002000, 0x1000),
("SPI", 0x40003000, 0x1000),
("GPIO", 0x40004000, 0x1000),
("INTC", 0x40005000, 0x1000),
("RSTPWR", 0x40006000, 0x1000),
]
SECURE_ONLY = {"SRAM1", "RSTPWR"}
SLOTS = ["DMA", "SBR", "UART", "SPI", "GPIO", "INTC", "RSTPWR"]
T_CPU, T_SYS, T_PER = 1e9 / 192e6, 1e9 / 96e6, 1e9 / 24e6
def end_of(base, size):
return base + size - 1
def overlaps(entries=MAP):
bad = []
xs = sorted(entries, key=lambda e: e[1])
for a, b in zip(xs, xs[1:]):
if b[1] < a[1] + a[2]:
bad.append((a[0], b[0]))
return bad
def misaligned(entries=MAP):
return [n for n, b, s in entries if b % s != 0]
def decode(addr, slots=SLOTS):
if addr >> 14 == 0:
return "BOOTROM"
if addr >> 16 == 0x2000:
return "SRAM0"
if addr >> 16 == 0x2001:
return "SRAM1"
if addr >> 16 == 0x4000:
slot = (addr >> 12) & 0xF
return slots[slot] if slot < 7 else "H3"
if addr < 0x20000000:
return "H1"
if addr < 0x40000000:
return "H2"
return "H4"
def response(addr, secure, slots=SLOTS):
t = decode(addr, slots)
if t in ("H1", "H2", "H4"):
return "DECERR"
if t == "H3":
return "SLVERR"
if t in SECURE_ONLY and not secure:
return "SLVERR"
return "OK"
def sustained(peak, beats):
return peak * beats / (beats + 1)
# ---- this lesson starts here ----
assert overlaps() == [] and misaligned() == []
for name, base, size in MAP:
assert end_of(base, size) == base + size - 1
assert base % size == 0 and size & (size - 1) == 0
SEL = {n: (i << 12, 0xF000) for i, n in enumerate(SLOTS)} # slot n: addr[15:12] == n
def hits(sel, addr):
return [n for n, (v, m) in sel.items() if addr >> 16 == 0x4000 and (addr & 0xFFFF) & m == v]
def select_audit(sel):
probes = [0x40000000 + (i << 12) + o for i in range(len(SLOTS)) for o in (0x000, 0x00C, 0xFFC)]
return [hex(a) for a in probes if hits(sel, a) != [SLOTS[(a >> 12) & 0xF]]]
def apb_write(sel, regs, addr, data):
for n in hits(sel, addr):
regs[n][addr & 0xFF] = data
assert select_audit(SEL) == []
BUG = dict(SEL, DMA=(0x0000, 0xE000)) # 8 KB window: addr[15:13] == 0
assert select_audit(BUG) == ["0x40001000", "0x4000100c", "0x40001ffc"]
for sel, expect in ((SEL, None), (BUG, 1)):
regs = {n: {} for n in SLOTS}
apb_write(sel, regs, 0x4000100C, 1) # SBR CTRL, START = 1
assert regs["DMA"].get(0x0C) == expect # DMA KEY: written only by the bug
buggy = [("DMA", 0x40000000, 0x2000) if n == "DMA" else (n, b, s) for n, b, s in MAP]
assert overlaps(buggy) == [("DMA", "SBR")]
print("PASS: map clean: 10 rows, no overlap, all aligned; 8 KB DMA window caught and its stray KEY write shown")One PASS line. The first asserts are the deliverable. The BUG select is the bug-1 demonstration at the place the bug lives, the select expression: the audit names the three probes in the SBR slot that hit two slaves, and the write model shows the symptom, KEY becoming 1. The last assert shows the table-level check catching the same fault from the other side.
An overlap hit names the two windows sharing an address: fix the select bits, never the software. A misalignment hit means a base is not a multiple of its size, so the power-of-two decoder cannot select it cleanly. An end-address mismatch means the table was copied wrongly, usually the ROM end 0x0000_3FFF.
Hint 1: sort by base and compare each base against the previous end, which is what overlaps() does. Hint 2: the 8 KB window starts at the right base but is twice the slot size, so it swallows its neighbour. Hint 3: addr[15:13] == 0 covers 0x4000_0000 to 0x4000_1FFF, both slots. Answer: decode addr[15:12] per 4 KB slot and add an elaboration-time no-overlap check on the ADDR_*_BASE/SIZE pairs.
A power-of-two select compares the top address bits only; an unaligned base needs extra compare logic and leaves a hole or an overlap at the edge the select cannot express.