Hardware Engineering · FPGAs

Clock Domain Crossing: Synchronizers, Handshakes, and False Paths

Why a flip-flop sampling an unrelated clock can go metastable, how a two-flop synchronizer bounds the failure rate, and how data buses cross domains without tearing.

A flip-flop samples its data input with respect to its own clock edge. When the data changes within the setup-and-hold window around that edge, the flop’s internal latch is driven by an input that is neither high nor low for long enough to resolve, and its output may settle to an intermediate voltage, or to the correct value late, or to either value unpredictably. The condition is metastability, and the standard answer to it is to stop treating the crossing as a timing path and start treating it as a probability.

What metastability actually is

A cross-coupled inverter pair has two stable states. When the input forces it towards one side and then releases, the pair’s state evolves towards the nearer stable point exponentially, with a time constant set by the transistors. If the input was released close enough to the balance point, the evolution starts near zero difference and takes a long time to reach a logic level. The register’s output during that interval is not a valid logic value.

The time available to resolve before the next stage samples is fixed by the clock period. Given a fixed resolution time $t_r$, the probability that a given edge sample is still unresolved after $t_r$ falls exponentially with $t_r$:

$$P(\text{failure}) \approx \frac{T_0}{T_{clk}} e^{-t_r/\tau}$$

where $\tau$ is the resolution time constant and $T_0$ is a width parameter, both properties of the process. The exponential is what makes synchronizers practical: each additional stage multiplies the failure probability by $e^{-T_{clk}/\tau}$. The point of two stages is not redundancy in the usual sense; it is that the first stage is permitted to be late, and the second stage gives it a full period to resolve. Because the factor is exponential in the period, the margin gained by the second stage grows quickly as the destination clock slows.

The parameters in that formula are device properties, typically published in a reliability or characterization note rather than the main data sheet. A design’s MTBF figure is derived from them, and the derivation is approximate. The practical conclusion holds regardless of the constants: one synchronizer stage is not enough, two is conventional, and three is used where the receiving clock is slow relative to the resolution time or where the consequence of failure is severe.

Single-bit crossings

A single bit crossing between unrelated clocks passes through two registers clocked by the destination clock, with the first register’s output feeding only the second, and the second feeding the destination logic. Two rules make this work and both are structural rather than declarative.

The first rule is that no logic may sit between the two synchronizer stages. Combinational logic between them adds delay that varies with the data, which shortens the resolution window unpredictably and can cause the second stage to sample a still-resolving value. Synthesis will happily absorb the first stage into a surrounding expression unless the synchronizer registers are marked as such or the code is written so that no logic exists in the path.

The second rule is that the first stage’s output must fan out to nothing but the second stage. A second consumer sees the metastable signal one cycle earlier than the synchronizer intended, and the entire argument collapses.

-- Two-stage synchronizer. ATTRIBUTE keep preserves both registers
-- when the tool would otherwise retime them.
signal sync_meta, sync_stable : std_logic := '0';
attribute keep : string;
attribute keep of sync_meta, sync_stable : signal is "true";

process (clk_dst)
begin
  if rising_edge(clk_dst) then
    sync_meta   <= bit_in;      -- may be metastable
    sync_stable <= sync_meta;   -- resolved with one period of margin
  end if;
end process;

A level signal crossing this way is safe but slow: the destination sees it some cycles after the source changes, and if the source changes twice within that window the destination may miss the first change entirely. A pulse crossing needs different treatment, either by widening the pulse to more than two destination periods, or by using a toggle in the source domain that the destination detects by comparing successive synchronizer outputs.

The toggle approach has a bounded amplitude of one change per source event, and an event detector in the destination domain that XORs the synchronized toggle with its own previous value. The detector produces a pulse in the destination domain for each detected transition. If events arrive faster than the destination can detect them, the detector misses events, so the design must guarantee a minimum spacing, usually documented as a few destination cycles.

Multi-bit buses

Synchronizing a bus bit by bit does not work, even when every bit is synchronized correctly. Each bit’s synchronizer has an independent and unpredictable latency of one or two destination cycles, so a bus whose value changes from 0x0F to 0xF0 can be observed as 0xFF or 0x00 at intermediate cycles. The destination sees a value that never existed.

Three constructions avoid this while preserving the data. The first is the handshake: the source holds the data stable, asserts a request through a single synchronizer, and waits for an acknowledge returning through another synchronizer. The data bus is then sampled only while it is guaranteed stable, and the bus itself needs no synchronizer, only a timing exception declaring that the crossing is not a real path. The cost is latency, four to six destination cycles for a round trip, and the throughput is limited to one transfer per handshake.

The second is the asynchronous FIFO. Dual-port memory with Gray-coded read and write pointers lets each side compare pointers without synchronizing a binary value, since Gray coding changes only one bit per increment and a sample therefore yields either the old or the new pointer, never a corrupt one. Full and empty are computed from the synchronized pointers, and the memory is written and read at addresses that are stable by construction. This is the standard mechanism for crossing streams, and it appears as a vendor primitive in most device families.

The third is a capture register pair driven by a strobe: the source holds the bus, asserts a strobe that crosses through a synchronizer, and the destination registers the whole bus on the cycle the synchronized strobe arrives. This works because the bus is guaranteed stable for multiple destination cycles, which the design must enforce by construction. It is the cheapest of the three when the source is slow and the destination is fast, and the most fragile if the assumption is violated.

Constraints for crossings

The timing analysis described in static timing analysis will, by default, analyse a crossing between two unrelated clocks with an arbitrary and meaningless budget, because it computes the edge relationship from the two clocks’ periods without knowing that no relationship exists. Two remedies exist, and they are not equivalent.

Declaring the two clocks asynchronous with set_clock_groups -asynchronous removes every path between them from analysis, at once and without per-path effort. This is the correct declaration when the design genuinely contains no paths that require a fixed relationship, and it is the one to prefer when the clock domains are independent.

The alternative is set_false_path on individual paths, more precise and harder to maintain. Its virtue is that it can be applied narrowly, for example to a single handshake bus while leaving a legitimate same-edge path constrained.

Either way, the constraint does not make the crossing safe. It only stops the tool from reporting nonsense. The safety comes from the synchronizer structure, which no timing check verifies, and from documentation that a reviewer can check. Vendors provide structural checkers that infer synchronizers from the netlist and report crossings with none, and those tools complement rather than replace the constraints.

There is a further subtlety in the timing model: the synchronizer’s first register has a setup requirement that the tool will report as violated or as unbounded. That report should be suppressed deliberately, with the exception justified in a comment naming the synchronizer, not by an exception applied to the whole clock group, because the latter hides crossings that have no synchronizer at all.

Where crossings go wrong

A reset that is asserted asynchronously in one domain and releases asynchronously in another will release at different cycles in each, so a state machine may leave reset with its inputs not yet valid. The standard fix is a reset synchronizer per domain, which asserts asynchronously and de-asserts synchronously to that domain’s clock.

A counter that is read across a domain without Gray coding produces a value that skips and repeats at the destination, and any logic that compares it for a threshold can fire spuriously. A bus that is assumed to be stable because “it only changes rarely” fails when the rare event lands inside the synchronizer window, which is exactly the case that probability theory says will eventually happen.

The structural discipline that prevents all of these is to make every crossing visible: name the synchronizer instances consistently, keep them in one place in the source tree, declare the constraints in one place, and review the set of crossings as a list rather than as a property of the whole design. The tooling that reports crossings is only useful if the crossing has a recognizable shape, and that shape is a decision the designer makes in the RTL.

References

  1. Clock Domain Crossing (CDC) Design & Verification Techniques Using SystemVerilog — Clifford E. Cummings, SNUG 2008
  2. Metastability and Synchronizers: A Tutorial — Ran Ginosar, IEEE Design & Test of Computers, 2011
  3. Vivado Design Suite User Guide: Using Constraints (UG903) — Xilinx / AMD