Clock Domain Crossing
CDC · Metastability · Synchronizers
One of the most dangerous and common sources of silicon bugs — learn how to safely pass signals between different clock domains.
What is Metastability?
When a flip-flop's setup or hold time is violated, its output enters a metastable state — neither a valid 0 nor 1 — and may take an unpredictable time to resolve. If it hasn't resolved before the next FF samples it, the metastability propagates.
Mean Time Between Failures (MTBF) for a synchronizer:
τ is technology-dependent (~25 ps in modern CMOS). More resolve time (more FF stages or slower clock) → exponentially better MTBF. Two FF stages typically achieve MTBF > 1 million years at GHz speeds.
CDC Techniques — Which to Use
| Signal type | Best technique | Why |
|---|---|---|
| Single-bit control (pulse ≥ 2 cycles) | 2-FF synchronizer | Simple, fast, proven |
| Single-bit pulse (may be 1 cycle) | Pulse stretcher + 2-FF sync | Ensure pulse lasts long enough |
| Multi-bit counter/address | Gray code + 2-FF per bit | Only 1 bit changes per step |
| Multi-bit data bus | Async FIFO or handshake | Guarantees data stability before capture |
| Streaming data flow | Async FIFO (dual-clock) | Decouples producer/consumer rates |
Verilog — CDC Implementations
2-FF Synchronizer (single bit)
// 2-FF synchronizer for single-bit control signal
module sync_2ff #(parameter STAGES=2) (
input logic clk_dst, rst_n,
input logic async_in,
output logic sync_out
);
// Prevent optimization: keep both FFs in series
(* DONT_TOUCH = "true" *)
logic [STAGES-1:0] chain;
always_ff @(posedge clk_dst or negedge rst_n)
if (!rst_n) chain <= '0;
else chain <= {chain[STAGES-2:0], async_in};
assign sync_out = chain[STAGES-1];
endmodule
Gray Counter (for async FIFO pointers)
// Gray-coded counter — only 1 bit changes per increment
module gray_counter #(parameter N=4) (
input logic clk, rst_n, en,
output logic [N:0] gray // N+1 bits for full/empty detect
);
logic [N:0] bin;
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) bin <= '0;
else if (en) bin <= bin + 1;
assign gray = bin ^ (bin >> 1); // binary to Gray
endmodule
Async FIFO (dual-clock, Gray pointer)
// Async FIFO: write in wclk domain, read in rclk domain
module async_fifo #(parameter W=8, DEPTH=16, AW=4) (
input logic wclk, rclk, wrst_n, rrst_n,
input logic wen, ren,
input logic [W-1:0] wdata,
output logic [W-1:0] rdata,
output logic full, empty
);
logic [W-1:0] mem [0:DEPTH-1];
logic [AW:0] wptr, rptr; // Gray coded
logic [AW:0] wptr_r, rptr_w; // synchronized
logic [AW:0] wbin, rbin;
// Write side
always_ff @(posedge wclk) if (wen && !full) begin
mem[wbin[AW-1:0]] <= wdata;
wbin <= wbin + 1;
end
assign wptr = wbin ^ (wbin >> 1);
// Read side
always_ff @(posedge rclk) if (ren && !empty)
rbin <= rbin + 1;
assign rdata = mem[rbin[AW-1:0]];
assign rptr = rbin ^ (rbin >> 1);
// Synchronize pointers across domains (2-FF each)
sync_2ff #(AW+1) s0(rclk, rrst_n, wptr, wptr_r);
sync_2ff #(AW+1) s1(wclk, wrst_n, rptr, rptr_w);
assign full = (wptr == {~rptr_w[AW:AW-1], rptr_w[AW-2:0]});
assign empty = (rptr == wptr_r);
endmodule
Frequently Asked Questions
What is metastability?
A FF output entering an indeterminate voltage state when setup/hold time is violated. It resolves probabilistically — the longer you wait, the more likely it resolves correctly. 2-FF synchronizers give it a full clock period to resolve.
Why can't a 2-FF synchronizer handle multi-bit signals?
Different bits may be captured from different clock cycles — one bit from the new value, another from the old. This creates an invalid intermediate value. Use Gray code (for counters), async FIFO, or handshake for multi-bit CDC.
How does an async FIFO handle CDC?
Write/read pointers are Gray-coded (1 bit changes per step) and synchronized into the opposite clock domain via 2-FF synchronizers. Gray coding ensures only one pointer bit can be metastable at a time — the comparison for full/empty remains safe.