Digital Design / Module 10: Clock domain crossing
55. Pulse crossing fast to slow
Avoid losing narrow pulses.
See it move
The idea
A one-cycle pulse in a fast may be shorter than a slow clock's period. It can fall between edges, in which case the slow domain simply never sees it. A two-flop doesn't help, since no edge sampled the pulse in the first place.
The toggle synchronizer
Convert the short event into a lasting change. Each source pulse flips a toggle flop. A flipped level stays there indefinitely, so the destination can't miss it. There, a synchronizer brings the level across, and an edge detector turns any change into a new pulse.
// Source domain: each pulse flips the level
always_ff @(posedge clk_a)
if (!rst_a_n) tog <= 1'b0;
else if (pulse) tog <= ~tog;
// Destination domain: synchronize, then detect any edge
always_ff @(posedge clk_b)
if (!rst_b_n) s <= 3'b000;
else s <= {s[1:0], tog};
assign pulse_b = s[2] ^ s[1];The limit
Pulses must be far enough apart. If two pulses arrive before the destination sees the first toggle, the level flips and then flips back, and both events disappear. As a guide, allow at least two to three destination clock cycles between source pulses. Above this rate, use a or a .
Above, compare direct sampling with the toggle path. Direct sampling catches only the pulses that happen to align with an edge, while the toggle path keeps them all, as long as they obey the spacing.
Handshake alternative
With a handshake, the source holds a request until the destination acknowledges it. This needs no rate assumptions, because the source simply waits, but it takes several cycles per event.
Common mistake
Stretching the pulse to a fixed width instead. That can work if you truly know both clock frequencies, but it fails silently if a clock changes later on. Toggle or handshake solutions don't depend on the frequency ratio in the same fragile way.
Key ideas
- Short pulses can fall between slow-clock edges.
- Toggle synchronizer: convert to toggle, synchronize, then edge-detect.
- Limit how quickly pulses may repeat.
Lab: Clock crossing lab
Your exercise
Find the bug
This code has one deliberate bug. Read the symptom, then click the line you think is wrong.
The fix
assign pulse_b = s[2] ^ s[1];
Why it failed
A toggle goes high on one pulse and low on the next, so each pulse makes one edge of either kind. s[1] & ~s[2] catches only rising edges, so every other event is lost. An XOR detects any change, giving one destination pulse per source pulse.
Interview questions
coreFast-to-slow pulse crossing?
Toggle in the source, sync, and edge-detect in the destination; or use a handshake.
basicA 400 MHz domain sends one-cycle pulses directly into a two-flop synchronizer on a 50 MHz clock. Roughly what fraction of pulses are seen?
The pulse lasts one source period, 2.5 ns. The destination samples every 20 ns, and a pulse is only seen if an edge lands inside it. With unrelated clocks, that happens about 2.5 / 20 = 1/8 of the time. So about seven pulses in eight are simply lost.
basicWould adding a third synchronizer stage help a fast-to-slow pulse crossing that loses pulses?
No. Extra stages give more settling time to a value that was sampled, but the problem here is that no edge ever sampled the pulse. A pulse that falls between two destination edges leaves nothing for any number of stages to pass on. You need to change what crosses, for example a toggle level or a handshake, not how long it settles.
coreThis destination side of a toggle synchronizer passes a test with one pulse, but in a test with six pulses only three arrive. What is wrong?
always @(posedge clk_b)
if (!rst_b_n) s <= 3'b000;
else s <= {s[1:0], tog};
assign pulse_b = s[1] & ~s[2]; // rising edge onlyIt only detects a rising edge of the synchronized toggle. Each source pulse flips the toggle, so it rises on one pulse and falls on the next, and half the changes are falling. The detector must react to any change: assign pulse_b = s[2] ^ s[1];. A single-pulse test cannot find this bug, which is why pulse tests should send several pulses in a row.
coreA toggle synchronizer carries pulses from a 250 MHz domain to a 25 MHz domain. Allowing two to three destination cycles between pulses, what minimum pulse spacing should the source guarantee?
The destination period is 40 ns, so two to three cycles is 80 to 120 ns. The source period is 4 ns, so that is 80 / 4 = 20 to 120 / 4 = 30 source cycles between pulses. The bare minimum is a little over one destination period, but real flops can take a change a cycle late, so the margin matters. If the source cannot promise that spacing, use a handshake or a FIFO.
coreTwo source pulses arrive closer together than one destination period on a toggle synchronizer. Is one of them lost, or both?
Usually both. The first pulse flips the toggle and the second flips it back before any destination edge has seen the new level. To the destination, the level never changed, so no pulse comes out at all. That is worse than losing one event, and it is why the source must respect a minimum spacing.
coreWhen would you use a request and acknowledge handshake instead of a toggle synchronizer for single events?
Use a handshake when events can come faster than the toggle's spacing rule allows, or when nobody can promise the clock ratio. The source raises a request and holds it until the synchronized acknowledge comes back, so it simply waits and no rate assumption is needed. The cost is a round trip through two synchronizers for every event, so it is slow. A toggle is cheaper when the source can guarantee enough spacing.
advancedA designer stretches each one-cycle pulse to 16 ns so a 14 ns destination clock always sees it. Later the destination clock moves to 20 ns. What happens?
A 16 ns window always contains an edge of a 14 ns clock, so it worked. With a 20 ns clock, a 16 ns window misses every edge whenever it starts in the wrong 4 ns of the period, so about 4 / 20 = 20 percent of pulses are lost. Nothing in the design or its tests warns anyone, because the clock ratio was built into the stretcher. A toggle synchronizer does not depend on the ratio in the same way, only on pulse spacing.
advancedA toggle synchronizer has separate resets in each domain. The source domain is reset alone while its toggle flop holds 1. What does the destination see?
The source reset clears the toggle from 1 to 0. To the destination that is just another change of level, so after synchronization the XOR detector produces a pulse even though no event was sent. This is a reset domain problem: one side reset while the other keeps running. Fixes include resetting both sides together, or holding off the destination's detector until both sides are known to be out of reset.