Module 69 min

Handshake and Pulse Synchronizers

Crossing pulses and arbitrary data safely

Not every crossing is a stream of data or a slow level. Sometimes you need to pass a single event, or move a data word across only occasionally. Handshake and pulse synchronizers handle these cases.

The pulse problem

A single-cycle pulse in a fast domain can be too narrow for a slow destination clock to catch. A plain two-flop synchronizer may miss it completely. The fix is to convert the pulse into something the destination cannot miss.

Toggle-based pulse synchronizer

Instead of sending the pulse itself, toggle a level signal in the source domain each time the event happens. The level is held until the next event, so the destination two-flop synchronizer always catches it. The destination then detects a change in the synchronized level and regenerates a one-cycle pulse.

Each event toggles a held level; the destination turns the edge back into a pulse (click to enlarge)
verilog
// Source: toggle on each event (level is held, easy to catch)
always @(posedge clk_src)
  if (pulse_src) toggle <= ~toggle;

// Destination: a 2-flop synchronizer (sync[0], sync[1]) plus one
// extra flop, so the edge is detected on two SETTLED stages and the
// first, possibly-metastable flop never reaches the output.
reg [2:0] sync;
always @(posedge clk_dst) begin
  sync <= {sync[1:0], toggle};      // sync[0] may be metastable
  pulse_dst <= sync[2] ^ sync[1];   // edge -> one-cycle pulse
end

Full handshake for data

To move an occasional data word safely, hold the data steady and synchronize a control handshake around it:

  1. The source places the data on the bus and raises a request signal.
  2. The request is synchronized into the destination domain with a two-flop synchronizer.
  3. Seeing request, the destination latches the data (which has been stable the whole time) and raises an acknowledge.
  4. The acknowledge is synchronized back to the source, which then knows the transfer is done and may send the next word.

Only the request and acknowledge control signals are synchronized, each a single bit, so they are safe. The data bus is never synchronized directly because it is guaranteed stable while the handshake completes.

Data held steady; only req and ack are synchronized across the boundary (click to enlarge)
Watch out

A handshake moves data safely but slowly: each transfer takes several cycles in both domains for the round trip. For high-throughput streams use an asynchronous FIFO instead. Match the structure to the traffic.

Pulse vs toggle: the same idea

The names "pulse synchronizer" and "toggle synchronizer" usually mean the same technique. Because a raw one-cycle pulse can be too narrow for a slower destination clock to catch, you do not send the pulse itself. You toggle a level in the source domain on every event, synchronize that steady level with a two-flop synchronizer, and regenerate a clean one-cycle pulse in the destination by detecting the edge (XOR the synchronized level with its own delayed copy). The event survives because a held level is easy to sample, unlike a fleeting pulse.

Which synchronizer, and when

The right structure depends entirely on what you are crossing:

What you are crossingUse thisWhy
A slow level or control bitTwo-flop synchronizerSimple and safe when the signal is stable long enough to be caught
A single-cycle pulse or eventToggle (pulse) synchronizerA held toggle survives a slower clock; a raw narrow pulse can be missed
An occasional data wordReq / ack handshakeOnly the 1-bit control crosses; the data is held stable and never synchronized
A continuous data streamAsynchronous FIFOBuffers and decouples the two rates for high throughput
A multi-bit counter or pointerGray code + two-flopOnly one bit changes per step, so a mid-flight sample is still valid