Without the power intent, simulation assumes every domain is always on — and the sleep-wake sequence passes by assumption. With the intent active, the simulator corrupts what the intent says is off, clamps what isolation says is clamped, and keeps what retention says is retained. NanoSoC's two missing interrupt clamps turn this run from green to red in one cycle: entering SLEEP drives the interrupt lines to X, INTC RAW shows X, and the wake logic sees an interrupt that is not there. Its one job is the power-aware check set: isolation, sequence, retention, each with its expected failure.
Three checks run in the power intent's order. Isolation first: with PD_SW powered down, every one of the 114 signals into the always-on domain must read 0 — the two interrupt lines included — and any X is a missing or wrong-polarity clamp, with planted bug 6 as its NanoSoC instance. Sequence second: the power-down order is isolate, gate clocks, assert reset, open the switch over a 64-cycle clk_per ramp, 2.917 us total; the wake order reverses it with the PLL relock and the staged reset release, 14.073 us total. The simulator asserts each step against its window: isolation before the switch opens, reset before isolation releases, restore after power is stable. Retention third: SRAM0 content survives SLEEP, so a pattern written before SLEEP must read back unchanged after wake, RST_CAUSE bit 3 (WAKE) must be set, and the ROM must jump to 0x2000_0000 without reloading. The CPU and the PD_SW logic keep nothing: whatever software needs after wake is saved into SRAM0 first.
// NanoSoC teaching values: clamp-0 isolation cell. When the switchable domain
// powers down, the enable clamps the output to 0; otherwise it passes through.
module nano_iso_clamp0 (
input logic in, // from the switchable domain
input logic iso_en, // asserted before power-down, released after restore
output logic out // to the always-on domain
);
assign out = iso_en ? 1'b0 : in;
endmodule# Power-sequence audit: build the down sequence as timed events, then check the
# ordering rules and the isolation list so each check can fail. Power sequence and isolation tables.
T_PER_NS, T_SYS_NS, T_CPU_NS = 1e9 / 24e6, 1e9 / 96e6, 1e9 / 192e6
def down_sequence(iso_first=True):
steps = [("iso_sw_en", 2), ("clocks_gated", 2), ("resets_asserted", 2), ("switch_open", 64)]
if not iso_first:
steps = [steps[1], steps[2], steps[3], steps[0]] # clamp enabled too late
t, events = 0.0, {}
for name, cycles in steps:
t += cycles * T_PER_NS
events[name] = t
return events, t
def order_ok(ev):
return ev["iso_sw_en"] < ev["clocks_gated"] < ev["resets_asserted"] < ev["switch_open"]
ev, down_ns = down_sequence()
assert order_ok(ev) and abs(down_ns - 2916.7) < 0.5, down_ns
assert not order_ok(down_sequence(iso_first=False)[0]) # bug: isolation after power is off
up_ns = (64 + 8 + 256 + 2) * T_PER_NS + (4 + 18 + 8) * T_SYS_NS + 2 * T_CPU_NS
assert abs(up_ns - 14072.9) < 0.5, up_ns
EXPECT = {"apb_cdc": 47, "sram0": 52, "rom": 13, "irq": 2}
def clamp_gaps(present):
return {g: EXPECT[g] - present.get(g, 0) for g in EXPECT if present.get(g, 0) != EXPECT[g]}
assert sum(EXPECT.values()) == 114 and clamp_gaps(dict(EXPECT)) == {}
assert clamp_gaps({"apb_cdc": 47, "sram0": 52, "rom": 13}) == {"irq": 2} # planted bug 6
print(f"power-down {down_ns / 1000:.3f} us, wake {up_ns / 1000:.3f} us")
print("PASS: isolate-first order enforced, 2.917 us down, 14.073 us up, bug 6 names its group")The isolation list per group totals 114 with the two interrupt lines named, the pair planted bug 6 left out.
Sequence assertions bind every order step: isolate before switch, reset before release, restore after stable.
Retention proves content equality across SLEEP, not just that the wake interrupt fires.
An X on an always-on input during power-down is isolation until proved otherwise: check the cell exists, the enable timing, then the polarity. A wake that hangs points at the release order — isolation released before reset is stable glitches the on domain. A retention mismatch points at what software saved before SLEEP: state added after the save routine was written is the usual gap, found by comparing the save list against the registers and variables the wake path needs, not by staring at the waveform.
Read the interrupt controller RAW during power-down: an X or 1 on an interrupt input means a missing clamp. Planted bug 6 predicts exactly this loop, and the isolation list names the two cells to add.