Topic 23 · Digital Electronics

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.

Metastability2-FF SynchronizerMTBF Async FIFOGray CodeHandshakeVerilog

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:

MTBF = eTresolve / (fclk × fdata)

τ 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.

The golden CDC rule: Never pass an unsynchronized signal from one clock domain to another. STA cannot check CDC paths — metastability is a functional bug that shows up in silicon, not simulation.

CDC Techniques — Which to Use

Signal typeBest techniqueWhy
Single-bit control (pulse ≥ 2 cycles)2-FF synchronizerSimple, fast, proven
Single-bit pulse (may be 1 cycle)Pulse stretcher + 2-FF syncEnsure pulse lasts long enough
Multi-bit counter/addressGray code + 2-FF per bitOnly 1 bit changes per step
Multi-bit data busAsync FIFO or handshakeGuarantees data stability before capture
Streaming data flowAsync 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.