NanoSoC never switches a running clock: its ratios are fixed and clk_per is the crystal. A real chip built from it will, for test, for a bypass or for a gear change, and the integrator then owns two things the circuit lesson does not: how long the clock is dead and which blocks must tolerate that. This lesson recalls the safe sequence in a few lines, proves it with a waveform, and spends the rest on the integration budget. Its one job is the sequence and its dead time; the ways it is violated are the next lesson.
The circuit construction, an AND gate per source with one-hot enables that each change only while their own clock is low, is taught in Glitch-Free Clock Mux and its CDC view in Clock Gating and Muxing; it is not repeated here. What the integrator needs is the order. Drop the old enable (synchronised to the old clock); wait until the old branch is seen steady low; raise the new enable (synchronised to the new clock); only then does the output follow the new source. Between the two enables the output is dead, a stretched low phase, which is safe: a missing pulse delays an edge, a runt pulse corrupts state.
// snippet: AND-gate clock mux with one-hot enables (partial: the
// synchronisers and the handshake controller that sequence ena/enb live
// outside this fragment, in the clock controller).
module nano_clk_mux2 (
input logic clka,
input logic clkb,
input logic ena, // one-hot with enb; changes only while clka is low
input logic enb, // one-hot with ena; changes only while clkb is low
output logic clkout
);
logic gated_a, gated_b;
assign gated_a = clka & ena;
assign gated_b = clkb & enb;
assign clkout = gated_a | gated_b;
endmoduleThe waveform below is simulation output for the wave source of this topic: a switch from the fast clock to the slow clock and back. One slot is one cycle of the fast clock, sampled shortly after its rising edge.
How to read it: sel_req rises in slot 4 while both enables are steady. ena falls in slot 5 and clkout is low in slots 5 and 6, with neither enable raised for that stretch. enb rises in slot 6, and clkout follows the slow clock from slot 7: high in slots 7 and 9 around the low slot 8. The return trip has a longer dead stretch. sel_req falls in slot 10 and enb has already fallen in the same slot, so clkout is low in slots 10 and 11. ena rises in slot 12, while the fast clock is low, and clkout is high in slot 12 and follows the fast clock from there. In the lanes no pulse looks short, but one sample per slot cannot show a runt. The proof is the monitor in the testbench source, which measures every clkout phase at 1 ns resolution and stops the run if one is narrower than the fast half-period of 5 ns: with the enable moved to a high phase of the fast clock it reports a 4 ns pulse, and with the enable in the low phase it stays silent.
Suppose a NanoSoC derivative switched clk_sys from the 96 MHz divider output to the 24 MHz crystal, for example to lower power. Both numbers below are illustrative, since NanoSoC itself never does this. The old enable is synchronised into the old clock (two clk_sys periods), then the new enable into the new clock (two clk_per periods), and the output is dead for the sum.
Step | Clock | Cost | Time |
|---|---|---|---|
Drop old enable, synchronised | clk_sys 96 MHz | 2 x 10.417 ns | 20.8 ns |
Raise new enable, synchronised | clk_per 24 MHz | 2 x 41.667 ns | 83.3 ns |
Dead time | sum | 104.2 ns = 10 clk_sys cycles |
# Dead time of a clk_sys -> crystal switch (illustrative scenario).
T_SYS, T_PER = 1e3 / 96, 1e3 / 24 # ns
dead = 2 * T_SYS + 2 * T_PER
assert abs(dead - 104.17) < 0.01
assert round(dead / T_SYS) == 10
# Who must tolerate it: an APB access normally takes 3 clk_sys = 31.25 ns,
# the bridge timeout is 256 clk_sys = 2666.7 ns, the PLL-independent crystal
# keeps the always-on logic alive meanwhile.
ACCESS_NS, TIMEOUT_NS = 3 * T_SYS, 256 * T_SYS
assert abs(ACCESS_NS - 31.25) < 0.01 and abs(TIMEOUT_NS - 2666.7) < 0.1
def safe(dead_ns, in_flight):
# An in-flight access finishes if dead time plus access fits the timeout.
return (not in_flight) or (dead_ns + ACCESS_NS < TIMEOUT_NS)
assert safe(dead, True) # the real switch is survivable
assert not safe(3000.0, True) # a 3 us dead time would trip the timeout
assert safe(3000.0, False) # but is harmless when the bus is quiet
print("PASS: dead time 104.2 ns = 10 clk_sys cycles; far inside the 2.667 us bridge timeout")The budget also shows why the switch is requested, not forced. The clock controller raises a clock-change request to every block on the affected clock; each block finishes its transfer and answers with an acknowledge when it is quiet; only when all acknowledges are high does the controller start the enable sequence, and it releases the request after the new clock is running. The bridge FIFOs and the 256-cycle APB timeout must tolerate the 10-cycle dead stretch; the acknowledge handshake keeps new accesses from starting inside it.
The controller that sequences the enables is named, with its request and acknowledge signals to every block on the clock.
Each enable change is synchronised to its own clock low phase; the evidence is a pulse-width check, not the per-slot picture.
The dead stretch is budgeted in cycles of the slower and faster clock, and compared with every timer and timeout on that clock.
Assert the two invariants in simulation: the enables are never high together, and every clkout pulse meets the minimum width of the slower source, measured at fine resolution. Sweep the request timing against both clocks, because the synchroniser latency moves with phase, and run the test once with a deliberately early enable to prove the width check can fail. A glitch that appears only at one alignment points at an enable changing on the wrong phase.
A stretched low phase only delays the next edge; every flop keeps its state. A runt pulse can clock some flops and not others, or violate recovery on asynchronous inputs, leaving the design in a state no reset sequence planned for.
Two crystal periods for the old enable plus two clk_sys periods for the new: 2 x 41.667 + 2 x 10.417 = 104.2 ns. The sum is the same in either direction; only which clock takes which term changes.