← RTL Design Interviews

Interview Question · Code Reading

The Toggle Pulse Synchronizer and Its Rate Limit

L1 JuniorL2 Mid-Levelcdcpulsessynchronizers

The Question

The interviewer asks

What does this circuit do, and what happens if two source pulses come in back-to-back source cycles?

systemverilog
module pulse_sync (input logic clk_s, clk_d, rst_n, pulse_s, output logic pulse_d);
  logic tog, d1, d2, d3;
  always_ff @(posedge clk_s or negedge rst_n)        // each source pulse flips a level
    if (!rst_n) tog <= 0; else if (pulse_s) tog <= ~tog;
  always_ff @(posedge clk_d or negedge rst_n)        // two synchronizer flops, then one for the edge
    if (!rst_n) {d3, d2, d1} <= 0; else {d3, d2, d1} <= {d2, d1, tog};
  assign pulse_d = d2 ^ d3;                          // one destination pulse per change
endmodule
Reveal the Model Answer

Each one-cycle source pulse flips tog, a level that stays put until the next pulse. Two destination flops synchronize the level, and the XOR of the last two flops gives one destination pulse per change. Back-to-back pulses flip tog twice before the destination samples it. Then the destination sees no change and both pulses vanish. Pulses must be spaced by a few destination periods.

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.

A 4 ns source and a 10 ns destination; three pulses ten source cycles apart.

RUNIcarus Verilog · in your browser
Edit the code, then run it
Expected: sent=3 seen=3 PASS: every pulse, spaced well apart, arrived once

Self-Check

Tick each key point you covered in your answer.

What Interviewers Listen For (Junior)

A good junior answer reads the toggle flop, the two synchronizer flops and the XOR. It explains why a level survives the crossing when a one-cycle pulse would not.

Cross-Questions

Where the interviewer goes next. Open a node to practise that follow-up.

WhyWhat IfProve ItBreak ItCompare

  1. The Toggle Pulse Synchronizer and Its Rate Limit
  2. WhyA Fast Pulse into a Slow Clock DomainL1 JuniorL2 Mid-Level

    Asked if they suggest synchronizing the pulse directly

    What if the source clock is faster than the destination clock and the signal is a one-cycle pulse?

    log
    Illustrative event excerpt (ns)
     0: destination rising edge; source pulse=0
     4: source pulse rises
     8: source pulse falls
    10: destination rising edge; source pulse=0
    Reveal the Model Answer

    The pulse can rise and fall between two destination edges and never be sampled. One fix is to hold the signal for at least about 1.5 destination periods. Another is to turn the pulse into a toggle, synchronize the level, and turn each change back into a pulse. For events that must not be lost or merged, use a handshake.

    One-Cycle Pulses Through a Two-Flop SynchronizerEdges 3 to 13 of a simulator bench, sampled on clk (10 ns). The sender's clock 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.

    Samples of level taken 1 ns after each edge never catch a pulse here. The run ends with sent=7 seen=1.

    Self-Check

    Tick each key point you covered in your answer.

    Missed Corner

    "A two-flop synchronizer carries any single-bit signal."

    It carries a level that stays stable long enough. A pulse shorter than a destination period can be missed completely, as the twin shows.

    Run It

    A one-cycle pulse from a 250 MHz domain into a 100 MHz domain, through two flops.

    RUNIcarus Verilog · in your browser
    Edit the code, then run it
    Expected: source sent 1 pulse, destination counted 0 FAIL: the pulse fell between two destination edges and was lost
    See the Fix
    module tb;
      logic sclk = 0, dclk = 0, tog = 0, s1 = 0, s2 = 0, s3 = 0;
      int sent = 0, seen = 0;
      always #2 sclk = ~sclk;                  // fast source: edges at 2, 6, 10, ...
      always #5 dclk = ~dclk;                  // slow destination: edges at 5, 15, 25, ...
      // The fix: turn the pulse into a level change, synchronize the level, and
      // turn each change back into one destination pulse (s2 ^ s3).
      always @(posedge dclk) begin s1 <= tog; s2 <= s1; s3 <= s2; end
      always @(posedge dclk) if (s2 ^ s3) seen++;
      initial begin
        @(posedge sclk); @(posedge sclk); tog <= ~tog; sent++;
        #60;
        $display("source sent %0d pulse, destination counted %0d", sent, seen);
        if (seen != sent) $display("FAIL: the pulse fell between two destination edges and was lost");
        else $display("PASS: the level change crossed and became one pulse");
        $finish;
      end
    endmodule
    

    Prints: source sent 1 pulse, destination counted 1 PASS: the level change crossed and became one pulse

  3. Prove ItSolve R6.3.1: One Event, One ByteSimulator-graded

    Show it, don't just say it: this Silicon Lab problem is graded by the simulator.

    Open Problem R6.3.1

  4. CompareOpen-Loop Toggle vs a Closed-Loop Handshake for EventsL2 Mid-Level

    Compare the toggle synchronizer with a handshake for carrying events.

    Reveal the Model Answer

    The toggle is open loop. The source never learns whether the event arrived, so a design rule must keep events far enough apart. A handshake is closed loop. The source waits for the acknowledge, so it cannot send too fast, but each event costs a round trip. Choose the toggle when the spacing is guaranteed, and the handshake when it is not.

    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.

    Self-Check

    Tick each key point you covered in your answer.

    Missed Corner

    "Open loop is fine because the source is slow on average."

    Average rate does not matter; the two closest events do. One burst is enough to lose pulses.

    Source Measured: the twin on design.cdc.pulse-synchronizer, Icarus Verilog 12

  5. Prove ItSolve R6.3.1: One Event, One ByteSimulator-graded

    Show it, don't just say it: this Silicon Lab problem is graded by the simulator.

    Open Problem R6.3.1

  6. Break ItTaking the Pulse from the First Synchronizer FlopL2 Mid-Level

    Break it: a reviewer sees assign pulse_d = d1 ^ d2;. What is wrong?

    systemverilog
    always_ff @(posedge clk_d) {d2, d1} <= {d1, tog};
    assign pulse_d = d1 ^ d2;   // uses the first flop
    Reveal the Model Answer

    d1 is the flop that samples the asynchronous toggle, so it is the one that can go metastable. Feeding it to the XOR lets an unsettled value reach logic. Take the edge from two flops after the synchronizer, such as d2 ^ d3.

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "It saves a cycle of latency, and the XOR is only one gate."

    Any logic on the first flop's output removes the settling time the second flop provides. One gate is enough to spread a metastable value.

    Source Mehta, ASIC/SoC Functional Design Verification (2018) ch. 8.3.1 Two-flop synchronizer (no logic between the flops)

  7. CompareBreaking a Two-Flop Synchronizer Without Touching Its FlopsL2 Mid-Level

    How can a two-flop synchronizer fail even though its two flops are exactly right?

    Reveal the Model Answer

    Combinational logic in front of the first flop can glitch, and the destination may catch the glitch. Synchronizing one signal in two places lets the two copies resolve differently. Signals synchronized separately and then combined again lose any timing relationship between them. Logic between the two flops eats the settling time.

    One Synchronizer per BitArrows show the bit wires from the sender's flops to the receiver. Each synchronizer decides alone in which clk cycle its bit changes.
    One synchronizer per bitArrows show the bit wires from the sender's flops to the receiver. Each synchronizer decides alone in which clk cycle its bit changes. clk_a count_tx 0111 to 1000 at112.75 ns clk bit 0 sync arrives 113.15 ns clk bit 1 sync arrives 113.95 ns clk bit 3 sync arrives 114.75 ns clk bit 2 sync arrives 115.55 ns clk count_q reads 1100 at edge12 0.4 ns1.2 ns2.0 ns2.8 ns
    1. Three bits make edge 11

      Edge 11 of clk is at 115 ns. Bits 0, 1 and 3 have their new values by then.

    2. Bit 2 misses it

      Bit 2 arrives at 115.55 ns, after the edge, so its synchronizer still holds the old 1.

    3. A torn value

      The receiver sees new 1, 0, 0 for bits 3, 1, 0 and old 1 for bit 2: 1100. The count never held 12 here.

    Read the diagram as text
    • count_tx: 0111 to 1000 at 112.75 ns
    • bit 0 sync: arrives 113.15 ns
    • bit 1 sync: arrives 113.95 ns
    • bit 3 sync: arrives 114.75 ns
    • bit 2 sync: arrives 115.55 ns
    • count_q: reads 1100 at edge 12
    • count_tx → bit 0 sync: 0.4 ns
    • count_tx → bit 1 sync: 1.2 ns
    • count_tx → bit 3 sync: 2.0 ns
    • count_tx → bit 2 sync: 2.8 ns
    • bit 0 sync → count_q
    • bit 1 sync → count_q
    • bit 3 sync → count_q
    • bit 2 sync → count_q

    Times from a simulator bench. With metastability added, this can happen even with perfectly matched wires, because each first flop settles on its own.

    Self-Check

    Tick each key point you covered in your answer.

    Missed Corner

    "Synchronize the signal once per module that needs it."

    Two synchronizers on one signal can resolve on different cycles. So the two modules disagree for a cycle.

    Source Golson, Synchronization and Metastability (SNUG 2014) §2.3 (Figure 9); Mehta, ASIC/SoC Functional Design Verification ch. 8.5 (PDF p186, re-convergence)

  8. What IfHow Close Can Two Events BeL2 Mid-LevelL3 Senior

    What if the spec allows events every few source cycles? How far apart must they be for the toggle synchronizer?

    Reveal the Model Answer

    Each toggle level must last long enough for the destination to sample it reliably. That is about 1.5 destination periods, so three destination edges see it. Add the synchronizer depth and convert to source cycles at the slowest destination clock. Check the rule with an assertion on the source side.

    Self-Check

    Tick each key point you covered in your answer.

    Spec Misquote

    "One destination period apart is enough."

    A level that lasts exactly one period can fall just between two edges after skew. The rule asks for about 1.5 periods.

    Source Cummings, Clock Domain Crossing (CDC) Design & Verification Techniques Using SystemVerilog (SNUG Boston 2008) §4.1.1

What People Get Wrong

Common wrong answers and why they fail. Run It shows the wrong belief failing on a real simulator.

Missed Corner

"A toggle synchronizer never loses a pulse."

It never loses an isolated pulse. Two pulses inside one destination period flip the level back before it is sampled. The twin sends five and sees three.

Run It

The same synchronizer, with two pulses in back-to-back source cycles first.

RUNIcarus Verilog · in your browser
Edit the code, then run it
Expected: sent=5 seen=3 FAIL: two pulses one source cycle apart flipped the toggle twice between destination edges, so both vanished
See the Fix
module pulse_sync (input logic clk_s, clk_d, rst_n, pulse_s, output logic pulse_d);
  logic tog, d1, d2, d3;
  always_ff @(posedge clk_s or negedge rst_n)        // each source pulse flips a level
    if (!rst_n) tog <= 0; else if (pulse_s) tog <= ~tog;
  always_ff @(posedge clk_d or negedge rst_n)        // two synchronizer flops, then one for the edge
    if (!rst_n) {d3, d2, d1} <= 0; else {d3, d2, d1} <= {d2, d1, tog};
  assign pulse_d = d2 ^ d3;                          // one destination pulse per change
endmodule

module tb;
  logic clk_s = 0, clk_d = 0, rst_n = 0, pulse_s = 0, pulse_d;
  int sent = 0, seen = 0;
  always #2 clk_s = ~clk_s;                          // 4 ns source clock
  always #5 clk_d = ~clk_d;                          // 10 ns destination clock
  pulse_sync u (.*);
  always @(posedge clk_s) if (pulse_s) sent++;
  always @(posedge clk_d) if (pulse_d) seen++;
  task automatic pulse(input int cycles);            // high for this many source cycles
    pulse_s = 1; repeat (cycles) @(negedge clk_s); pulse_s = 0;
  endtask
  initial begin
    #11 rst_n = 1;                                   // both clocks have reset the flops
    repeat (4) @(negedge clk_s);                     // t = 24

    repeat (6) @(negedge clk_s); pulse(1);           // then pulses ten source cycles apart
    repeat (9) @(negedge clk_s); pulse(1);
    repeat (9) @(negedge clk_s); pulse(1);
    repeat (20) @(negedge clk_s);
    $display("sent=%0d seen=%0d", sent, seen);
    if (seen == sent) $display("PASS: every pulse, spaced well apart, arrived once");
    $finish;
  end
endmodule

Prints: PASS: every pulse, spaced well apart, arrived once

Misconception

"Just synchronize the pulse itself through two flops."

A pulse shorter than a destination period can fall between two edges and never be sampled. The toggle turns it into a level that waits.

Source Mehta, ASIC/SoC Functional Design Verification (2018) ch. 8.3.3 Synchronizing fast-clock into slow-clock domains

Learn It, Then Prove It