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.
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.”
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:
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:
| Method | Principle | Best for | Caveats |
|---|---|---|---|
| Handshake (req/ack) | Source asserts req; destination acknowledges ack before data transfer | Low-frequency, occasional transfers | Low throughput; req/ack each need synchronization |
| Async FIFO | Read/write pointers count in their own domain; only pointers cross domains | Continuous data streams (video, network) | Pointers need Gray-code encoding |
| Gray code | Adjacent values differ by 1 bit; after sync, at most one cycle off | Counter/pointer crossing | Only 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).
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
always @(posedge clk_a) reads a signal from the clk_b domain, you must be able to state its synchronization scheme.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.