On a cold boot the first thing the chip waits for is not software but physics: the crystal must start, and the PLL must lock to it. An integrator who cannot quote that chain cannot budget boot time, cannot say which logic runs during the wait, and cannot answer the first bring-up question, which clocks are alive right now. On NanoSoC the answer fits in one line: 24 MHz in, times 32, divided by 4 and 8, with the slow domain wired straight to the crystal.
The crystal oscillator produces clk_xtal at 24.000 MHz and runs whenever the supply is on. It is the only timing reference the chip trusts at power-up, and the reset controller counts oscillator-ready cycles on it before anything else happens. The PLL takes that reference with divide R equal to 1 and feedback multiply N equal to 32, so the voltage-controlled oscillator runs at 24 times 32, which is 768 MHz. That sits inside the teaching VCO range of 600 to 1200 MHz with margin on both sides: far enough from the bottom that the loop stays stable, far enough from the top that it never saturates.
Two post-VCO dividers derive the fast domains: divide by 4 gives clk_cpu at 192 MHz, divide by 8 gives clk_sys at 96 MHz. Integer divides of one VCO keep the two clocks phase-related, which matters for the domain plan later. The third domain, clk_per at 24 MHz, is the crystal itself, bypassing the PLL. The bypass is deliberate, not a shortcut: peripherals, the reset controller and the wake logic must run while the PLL is off, during lock, and in sleep, so they cannot depend on it.
Clock | Source | Frequency | Period | Runs when the PLL is off |
|---|---|---|---|---|
clk_cpu | VCO / 4 | 192 MHz | 5.208 ns | No |
clk_sys | VCO / 8 | 96 MHz | 10.417 ns | No |
clk_per | Crystal direct | 24 MHz | 41.667 ns | Yes |
# not-run: NanoSoC frequency chain, NanoSoC teaching values
VCO = 24 MHz x 32 / 1 = 768 MHz; range check 600 <= 768 <= 1200 passes
clk_cpu = 768 / 4 = 192 MHz, period 1 / 192 MHz = 5.208 ns
clk_sys = 768 / 8 = 96 MHz, period 1 / 96 MHz = 10.417 ns
clk_per = 24 MHz direct, period 1 / 24 MHz = 41.667 ns
Ratios clk_cpu : clk_sys : clk_per = 8 : 4 : 1One short script derives the whole chain and checks it, so no lesson ever quotes a frequency it did not compute. Run it whenever the NanoSoC specification changes; it asserts the VCO range, both divides and the exact 8:4:1 ratios.
XTAL = 24e6
R, N = 1, 32
VCO_LO, VCO_HI = 600e6, 1200e6
vco = XTAL * N / R
assert VCO_LO <= vco <= VCO_HI, vco
cpu = vco / 4
sys = vco / 8
per = XTAL
assert cpu == 192e6, cpu
assert sys == 96e6, sys
assert per == 24e6, per
assert (cpu / per, sys / per) == (8.0, 4.0)
periods = {n: 1e9 / f for n, f in [("cpu", cpu), ("sys", sys), ("per", per)]}
assert abs(periods["cpu"] - 5.2083) < 0.001, periods
assert abs(periods["sys"] - 10.4167) < 0.001, periods
assert abs(periods["per"] - 41.6667) < 0.001, periods
print("PASS: 24 MHz x32 = 768 MHz VCO; /4 = 192, /8 = 96, bypass = 24")Every quoted frequency traces to the crystal through N, R and one divider, with the VCO range check attached.
The bypass is drawn as a design decision with its reason, never as an accident: always-on logic must not depend on the PLL.
No block takes a clock the table does not list; the domain plan assigns each block one of these three.
On the bench, measure all three frequencies against the crystal before anything else: a wrong divider strap shows up as an exact integer ratio error, and a PLL that never locks leaves the two fast clocks dead while the slow one runs. If only the fast clocks are wrong, suspect the divider programming; if they are absent, suspect lock, which is the next lesson.
R and N change together. 768 = 20 x N / R gives N / R = 38.4 = 192 / 5, so R = 5 and N = 192, with the phase detector at 4 MHz (the calculator's N range of 64 would have to grow). Moving the VCO target to 760 or 800 MHz would change the domain frequencies, so it is not an option. The dividers stay and clk_cpu and clk_sys stay at 192 and 96 MHz. But clk_per is the crystal itself and becomes 20 MHz, so everything counted in clk_per cycles changes: the UART and SPI divisors, the 41.667 ns period (50 ns), the 256-cycle lock time (12.8 us instead of 10.667 us) and every boot wait in the release table.