SLEEP on NanoSoC is only worth entering because the wake does not redo the whole boot: the SRAM0 image is still there, so the ROM jumps straight to 0x2000_0000 instead of reloading 8 KB over a 6 Mb/s SPI link. That shortcut is retention, and it has a price in leakage every second the chip sleeps. This lesson prices it and draws its boundary.
Exactly two things are kept across SLEEP. The SRAM0 arrays keep every bit, held by the always-on supply at 60 percent of normal leakage, 0.72 mW instead of 1.20 mW. Everything in PD_AON that never lost power simply continues: peripheral state, the power controller, and RST_CAUSE, whose bit 3 tells wake software this was a wake rather than a cold boot. Nothing in PD_SW survives: its flops go dark with the switch, the CPU and DMA register state is gone, and the PLL must relock from scratch at 256 peripheral clocks.
Retention costs 0.72 mW for as long as the chip sleeps; reloading the 8 KB image costs about 904 µJ and 10.9 ms of extra wake time. Retention is cheaper for any sleep shorter than about 1.26 s (904.2 µJ / 0.72 mW), and gives the faster wake at any length, so it is the default for NanoSoC. A sleep of many seconds could in principle save energy by discarding the image, but only if SRAM0 could be switched off, which the NanoSoC partition does not allow. The script below prices it.
# Retention versus reload pricing, NanoSoC teaching values.
RET_SRAM0_MW = 0.72 # SRAM0 retention leakage (mW)
NORM_SRAM0_MW = 1.20 # SRAM0 normal leakage (mW)
SPI_RELOAD_US = 10928.0 # 8 KB image over 6 Mb/s SPI (boot table)
RUN_MW = 82.74
reload_nj = RUN_MW * SPI_RELOAD_US # 1 mW * 1 us = 1 nJ
reload_uj = reload_nj / 1000.0 # 904.2 uJ
assert abs(reload_uj - 904.2) < 0.1, reload_uj
breakeven_s = reload_nj / 1e9 / (RET_SRAM0_MW / 1e3) # ~1.256 s
assert 1.2 < breakeven_s < 1.3, breakeven_s
def cheaper(sleep_s):
ret_uj = RET_SRAM0_MW * 1e3 * sleep_s # retention leakage over the sleep (uJ)
return "retain" if ret_uj < reload_uj else "reload"
assert cheaper(1.0) == "retain" # a 1 s sleep: 720 uJ of leakage < 904 uJ
assert cheaper(2.0) == "reload" # a 2 s sleep would favour discarding the image
assert RET_SRAM0_MW / NORM_SRAM0_MW == 0.6
print("PASS: retention 0.72 mW; reload 904.2 uJ; break-even ~1.26 s")The retention list is closed: SRAM0 arrays (by retention) and all PD_AON state (because it never lost power), nothing claimed for PD_SW flops.
The wake software reads RST_CAUSE before touching any retained state, and the cold path re-initialises what the warm path assumes.
The 0.6 retention factor is quoted as the NanoSoC teaching value it is, with its source named.
Test retention with a state-compare run: fill SRAM0 with a known pattern, enter SLEEP, wake on a GPIO edge from the bench fixture, and compare every word before jumping to the image. The wake path does not checksum the retained image; if the product needs integrity, add a checksum over SRAM0 before the jump and treat a mismatch as a cold boot.
Because only bit 3 tells a wake from a cold boot. On a cold boot SRAM0 holds garbage and must be loaded over SPI; on a wake it holds the image. Reading the cause first selects the path; assuming retention is how images jump into garbage.