The 2-FF synchronizer is great for stable signals, but what about narrow pulses? A pulse might last only 1 or 2 clock cycles in the source domain — the receiving clock might miss it entirely. Pulse synchronizers and handshake protocols solve this by stretching events and coordinating transfers across clock boundaries.
Imagine a 1-cycle pulse on event_A in clock domain A. When it arrives at a 2-FF synchronizer in domain B:
This is catastrophic for control signals: a request that never arrives will hang the system.
If your signal is a 1-cycle pulse, you must use a pulse synchronizer or handshake protocol. The 2-FF synchronizer alone cannot guarantee narrow pulses will be captured.
A simple pulse synchronizer stretches a narrow pulse into a wide pulse (typically several clock cycles) before crossing, so the receiving clock definitely sees it:
// Pulse Synchronizer
// Input: 1-cycle pulse on pulse_in (clk_a)
// Output: Synchronized pulse on pulse_out (clk_b)
module pulse_sync (
input clk_a, rst_n_a,
input pulse_in, // 1-cycle pulse in domain A
input clk_b, rst_n_b,
output pulse_out // Synchronized pulse in domain B
);
wire pulse_stretched; // Stretched version in domain A
reg stretch_en; // Stretch enable
reg [2:0] stretch_cnt;
// Stretch pulse in source domain (3 cycles)
always @(posedge clk_a or negedge rst_n_a) begin
if (!rst_n_a) begin
stretch_cnt <= 0;
stretch_en <= 0;
end else if (pulse_in) begin
stretch_cnt <= 3; // Stretch for 3 cycles
stretch_en <= 1;
end else if (stretch_cnt > 0) begin
stretch_cnt <= stretch_cnt - 1;
stretch_en <= 1;
end else begin
stretch_en <= 0;
end
end
assign pulse_stretched = stretch_en;
// Sync stretched pulse to domain B with 2-FF sync
wire pulse_sync_ff1, pulse_sync_ff2;
always @(posedge clk_b or negedge rst_n_b) begin
if (!rst_n_b)
pulse_sync_ff1 <= 0;
else
pulse_sync_ff1 <= pulse_stretched;
end
always @(posedge clk_b or negedge rst_n_b) begin
if (!rst_n_b)
pulse_sync_ff2 <= 0;
else
pulse_sync_ff2 <= pulse_sync_ff1;
end
// Detect rising edge in domain B
reg pulse_sync_ff2_r;
always @(posedge clk_b or negedge rst_n_b) begin
if (!rst_n_b)
pulse_sync_ff2_r <= 0;
else
pulse_sync_ff2_r <= pulse_sync_ff2;
end
assign pulse_out = pulse_sync_ff2 & ~pulse_sync_ff2_r;
endmodule
3. Handshake protocols: req-ack and valid-ready
For more complex transactions (especially when data must cross), handshake protocols provide reliable flow control:
Request-Acknowledge (req-ack) protocol:
- Sender asserts
req and places data on output bus
- Receiver samples
req, captures data, and asserts ack
- Sender sees
ack and deasserts req
- Receiver sees
req deassert and deasserts ack
The handshake ensures the receiver has captured data before the sender releases it. This is atomic: no data can be lost or duplicated.
handshake_req_ack.sv
// Handshake: Sender (req-side, domain A)
module handshake_sender #(parameter DWIDTH = 8) (
input clk_a, rst_n_a,
input [DWIDTH-1:0] data_out,
input data_valid, // User says: send this data
output reg req_a, // Request signal
input ack_sync // Ack from receiver (synced to domain A)
);
always @(posedge clk_a or negedge rst_n_a) begin
if (!rst_n_a)
req_a <= 0;
else if (data_valid)
req_a <= 1; // Assert request
else if (ack_sync)
req_a <= 0; // Deassert when ack received
end
endmodule
// Handshake: Receiver (ack-side, domain B)
module handshake_receiver #(parameter DWIDTH = 8) (
input clk_b, rst_n_b,
input [DWIDTH-1:0] data_in,
input req_sync, // Request (synced to domain B)
output reg ack_b, // Acknowledge signal
output reg [DWIDTH-1:0] data_captured,
output data_valid_b // Output: data is ready
);
reg req_sync_r;
always @(posedge clk_b or negedge rst_n_b) begin
if (!rst_n_b) begin
req_sync_r <= 0;
ack_b <= 0;
data_captured <= 0;
end else begin
req_sync_r <= req_sync;
// Detect rising edge on req_sync
if (req_sync & ~req_sync_r) begin
data_captured <= data_in; // Capture data
ack_b <= 1; // Assert ack
end else if (~req_sync & req_sync_r) begin
ack_b <= 0; // Deassert ack when req goes low
end
end
end
assign data_valid_b = ack_b;
endmodule
// Typical instantiation:
// - req_a synced to domain B with 2-FF: req_sync_b
// - ack_b synced to domain A with 2-FF: ack_sync_a
// - data_out tied to same bus in both domains during handshake
4. Toggle synchronizer (alternative to pulse sync)
A toggle synchronizer is another way to safely cross pulses. Instead of stretching, it toggles a bit on each event, then detects the toggle in the receiving domain:
- Event in domain A → toggle a bit
- Sync toggled bit to domain B (2-FF)
- Domain B detects edge via XOR with previous value
This is more elegant than stretching and works well when clock frequencies are very different.
toggle_sync.sv
// Toggle Synchronizer
module toggle_sync (
input clk_a, rst_n_a,
input event_in, // Pulse or event in domain A
input clk_b, rst_n_b,
output pulse_out // Synchronized pulse in domain B
);
// Toggle on each event (domain A)
reg toggle_a;
always @(posedge clk_a or negedge rst_n_a) begin
if (!rst_n_a)
toggle_a <= 0;
else if (event_in)
toggle_a <= ~toggle_a; // Toggle
end
// Sync toggle to domain B (2-FF)
reg toggle_sync_ff1, toggle_sync_ff2;
always @(posedge clk_b or negedge rst_n_b) begin
if (!rst_n_b) begin
toggle_sync_ff1 <= 0;
toggle_sync_ff2 <= 0;
end else begin
toggle_sync_ff1 <= toggle_a;
toggle_sync_ff2 <= toggle_sync_ff1;
end
end
// Detect edge in domain B
reg toggle_prev;
always @(posedge clk_b or negedge rst_n_b) begin
if (!rst_n_b)
toggle_prev <= 0;
else
toggle_prev <= toggle_sync_ff2;
end
// Output pulse = edge detected
assign pulse_out = toggle_sync_ff2 ^ toggle_prev;
endmodule
5. When to use each technique
Technique Best for Pros Cons
2-FF Sync Stable signals, low-frequency changes Simple, low latency Loses narrow pulses
Pulse Stretcher 1-cycle pulses, events Easy to understand, stretches before sync Adds latency, fixed stretch duration
Toggle Sync Pulses, events, variable clock rates Elegant, handles any pulse width Slightly more logic
Req-Ack Handshake Data transactions, flow control Atomic, works with multi-bit data Slower (roundtrip latency)
Valid-Ready Streaming data, pipelined crossings Flexible, supports backpressure More complex to implement
✅ Rule of thumb
Single-bit pulse? Use toggle synchronizer or pulse stretcher. Data + flow control? Use req-ack handshake. Streaming data? Use valid-ready or dual-clock FIFO (Day 5).
🎯 Day 4 takeaways
- Pulse problem: 2-FF synchronizer can miss 1-cycle pulses if clock B samples between edges
- Pulse stretcher: Widen pulse in source domain before sync, detect edge in receiving domain
- Toggle synchronizer: Toggle a bit on each event, sync the toggled value, detect edges via XOR
- Handshake (req-ack): Sender asserts req, receiver acks, sender deasserts req when ack seen
- Atomic transactions: Handshakes ensure data is captured before sender releases it
- Latency trade-off: Pulse sync: low latency, Handshake: higher latency but coordinated
- Implementation: Use library CDC modules when available (Synopsys DesignWare, foundry cells)
FAQ
What is a pulse synchronizer?
A circuit that stretches a narrow pulse into a wide pulse before crossing clock domains. The wide pulse is guaranteed to be seen by the receiving clock, preventing event loss.
When should I use pulse synchronizer vs handshake?
Pulse sync: for simple events that don't require acknowledgment or feedback. Handshake: for transactions where sender must know the receiver captured the data.
What is a toggle synchronizer?
A synchronizer that toggles a bit on each event, then syncs the toggled bit to the other domain. The receiving clock detects edges via XOR. It's more elegant and flexible than pulse stretching.
How do req-ack handshakes ensure atomicity?
The handshake creates a guaranteed sequence: sender puts data, asserts req; receiver captures, asserts ack; sender deasserts req only after seeing ack. This prevents race conditions and duplicates.
Can I use pulse synchronizer for multi-bit data?
Pulse sync is best for single-bit events. For multi-bit data, use a handshake (req-ack) or dual-clock FIFO (Day 5).
What's the difference between valid-ready and req-ack?
Valid-ready is for unidirectional pipelined flow (source says valid=1, sink must be ready). Req-ack is bidirectional handshake (initiator req, responder ack). Both are CDC-safe when properly synchronized.