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.