PLL settings arrive as three numbers in a header, and a typo in any one of them overclocks the chip or stalls it. The integrator's defence is a calculator that derives the settings from the wanted frequencies and refuses anything outside the VCO range. This lesson builds that calculator for NanoSoC and uses it to prove the chosen R equal to 1 and N equal to 32 are the natural settings, not just familiar ones. Its one job is the settings search; lock behaviour belongs to the previous lesson.
The search space is small: reference divide R from 1 to 8, feedback multiply N from 1 to 64, and the fixed post-dividers 4 and 8. A setting is accepted only if the VCO sits inside 600 to 1200 MHz and both post-divided clocks land exactly on 192 and 96 MHz. Exactness matters: a VCO of 772 MHz would give 193 and 96.5 MHz, and every cycle-counted budget in the course would silently drift.
XTAL = 24e6
WANT_CPU, WANT_SYS = 192e6, 96e6
VCO_LO, VCO_HI = 600e6, 1200e6
good = []
for r in range(1, 9):
for n in range(1, 65):
vco = XTAL * n / r
if not (VCO_LO <= vco <= VCO_HI):
continue
if vco / 4 == WANT_CPU and vco / 8 == WANT_SYS:
good.append((r, n, vco))
assert (1, 32, 768e6) in good, good
assert len(good) >= 1, good
# Every accepted setting is the same VCO: the dividers fix it at 768 MHz.
assert {v for _, _, v in good} == {768e6}, good
# Near misses the calculator must reject: N=31 and N=33 miss both targets.
for n in (31, 33):
assert (XTAL * n) / 4 != WANT_CPU and (XTAL * n) / 8 != WANT_SYS
assert (1, n, XTAL * n) not in good
# The N = 31 typo in numbers: VCO 744 MHz, clocks 186 and 93 MHz, 3.125 % slow.
vco31 = XTAL * 31
assert vco31 == 744e6 and vco31 / 4 == 186e6 and vco31 / 8 == 93e6
assert abs((1 - 186e6 / WANT_CPU) * 100 - 3.125) < 1e-9
print(f"PASS: {len(good)} valid (R,N) settings, all at 768 MHz VCO; N=31 gives 186/93 MHz and is rejected")The taped-out settings appear in the calculator output with the VCO range check beside them.
No accepted setting is approximate: both derived clocks match exactly, or the setting is rejected.
The lock time quoted for the chosen setting matches the wrapper lesson: the counter runs on clk_per either way.
Rerun the sweep after any crystal or frequency change and confirm the taped-out pair is still in the accepted list. If the list is empty, the wanted frequencies are unreachable with integer divides and the architecture must move, either the VCO target or a divider, before any RTL changes. A VCO near the range edge deserves a second look at margin even when the calculator accepts it.
Small R and N keep the loop dynamics simple and the reference spur far out, and R = 1 feeds the phase detector the full 24 MHz reference. R = 2, N = 64 gives a 12 MHz phase-detector frequency and a 64-way feedback divide: the same VCO with a higher divide ratio, and as a general guideline about 6 dB more in-band phase noise per doubling of N, for no benefit here.