Every event that reaches the always-on logic faces the same choice: wake the chip with a reset, interrupt it, or wait until software polls. A source with two dispositions races itself; a source with none is a dead wake pin on the package. This lesson writes the NanoSoC policy as one table and gives the audit that proves each source appears exactly once. Its one job is the policy; the placement it relies on came first.
Sleep exit is a wake reset. Three sources can start it: an enabled GPIO level, UART activity, and the controller's own irq_pwr (power and PLL events). Each wakes nano_rstpwr, which runs the power-up sequence, sets RST_CAUSE bit WAKE and lets the ROM resume the retained SRAM0 image at 0x2000_0000 instead of reloading it. GPIO is a level interrupt, not an edge detector: the wake condition is a pin held high with its INT_EN bit set, so the source must stay asserted until the chip is up and software removes the condition. In run, the same sources are ordinary interrupts into the controller, with irq_pwr at priority 0. Nothing is both in the same state, and the table below is the whole policy:
Source | In run | In sleep | Disposition | Record |
|---|---|---|---|---|
GPIO level | Level interrupt, input 5 | Wake | Interrupt; from sleep, wake reset | INT_EN and DATA_IN; cleared by removing the condition |
UART activity | TX/RX interrupt, input 3 | Wake | Interrupt; from sleep, wake reset | UART flags |
irq_pwr (power, PLL) | Input 0, priority 0 | Wake | Interrupt; from sleep, wake reset | PWR_IRQ W1C |
Software reset (SWRST) | Reset sys and cpu | n/a | Reset only | RST_CAUSE SW |
POR, pad reset | Reset everything | Reset everything | Reset only | RST_CAUSE POR or PAD |
# Policy audit against the NanoSoC specification: three wake sources, one disposition each.
WAKE_FACT = {"gpio", "uart", "irq_pwr"} # sleep state table
POLICY = {
"gpio": ("interrupt", "wake-reset"),
"uart": ("interrupt", "wake-reset"),
"irq_pwr": ("interrupt", "wake-reset"),
"swrst": ("reset", None),
"por-pad": ("reset", "reset"),
}
def audit(policy):
errors = []
wakes = {s for s, (_, sleep) in policy.items() if sleep == "wake-reset"}
if wakes != WAKE_FACT:
errors.append("wake set differs: " + str(sorted(wakes ^ WAKE_FACT)))
for src, (run, sleep) in policy.items():
if run not in ("interrupt", "reset"):
errors.append(src + ": bad run disposition")
if sleep == "wake-reset" and run != "interrupt":
errors.append(src + ": wakes from sleep but is not an interrupt in run")
return errors
assert audit(POLICY) == []
# The audit can fail: SPI added as a wake source, GPIO dropped, or a wake
# source that is a reset in run are all rejected.
assert audit({**POLICY, "spi": ("interrupt", "wake-reset")}) != []
assert audit({k: v for k, v in POLICY.items() if k != "gpio"}) != []
assert audit({**POLICY, "uart": ("reset", "wake-reset")}) != []
print("PASS: 3 wake sources (GPIO level, UART, irq_pwr); policy audit rejects 3 bad variants")The table above, current, with each row's enable, priority and record named.
The sleep-exit path tested per row: each wake source demonstrated from sleep, not just from run.
The ROM's WAKE-versus-POR branch covered: retained image resumed on wake, full load on POR.
A watchdog or timer is not in the table: if one exists, it came from an extension and its rows carry that label.
Drive each wake source with the chip in sleep and confirm the sequence: power-up (14.073 us), cause set, ROM resume. A wake that works in run but not in sleep points at the sleep disposition row, usually an enable that software cleared on the way down. A wake that fires again immediately after returning to sleep points at a GPIO level that was never removed.
The GPIO enable on the sleep path, which may have been cleared with the run configuration, and the pin's level: GPIO is a level interrupt, so a pulse that ended before the controller woke is gone, and the pin must be held high with INT_EN set.