fpga.loveHomeNewsIssuesVideosToolsWiki
Home / Issues / Issue 202606 / Technical Deep Dive
Technical Deep Dive

Clock Domain Crossing in Practice: Keeping the “Random” Out of Your Synchronizers

Editorial Board · Issue 202606 · FPGA in the AI Era

Clock Domain Crossing in Practice: Keeping the “Random” Out of Your Synchronizers

Fig. Clock Domain Crossing in Practice: Keeping the “Random” Out of Your Synchronizers · themed illustration

In FPGA design, failures caused by clock domain crossing (CDC) are often the most baffling: simulation is all green, the board fails intermittently, and swapping boards makes the bug disappear. There is one reason — data falls into a “metastable” state at the clock-domain boundary, and it is fundamentally random.

1. Know Your Enemy First: Metastability and MTBF

A flip-flop has a setup/hold window. If input data changes within this window, the flip-flop output sits in a middle state that is neither 0 nor 1 — that is metastability. Worse, it does not settle immediately: it oscillates and decays over time, and the value it finally settles on is unpredictable.

In engineering, MTBF (mean time between failures) measures how “safe” a synchronizer is: the larger the MTBF, the lower the probability that metastability propagates into downstream logic. MTBF depends strongly on clock frequency, data-toggle frequency, and device process — the same two-flop synchronizer may have an MTBF of decades at 100MHz but only milliseconds at 500MHz. This explains why “low-speed circuits are fine, but they fall apart at high speed.”

2. Single-Bit Signals: The Two-Flop Synchronizer

For single-bit control signals (enable, handshake, interrupt, etc.), the standard approach is a two-flop synchronizer: the first stage “catches” a potentially metastable signal and lets it decay; the second stage outputs a clean signal. Key points:

DFF 1 DFF 2 Metastability decays here in (async) out (synced) clk (destination domain)
Fig. 1: Two-flop synchronizer — the first stage tolerates metastability, the second outputs a clean signal (illustration)
  • The synchronizer must be entirely in the destination clock domain, with no combinational logic between the two flip-flops — otherwise catching metastability is pointless.
  • The input signal must remain stable for at least two clock cycles in the destination domain, or it may still be missed (add pulse stretching or handshaking).
  • A 3-stage synchronizer further reduces MTBF at the cost of added latency; two stages are usually enough, three for critical paths.

3. Multi-Bit Data: Handshake, Async FIFO, and Gray Code

Multi-bit buses cannot simply be synchronized — each bit has a different propagation delay, and after synchronization the bits may be “misaligned” (some bits captured at the new value, others still old), producing dirty data. Three standard solutions:

MethodPrincipleBest forCaveats
Handshake (req/ack)Source asserts req; destination acknowledges ack before data transferLow-frequency, occasional transfersLow throughput; req/ack each need synchronization
Async FIFORead/write pointers count in their own domain; only pointers cross domainsContinuous data streams (video, network)Pointers need Gray-code encoding
Gray codeAdjacent values differ by 1 bit; after sync, at most one cycle offCounter/pointer crossingOnly for consecutive counting scenarios

One sentence: only signals that remain safe after crossing may cross clock domains — either single bits (synchronizers) or Gray-coded pointers / handshake control (async FIFO).

4. Don’t Forget Reset Synchronization

An asynchronous reset used directly across clock domains may fall within a setup/hold window on deassertion, leaving some registers reset and others not (reset collapse). Standard practice: asynchronous assert, synchronous deassert:

// Asynchronous reset, synchronous release (Verilog example)
reg rst_n_sync1, rst_n_sync2;
always @(posedge clk or negedge rst_n) begin
  if (!rst_n) begin
    rst_n_sync1 <= 1'b0;
    rst_n_sync2 <= 1'b0;
  end else begin
    rst_n_sync1 <= 1'b1;
    rst_n_sync2 <= rst_n_sync1;
  end
end
assign rst_n_sync = rst_n_sync2;  // use in this clock domain

5. Verification: Kill CDC Problems in Simulation

  • CDC assertions: write SystemVerilog assertions (SVA) for handshake/enable protocols to check cross-domain timing in simulation in real time.
  • CDC analysis tools: combine commercial tools (e.g., CDC checks from EDA vendors) with open-source flows (e.g., Verilator-based static checks) to find unsynchronized paths.
  • Cross-domain stimulus: deliberately toggle data close to clock edges (random phase) in simulation to raise the odds of triggering metastability and expose problems early.
  • Code review checklist: anywhere an always @(posedge clk_a) reads a signal from the clk_b domain, you must be able to state its synchronization scheme.

6. Practical Checklist (Pin It to Your Desk)

  1. For every crossing signal, answer three questions: single-bit or multi-bit? Low-frequency or continuous stream? Can cycles be dropped?
  2. Single-bit → two-flop synchronizer, with no combinational logic between the flops.
  3. Continuous data stream → async FIFO + Gray-coded pointers.
  4. Occasional transfer → handshake protocol, req/ack each synchronized.
  5. Reset crossing → asynchronous assert, synchronous deassert.
  6. All synchronization paths get comments and assertions, and pass CDC tool checks.

Further reading: metastability MTBF models, async FIFO depth design, CDC check flows in Vivado/Quartus, and the open-source OSVVM methodology — watch this issue’s upcoming “Open Source Workshop” column.

← Previous article · FPGA Radar: June–September 2026 Highlights (Global + Domestic)Next article · A 100-Day Roadmap for FPGA Beginners →
← Back to this issue