The dividers are the quietest blocks in the tree and the easiest to get subtly wrong. A divide-by-8 built as three toggle flip-flops in series (a ripple divider) still divides exactly by 8, but each stage is clocked by the previous stage's output, so its edges arrive up to three clk-to-q delays after the VCO edge and are skewed against the divide-by-4 chain; the phase relation the domain plan relies on is gone. On NanoSoC both dividers count from the same 768 MHz VCO, so every clk_sys rising edge, one every 10.417 ns, coincides with a clk_cpu rising edge. This lesson states what coincides and what does not, because the crossing inventory depends on it. Its one job is the dividers and the tree's lack of a runtime mux; the switching sequence is next.
An integer divider is a counter that toggles its output every N VCO edges for a divide by N, using both clock phases where an even divide must keep 50 percent duty cycle. Divide by 4 toggles every 2 VCO edges; divide by 8 toggles every 4. Because both counters see the same VCO edge, every rising edge of clk_sys coincides with a rising edge of clk_cpu (every 10.417 ns, with two clk_cpu edges per clk_sys period). The crystal clock is the PLL's reference, so the ratios are exact, 8 : 4 : 1, and the clocks do not drift apart. But the phase between clk_per and the divided clocks at the flops depends on the PLL feedback path, the divider reset and the separate clock trees: it is fixed in time but neither known nor bounded by the design, so signoff treats clk_per as asynchronous to both. Do not read the fact that every eighth clk_cpu edge corresponds to one clk_per edge as alignment: the edge count is exact, the offset is unbounded.
NanoSoC as specified has no runtime clock mux: clk_per is the crystal itself, wired directly past the PLL, and the two fast clocks have fixed ratios. A real chip usually adds muxes at the roots (a test clock against the functional clock, PLL against bypass); those are a teaching extension here, not part of NanoSoC. Wherever such a mux exists, its select needs an owner and a reset value, and it is constrained so the analysis tools use the right clock per mode. For constraining dividers and muxes see Generated Clocks & Dividers and the constraint merge itself belongs to IP integration.
VCO = 768e6
CPU_DIV, SYS_DIV = 4, 8
cpu, sys, per = VCO / CPU_DIV, VCO / SYS_DIV, 24e6
def rising_edges_in_vco_ticks(div, ticks):
# A divider that toggles every div/2 VCO edges rises once per div ticks.
return {t for t in range(0, ticks, div)}
SYS_PERIOD_TICKS = SYS_DIV # one sys period in VCO ticks
cpu_edges = rising_edges_in_vco_ticks(CPU_DIV, 64)
sys_edges = rising_edges_in_vco_ticks(SYS_DIV, 64)
# Every sys rising edge coincides with a cpu rising edge (same VCO count).
assert sys_edges <= cpu_edges
# Two cpu edges per sys period, so half of the cpu edges are alone.
assert len(cpu_edges) == 2 * len(sys_edges)
assert abs(1e9 / sys - 10.4167) < 1e-3 # coincidence period in ns
# Crystal clock: frequency ratios only (VCO edges per crystal period = 32).
assert round(VCO / per) == 32 and round(cpu / per) == 8 and round(sys / per) == 4
# A wrong divider (/5 instead of /4) breaks the coincidence: the test can fail.
assert not (rising_edges_in_vco_ticks(SYS_DIV, 64) <= rising_edges_in_vco_ticks(5, 64))
print("PASS: cpu/sys edges coincide every 10.417 ns; crystal shares only the 8:4:1 ratios")Both dividers count the same VCO edge, stated in the design, not assumed from the ratio.
Every mux select has an owner, a reset value and a timing-analysis mode that matches it.
Duty cycle is specified where gated clocks need it: a halved duty cycle breaks the enable-timing budget downstream.
Measure the coincidence on the bench: trigger on a clk_sys rising edge and confirm a clk_cpu rising edge under it, then confirm that the offset to clk_per is not something the design promises. A divider that occasionally slips points at its reset: both counters must come out of reset together, or the alignment starts with a permanent offset. That reset-together rule returns in the reset tree topic.
The coincidence of their edges is what makes the clocks phase-related. If one counter starts a VCO cycle late, every edge pair carries that offset forever, and any logic that assumed alignment, such as a testbench that samples one clock on the other's edge, samples one VCO cycle off.