Pulse Synchronizer (Toggle CDC)

Medium · Clock Domain Crossing

Transfer single-cycle pulses (events) from clock domain A to an unrelated clock domain B.

Why a two-flop synchronizer is not enough

A two-flop synchronizer works for a slowly changing level. A one-cycle pulse is a different story: if clk_a is faster than clk_b, the pulse can start and end between two clk_b edges and the destination never sees it. Even when it is seen, a slow clk_a pulse may be sampled high on two clk_b edges and counted twice.

The classic fix is the toggle synchronizer:

  1. Source domain: a flip-flop toggle_a flips its value on every input pulse. Each event is

now a level change that stays put until the next event.

  1. Crossing: toggle_a is a single-bit level, so a normal two-flop synchronizer carries it

safely into clk_b.

  1. Destination domain: one more flop remembers the previous synchronized value; whenever the

synchronized value differs from it (XOR), emit a one-cycle pulse_b.

The price: events must not come too fast. The destination has to see every level of toggle_a, so events need a minimum spacing (see below). (Like every CDC problem, simulation cannot show metastability; what is graded is the counting and the timing below.)

Specification

module pulse_sync (
    input  logic clk_a,    // source clock
    input  logic rst_a,    // synchronous to clk_a, active-high
    input  logic pulse_a,  // event: high for exactly one clk_a cycle
    input  logic clk_b,    // destination clock, unrelated to clk_a
    input  logic rst_b,    // synchronous to clk_b, active-high
    output logic pulse_b   // one clk_b-cycle pulse per event
);

that edge the event's capture edge.

clk_b cycle. pulse_b is never high except for an event (no extra pulses). Pulses come out in the same order as the events.

e1, e2, e3, ... The event's output pulse must be the clk_b cycle that starts at e2, e3 or e4. (So at least two clk_b flops must be passed, and the pulse must arrive within four clk_b cycles.) The reference design above produces it at e2.

logic uses it: the edge detector may only read flops at or after the second synchronizer stage, never the first one. The first flop is the one that can go metastable, and XOR-ing it (even into a register) lets a metastable value spread into the destination logic. Simulation cannot see metastability, so this rule is not graded, but a design that breaks it is wrong in silicon even if it passes the tests.

time, and the capture edges of two consecutive events are at least 2·T_a + 3·T_b apart, where T_a and T_b are the clock periods. Under this rule, every event must be delivered.

(each synchronous to its own clock, active-high; source state → 0, destination state → 0). While rst_b is held, pulse_b must be 0, starting right after the first clk_b edge with rst_b = 1. The tests assert both resets at the same moment, hold them for at least 5 cycles of the slower clock, release them at the same moment, and send no events while they are asserted. No pulse_b may appear because of anything that happened before the reset (for example after an odd number of events, when the toggle was 1).

The hidden tests use several clock pairs: fast to slow (10 ns → 17 ns), slow to fast (17 ns → 10 ns), nearly equal (10 ns → 12.3 ns) and extreme ratios (10 ns ↔ 37 ns). Clock edges of the two domains never coincide in the tests.

Example (not to scale; clk_b drawn twice as slow as clk_a)

The event is captured at the clk_a edge at t = 3. The next clk_b edges are at t = 4 (e1) and t = 6 (e2); pulse_b is high in the clk_b cycle that starts at e2.

Sign up free to solve Pulse Synchronizer (Toggle CDC) in your browser.

More Clock Domain Crossing problems