Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • RTL Foundations36
  • Combinational Design25
  • Sequential Design27
  • FSM & Control27
  • Timing & Clocking39
  • Reset & CDC34
    • Reset Architecture
    • Metastability and Synchronisation
    • Asynchronous FIFO
    • CDC Protocols
      • Choose a Protocol by Traffic Shape
      • 4-Phase Request/Acknowledge
      • Pulse Synchronisers and Event Toggles
      • Mux Recirculation for Multi-Bit Capture
      • CDC Handshake Checklist
    • Reset Domain Crossing
    • CDC Verification
  • Memory & Buses57
  • Advanced RTL28
  • Senior & Interview76
  • Glossary and Notation
Loading lesson

‹CDC Protocols
  1. Topics
  2. RTL Design
  3. Reset & CDC
  4. CDC Protocols
  5. Pulse Synchronisers and Event Toggles

Part 6 · Reset & CDC · Intermediate

Pulse Synchronisers and Event Toggles

Send a one-cycle pulse across clocks with a toggle, a synchroniser and an XOR.

On this page

    Why this matters

    A one-cycle pulse in a fast domain can be shorter than a destination period, so a plain synchroniser never sees it. In the lesson's run, pulses 0.97 destination periods apart lost 352 of 5,000.

    After this lesson you can: draw the toggle synchroniser, explain how it turns an event into a level change, and state the pulse-spacing limit and how to enforce it.

    A pulse is too short to cross directly

    A one-cycle pulse in a fast domain can be shorter than a destination period. A plain synchroniser may never see it.

    The fix is a source flop that toggles on every pulse. The level persists, a 2FF synchronises it, and the destination XORs the output with its previous value.

    Part

    Job

    toggle_s flop (source)

    Flips on every source pulse

    Two meta_ff stages (destination)

    Synchronise the level

    q2_prev flop

    Remembers the previous synchronised value

    pulse_d = q2 ^ q2_prev

    High for exactly one destination cycle after every change

    The toggle pulse synchroniser: the source flop toggles on each pulse, two destination flops carry the level across, and an XOR with the previous value makes a one-cycle pulse

    Read the circuit left to right. The only signal that crosses between clocks is toggle_s, a level, so an ordinary two-flop synchroniser can carry it.

    • The XOR in front of toggle_s flips it on every pulse_s, so the event becomes a level change that lasts.

    • q1 and q2 are the synchroniser. q2_prev holds q2 from one destination cycle earlier.

    • The XOR at the output compares q2 with q2_prev, so it is high only in the cycle right after a flip.

    Read the waveform with one slot per source cycle. Because clk_d is sampled in the same slots, it alternates every slot, so one clk_d period is two slots.

    • In slot 0 pulse_s is high for a single source cycle, shorter than one clk_d period. toggle_s goes high in slot 1 and stays high.

    • q2 rises in slot 5, the second clk_d edge after toggle_s changed (q1 rises one edge earlier, in slot 3). q2_prev follows two slots later, so pulse_d is high in slots 5 and 6 only: one clk_d period.

    • The second pulse_s in slot 4 is exactly two destination periods after the first. It flips toggle_s back to 0, and pulse_d fires again in slots 9 and 10.

    • pulse_d appears on both the rising and the falling change of q2, which is why the circuit counts flips and not levels.

    Pulse spacing limit

    Each flip must be seen separately in the destination domain. Two flips inside one destination period can merge and lose a pulse.

    The first test starts pulses at least 5 source cycles (50 ns, about 7 destination periods) apart and receives all 5,000. A second run with a faster source (4.9 ns) and a slower destination (10.13 ns) varies the gap:

    Pulse starts

    Gap

    Spacing / destination period

    Pulses sent

    Pulses received

    Pulse starts every 2 cycles

    9.8 ns

    0.97

    5,000

    4,648 (352 lost)

    Every 3 cycles

    14.7 ns

    1.45

    5,000

    5,000

    Every 4 cycles

    19.6 ns

    1.93

    5,000

    5,000

    Every 5 or more cycles

    24.5 ns or more

    2.4 or more

    5,000

    5,000

    At least two destination periods is an open-loop engineering heuristic, not a delivery guarantee: arbitrary late resolution can defeat a fixed spacing bound. If each event must be conserved, use an acknowledged transfer, a sized event counter with wrap/observation bounds, or a FIFO with backpressure.

    Warning

    Reset is a link protocol: reset both ends together, suppress source pulses, and hold reset for at least three clean destination edges after toggle_s is zero (and at least one source edge). Release each reset synchronously in its domain before sending again. The behavioural meta_ff stages below are not resettable, so this hold interval flushes their history; pulse_d is masked during destination reset. An independent reset or a stopped destination clock requires a recovery handshake/epoch protocol instead of this open-loop toggle. A synthesis wrapper may reset all destination history flops consistently, but still needs coordinated recovery.

    Common pitfall

    If a source can generate pulses faster than the destination can see them, no structure helps; the information is lost. You must rate-limit the source, accumulate events in a counter (crossed with Gray code), or use a FIFO.

    Tip

    The destination output pulse is delayed by 2 to 3 destination cycles. If the destination needs to know the order of an event relative to data crossing separately, send the data first, then the pulse, and give the data enough stable time (see the next lesson).

    Interview

    Draw the toggle synchroniser and explain why a plain 2FF would miss a short pulse. Then say what the pulse spacing limit is and how you would enforce it.

    Check yourself Why is pulse_d exactly one destination cycle wide?

    Because q2 changes at one destination edge and q2_prev copies it one edge later. The XOR is high only between those two edges.

    Testbench

    The toggle synchroniser with a testbench that sends 5,000 spaced pulses.

    meta_ff.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    `timescale 1ns/1ps
    
    module meta_ff #(parameter real TCQ = 0.25, TSU = 0.3, THD = 0.15) (input logic clk, d, output logic q);
      realtime t_d = -100.0, t_clk = -100.0;
      initial q = 1'b0;
      always @(d) begin
        t_d = $realtime;
        if ($realtime - t_clk < THD) q <= #TCQ $urandom_range(0, 1);
      end
      always @(posedge clk) begin
        t_clk = $realtime;
        if ($realtime - t_d < TSU) q <= #TCQ $urandom_range(0, 1);
        else                       q <= #TCQ d;
      end
    endmodule
    
    // Pulse crossing by toggling: every source pulse flips a level, which crosses like any single-bit level;
    // the destination turns each flip back into a one-cycle pulse.
    module pulse_sync (
      input  logic clk_s, rst_s_n, pulse_s,
      input  logic clk_d, rst_d_n,
      output logic pulse_d
    );
      logic toggle_s, q1, q2, q2_prev;
      always_ff @(posedge clk_s or negedge rst_s_n)
        if (!rst_s_n)      toggle_s <= 1'b0;
        else if (pulse_s)  toggle_s <= ~toggle_s;                // one flip per pulse
      meta_ff f1 (.clk(clk_d), .d(toggle_s), .q(q1));
      meta_ff f2 (.clk(clk_d), .d(q1),       .q(q2));
      always_ff @(posedge clk_d or negedge rst_d_n)
        if (!rst_d_n) q2_prev <= 1'b0; else q2_prev <= q2;
      assign pulse_d = rst_d_n && (q2 ^ q2_prev);                 // ignore history while the link is reset/flushed
    endmodule
    

    Remember

    • Only the toggle level crosses. The XOR of q2 and q2_prev makes a one-destination-period pulse on every flip.

    • Keep pulse starts at least two destination periods apart. Closer flips can merge and a pulse is lost.

    • If each event must be conserved, use an acknowledged transfer, a sized counter crossed in Gray code, or a FIFO.

    Rule of thumb: if the source can pulse faster than the destination can see, no structure helps; rate-limit the source.

    How this was checked

    The spacing counts (source 4.9 ns, destination 10.13 ns) come from one digital-model run. Two destination periods is an open-loop engineering heuristic under bounded clock, jitter and sampling assumptions, not a deterministic physical delivery guarantee.

    Keep going

    • Practice: Pulse synchroniser: stretching pulses across clock domains

    • Practice: Missing pulse in a CDC from a narrow pulse

    • Code lab: CDC pulse stretch

    • Interview: run a mock interview on this topic

    Related topics

    • Mux recirculation

    Previous

    174 / 350

    Next: Mux Recirculation for Multi-Bit Capture

    Up next

    Up nextMux Recirculation for Multi-Bit Capture

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Choose a Protocol by Traffic Shape
    • CDC Handshake Checklist
    pulse_stoggle_sclk_dq2q2_prevpulse_d
    Simulation output: two one-cycle pulses 40 ns apart each flip toggle_s, and each produces one clk_d-wide pulse_d. One slot is one clk_s cycle (10 ns); clk_d has a 20 ns period. The meta_ff pair is modelled with plain flops, and q1 is not drawn.