NanoSoC has one 0.9 V core rail and an I/O rail for the pad drivers. Power them in the wrong order, or release things in the wrong order afterwards, and the I/O drivers may assert garbage on the board while the core is still rising, or the core wakes with its isolation released too early. The power-up order is therefore a deliverable with an owner, and this lesson writes it for the two cases NanoSoC has: cold boot and wake from SLEEP.
The path has five segments, and the integrator names the drop owner of each: the board regulator and planes, the package traces from ball to the bond wire, the bond wires themselves, the on-chip grid, and the on-die header switch into the switchable domain. There is no separate always-on supply pad: the always-on domain is fed straight from the core rail, and the switchable domain from the same rail through the header switch, which is on the die and not on the package.
The two sequences differ and must not be mixed. At cold boot the order is: I/O rail, then core rail, then the power-on reset releases, then 1024 cycles of the 24 MHz oscillator pass (42.667 µs) before the peripheral reset releases, then the PLL locks (10.833 µs), the clock gates open, and the system and CPU resets release last. The CPU is the last thing to leave reset. At wake from SLEEP the always-on domain is already running: isolation is already asserted, the header switch closes, the supply is seen good, the PLL relocks (256 cycles), the gates open, isolation releases, and then the resets release. The order of I/O before core is a teaching choice: the real order is whatever the I/O library and package supply specification require, and the review checks the sequence against that document. What makes the choice safe on the pads is the pad cell's own power-on control, described in the pad lessons, which keeps every driver off until the core supply is valid.
Cold-boot step | Waits for | Time (NanoSoC reset table) |
|---|---|---|
Oscillator ready, then peripheral reset releases | 1024 cycles of the 24 MHz clock, then a 2-flop synchroniser | 42.667 µs, then 83.333 ns |
PLL enabled and locked | 4 start-up and 256 lock cycles | 10.833 µs |
Clock gates open, then system reset releases | 2 cycles, then 18 system cycles | 83.333 ns, then 187.5 ns |
CPU reset releases last | 8 system plus 2 CPU cycles | 93.75 ns |
The script below holds the two orders as lists of events and checks each against its own rules. It encodes the order of events, not their times; the times are in the table above. It then runs the same checker on three bad sequences, each reproducing a real failure: the clock gates opened before the PLL locks at cold boot, isolation asserted after the switch closes at wake, and a missing event.
COLD = ["VDD_IO", "VDD_CORE", "POR_RELEASE", "XTAL_OK", "RST_PER_RELEASE", "PLL_LOCK",
"GATES_OPEN", "RST_SYS_RELEASE", "RST_CPU_RELEASE"]
COLD_RULES = [("VDD_CORE", "POR_RELEASE"), ("POR_RELEASE", "XTAL_OK"), ("XTAL_OK", "RST_PER_RELEASE"),
("RST_PER_RELEASE", "PLL_LOCK"), ("PLL_LOCK", "GATES_OPEN"),
("GATES_OPEN", "RST_SYS_RELEASE"), ("RST_SYS_RELEASE", "RST_CPU_RELEASE")]
WAKE = ["ISO_ASSERT", "SW_CLOSE", "PGOOD", "PLL_LOCK", "GATES_OPEN", "ISO_RELEASE",
"RST_SYS_RELEASE", "RST_CPU_RELEASE"]
WAKE_RULES = [("ISO_ASSERT", "SW_CLOSE"), ("SW_CLOSE", "PGOOD"), ("PGOOD", "PLL_LOCK"),
("PLL_LOCK", "GATES_OPEN"), ("GATES_OPEN", "ISO_RELEASE"),
("ISO_RELEASE", "RST_SYS_RELEASE"), ("RST_SYS_RELEASE", "RST_CPU_RELEASE")]
def check(seq, rules, events):
assert sorted(seq) == sorted(events), "events missing or repeated"
pos = {s: i for i, s in enumerate(seq)}
for a, b in rules:
assert pos[a] < pos[b], a + " must come before " + b
return True
assert check(COLD, COLD_RULES, COLD) and check(WAKE, WAKE_RULES, WAKE)
assert COLD[-1] == "RST_CPU_RELEASE" and WAKE[-1] == "RST_CPU_RELEASE" # the CPU leaves reset last
bad_cold = list(COLD)
i, j = bad_cold.index("PLL_LOCK"), bad_cold.index("GATES_OPEN")
bad_cold[i], bad_cold[j] = bad_cold[j], bad_cold[i]
bad_wake = list(WAKE)
bad_wake.remove("ISO_ASSERT")
bad_wake.insert(bad_wake.index("SW_CLOSE") + 1, "ISO_ASSERT")
for seq, rules, ev in ((bad_cold, COLD_RULES, COLD), (bad_wake, WAKE_RULES, WAKE),
([s for s in COLD if s != "XTAL_OK"], COLD_RULES, COLD)):
try:
check(seq, rules, ev)
raise SystemExit("a bad sequence was accepted")
except AssertionError:
pass
print("PASS: cold-boot and wake orders hold; early gates, late isolation and a missing event are rejected")The rail order written with an owner per segment: board, package and chip names attached.
Cold boot and wake kept as two sequences, each with isolation, clock gating and reset release in its own order.
The I/O-versus-core order checked against the supply specification of the I/O library and package.
Every pad's power-on control tied to a core-supply-valid signal, so no driver is on before the core is up.
Verify the sequence in power-aware simulation with the real delays from the NanoSoC reset and power tables. On the bench, a sequencing suspect shows as the reset-cause register pointing at the wrong source, or as pins that glitch during the ramp. Both are symptoms of a rail that was not ready when something depended on it.
Because they start from different states. At cold boot nothing is powered, the oscillator must settle and the PLL locks without PD_SW being a factor. At wake the always-on domain is running and the order is about isolation, the header switch and relock. One merged list would hide a violation of either.