Volume 05 Advanced 5 sub-modules ~60 min read

Clock Domain Crossing & Reset Synchronizers

Everything so far assumed one clock. Real chips have dozens. The moment a signal crosses between two clocks with no fixed phase relationship, setup and hold cannot be guaranteed - not because your design is slow, but because no design can guarantee them. CDC is the discipline of making that unavoidable failure so rare it never happens, and it is the single most common source of bugs that pass simulation and fail in the lab.

Why CDC bugs are uniquely nasty They are probabilistic. Your RTL simulation will never show them, because simulators have no notion of metastability - a flop samples cleanly every time. The bug appears once a week on one board in the lab, disappears when you add a probe, and comes back at a different temperature. If you learn one thing from this volume: CDC is verified structurally, not by simulation.

5.1 The physics of metastability & MTBF

Volume 3.1 established that a flip-flop has a setup/hold window in which data must not change. When the source is in a different clock domain, its transitions land wherever they like relative to your clock edge - so sooner or later, one lands inside that window. What happens then is not "the flop reads the wrong value". It is stranger than that.

Inside every flop is a pair of cross-coupled inverters - a bistable element with two stable points and, between them, an unstable equilibrium. Sample right at the window and the loop can be left balanced near that point, with its output sitting at neither a valid 0 nor a valid 1.

Energy well analogy for metastability alongside a waveform showing a flip-flop output hovering before resolving THE BISTABLE ELEMENT logic 0 stable logic 1 stable metastable It always falls eventually. Nothing bounds how long that takes. WHAT THE OUTPUT ACTUALLY DOES clk D changes inside the window Q resolves to 1… …or to 0. Unknowable. resolution time unbounded, but exponentially unlikely to be long
Figure 5.1 - Metastability is not a wrong value; it is no valid value yet. The output hovers near mid-rail, which downstream gates may read as 0 or as 1 - and two different gates reading the same wire can disagree.

The recovery is exponential: the imbalance grows like e^(t/τ), where τ is the regeneration time constant of the latch. So the probability of still being unresolved after time tr decays exponentially - which gives the central equation of the whole field.

Mean time between failures MTBF = e(tr / τ)  /  ( T0 × fclk × fdata )
Term Meaning Who controls it
tr Resolution time available before anything reads the output You - add synchronizer stages
τ Regeneration time constant of the flop The silicon process and cell library
T0 Metastability aperture - effective width of the bad window The silicon process
fclk Destination sampling frequency System architecture
fdata How often the asynchronous signal actually toggles The traffic pattern

Worked numbers - why the second flop matters so much

Take a plausible set of values: τ = 0.2 ns, T₀ = 1 ns, f_clk = 100 MHz (10 ns period), f_data = 10 MHz. The denominator is then 1e-9 × 1e8 × 1e7 = 10⁶.

Design tr available e(tr/τ) MTBF
No synchronizer
async straight into logic
≈ 2 ns e¹⁰ ≈ 2.2 × 10⁴ 22 ms
2-flop synchronizer ≈ 9.9 ns e⁴⁹·⁵ ≈ 3.1 × 10²¹ ≈ 98 million years
3-flop synchronizer ≈ 19.9 ns e⁹⁹·⁵ ≈ 10⁴³ ≈ 3 × 10²⁹ years
Read that table again - 22 ms to 98 million years One extra flip-flop. That is the entire argument, and it is why the two-flop synchronizer is universal. But notice where the leverage is: MTBF is exponential in tr and only linear in frequency. Doubling your clock does not halve MTBF - it halves tr too, and halving an exponent is catastrophic. That is why multi-GHz designs routinely need three stages.
A synchronizer does not eliminate metastability Say this precisely in an interview. The second flop can also go metastable - it is just overwhelmingly unlikely, because its input has had a full clock period to settle. CDC is risk management with an exponential lever, not a guarantee.

5.2 Single-bit control signal synchronization

A two flip-flop synchronizer with the first stage possibly metastable and the second stage clean SOURCE DOMAIN - clk_src DESTINATION DOMAIN - clk_dst SRC FF the crossing NO logic allowed here FF1 may be metastable 1 clk to settle FF2 clean safe clk_src clk_dst Rules that make this valid: 1. No combinational logic between SRC FF and FF1 - a glitch would be sampled as real. 2. FF1 and FF2 placed adjacent, and the signal fans out to exactly ONE synchronizer.
Figure 5.2 - FF1 absorbs the metastability; FF2 reads a value that has had a full period to settle. The two rules underneath are not style advice - violating either one silently destroys the MTBF you just calculated.

module sync_2ff #(
  parameter STAGES = 2,        // 3 for very high frequency domains
  parameter INIT   = 1'b0      // value held during reset
) (
  input  wire clk_dst,
  input  wire rst_n,
  input  wire din_async,       // from another clock domain
  output wire dout
);

  // ASYNC_REG tells the place-and-route tool to keep these flops adjacent,
  // which maximises the settling time actually available to FF1.
  (* ASYNC_REG = "TRUE" *) reg [STAGES-1:0] sync;

  always @(posedge clk_dst or negedge rst_n)
    if (!rst_n) sync <= {STAGES{INIT}};
    else        sync <= {sync[STAGES-2:0], din_async};

  assign dout = sync[STAGES-1];

endmodule

The direction problem: fast to slow

A two-flop synchronizer only works if the signal is stable long enough to be sampled. Going slow → fast, that is automatic. Going fast → slow, it is not: a one-cycle pulse in a 200 MHz domain is 5 ns wide, and a 25 MHz destination samples every 40 ns. The pulse can vanish entirely between two edges.

The rule For a level to be reliably captured, it must be stable for at least one and a half destination clock periods - in practice, design for two or more. If your source pulse cannot guarantee that, do not synchronize the pulse. Convert it into something that can survive: a toggle.

// Crossing a ONE-CYCLE PULSE from a fast domain into a slow one.
// Trick: a pulse cannot survive the crossing, but a LEVEL CHANGE can.
// So toggle a flop on each pulse, synchronize the level, and rebuild the
// pulse on the far side with an edge detector.

module pulse_sync (
  input  wire clk_src,
  input  wire rst_n_src,
  input  wire pulse_in,        // 1 cycle wide in clk_src

  input  wire clk_dst,
  input  wire rst_n_dst,
  output wire pulse_out        // 1 cycle wide in clk_dst
);

  // ---- source: pulse -> level change -----------------------------------
  reg toggle;
  always @(posedge clk_src or negedge rst_n_src)
    if (!rst_n_src)   toggle <= 1'b0;
    else if (pulse_in) toggle <= ~toggle;

  // ---- destination: 3 flops. Two to synchronize, one to remember the
  //      previous value so we can detect the edge. ------------------------
  (* ASYNC_REG = "TRUE" *) reg [2:0] sync;
  always @(posedge clk_dst or negedge rst_n_dst)
    if (!rst_n_dst) sync <= 3'b000;
    else            sync <= {sync[1:0], toggle};

  // Either edge of the toggle means "one pulse happened".
  assign pulse_out = sync[2] ^ sync[1];

endmodule
The toggle synchronizer's limit It transports one pulse at a time. If two source pulses arrive closer together than about two destination clock periods, the toggle flips twice before the destination looks - and both pulses are lost as one. For sustained rates you need a handshake (§5.4) or a FIFO. Always state this limitation when you offer this circuit as an answer.

5.3 Why 2-FF synchronizers fail on multi-bit buses

The obvious next step is wrong. Instantiate a synchronizer per bit of an 8-bit bus and you have not built a safe crossing - you have built eight independent random events. Each bit's metastability resolves on its own schedule, so bits that left the source together can arrive on different destination edges.

A four bit counter crossing clock domains where staggered bit arrival produces a value that never existed on the bus BUS GOES 0111 → 1000. EACH BIT IS SYNCHRONIZED SEPARATELY. destination clock edge samples here b3 resolves early b2 b1 b0 these three resolve one cycle later 0111 old value - valid 1000 new value - valid 1111
Figure 5.3 - b3 resolved a cycle before the rest, so the destination latches 1111 - a value the counter never produced. Every individual synchronizer worked perfectly. The system is still broken.
Also banned: reconvergence The same failure appears whenever two related signals are synchronized separately and then recombined. If start and mode are meant to be interpreted together, synchronizing them through two independent 2-FF chains lets them arrive on different cycles, and the destination briefly sees a combination that never existed. Cross one signal, or cross them as a protocol - never as parallel independent bits.

The four correct answers

Technique How it avoids the problem Use when
Gray code Only one bit changes per step, so a mistimed sample yields the old or the new value - never a third Counters and FIFO pointers only
Data + synchronized flag The bus is never synchronized at all - it is held stable while a single control bit crosses Occasional transfers, low rate
Handshake Explicit request/acknowledge means neither side moves until the other confirms Slow control buses, config registers
Asynchronous FIFO Gray-coded pointers plus dual-port memory Sustained high-throughput data. Volume 06
The unifying idea Every one of these works the same way: reduce the crossing to a single bit. Gray code makes only one bit change; the flag scheme crosses one bit and leaves the data alone; the handshake crosses one bit in each direction; the FIFO crosses Gray pointers. If you can say that sentence in an interview, you have understood §5.3.

5.4 Multi-bit handshake synchronizers

The handshake is the general-purpose answer for control and configuration buses. The data bus itself gets no synchronizer - instead the protocol guarantees the data is already stable and unchanging by the time the destination reads it.

Four phase handshake waveform showing request and acknowledge crossing between two clock domains FOUR-PHASE HANDSHAKE data HELD STABLE - never synchronized, never changes while req is high req ack 1 2 3 4 src asserts req with data valid dst sees req, grabs data, acks src sees ack, drops req dst sees req low, drops ack. Done. Cost: two full round trips through synchronizers - roughly 4 to 6 clocks each way. Correct, but slow.
Figure 5.4 - Each of req and ack is a single bit crossing through its own 2-FF synchronizer. The wide data bus rides along beneath them, unsynchronized and completely safe, because it provably does not move while req is asserted.

// ---------- SOURCE SIDE ------------------------------------------------
module cdc_handshake_src #(
  parameter W = 8
) (
  input  wire         clk_src,
  input  wire         rst_n,
  input  wire         send,          // request a transfer
  input  wire [W-1:0] din,
  input  wire         ack_sync,      // ack, already synced INTO clk_src
  output reg          busy,
  output reg          req,
  output reg  [W-1:0] data_held      // crosses UNSYNCHRONIZED, on purpose
);

  always @(posedge clk_src or negedge rst_n) begin
    if (!rst_n) begin
      req <= 1'b0; busy <= 1'b0; data_held <= {W{1'b0}};
    end else if (!busy && send) begin
      // Capture and FREEZE the data, then raise req. Because data_held
      // cannot change again until the whole handshake completes, the
      // destination can read it with no synchronizer at all.
      data_held <= din;
      req       <= 1'b1;             // phase 1
      busy      <= 1'b1;
    end else if (busy && req && ack_sync) begin
      req <= 1'b0;                   // phase 3: ack seen, drop req
    end else if (busy && !req && !ack_sync) begin
      busy <= 1'b0;                  // phase 4 complete, ready for the next
    end
  end

endmodule


// ---------- DESTINATION SIDE -------------------------------------------
module cdc_handshake_dst #(
  parameter W = 8
) (
  input  wire         clk_dst,
  input  wire         rst_n,
  input  wire         req_sync,      // req, already synced INTO clk_dst
  input  wire [W-1:0] data_held,     // stable by protocol, not by synchronizer
  output reg  [W-1:0] dout,
  output reg          valid,
  output reg          ack
);

  always @(posedge clk_dst or negedge rst_n) begin
    if (!rst_n) begin
      ack <= 1'b0; valid <= 1'b0; dout <= {W{1'b0}};
    end else begin
      valid <= 1'b0;                 // single-cycle pulse

      if (req_sync && !ack) begin
        // req has come through 2 flops, so data_held has been stable for
        // at least two destination clocks. Safe to sample directly.
        dout  <= data_held;
        valid <= 1'b1;
        ack   <= 1'b1;               // phase 2
      end else if (!req_sync && ack) begin
        ack <= 1'b0;                 // phase 4
      end
    end
  end

endmodule
Interview grilling - "Why is the data bus safe without a synchronizer? Isn't that exactly what you just told me never to do?"

This is the question that separates people who memorised the circuit from people who understand it. The answer is about timing, not about synchronizers.

"The rule is not 'never cross a bus unsynchronized' - it is 'never sample a signal that might be changing near the clock edge'. Here the data is written once, then frozen. By the time req has propagated through two destination flops, the data has been stable for at least two destination clock periods. There is no transition anywhere near the sampling edge, so there is nothing to go metastable about."

The follow-up: "What if the source changes the data early?" Then the scheme breaks, which is why busy exists - the source is structurally prevented from touching data_held until phase 4 completes. The protocol, not the wire, is what provides the safety.

And the one they really want: "What does this cost?" Two full round trips - about 4-6 clocks in each direction, so roughly 10-20 clocks per transfer. Fine for configuration registers. Useless for streaming data, which is what asynchronous FIFOs are for.

5.5 Reset domain crossing & reset trees

Volume 3.2 built the AASD reset synchronizer - asynchronous assert, synchronous de-assert - and noted that every clock domain needs its own. Here is the consequence nobody expects: once you have several reset signals, you have created a second, independent crossing problem. And unlike CDC, it can bite you inside a single clock domain.

Reset domain crossing where a flip-flop reset by one reset drives a flip-flop reset by a different reset in the same clock domain SAME CLOCK. DIFFERENT RESETS. STILL A CROSSING. RESET DOMAIN A FF A rst_a_n RESET DOMAIN B FF B rst_b_n reset domain crossing clk one shared clock When rst_a_n asserts but rst_b_n does not: FF A clears asynchronously - its output moves with no relationship to the clock edge. FF B is still running, and can sample that transition inside its setup/hold window. Result: metastability, with no clock domain crossing anywhere in sight.
Figure 5.5 - The classic blind spot. Engineers audit every clock crossing meticulously and never think to audit reset crossings, because "it is all one clock".
Fix How it works Trade-off
One reset domain Reset both flops from the same synchronized reset Simplest - do this unless you truly need independent resets
Qualify the crossing Gate the path with an enable that is held off while either reset is active Needs care to get the enable's own timing right
Synchronize the path Put a 2-FF synchronizer on the crossing signal, reset by domain B Adds latency; only valid for control, not wide data
Sequence the resets Assert and release resets in a defined order so no crossing is ever live System-level discipline, easy to break later

The reset tree

Reset is a clock-scale net Reset reaches essentially every flop in the design, which makes it the second highest fanout net after the clock - and it needs the same treatment: a balanced buffer tree, with its skew explicitly budgeted. An unbalanced reset tree is what lets one part of the chip leave reset a cycle before another, which is the split-brain start-up problem from Volume 3.2 showing up again at the physical level.

The CDC verification reality

You cannot simulate your way to CDC confidence Because RTL simulation models every flop as sampling perfectly, CDC bugs are invisible to it. Industry therefore uses static CDC analysis tools - Spyglass CDC, Questa CDC, Conformal - which structurally trace every path between clock domains and flag any crossing without a recognised synchronizer. A clean CDC report is a hard tape-out gate at every serious semiconductor company. Some flows add metastability injection in simulation, deliberately randomising synchronizer outputs to prove the design tolerates the delay.

# CDC paths must be excluded from normal timing analysis - the tool would
# otherwise try (and fail) to close timing between unrelated clocks.

# Tell STA the two clocks have no phase relationship at all.
set_clock_groups -asynchronous \
  -group [get_clocks clk_src] \
  -group [get_clocks clk_dst]

# Bound the crossing wire anyway: keep the source-flop-to-FF1 delay short so
# FF1 gets the maximum possible settling time. A bare false_path would let
# the router make this arbitrarily long.
set_max_delay -datapath_only \
  -from [get_cells src_ff_reg] \
  -to   [get_cells {sync_inst/sync_reg[0]}] 4.0
Do not just set_false_path and walk away A false path tells the tool to ignore the crossing completely, which means the router is free to give that wire a huge delay. Every nanosecond spent on the wire is a nanosecond stolen from FF1's settling budget - directly reducing the MTBF you calculated in §5.1. Use set_max_delay -datapath_only so the path is still bounded.

Volume 05 recap

Concept The one thing to remember
Metastability Not a wrong value - no valid value yet. Resolution time is unbounded.
MTBF Exponential in resolution time, linear in frequency. That asymmetry is the whole game.
2-FF synchronizer Turns ~22 ms into ~98 million years. Reduces risk; never eliminates it.
Synchronizer rules No logic before FF1. Flops placed adjacent. Fan out to exactly one synchronizer.
Fast → slow pulses A pulse can vanish. Convert to a toggle, synchronize, then edge-detect.
Multi-bit buses Per-bit synchronizers produce values that never existed. Never do it.
The unifying fix Reduce every crossing to a single bit - Gray, flag, handshake, or FIFO.
Handshake data bus Unsynchronized and safe, because the protocol freezes it.
Reset domain crossing Happens inside one clock domain. Audit resets, not just clocks.
Verification Simulation cannot see CDC bugs. Static CDC analysis is the sign-off gate.