The DMA was verified against a 96 MHz assumption, but nothing in its file knows that 96 MHz comes from a 768 MHz VCO divided by eight, or that the 192 MHz CPU clock shares the same VCO through a divide by four, or that the 24 MHz peripheral clock bypasses the PLL entirely. When the PLL plan changes, every block constraint written against the old frequencies lies. The fix is top-level ownership: the clock definitions are written once at the top from the PLL plan, and block files reference them instead of defining their own.
The top file defines the source first: the 24 MHz crystal, the multiply by 32 to the 768 MHz VCO, and the divides that produce 192 and 96 MHz. Both dividers are declared as generated clocks of the PLL output. Because signoff treats clk_cpu and clk_sys as asynchronous (separate clock trees after the divider), the file also declares them as an asynchronous clock group; otherwise the tool, seeing one master and two divide relationships, would time the crossings inside the asynchronous AXI bridge as synchronous paths and report violations that cannot happen in silicon. clk_per is the crystal clock itself, defined once at the crystal pin; it is the source clock, not a second clock, and it keeps running when the PLL is off in sleep. Block files must not redefine any of this: their clock assumptions become comments pointing at the top definitions, and any block file that still creates a clock with the same name fails the merge check the next lesson runs.
# not-run: top-level clock definitions, NanoSoC teaching values
source: clk_xtal 24.000 MHz, period 41.667 ns
VCO: 24 x 32 / 1 = 768 MHz (inside the 600 to 1200 MHz teaching range)
clk_cpu: VCO / 4 = 192 MHz, period 5.208 ns
clk_sys: VCO / 8 = 96 MHz, period 10.417 ns
clk_per: the crystal clock itself, defined once at the crystal pin, 24 MHz, period 41.667 ns
asynchronous group: clk_cpu and clk_sys (separate trees; crossings are FIFO-protected)
PLL lock: 256 clk_per cycles = 10.667 us, qualified before gates open# Recompute every NanoSoC clock definition from the PLL plan.
XTAL_MHZ, N, R = 24.0, 32, 1
VCO_MHZ = XTAL_MHZ * N / R
assert VCO_MHZ == 768.0
assert 600.0 <= VCO_MHZ <= 1200.0 # teaching VCO range
clocks = {"clk_cpu": VCO_MHZ / 4, "clk_sys": VCO_MHZ / 8,
"clk_per": XTAL_MHZ} # bypasses the PLL
assert clocks == {"clk_cpu": 192.0, "clk_sys": 96.0, "clk_per": 24.0}
periods = {name: 1000 / f for name, f in clocks.items()} # ns
assert abs(periods["clk_cpu"] - 5.2083) < 0.001
assert abs(periods["clk_sys"] - 10.4167) < 0.001
assert abs(periods["clk_per"] - 41.6667) < 0.001
lock_us = 256 / clocks["clk_per"] # MHz -> us per cycle
assert abs(lock_us - 10.6667) < 0.001
# Both dividers come from one master, so a tool relates them unless told not to.
GENERATED_FROM = {"clk_cpu": "pll_out", "clk_sys": "pll_out"}
def timed_as_synchronous(a, b, async_groups):
related = GENERATED_FROM.get(a) is not None and GENERATED_FROM.get(a) == GENERATED_FROM.get(b)
split = any(a in g1 and b in g2 for g1 in async_groups for g2 in async_groups if g1 is not g2)
return related and not split
assert timed_as_synchronous("clk_cpu", "clk_sys", []) # missing group: wrong
assert not timed_as_synchronous("clk_cpu", "clk_sys", [{"clk_cpu"}, {"clk_sys"}])
print("PASS: 192/96/24 MHz with periods; lock 10.667 us; missing async group is detectable")Exactly one definition per clock in the merged constraints, authored at the top, with the PLL arithmetic above attached.
No block file defining a clock: the merge check fails any file that does.
The PLL-bypass clock visibly independent, so sleep-mode timing still constrains the wake logic when the PLL is off.
Diff the clock names against the clock-controller documentation: a renamed divider output with old constraints still referencing it constrains nothing and fails loudly only if the review diffs names. When timing degrades after a PLL-plan change, recheck the divide relationships before the paths; a divide-by value edited in one place and not the other doubles or halves every derived period silently.
Merge failure. Two definitions of one name disagree the day either changes, and tools resolve the conflict by rules nobody reviewed. Delete the block copy and reference the top definition.