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.
// 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
endFull handshake for data
To move an occasional data word safely, hold the data steady and synchronize a control handshake around it:
- The source places the data on the bus and raises a request signal.
- The request is synchronized into the destination domain with a two-flop synchronizer.
- Seeing request, the destination latches the data (which has been stable the whole time) and raises an acknowledge.
- 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.
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 crossing | Use this | Why |
|---|---|---|
| A slow level or control bit | Two-flop synchronizer | Simple and safe when the signal is stable long enough to be caught |
| A single-cycle pulse or event | Toggle (pulse) synchronizer | A held toggle survives a slower clock; a raw narrow pulse can be missed |
| An occasional data word | Req / ack handshake | Only the 1-bit control crosses; the data is held stable and never synchronized |
| A continuous data stream | Asynchronous FIFO | Buffers and decouples the two rates for high throughput |
| A multi-bit counter or pointer | Gray code + two-flop | Only one bit changes per step, so a mid-flight sample is still valid |