Two failures share one stage. Entering SLEEP, irq_cpu goes high the moment the switch opens and the chip loops sleep and wake: 114 clamps are expected and only 112 are present, because the two interrupt lines float. And after an innocent CLK_EN write the CPU hangs on its first DMA access with no error: the gated DMA cannot drive PREADY and the bridge waits forever. Power work is counting plus sequencing, and this lesson does both.
Goal: the domain table, the 114-clamp count, both sequences with times, and two refusal rules.
Inputs: PD_AON against PD_SW, the four clamp groups, the 70-cycle down and 330-cycle up counts.
Steps: file the always-on list; list every clamped signal and audit it; walk both timelines; drop the interrupt clamps and confirm the audit flags them and the sleep model loops; gate an idle DMA clock and confirm the first access hangs without the watchdog and completes with PSLVERR with it.
PD_AON holds RSTPWR, PLL, INTC, UART, SPI, GPIO, SRAM0 and ROM on 0.9 V forever; PD_SW holds the CPU, bridges, crossbar, SRAM1, DMA, SBR and APB decode behind a header switch, with SRAM0 arrays in AON and only their wrapper logic switched. One voltage means isolation only, no level shifters: 47 APB cdc outputs, 52 SRAM0 macro port bits, 13 ROM port bits and 2 interrupt lines, 114 clamp-to-0 cells driven by iso_sw_en. Power-down takes 70 clk_per (2.917 us): quiesce, isolate, gate clocks, assert resets, open the switch. Power-up takes 14,072.9 ns: 64-cycle ramp, 8-cycle pgood wait, 256-cycle relock, 2-cycle clock open, isolation release 4 clk_sys later, rst_sys_n after 18 clk_sys, rst_cpu_n after 8 clk_sys plus 2 clk_cpu. Going down: isolate, gate clocks, reset, open the switch. Coming up: close the switch, wait for power good, relock the PLL, open the clocks, release isolation while the resets are still asserted, then release rst_sys_n and last rst_cpu_n.
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 ----
GROUPS = {"apb_cdc": [("paddr", 12), ("pwdata", 32), ("psel", 1), ("penable", 1), ("pwrite", 1)],
"sram0": [("addr", 14), ("wdata", 32), ("be", 4), ("ce", 1), ("we", 1)],
"rom": [("addr", 12), ("ce", 1)], "irq": [("irq_dma", 1), ("irq_sbr", 1)]}
def signals():
return [g + "." + n + "[" + str(i) + "]" for g, items in GROUPS.items() for n, w in items for i in range(w)]
def clamp_audit(clamped):
return [s for s in signals() if s not in clamped]
def sleep_effects(missing):
fx = set()
if any(s.startswith("irq.") for s in missing):
fx.add("spurious irq_cpu, sleep and wake loop")
if any(s.startswith("sram0.") for s in missing):
fx.add("X on the SRAM0 macro port")
return fx
ALL = set(signals())
assert len(ALL) == 114 and clamp_audit(ALL) == []
assert {g: sum(w for _, w in v) for g, v in GROUPS.items()} == {"apb_cdc": 47, "sram0": 52, "rom": 13, "irq": 2}
no_irq = {s for s in ALL if not s.startswith("irq.")} # bug 6
assert len(no_irq) == 112 and clamp_audit(no_irq) == ["irq.irq_dma[0]", "irq.irq_sbr[0]"]
assert sleep_effects(clamp_audit(no_irq)) == {"spurious irq_cpu, sleep and wake loop"}
assert sleep_effects(clamp_audit(ALL - {"sram0.wdata[7]"})) == {"X on the SRAM0 macro port"} and sleep_effects([]) == set()
down = [("isolate", 2), ("gate_clocks", 2), ("assert_resets", 2), ("open_switch", 64)]
assert [n for n, _ in down] == ["isolate", "gate_clocks", "assert_resets", "open_switch"]
assert abs(sum(c for _, c in down) * T_PER - 2916.7) < 1.0
def wake():
t = {"pgood": (64 + 8) * T_PER}
t["clocks_open"] = t["pgood"] + (256 + 2) * T_PER
t["iso_release"] = t["clocks_open"] + 4 * T_SYS
t["rst_sys_n"] = t["iso_release"] + 18 * T_SYS
t["rst_cpu_n"] = t["rst_sys_n"] + 8 * T_SYS + 2 * T_CPU
return t
w = wake()
assert abs(w["rst_cpu_n"] - 14072.9) < 1.0
assert w["clocks_open"] < w["iso_release"] < w["rst_sys_n"] < w["rst_cpu_n"] # isolation drops while resets are still asserted
def apb_access(clk_en, wdog):
for cycle in range(1, 2000):
if clk_en and cycle >= 3:
return "OK" # a DMA access takes 3 clocks
if wdog and cycle >= wdog:
return "PSLVERR" # the bridge watchdog gives up
return "HANG"
def clk_en_write(busy, old, new):
return old if busy else new # refuse to gate a block that is BUSY
assert apb_access(True, None) == "OK"
assert apb_access(False, None) == "HANG" # bug 10: idle DMA gated, no watchdog
assert apb_access(False, 256) == "PSLVERR" and abs(256 * T_SYS / 1000 - 2.667) < 0.001
assert clk_en_write(True, 0x3, 0x0) == 0x3 and clk_en_write(False, 0x3, 0x0) == 0x0
assert apb_access(clk_en_write(False, 0x3, 0x0) & 1 == 1, None) == "HANG" # the BUSY rule alone does not fix the idle case
print("PASS: 114 clamps audited; 112 flagged with a wake loop; wake 14.073 us; gated idle DMA hangs without the 256-cycle watchdog")One PASS line. The no_irq set is the bug-6 demonstration: the audit names exactly the two interrupt clamps and the sleep model returns the wake loop. The apb_access calls are the bug-10 demonstration: gating an idle DMA hangs the access, the watchdog turns it into PSLVERR after 256 clk_sys cycles, and the last assert shows that refusing to gate a BUSY block is a separate rule that does not rescue the idle case.
A clamp failure means a group was mis-sized: APB outputs are 12 plus 32 plus 3, SRAM0 is 14 plus 32 plus 4 plus 2, ROM is 12 plus 1. A timeline failure means cycles were charged to the wrong domain, or isolation was released after the resets instead of before. A wrong apb_access result means the watchdog was modelled as a refusal.
Hint 1: count every signal from PD_SW into PD_AON, including the two that look like control. Hint 2: isolation asserts before reset and power off; on wake it releases 4 clk_sys after the clocks open, while the resets are still asserted. Hint 3: a bridge with no timeout turns a gated clock into a hang, and it hangs on an idle block as readily as a busy one. Answer: clamp-0 on all 114 nets via iso_sw_en, and a 256-cycle watchdog in the AXI-to-APB bridge completing with PSLVERR; a rule that refuses to gate a BUSY block's clock protects a job in flight but is not the fix for the hang.
The reset controller and wake logic, the always-on peripherals that can wake the chip, and the power-switch and isolation sequencer itself.