How Do You Handle Clock Domain Crossing (CDC) and Metastability in FPGA Design?
Last updated 20 July 2026 · 17 min read
Direct Answer
Metastability happens when a flip-flop's data input changes too close to its clock edge, violating setup or hold time, and the flip-flop's output settles into a valid 0 or 1 only after an unpredictable delay instead of immediately. Any signal moving between two clock domains is asynchronous to the receiving clock unless a specific timing relationship has been proven and constrained, which makes clock domain crossing (CDC) a source of metastability risk anywhere it isn't handled deliberately. Metastability can't be eliminated, only made rare enough to be an acceptable risk, which is why every CDC path needs an explicit synchronisation strategy rather than an assumption that the signal is 'probably stable' by the time it's sampled. For a single control bit, a two-flop (double-flop) synchroniser in the receiving clock domain is the standard fix. For multi-bit buses, a two-flop synchroniser on each bit is not enough, because different bits can resolve on different clock edges and produce a value that never existed on either side; Gray-coded pointers, handshake protocols, or an asynchronous FIFO are the correct tools there.
Detailed Explanation
Clock domain crossing bugs share a specific signature: the design passes synthesis, passes place-and-route timing, passes simulation, and then fails intermittently in hardware, sometimes after running correctly for hours or days. That signature exists because the bug isn't a timing violation the tools can see. It's a missing or incorrect synchroniser on a signal moving between two clocks, and neither static timing analysis nor behavioural simulation models the mechanism that causes the failure.
What Metastability Actually Is
A flip-flop needs its data input to be stable for a minimum window around the clock edge: stable for a setup time before the edge, and stable for a hold time after it. If the input changes inside that window, the flip-flop's internal storage element doesn't get pushed cleanly to one state or the other. It can sit at an in-between voltage level for longer than a normal propagation delay before settling, unpredictably, to a valid 0 or 1. That in-between condition is metastability.
The physical cause is a bistable circuit (typically a pair of cross-coupled inverters at the heart of the flip-flop) driven to a point of unstable equilibrium, similar to a ball balanced exactly on the top of a ridge. Small enough, it can stay balanced for an arbitrarily long time before rolling to one side. In a flip-flop, "rolling to one side" means the output resolves to 0 or 1, and the time it takes to do so follows a probability distribution rather than a fixed value: most of the time resolution is very fast, but the probability of a long resolution time never reaches exactly zero.
This is why metastability can't be engineered away. No flip-flop, however fast, has zero setup or hold time, so any input that changes asynchronously to the sampling clock will occasionally land inside the timing window. The only lever available is reducing how often that happens, and how much time is given for the flip-flop to resolve before the resulting value is used elsewhere in the design. That reframes the problem: not "prevent metastability" but "make the mean time between metastability-induced failures acceptably long for the product."
Why Clock Domain Crossing Matters
A clock domain crossing is any point where a signal generated in one clock's timing domain is sampled by logic running on a different clock, whether that's a genuinely free-running independent oscillator or a clock derived from the same source through a different PLL output. If the receiving clock's edges have no fixed, provable phase relationship to the transitions of the incoming signal, every sampling edge has some chance of landing during a transition.
Static timing analysis cannot help here, because it works by checking that a signal launched from one clock edge arrives before the required setup time ahead of the capturing clock edge, which requires a known, fixed relationship between launch and capture clocks. Two truly asynchronous clocks have no such relationship: the phase between them drifts continuously, so any timing check between them is meaningless. In practice, an unconstrained CDC path is usually just excluded from the tool's timing analysis entirely, rather than being reported as a failing path. That's the trap: the timing report looks clean not because the crossing is safe, but because the tool never checked it in the first place.
The Two-Flop Synchroniser for Single-Bit Signals
For a single-bit control signal (an enable, a flag, a done pulse) crossing into a new clock domain, the standard solution is a chain of two flip-flops clocked by the destination domain:
Signal from ┌────┐ ┌────┐
clock domain A: ────┤ FF ├────┤ FF ├────→ Synchronised to clock B
└────┘ └────┘
CLK_B CLK_B
The first flip-flop is the one exposed to the asynchronous input, so it's the one that can go metastable. Its output feeds directly into the second flip-flop, which samples it a full clock period later. Given a typical flip-flop's resolution time constant, one clock period at ordinary FPGA clock frequencies is enough time for the first flip-flop's output to resolve to a valid level with very high probability before the second flip-flop captures it. The second flip-flop then presents a clean, glitch-free signal to the rest of the destination domain.
One flip-flop is not enough on its own: if the first flip-flop is still metastable exactly when downstream combinational logic reads it, that logic can interpret the in-between voltage inconsistently (different gates seeing it as different logic levels), producing a genuine logic-level fault rather than just a delayed clean value. The second flip-flop exists specifically to give metastability a full clock period to resolve before anything downstream depends on the value.
Why a Third Flip-Flop Rarely Helps Much
Adding a third flip-flop in series gives the signal another clock period of resolution margin, and it does lower the probability of a downstream fault further. But the improvement is not proportional. The probability that a flip-flop is still metastable falls off roughly exponentially with the time available to resolve, so most of the benefit is captured going from zero synchronising flops to one extra clock period of margin (the second flop); the second extra period (the third flop) buys a much smaller additional reduction in an already very small failure probability. Two flops are the standard for most designs. Three (or a purpose-built synchroniser cell with a characterised, vendor-published MTBF) are used in high-reliability, high-frequency, or safety-critical designs where the target failure rate is very demanding.
MTBF: Putting a Number on "Acceptably Rare"
Metastability failure rate is commonly expressed as a mean time between failures (MTBF), and vendor application notes typically present it in a form similar to:
MTBF = e^(t_r / τ) / (T_w × F_clk × F_data)
Where t_r is the resolution time available (roughly one clock period per synchronising flip-flop beyond the first), τ is a device- and process-specific time constant describing how quickly the flip-flop resolves out of metastability, T_w is the width of the metastability-inducing window, F_clk is the sampling clock frequency, and F_data is how often the asynchronous signal toggles. The formula's exact coefficients (τ and T_w) are specific to a given silicon process, flip-flop design, and operating conditions, so treat any numbers plugged into it as illustrative rather than a guarantee for a specific device. The practically useful takeaway from the shape of the formula, not the numbers, is that MTBF improves exponentially with each additional clock period of resolution time and degrades linearly with clock frequency and data toggle rate. That's the underlying reason two flops are dramatically better than one, and why a third flop's marginal benefit is smaller: the exponential term is already large after one extra clock period.
FPGA vendors and CDC-focused application notes publish MTBF figures for their own synchroniser primitives under stated conditions; where a design has a specific reliability requirement (a safety standard, a contractual MTBF target), use the vendor's own characterised numbers for the target device and speed grade rather than a generic formula.
Why You Cannot Double-Flop a Multi-Bit Bus
The two-flop synchroniser is correct for a single bit. It is not correct, on its own, for a multi-bit value such as a counter, an address, or a data word, even if you put a two-flop synchroniser on every individual bit.
The problem is that a multi-bit value can change more than one bit at a time (a counter going from 7 to 8 in binary changes every bit at once: 0111 to 1000). If a sampling edge in the destination domain lands during that transition, different bits can resolve from their own metastability at different times, or simply capture the old value on some bits and the new value on others. The composite word that results is neither the old value nor the new value: it's a value that may never have existed in the source domain at all. This is exactly the failure mode that produces the classic symptom of a counter jumping to an impossible value that a design's own logic should never be able to produce.
Synchronising each bit independently doesn't fix this, because independence is the problem: nothing coordinates which bits happen to resolve on the "old" side and which resolve on the "new" side of the transition.
Gray-Coded Counters and Pointers
One robust fix for a value that only ever counts up or down by one, such as a FIFO read or write pointer, is to encode it in Gray code before it crosses the domain. In a Gray code sequence, exactly one bit changes between any two adjacent values. That property means a sampling edge landing mid-transition can only ever be ambiguous about a single bit, and the resulting captured value is guaranteed to be either the value just before the transition or the value just after it, never something in between. After synchronising the Gray-coded value with a per-bit two-flop synchroniser, it's decoded back to binary in the destination domain.
Gray-coded pointers are the standard mechanism inside asynchronous FIFO designs (see below) and are appropriate whenever the crossing value is a monotonic counter or pointer rather than an arbitrary data word.
Handshake Protocols for Multi-Bit Data
When the crossing value is an arbitrary data word rather than a monotonic counter, Gray coding doesn't apply, since the value doesn't move through adjacent codes one step at a time. The alternative is a handshake: the source domain asserts a request signal (synchronised into the destination domain with a standard two-flop synchroniser) once its data is stable and held, the destination domain captures the data only after seeing the synchronised request, and it returns an acknowledge signal (synchronised back into the source domain) once the data has been captured. The source domain doesn't change the data or issue a new request until it sees the acknowledge. Because the data bus is only ever sampled after its own request line has been synchronised and confirmed stable, the data itself never needs its own synchroniser; it just needs to be held steady for the whole handshake.
Handshake synchronisers are simple to reason about but add multiple clock cycles of latency per transfer (crossing the request, crossing the acknowledge, and any margin built into the protocol), which makes them a poor fit for high-throughput or continuously streaming data.
Asynchronous FIFOs
For continuous or bursty data transfer between clock domains, an asynchronous (dual-clock) FIFO is the standard building block. It combines a dual-port memory (written on the source clock, read on the destination clock) with Gray-coded write and read pointers that are synchronised across the domain boundary to generate full and empty flags safely. The FIFO encapsulates all of the CDC handling: the design on either side just sees a normal FIFO interface (write data and write enable on one side, read data and read enable on the other) and never has to reason about the crossing directly.
FPGA vendor tools typically provide a dual-clock FIFO as generated IP (the FIFO Generator core in Xilinx Vivado, the DCFIFO megafunction in Intel/Altera Quartus, and equivalent primitives from other vendors), verified against the target device's timing and characterised for the crossing. Using the vendor-provided core is generally the right default over writing one from scratch, since the pointer synchronisation and empty/full flag generation are easy to get subtly wrong. Clifford Cummings' paper on asynchronous FIFO design (see External References) is the reference most FPGA engineers point to if you do need to understand or implement the internals directly, for example when a vendor core doesn't fit an unusual width or depth requirement.
Constraining CDC Paths in the Timing Tool
A synchroniser in the RTL is necessary but not sufficient. The timing tool also needs to be told, explicitly, that a given clock domain crossing is asynchronous, or it may attempt (and fail) to apply standard setup/hold analysis across a boundary that has no defined timing relationship, or worse, silently skip the path in a way that hides a missing synchroniser rather than confirming a correctly designed one.
The relevant constraints (Vivado XDC / Quartus SDC syntax) include:
set_clock_groups -asynchronous \
-group [get_clocks clk_A] \
-group [get_clocks clk_B]
This tells the tool the two clock groups have no fixed relationship, so it should not attempt setup/hold analysis between them. Some designs instead use set_false_path on the specific crossing paths rather than grouping entire clocks as asynchronous, which is more targeted when only some paths between two clocks are asynchronous and others are genuinely related.
Vivado's report_cdc (Reports → Report CDC, or report_cdc -details in the Tcl console) runs a structural check across the design and classifies each detected crossing as safe (a recognised synchroniser structure is present), a warning (the tool can't confirm the crossing is handled), or critical (no synchroniser structure detected at all). Quartus provides the equivalent through TimeQuest's Report Clock Domain Crossings. Both are worth running on every design with more than one clock domain, because a set_clock_groups -asynchronous or set_false_path constraint only tells the tool to stop checking timing on a path; it does not verify that a synchroniser actually exists there. A design can have a fully clean timing report and a completely unprotected CDC path at the same time.
Reset Synchronisation: A Related but Distinct Problem
Releasing a reset signal is its own clock domain crossing risk, separate from data CDC but often overlooked alongside it. Asserting a reset asynchronously is fine and often desirable, since you generally want reset to take effect immediately regardless of clock state. Releasing it asynchronously is the risk: if reset deasserts at a moment close to a clock edge, some flip-flops can come out of reset in an inconsistent state relative to others. The standard fix is a reset synchroniser: assert the reset asynchronously, but synchronise its release through a small flip-flop chain in each destination clock domain so that deassertion always lines up cleanly with a clock edge. See combinational vs sequential logic for more on synchronous versus asynchronous reset behaviour in sequential logic generally.
For FPGA CDC implementation, structural CDC verification, and general RTL design and timing closure work, Zeus Design's engineering team provides FPGA design and HDL development services for embedded hardware products.
Design Considerations
Use a two-flop synchroniser when the crossing is a single control bit, whether level or pulse. Enable signals, done flags, mode-select bits, and similar single-bit control signals are the correct case for the plain two-flop synchroniser. A pulse that's only one source-clock cycle wide needs special handling if the destination clock is slower than the source: a fast, narrow pulse can be missed entirely by a slower sampling clock, so either stretch the pulse to guarantee it's wide enough to be caught, or convert it to a toggle signal that changes state once per event and let the receiving domain detect the toggle rather than the pulse itself.
Use Gray coding when the crossing value is a monotonic counter or pointer. Read/write pointers, sample counters, and similar values that only increment (or decrement) by a known step between updates are the textbook case for Gray-coded synchronisation: cheap in logic, exact rather than approximate, and the standard mechanism inside async FIFO designs.
Use a handshake or an async FIFO when the crossing value is arbitrary multi-bit data. A data word that can change to any value on any given update (not just count up by one) needs either a request/acknowledge handshake for occasional, low-rate transfers, or a full asynchronous FIFO for continuous or bursty data. The FIFO is almost always the better choice once throughput matters, since a handshake's per-transfer latency limits how much data it can move per second.
Constrain every CDC path explicitly, and verify with a structural CDC check, not just a clean timing report. A clean WNS/TNS timing summary says nothing about whether a crossing is synchronised correctly, since a correctly excluded asynchronous path and a silently unconstrained one can both produce a clean timing report. Run report_cdc (Vivado) or the equivalent Quartus report on any design with more than one clock domain.
Don't trust simulation to catch a missing synchroniser. Behavioural RTL simulation treats every signal as resolving instantly and cleanly; it has no model of a flip-flop's setup/hold window or metastable resolution behaviour. A design with a missing or broken synchroniser can pass simulation completely and still fail intermittently in hardware, because the two environments are testing fundamentally different things.
Common Mistakes
Assuming a signal is "probably stable" by the time it's sampled. A signal that appears to change well away from the destination clock's edges under nominal simulation conditions can still occasionally align with an edge in real hardware, where clock jitter, temperature, voltage, and process variation all shift the exact timing relationship. There's no safe margin that eliminates the need for a synchroniser once two clocks are genuinely independent.
Double-flopping a multi-bit bus directly instead of using Gray coding or a FIFO. This is the single most common CDC bug in new FPGA designs: each bit gets its own two-flop synchroniser, timing analysis and simulation both look fine, and the design fails intermittently once individual bits happen to resolve on different sides of a multi-bit transition. The fix is Gray coding for monotonic pointers or a full async FIFO/handshake for arbitrary data, not more flip-flops per bit.
Forgetting the timing constraint on a CDC path. Even a correctly implemented two-flop synchroniser needs set_clock_groups -asynchronous (or an equivalent set_false_path) so the timing tool doesn't attempt setup/hold analysis across a boundary with no defined phase relationship, or leave the crossing unconstrained by accident. An unconstrained crossing can produce misleading timing reports either way, since the tool may not model the path the way the designer assumes.
Treating a clean timing report as proof the design is CDC-safe. WNS and TNS describe whether constrained paths meet their timing budget. They say nothing about whether an unconstrained, asynchronous path has a synchroniser at all. Run a structural CDC report as a separate step; don't infer CDC safety from timing closure.
Ignoring reset release timing across clock domains. A reset that deasserts asynchronously can leave flip-flops in different domains, or even different parts of the same domain, coming out of reset on different clock edges relative to each other. Use a reset synchroniser per clock domain (assert asynchronously, release synchronously) rather than tying an asynchronous reset directly to every flip-flop's reset pin.
Assuming two clocks are synchronous just because they share a PLL. Two outputs of the same PLL are only safe to treat as related if their phase relationship is fixed and explicitly constrained. Independent phase-shift settings, different output dividers without a constrained relationship, or any uncertainty about the actual phase alignment all mean the crossing should be treated, and synchronised, as fully asynchronous.
Frequently Asked Questions
- Why doesn't adding a third flip-flop to a synchroniser help much?
- The first flip-flop in a synchroniser chain is the only one that can go metastable, because it's the only one sampling a signal with no defined timing relationship to its clock. The second flip-flop samples the first flip-flop's output one full clock period later, by which point metastability has almost always resolved, since resolution time is typically a small fraction of a clock period at normal operating frequencies. A third flip-flop adds another clock period of resolution margin, which lowers the failure rate further, but the improvement is exponentially smaller than the jump from one flop to two, because the first flop already had to clear the highest-risk window. Two flops are the standard for most designs; three are sometimes used in very high-reliability applications (aerospace, safety-critical systems) or at very high clock frequencies where the margin from two flops is judged too thin, and the decision is usually driven by a project's required MTBF target rather than a fixed rule.
- Does CDC still matter if both clocks come from the same PLL?
- It depends on the exact phase and frequency relationship, not just on sharing a source oscillator. Two clocks generated from the same PLL are only safe to treat as synchronous if they have a fixed, known phase relationship at the point where the signal crosses, which the timing tool can verify with standard setup/hold analysis. This is true for clocks that are simple integer multiples or divisions of each other with a constrained phase offset. It is not automatically true for clocks with different feedback dividers, clocks passing through separate MMCM/PLL outputs with independent phase-shift settings, or any pair where the phase relationship isn't explicitly constrained in the timing tool. When in doubt, treat the crossing as asynchronous and synchronise it; incorrectly assuming a synchronous relationship because two clocks 'come from the same place' is a common source of the exact bugs CDC synchronisation exists to prevent.
References
Related Questions
What Is the Difference Between Combinational and Sequential Logic?
How flip-flops differ from logic gates: sequential vs combinational logic, D flip-flop setup and hold time, registers, and clock domain crossing synchronisers.
FPGA Development Flow: From HDL to Working Hardware
Learn the complete FPGA development flow: synthesis, place-and-route, timing constraints, timing closure, and bitstream generation in Vivado and Quartus.
How Do You Write Verilog and VHDL for an FPGA?
Learn to write synthesisable Verilog and VHDL for FPGAs — modules, always blocks, non-blocking assignments, latch inference rules, and test bench basics.
What Is an FPGA and How Does It Work?
What is an FPGA, how do LUTs implement any logic function, when to choose FPGA vs MCU vs ASIC, and the basics of Verilog and VHDL for digital design.
How Do You Design a Finite State Machine?
FSM design: Moore vs Mealy machines, state diagrams, one-hot vs binary encoding, implementation in C switch statements and FPGA, and common design pitfalls.
FPGA vs Microcontroller vs ASIC: Which Should You Use?
Learn when to choose an FPGA, microcontroller, or ASIC — covering parallel vs sequential workloads, volume economics, NRE costs, and hybrid approaches.