Pulse, toggle and handshake synchronizers
See why a short pulse is lost on its way to a slower clock, carry it with a toggle synchronizer, and close the loop with a four-phase handshake that also carries data.
- Lesson
- R6.3
- Time
- 35 min
- Runs on
- Your browser
- Check
- 5 questions
A one-cycle done pulse from a fast domain went to a slower domain through a two-flop synchronizer. Most pulses were never seen: the slow clock had no edge while the pulse was high. On the lab bench, seven pulses are sent and one arrives.
level lasts 3 ns.- Cycle 1
Edge 4: the first pulse has come and gone between two edges. No edge of clk ever saw
levelat 1. - Cycle 5
Edge 8: the second pulse is lost the same way.
- Cycle 9
Edge 12: edge 11 fell inside the third pulse, so
pulse_sfires once. The other pulses of the run are all lost.
Measured on the lab bench. In these edges, the samples of level taken 1 ns after each edge never catch a pulse. The final line reads sent=7 seen=1.
The toggle synchronizer
A pulse shorter than the receiving period can fall between two edges. Mehta and Cummings give the rule: a value must be held for at least 1.5 receiving periods to be sampled reliably. A one-cycle pulse from a faster clock never is.
The toggle synchronizer turns each pulse into a change of level, which stays until the next pulse. The sender flips a flop on every pulse. The flop crosses through a normal two-flop synchronizer. The receiver makes one pulse of its own for every change it sees.
- Flip
A pulse flips
level. The new level stays until the next pulse, so the slow clock has many edges to catch it. - Synchronize
The level crosses through two flops, like any slow bit.
- Regenerate
In the cycle where s2 differs from last cycle's s2,
pulse_sis 1. A rise and a fall both count, because both are pulses.
Read the diagram as text
- pulse_f: one clk_f cycle
- level: flips once per pulse
- s1, s2: two-flop synchronizer
- s2_q: last cycle's s2
- pulse_s: s2 ^ s2_q
- pulse_f → level: flip
- level → s1, s2: crosses
- s1, s2 → s2_q
- s1, s2 → pulse_s
- s2_q → pulse_s
Two lines change from the lab design: level <= level ^ pulse_f; and pulse_s = s2 ^ s2_q;. Synthesis on this flow: 6 cells, 4 flops, 1 level of logic.
- Cycle 2
Edge 5: the first pulse has flipped
levelto 1, and it stays there. - Cycle 3
Edge 6: s2 has the new level, and
pulse_sfires once. - Cycle 6
Edge 9: the second pulse flipped
levelback to 0. The fall makes a pulse too.
Measured on the lab bench. One regenerated pulse per sent pulse, two or three clk cycles after the flip.
The toggle has a limit too. The last two pulses of the bench are 6 ns apart, inside one clk period. The level flips and flips back before any edge samples it, and both pulses vanish: sent=7 seen=5. Each level must still last 1.5 receiving periods, so the sender must space its pulses at least that far apart.
The four-phase handshake
A handshake removes the spacing rule by closing the loop. The sender raises req and holds its data still. The receiver synchronizes req, takes the data, and raises ack. The sender synchronizes ack and drops req. The receiver drops ack, and only then may the sender start again.
- 1. req rises
The sender raises
reqwith the data ondata_x, and holds both.Follow the calls, values and waits
- data rise: hs_tx → r1, r2
- data rise: r1, r2 → hs_rx
- 2. ack rises
The receiver sees r2, takes
data_x, which has been still for two clk edges, and raisesack.Follow the calls, values and waits
- data rise: hs_rx → a1, a2
- data rise: a1, a2 → hs_tx
- 3. req falls
The sender sees a2 and drops
req.Follow the calls, values and waits
- data fall: hs_tx → r1, r2
- data fall: r1, r2 → hs_rx
- 4. ack falls
The receiver drops
ack. When a2 falls, the sender is free for the next transfer.Follow the calls, values and waits
- data fall: hs_rx → a1, a2
- data fall: a1, a2 → hs_tx
Read the diagram as text
- hs_tx: clk_f; holds data_x
- r1, r2: req into clk
- hs_rx: clk; takes data_x
- a1, a2: ack into clk_f
- hs_tx → r1, r2: req
- r1, r2 → hs_rx: r2
- hs_rx → a1, a2: ack
- a1, a2 → hs_tx: a2
Every phase waits for the other side, so no clock ratio can lose an event. The cost is four synchronizer crossings per transfer.
- Cycle 1
Edge 5:
reqhas risen in clk_f. - Cycle 3
Edge 7: r2 was 1 at this edge, so the receiver took 5a and raised
ack. - Cycle 4
Edge 8: the sender saw
ackthrough a1 and a2, and droppedreq. - Cycle 6
Edge 10:
ackis down.busyfalls in clk_f at 109.75 ns, after a2 drops; the nextreqshows at edge 11.
Measured: req rose at clk_f edge 16 and the sender was free at clk_f edge 36, 20 fast cycles. The three transfers started 22 and 23 fast cycles apart, and all three arrived.
The handshake source (hs_tx and hs_rx)
// hs_tx: the fast side of a four-phase handshake. A pulse starts a transfer when the side is free.
module hs_tx (
input logic clk_f,
input logic rst_n,
input logic pulse_f, // start a transfer (only when busy is 0)
input logic [7:0] data_f,
input logic ack, // from the clk domain
output logic req, // to the clk domain
output logic [7:0] data_x, // held still while req is 1
output logic busy
);
logic a1, a2; // ack, synchronized into clk_f
always_ff @(posedge clk_f or negedge rst_n)
if (!rst_n) begin a1 <= 1'b0; a2 <= 1'b0; end
else begin a1 <= ack; a2 <= a1; end
always_ff @(posedge clk_f or negedge rst_n)
if (!rst_n) begin req <= 1'b0; data_x <= 8'd0; end
else if (!busy && pulse_f) begin req <= 1'b1; data_x <= data_f; end // 1: raise req
else if (req && a2) req <= 1'b0; // 3: ack seen, drop req
assign busy = req | a2; // free again once ack has dropped (4)
endmodule
// hs_rx: the slow side.
module hs_rx (
input logic clk,
input logic rst_n,
input logic req,
input logic [7:0] data_x,
output logic ack,
output logic pulse_s, // 1 for one cycle, when data_s is new
output logic [7:0] data_s
);
logic r1, r2; // req, synchronized into clk
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) begin r1 <= 1'b0; r2 <= 1'b0; end
else begin r1 <= req; r2 <= r1; end
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) begin ack <= 1'b0; pulse_s <= 1'b0; data_s <= 8'd0; end
else begin
pulse_s <= r2 && !ack;
if (r2 && !ack) begin ack <= 1'b1; data_s <= data_x; end // 2: take the data, raise ack
else if (!r2 && ack) ack <= 1'b0; // 4: req dropped, drop ack
end
endmoduleThe data bits cross with no synchronizer of their own. They are safe because they never change while the receiver may read them: that is the data-plus-valid scheme of R6.2, with the timing enforced by ack.
Which one to use
| Measured on the lab clocks | Two flops on the pulse | Toggle | Handshake |
|---|---|---|---|
| Pulses arriving | 1 of 7 | 5 of 7 (two 6 ns apart cancel) | all 3 (the sender waits for busy) |
| Spacing the sender must keep | pulse 1.5 receiving periods wide | each level 1.5 receiving periods | none: it waits |
| Carries data | no | with data held still | yes, 8 bits here |
| Synthesis on this flow | 5 cells, 4 flops | 6 cells, 4 flops | 30 cells, 23 flops |
The toggle is the cheap choice when the sender can guarantee spacing; the handshake when it cannot, or when data travels with the event.
| Port | Direction | Width | Meaning |
|---|---|---|---|
| clk_f | input | 1 | the sender's clock |
| clk | input | 1 | the receiver's clock |
| rst_n | input | 1 | reset, asynchronous, active-low, both domains |
| pulse_f | input | 1 | a one-cycle pulse in the clk_f domain |
| pulse_s | output | 1 | one pulse_s of one clk cycle for every pulse_f |
- Clock
clk_f3 ns for the sender,clk10 ns for the receiver, unrelated. Crossing: one bit, from a flop, through a two-flop synchronizer- Corner cases
- a pulse between two clk edges; two pulses inside one clk period
The lab ships with the pulse crossing as a level. Run it: sent=7 seen=1. Make the two toggle changes from the diagram caption and run again: sent=7 seen=5. Then find the two pulses that cancel.
Asked in interviews
How do you send a one-cycle pulse to a slower clock?
Turn it into a level change in the sending domain, synchronize the level, and regenerate a pulse from every change on the receiving side. Pulses must still be spaced so each level lasts about 1.5 receiving periods.
Toggle or handshake?
A toggle is a flop, two synchronizer flops and an XOR, and it is open-loop: the sender must keep its spacing. A handshake closes the loop with req and ack, works for any clock ratio, and costs several cycles of each clock per event.
How do you pass data with a handshake?
The sender puts the data on the bus, holds it still and raises req. The receiver synchronizes req and only then reads the data, so the data bits never need synchronizers. The sender changes the data only after ack returns.
Try it
The sender's first five pulses are 33 ns apart; the last two are 6 ns apart. level shows 1 only at edges 14 and 20, where a print fell inside a pulse, and only one pulse was caught by an edge in time to reach pulse_s.
With the toggle changes, level holds each value until the next pulse and pulse_s fires at edges 6, 9, 12, 16 and 19. The last two pulses flip level twice between edges 20 and 21, so the last line reads sent=7 seen=5. Synthesize: 6 cells instead of 5, the same 4 flops.
Synthesize the design to measure its cells and inspect the mapped gates.
e0 sent=0 level=0 pulse_s=0 seen=0 e1 sent=0 level=0 pulse_s=0 seen=0 e2 sent=0 level=0 pulse_s=0 seen=0 e3 sent=0 level=0 pulse_s=0 seen=0 e4 sent=1 level=0 pulse_s=0 seen=0 e5 sent=1 level=0 pulse_s=0 seen=0 e6 sent=1 level=0 pulse_s=0 seen=0 e7 sent=1 level=0 pulse_s=0 seen=0 e8 sent=2 level=0 pulse_s=0 seen=0 e9 sent=2 level=0 pulse_s=0 seen=0 e10 sent=2 level=0 pulse_s=0 seen=0 e11 sent=3 level=0 pulse_s=0 seen=0 e12 sent=3 level=0 pulse_s=1 seen=1 e13 sent=3 level=0 pulse_s=0 seen=1 e14 sent=4 level=1 pulse_s=0 seen=1 e15 sent=4 level=0 pulse_s=0 seen=1 e16 sent=4 level=0 pulse_s=0 seen=1 e17 sent=4 level=0 pulse_s=0 seen=1 e18 sent=5 level=0 pulse_s=0 seen=1 e19 sent=5 level=0 pulse_s=0 seen=1 e20 sent=6 level=1 pulse_s=0 seen=1 e21 sent=7 level=0 pulse_s=0 seen=1 e22 sent=7 level=0 pulse_s=0 seen=1 e23 sent=7 level=0 pulse_s=0 seen=1 e24 sent=7 level=0 pulse_s=0 seen=1 e25 sent=7 level=0 pulse_s=0 seen=1 e26 sent=7 level=0 pulse_s=0 seen=1 e27 sent=7 level=0 pulse_s=0 seen=1 e28 sent=7 level=0 pulse_s=0 seen=1 e29 sent=7 level=0 pulse_s=0 seen=1 e30 sent=7 level=0 pulse_s=0 seen=1 e31 sent=7 level=0 pulse_s=0 seen=1 sent=7 seen=1Original design · 5 cells · 4 flip-flops · 0 latches · 1 logic levels. Synthesis uses Yosys; counts depend on the toolchain.
Read included file: tb.sv
`timescale 1ns/1ps
module tb;
logic clk = 0;
always #5 clk = ~clk; // the sampling clock: rising edges at 5, 15, 25, ... ns
logic clk_f = 0; // the second clock: rising edges at 1.75, 4.75, ... ns
initial begin
#1.75;
forever begin clk_f = 1; #1.5; clk_f = 0; #1.5; end
end
logic rst_n = 1; // falls at 1 ns: a real edge, so asynchronous resets act before any clock
int edge_n = -1; // the number of the last rising edge of clk
always @(posedge clk) edge_n = edge_n + 1;
logic pulse_f = 0, level, pulse_s;
int sent = 0, seen = 0;
task automatic pulse_after(input int gap); // gap idle edges of clk_f, then a one-cycle pulse
repeat (gap) @(posedge clk_f);
#0.5 pulse_f = 1; sent++;
@(posedge clk_f); #0.5 pulse_f = 0;
endtask
initial begin : sender // the fast domain's pulses
@(posedge rst_n);
pulse_after(10);
pulse_after(10);
pulse_after(10);
pulse_after(10);
pulse_after(10);
pulse_after(8);
pulse_after(1);
end
pulse_tx u_tx (.clk_f(clk_f), .rst_n(rst_n), .pulse_f(pulse_f), .level(level));
pulse_rx u_rx (.clk(clk), .rst_n(rst_n), .level(level), .pulse_s(pulse_s));
// Advance to the next rising edge of clk, wait for the design to settle, then print.
task automatic tick();
@(posedge clk); #1;
if (pulse_s) seen++;
$display("e%0d sent=%0d level=%b pulse_s=%b seen=%0d", edge_n, sent, level, pulse_s, seen);
endtask
initial begin
#1 rst_n = 0; tick(); tick(); rst_n = 1; // asserted at 1 ns, clk edges 0 and 1 in reset, released 1 ns after edge 1
repeat (30) tick();
$display("sent=%0d seen=%0d", sent, seen);
$finish(0); // quiet: no "$finish called" line
end
endmodule
Check yourself
Remember
- A pulse shorter than about 1.5 receiving periods can fall between edges and be lost; a synchronizer cannot help, because the pulse was never sampled.
- The toggle synchronizer flips a level per pulse, synchronizes the level, and makes one pulse per change with
s2 ^ s2_q; pulses must still be spaced. - A four-phase handshake (req up, ack up, req down, ack down) loses nothing at any clock ratio and carries held data, at several cycles of each clock per event.
- Data that crosses beside a synchronized flag needs no synchronizer of its own, as long as it never changes while it may be read.