A design that switches its clock source, for example between a fast and a slow oscillator, needs a mux on the clock net. A logic mux on a clock is one of the few places where an ordinary-looking gate can destroy a whole chip: a runt pulse clocks some flops and not others, leaving the state permanently inconsistent.
// bad-example
assign clk_o = sel ? clk_b : clk_a; // if sel changes near a clock edge, clk_o gets a runt pulseIf sel changes while the selected clock is high, the output follows it at once: a shortened pulse, or an extra edge when the other clock happens to be high. Either violates the minimum pulse width every flop was characterised with.
Synchronise the select into each clock domain. The request to use clock A is captured by two flops on clock A; the request to use clock B by two flops on clock B. Neither side ever acts on a select that is still settling.
One enable latch per side, transparent while its clock is low. The latch can only change the enable while its clock is safely low, so a pulse that has started always completes at full width.
Mutual exclusion through feedback. Each latch's input includes the other side's enable: side A may turn on only while side B is off, and vice versa. The switch therefore sequences itself: the old clock stops first, then the new clock starts.
Switching takes a few cycles of the slower clock, during which the output sits still. That dead time is the price of safety; downstream logic must tolerate a paused clock (see clock switching and reset).
The mux below follows the structure above, with an asynchronous reset standing in for power-on reset. The testbench switches in both directions and checks two things: every phase of clk_o is full width (no runt pulses), and the two enables never overlap.
`timescale 1ns/1ps
module glitch_free_clk_mux (
input logic clk_a, clk_b, rst_n, sel,
output logic en_a, en_b, output logic clk_o
);
logic q_a1, q_a2, q_b1, q_b2, ga, gb;
always_ff @(posedge clk_a or negedge rst_n)
if (!rst_n) {q_a1, q_a2} <= 2'b00;
else {q_a1, q_a2} <= {~sel, q_a1}; // select-A request into A
always_ff @(posedge clk_b or negedge rst_n)
if (!rst_n) {q_b1, q_b2} <= 2'b00;
else {q_b1, q_b2} <= {sel, q_b1}; // select-B request into B
always_latch
if (!rst_n) en_a = 1'b0;
else if (!clk_a) en_a = q_a2 & ~en_b; // A starts only after B stopped
assign ga = clk_a & en_a;
always_latch
if (!rst_n) en_b = 1'b0;
else if (!clk_b) en_b = q_b2 & ~en_a; // B starts only after A stopped
assign gb = clk_b & en_b;
assign clk_o = ga | gb;
endmodule
How to read it: the lanes are sampled every half cycle of clk_a, so odd slots show clk_a high. sel rises between slots 20 and 21. en_a falls at slot 24 and clk_o sits at 0 through slots 24 to 29, the dead time while the switch completes. en_b rises at slot 28 and clk_o follows clk_b again from slot 30. After sel falls back around slot 32, the enables swap back around slot 40. No lane ever shows a narrow pulse: every clk_o phase is a full half period of whichever clock is selected.
Check | What it means |
|---|---|
Select synchronisation | The select reaches each side through destination-clock flops, never directly. |
Latch structure preserved | The enable latches keep their hold behaviour through synthesis; the mux is a recognised cell or a do-not-touch block. |
No combinational select | Nothing decodes the select combinationally into the enables. |
Dead time tolerated | Downstream logic is specified to ride through a paused clock. |
No. The pause is the mutual-exclusion sequencing: the old side must be observably off before the new side starts. Budget for it and make sure downstream state machines tolerate a still clock.
The select may then violate the enable latch timing and the switch can produce exactly the runt pulse the structure exists to prevent. The synchroniser delay is part of the safety, not overhead.