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.

One-cycle pulses through a two-flop synchronizerEdges 3 to 13 of the lab bench, sampled on clk (10 ns). The sender's clock clk_f has a 3 ns period, so each pulse on level lasts 3 ns.
012345678910clksent0123levelpulse_sseen
  1. Cycle 1

    Edge 4: the first pulse has come and gone between two edges. No edge of clk ever saw level at 1.

  2. Cycle 5

    Edge 8: the second pulse is lost the same way.

  3. Cycle 9

    Edge 12: edge 11 fell inside the third pulse, so pulse_s fires 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, synchronize, detect the changeArrows show signals, left in clk_f, right in clk.
Flip, synchronize, detect the changeArrows show signals, left in clk_f, right in clk. clk_f pulse_f one clk_f cycle clk_f level flips once per pulse clk s1, s2 two-flop synchronizer clk s2_q last cycle's s2 clk pulse_s s2 ^ s2_q flipcrosses
  1. Flip

    A pulse flips level. The new level stays until the next pulse, so the slow clock has many edges to catch it.

  2. Synchronize

    The level crosses through two flops, like any slow bit.

  3. Regenerate

    In the cycle where s2 differs from last cycle's s2, pulse_s is 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.

The same pulses through the toggle synchronizerEdges 3 to 13 of the lab bench with the toggle design, sampled on clk.
012345678910clksent0123levelpulse_sseen0123
  1. Cycle 2

    Edge 5: the first pulse has flipped level to 1, and it stays there.

  2. Cycle 3

    Edge 6: s2 has the new level, and pulse_s fires once.

  3. Cycle 6

    Edge 9: the second pulse flipped level back 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.

Four phases, two synchronizersArrows show signals. req crosses into clk, ack crosses back into clk_f, each through two flops. data_x crosses with no synchronizer.
Four phases, two synchronizersArrows show signals. req crosses into clk, ack crosses back into clk_f, each through two flops. data_x crosses with no synchronizer. clk_f hs_tx clk_f; holds data_x clk r1, r2 req into clk clk hs_rx clk; takes data_x clk_f a1, a2 ack into clk_f reqr2acka2
  1. 1. req rises

    The sender raises req with the data on data_x, and holds both.

    Follow the calls, values and waits
    1. data rise: hs_tx → r1, r2
    2. data rise: r1, r2 → hs_rx
  2. 2. ack rises

    The receiver sees r2, takes data_x, which has been still for two clk edges, and raises ack.

    Follow the calls, values and waits
    1. data rise: hs_rx → a1, a2
    2. data rise: a1, a2 → hs_tx
  3. 3. req falls

    The sender sees a2 and drops req.

    Follow the calls, values and waits
    1. data fall: hs_tx → r1, r2
    2. data fall: r1, r2 → hs_rx
  4. 4. ack falls

    The receiver drops ack. When a2 falls, the sender is free for the next transfer.

    Follow the calls, values and waits
    1. data fall: hs_rx → a1, a2
    2. 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.

One transfer, sampled on clkThe handshake below on a bench like the lab's: clk 10 ns, clk_f 3 ns, the sender sends 5a, 3c and 7e, each when busy is 0.
01234567clkreqr2ackpulse_sdata_s005abusy
  1. Cycle 1

    Edge 5: req has risen in clk_f.

  2. Cycle 3

    Edge 7: r2 was 1 at this edge, so the receiver took 5a and raised ack.

  3. Cycle 4

    Edge 8: the sender saw ack through a1 and a2, and dropped req.

  4. Cycle 6

    Edge 10: ack is down. busy falls in clk_f at 109.75 ns, after a2 drops; the next req shows 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)
Synthesis on this flow, both sides joined: 30 cells, 23 flops, 3 levels of logic.
// 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
endmodule

The 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 clocksTwo flops on the pulseToggleHandshake
Pulses arriving1 of 75 of 7 (two 6 ns apart cancel)all 3 (the sender waits for busy)
Spacing the sender must keeppulse 1.5 receiving periods wideeach level 1.5 receiving periodsnone: it waits
Carries datanowith data held stillyes, 8 bits here
Synthesis on this flow5 cells, 4 flops6 cells, 4 flops30 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.

Design brief
PortDirectionWidthMeaning
clk_finput1the sender's clock
clkinput1the receiver's clock
rst_ninput1reset, asynchronous, active-low, both domains
pulse_finput1a one-cycle pulse in the clk_f domain
pulse_soutput1one pulse_s of one clk cycle for every pulse_f
Clock
clk_f 3 ns for the sender, clk 10 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
  1. 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.

  2. 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.

  3. 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.

RTLIcarus Verilog · in your browser · Lint: Verilator service · Synthesize: Yosys service
Edit the design, then Run, Lint or Synthesize
Expected: 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=1

Original 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

Predict the outputThe toggle design runs on the lab clocks. The sender sends five one-cycle pulses, 33 ns apart, and the bench prints how many it sent and how many arrived. What does it print?
// One-cycle pulses cross from a fast domain (clk_f) to a slow one (clk).
// pulse_tx: the fast side. What crosses comes straight from a flop.
module pulse_tx (
  input  logic clk_f,
  input  logic rst_n,
  input  logic pulse_f,          // a one-cycle pulse in the clk_f domain
  output logic level             // the wire that crosses to the clk domain
);
  always_ff @(posedge clk_f or negedge rst_n)
    if (!rst_n) level <= 1'b0;
    else        level <= level ^ pulse_f;   // flip once per pulse
endmodule

// pulse_rx: the slow side. A two-flop synchronizer, then an edge detector.
module pulse_rx (
  input  logic clk,
  input  logic rst_n,
  input  logic level,
  output logic pulse_s           // a one-cycle pulse in the clk domain
);
  logic s1, s2, s2_q;
  always_ff @(posedge clk or negedge rst_n)
    if (!rst_n) begin s1 <= 1'b0; s2 <= 1'b0; s2_q <= 1'b0; end
    else        begin s1 <= level; s2 <= s1; s2_q <= s2; end
  assign pulse_s = s2 ^ s2_q;    // 1 for one cycle when s2 changes
endmodule

// pulse_cdc: both sides joined, the top for synthesis.
module pulse_cdc (
  input  logic clk_f,
  input  logic clk,
  input  logic rst_n,
  input  logic pulse_f,
  output logic pulse_s
);
  logic level;
  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));
endmodule
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);
  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++;
  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 (26) tick();
    $display("sent=%0d seen=%0d", sent, seen);
    $finish(0);                             // quiet: no "$finish called" line
  end
endmodule
Spot the bugEach option makes pulse_s in the receiver of a toggle synchronizer, from the synchronized toggle s2 and its copy s2_q. The bench flips the toggle five times, far apart, and expects five pulses. Which option fails?
Read the waveformThis is the lab bench with the toggle design, edges 19 to 23, sampled on clk. Why did the last two pulses never arrive?
Two pulses 6 ns apart
01234clksent567levelpulse_s

One value per edge, printed 1 ns after it, from edge 19 on the left. Edge 20 is at 205 ns and edge 21 at 215 ns.

ConceptA one-cycle pulse from a 300 MHz domain goes to a 100 MHz domain through a two-flop synchronizer. What happens?
ConceptWhat does a four-phase handshake give you that a toggle synchronizer does not, and what does it cost?

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.