Skip to content

VHDL · Chapter 17.5 · FPGA-Oriented VHDL Design

Clock Domain Crossing

As soon as a design has two unrelated clocks, passing a signal between them becomes dangerous. A destination flip-flop can sample a signal from another domain mid-transition, violate setup and hold, and go metastable, hovering at an indeterminate level before resolving unpredictably. You cannot eliminate metastability, only make it astronomically unlikely to propagate. The standard fix for a single-bit crossing is the two-flip-flop synchronizer in the destination domain: the first flop may go metastable but is given a full clock to settle before the second flop samples it. This works only for single-bit or slow signals. You cannot put a synchronizer on each bit of a multi-bit bus, because the bits resolve independently and you capture an incoherent value. This lesson covers metastability, the two-flip-flop synchronizer, and why buses need Gray code or a handshake and FIFO.

Foundation15 min readVHDLFPGACDCMetastabilitySynchronizerClock Domains

1. Engineering intuition — sampling something that isn't ready

A flip-flop promises a clean 0/1 only if its input is stable around the clock edge (setup/hold). A signal from another clock domain has no such courtesy — it can change at any time relative to the destination clock, including exactly at the sampling edge. When that happens the flop can go metastable: balanced between 0 and 1, resolving to one or the other only after an unpredictable delay. You cannot prevent this, but you can give the glitch time to die out before anything downstream uses it — which is what a synchronizer does: sacrifice the first flop to metastability, then wait a clock for it to settle before the second flop reads it. The whole art of CDC is making sure a metastable value never reaches logic that acts on it.

2. Formal explanation — metastability and the two-FF synchronizer

two_ff_synchronizer.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- METASTABILITY: a destination FF sampling an ASYNCHRONOUS input near its edge may go metastable
--   (output indeterminate for a while). Cannot be eliminated → reduce propagation probability (MTBF).
 
-- TWO-FLIP-FLOP (double) SYNCHRONIZER for a SINGLE-BIT signal, in the DESTINATION domain:
signal sync_ff1, sync_ff2 : std_logic;
attribute async_reg : string;
attribute async_reg of sync_ff1, sync_ff2 : signal is "true";   -- mark as synchronizer FFs (17.4/16.8)
 
process (dst_clk) begin
  if rising_edge(dst_clk) then
    sync_ff1 <= async_in;     -- FF1 may go METASTABLE (samples the async signal)
    sync_ff2 <= sync_ff1;     -- FF2 samples FF1 a full clock LATER → metastability has settled
  end if;
end process;
sync_out <= sync_ff2;         -- safe to use in the destination domain
 
-- RULE: ONLY for single-bit / slow-changing signals. A multi-bit BUS canNOT use parallel 2-FF
--   synchronizers (each bit resolves independently → an INCOHERENT combined value).

A destination FF sampling an asynchronous input can go metastable; the two-FF synchronizer gives the first FF a full clock to settle before the second reads it, cutting propagation probability (raising MTBF). It is valid only for single-bit crossings — parallel synchronizers on a bus capture independently-resolved, incoherent bits.

3. Production usage — what to synchronize, and what not to

cdc_usage.txt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- SAFE with a 2-FF synchronizer: a SINGLE control bit / flag / slow status.
--   e.g. a request pulse, an enable, a "done" flag — one bit, double-sync it in the destination domain.
 
-- NOT SAFE with parallel 2-FF synchronizers: a MULTI-BIT BUS (data, address, counter, pointer).
--   Each bit's synchronizer resolves on its own; on the capture edge different bits may be old/new
--   → the assembled value is GARBAGE (never existed). Use instead:
--     • GRAY CODE for pointers/counters (only ONE bit changes per step → at most one bit is uncertain)
--     • HANDSHAKE (req/ack across the boundary) to pass a held, stable bus value
--     • ASYNC FIFO (dual-clock) for streaming data            (next lesson, 17.6)
 
-- ALWAYS: synchronize an async signal BEFORE any logic uses it. Never feed it raw into a comparator/FSM.

What hardware does this become? The two-FF synchronizer is two flip-flops in series in the destination domain (marked async_reg so the tools place them close and constrain them) — the standard single-bit CDC cell. For buses it becomes something larger: a Gray-coded pointer crossing (used in async FIFOs), a handshake (req/ack with the data held stable while it crosses), or a dual-clock FIFO for streams. The non-negotiable rule in silicon: an asynchronous signal must pass through a synchronizer before any combinational logic or FSM uses it — feeding it raw means metastability propagates into your logic and the design fails intermittently and un-reproducibly.

4. Structural interpretation — the two-FF synchronizer across domains

single-bit signal from source domain through two destination-domain flip-flops to safe outputasynccrossingsettlesource domain FFlaunches async_indest FF1may go METASTABLEdest FF2settled (one clock later)destination logicsafe to use sync_out12
A two-flip-flop synchronizer safely passes a single-bit signal between clock domains. A signal launched in the source domain is asynchronous to the destination clock, so the first destination flip-flop may go metastable when it samples it; giving that flop a full destination clock to settle before the second flop samples it makes a metastable value extremely unlikely to propagate (high MTBF). The two synchronizer flops are marked async_reg. This works only for single-bit or slow signals — a multi-bit bus cannot use parallel synchronizers because its bits resolve independently into an incoherent value, requiring Gray code or a handshake/FIFO instead. This is a CDC-synchronizer structure; the waveform below shows the metastable first stage settling.

5. Simulation interpretation — metastable first stage settling

Two-FF synchronizer: FF1 metastable, FF2 clean

8 cycles
Two-FF synchronizer: FF1 metastable, FF2 cleanasync_in changes near the edge → sync_ff1 goes METASTABLE (X)async_in changes near …given a full clock, FF1 settles; FF2 may briefly be Xgiven a full clock, FF…sync_ff2 is clean and stable → safe to use in the destination domainsync_ff2 is clean and …dst_clkasync_in01111111sync_ff10X111111sync_ff200X11111t0t1t2t3t4t5t6t7
async_in changes asynchronously to dst_clk, so sync_ff1 is sampled mid-transition and goes metastable (shown as X). It is given a full destination clock to resolve before sync_ff2 samples it, so by the time the value reaches sync_ff2 it is clean. This is the whole purpose of the second flop: never let a possibly-metastable value reach logic that acts on it. (Real metastability is analog; X here represents the indeterminate window.)

6. Debugging example — synchronizing a bus bit-by-bit

Expected: a multi-bit value crosses cleanly between domains. Observed: the destination occasionally reads a value that never existed on the source (some bits old, some new), causing rare, un-reproducible failures — or an async signal fed straight into an FSM makes the FSM jump to impossible states. Root cause: a multi-bit bus was crossed with parallel two-FF synchronizers (one per bit), so each bit resolved independently and the captured word mixed old and new bits; or an async signal was used by logic before being synchronized. Fix: synchronize single bits with a two-FF cell; cross buses coherently — Gray code for pointers/counters (only one bit changes per step), a handshake to hold the bus stable while it crosses, or a dual-clock FIFO (17.6); and never feed an async signal into logic before it is synchronized. Engineering takeaway: two-FF synchronizers are for single bits only — parallel-syncing a bus captures incoherent values; use Gray code or a handshake/FIFO for multi-bit crossings.

bus_needs_more_than_2ff.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- BUG: per-bit 2-FF sync on a bus → bits resolve independently → incoherent value.
-- for i in bus'range loop bus_s1(i) <= bus_a(i); bus_s2(i) <= bus_s1(i); end loop;  -- WRONG for a bus
-- FIX: cross the bus coherently — Gray-coded pointer, or a handshake/async FIFO (17.6).
--   (single CONTROL bits use the 2-FF synchronizer; the DATA crosses via FIFO/handshake)

7. Common mistakes & what to watch for

  • Bit-by-bit bus synchronizing. Parallel 2-FF synchronizers on a bus give incoherent values; use Gray code or a handshake/FIFO.
  • Using an async signal before syncing. Always pass it through a synchronizer first; never feed it raw to logic/FSMs.
  • Single synchronizer flop. One FF is not enough; use two (or more in very high-speed/critical designs) to settle metastability.
  • Not marking synchronizer FFs. Use async_reg (and proper CDC constraints) so tools place them close and treat the path correctly.
  • Forgetting the source must hold. For handshake-based bus crossings, the source must keep data stable until the destination has captured it.

8. Engineering insight & continuity

Clock domain crossing is governed by metastability: a flop sampling an asynchronous input can become indeterminate, so a two-FF synchronizer gives it a clock to settle — valid for single-bit signals only, since a bus crossed bit-by-bit resolves into an incoherent value. Single control bits double-sync; multi-bit data needs Gray code or a handshake/FIFO, and nothing async touches logic before synchronization. Those coherent multi-bit mechanisms are exactly the next lesson: CDC FIFOs and Handshake Synchronizers — passing buses and streams safely between clock domains.