A synchroniser can only tame one uncertainty: when the signal changes relative to the destination clock. It cannot tame a signal that glitches, because a glitch looks exactly like a fast real change. Launching every crossing from a register removes the glitch, leaving the synchroniser one job instead of two.
The driver of a crossing must be the output of a flip-flop in the source domain, with no combinational logic between that flop and the first synchroniser flop. Structural analysis flags anything else, generally under a category like combinational logic before the synchroniser.
// bad-example
// a decoded strobe crossed directly: it can glitch when its inputs settle
assign cross_strobe = (state == 2'b10) && req;
sync2 u_sync (.clk(clk_d), .d(cross_strobe), .q(strobe_d));The comparator output can flicker as state and req settle at slightly different times. The synchroniser faithfully carries the flicker across, and the destination sees a strobe that never existed.
// snippet
// register the strobe in the source domain first, then cross the register
always_ff @(posedge clk_s) cross_strobe_q <= (state == 2'b10) && req;
sync2 u_sync (.clk(clk_d), .d(cross_strobe_q), .q(strobe_d));The registered version changes at most once per source cycle and never glitches. It can still change near a destination edge, which is exactly the case the synchroniser is built for.
Check | What it means |
|---|---|
Source is a flop | The crossing net traces back to a register output in the source domain, not to combinational logic or a primary input. |
No logic on the path | No gates sit between the source flop and the first synchroniser flop, or between the synchroniser flops. |
One destination clock | Every flop of the synchroniser runs on the destination clock. |
Single fanout | Only the last synchroniser flop drives destination logic; the intermediate flop drives nothing else. |
Run structural analysis on the whole design and clear every combinational-source finding, either by registering the source or by documenting why the path is provably glitch-free. Then re-check after synthesis, because optimisation can push logic across the boundary that was clean in RTL.
The mux output can glitch when the counter bits settle at different times, and the synchroniser can capture the glitch as a real event. Register the mux output in the source domain and cross the register.