Clock-Domain Crossing & Metastability
Modern SoCs have dozens of unrelated clocks. Learn what metastability is, why it cannot be eliminated, the MTBF equation, two-flop synchronizers, pulse and handshake synchronizers, Gray-coded asynchronous FIFOs, reset synchronizers, reconvergence problems and how CDC checking tools find bugs that simulation misses.
When a signal from one clock domain is sampled by a flip-flop clocked by an unrelated clock, the data can change inside the flop's setup/hold window. The flop may then enter a metastable state — an output hovering between 0 and 1 — for an unpredictable time before settling.
You cannot prevent metastability; you can only make its failure probability astronomically small by giving the flop time to resolve before anyone uses its output. CDC bugs are rare, intermittent and pass most simulations, which makes them some of the most expensive silicon bugs.
An analogy
Metastability is a ball balanced on a hilltop between two valleys (0 and 1). It will roll into one of them — the question is how long it wobbles. A synchronizer simply waits an extra clock cycle before looking.
Why crossing clocks is dangerous
Within one clock domain, static timing analysis guarantees setup and hold. Between unrelated clocks there is no fixed phase relation, so sooner or later data changes exactly at a sampling edge. The capturing flop goes metastable, and different downstream flops may read its output differently — corrupting state.
Synchronizing single bits
Choose the scheme by what is crossing:
- Slow level signal → 2 (or 3) flop synchronizer
- Short pulse from a fast domain → toggle synchronizer (convert pulse to level, sync, edge-detect)
- Control with acknowledgement → req/ack handshake
- Reset → asynchronous assert, synchronous de-assert (reset synchronizer)
Multi-bit data
Never synchronize each bit of a bus separately: bits resolve on different cycles and the receiver sees a mixture (reconvergence). Instead synchronize a single 'valid' control bit and hold the data stable (MUX recirculation), use Gray-coded counters, or use an asynchronous FIFO whose read and write pointers are exchanged in Gray code.
Verification and sign-off
Simulation almost never shows metastability because models are ideal. Structural CDC tools (Spyglass CDC, Questa CDC, Meridian) find every crossing, check that it is synchronized with an accepted scheme, and flag reconvergence and glitchy combinational logic before synchronizers. STA marks crossings as false paths or max-delay constrained (set_max_delay -datapath_only) for FIFO pointers. Reset-domain crossing (RDC) checks are the newer sibling.
Formulas
- Synchronizer MTBF:
MTBF = e^(t_r/τ) / (T₀ · f_clk · f_data) - Resolution time (2-flop):
t_r ≈ T_clk − t_setup − t_cq (per extra stage add ≈ T_clk) - Binary → Gray:
g = b ⊕ (b >> 1) - Gray → Binary:
b[n−1] = g[n−1]; b[i] = b[i+1] ⊕ g[i] - Async FIFO depth (burst):
depth ≥ B − B·(f_rd/f_wr) (burst length B)
Worked example: Gray code
Given: b = 1011
Find: Gray code.
- b >> 1 = 0101
- 1011 ⊕ 0101
Answer: g = 1110
Worked example: MTBF
Given: τ = 20 ps, T₀ = 0.1 ns, 500 MHz, 50 MHz data, t_r = 2.3 ns
Find: MTBF.
- e^(2.3n/20p) = e^115 ≈ 8.8e49
- Divide by 0.1e-9 × 500e6 × 50e6 = 2.5e6
Answer: ≈ 3.5e43 s
Worked example: Async FIFO depth
Given: Write 100 MHz bursts of 80 words, read 80 MHz continuously
Find: Minimum depth.
- Burst time = 80 / 100 MHz = 0.8 µs
- Read in that time = 0.8 µs × 80 MHz = 64
- Backlog = 80 − 64
Answer: ≥ 16 words (round up to a power of two: 16 or 32 with margin)
Two-flop synchronizer and Gray-code converters
// Two-flop synchronizer for a single-bit level signal
module sync_2ff (
input wire clk_dst,
input wire rst_n,
input wire d_async, // comes from another clock domain
output wire q_sync
);
(* ASYNC_REG = "TRUE" *) reg meta, sync;
always @(posedge clk_dst or negedge rst_n)
if (!rst_n) {sync, meta} <= 2'b00;
else {sync, meta} <= {meta, d_async};
assign q_sync = sync;
endmodule
// Binary <-> Gray conversion used for multi-bit FIFO pointers
module bin2gray #(parameter W = 4) (input wire [W-1:0] bin, output wire [W-1:0] gray);
assign gray = bin ^ (bin >> 1);
endmodule
module gray2bin #(parameter W = 4) (input wire [W-1:0] gray, output reg [W-1:0] bin);
integer i;
always @* begin
bin[W-1] = gray[W-1];
for (i = W - 2; i >= 0; i = i - 1)
bin[i] = bin[i+1] ^ gray[i];
end
endmoduleThe ASYNC_REG attribute tells FPGA tools to place the two flops next to each other and not to optimise them away. Gray code changes exactly one bit per increment, so a pointer sampled mid-change is either the old or the new value — never garbage. All three modules elaborate cleanly in Yosys.
MTBF of one vs two synchronizer flops
import math
tau, T0 = 20e-12, 0.1e-9 # flip-flop resolution constant, metastability window
f_clk, f_data = 500e6, 50e6 # destination clock, async data toggle rate
def mtbf(resolve_time):
return math.exp(resolve_time / tau) / (T0 * f_clk * f_data)
for label, tr in (("1 flop (0.5 ns slack)", 0.5e-9), ("2 flops (2.3 ns)", 2.3e-9)):
m = mtbf(tr)
print(f"{label}: MTBF = {m:.3g} s = {m/3.156e7:.3g} years")
# 1 flop (0.5 ns slack): MTBF = 2.88e+04 s = 0.000913 years
# 2 flops (2.3 ns): MTBF = 3.52e+43 s = 1.11e+36 yearsOne flop fails about every 8 hours; adding a second flop gives the signal a full extra cycle to resolve and the MTBF becomes longer than the age of the universe. That exponential is why the 2-flop synchronizer is the universal answer.
Common mistakes
- Wrong: Synchronizing each bit of a bus independently. — Right: Sync one control bit or use Gray/FIFO/handshake.
- Wrong: Combinational logic before the synchronizer. — Right: Register the signal in the source domain; glitches can be captured.
- Wrong: Passing a one-cycle pulse from fast to slow clock. — Right: Use a toggle or handshake synchronizer; the pulse can be missed.
- Wrong: Relying on simulation to find CDC bugs. — Right: Run structural CDC checks.
Applications
- SoC interconnect: Bridges between CPU, DDR and peripheral clock domains.
- Async FIFOs: Streaming video, networking and ADC data between domains.
- Interrupts & GPIO: Synchronizing external asynchronous inputs.
- Reset distribution: Reset synchronizers in every clock domain.
Key takeaways
- Metastability is unavoidable; MTBF is exponential in t_r.
- 2-flop sync for bits, Gray/FIFO/handshake for buses.
- No combinational logic before synchronizers.
- Structural CDC tools are mandatory sign-off.
Going further
- Mesochronous & plesiochronous: Same-frequency clocks with unknown phase use cheaper schemes.
- Reset-domain crossing: Asynchronous resets of one domain corrupting another.
- Glitch-free clock muxing: Switching clocks safely also needs synchronization.