The PLL is an analog macro in a digital tree, and the boundary between the two is where start-up bugs live. Software writes one enable bit, the macro answers with one lock bit, and between them sit a start-up delay, a 256-cycle lock counter and a synchroniser. This lesson owns that boundary: who drives what, how long each step takes, and which gates stay shut until lock is real. Its one job is the wrapper and the handshake; the frequency arithmetic belongs to the previous lesson.
The wrapper lives in nano_rstpwr on clk_per, because the controller that sequences lock must itself run on a clock that does not need the PLL. Software enables the macro through RSTPWR PLL_CTRL bit EN, which resets to 1 so the cold-boot sequence starts locking without firmware. The macro answers with a lock flag that is asynchronous to every digital clock, so it crosses through a two-flop synchroniser (crossing X7) before anything acts on it. Between enable and lock the controller counts: 4 clk_per cycles of start-up, then 256 clk_per cycles of lock qualification. The lock counter alone is 256 cycles, 10.667 us; with the start-up the total is 260 cycles, 10.833 us. The gates open 2 clk_per after that, so the enable-to-gates time is 262 cycles, 10.917 us. Only then do the clk_sys and clk_cpu gates open, and the resets release later still, in the release-order lesson.
Two states must never disagree: the enable the controller holds and the bypass the tree assumes. If firmware clears EN to save power in sleep, the tree must already be on the crystal path for every domain that still runs, and the wake sequence must re-lock before reopening the fast gates. The bypass inconsistency, running fast logic on a clock assumed PLL-clean while the PLL is off, is the subtler half of the classic PLL failure.
The counter values are NanoSoC teaching values from the NanoSoC specification, and the script recomputes them in slow-clock cycles and microseconds so the release-order lesson can reuse the total:
T_PER_NS = 1e9 / 24e6 # 41.6667 ns slow-clock period
STARTUP = 4 # NanoSoC teaching value: macro start-up
LOCK = 256 # NanoSoC teaching value: lock counter
R3 = STARTUP + LOCK
lock_us = LOCK * T_PER_NS / 1000
r3_us = R3 * T_PER_NS / 1000
assert abs(lock_us - 10.6667) < 0.001, lock_us
assert abs(r3_us - 10.8333) < 0.001, r3_us
# The gates open 2 clk_per later (step R4): enable to gates is 262 cycles.
sync_ns = 2 * T_PER_NS
assert abs(sync_ns - 83.333) < 0.01, sync_ns
gates_us = (R3 + 2) * T_PER_NS / 1000
assert abs(gates_us - 10.9167) < 0.001, gates_us
# A counter that skipped the start-up would open the gates 4 cycles early.
assert (LOCK + 2) * T_PER_NS / 1000 < gates_us
print("PASS: lock 256 cycles = 10.667 us; enable-to-lock-qualified 260 = 10.833 us; to gates 262 = 10.917 us")The wrapper diagram names the driver of EN, the synchroniser on lock, and every gate qualified by the synchronised lock.
The lock counter value appears once, in the controller, and every quoted wait derives from it.
The sleep path clears EN only after the tree is on bypass, and the wake path re-locks before reopening fast gates.
Simulate the sequence with a lock model that deasserts for a bounded time, then asserts and stays: the gates must not open one cycle early, and a lock dropout mid-run must re-qualify through the full counter rather than resuming it. On silicon, a start-up that fails only sometimes, and only cold, points at lock timing before it points at reset: read back PLL_STATUS LOCK and the PWR_IRQ lock event before touching the reset tree.
The full lock sequence again: 4 start-up plus 256 lock cycles on clk_per, about 10.833 us, seen through the synchronised LOCK flag. Reopening on the raw macro flag skips the sync latency and risks acting on a metastable lock.