How to cross a clock domain without corrupting your data
Every non-trivial FPGA design ends up with more than one clock: a 100 MHz core and a 125 MHz Ethernet MAC, a fast datapath and a slow control bus, an ADC clock and everything else. The moment a signal made in one clock domain is read in another you have a clock domain crossing (CDC), and if you wire it up naively it will work in simulation, pass on the bench, and then fail in the field one time in ten thousand. Here is why, and here is the small set of patterns that make it safe.
The problem: metastability
A flip-flop only behaves if its D input is stable for a short window around the clock edge (the setup and hold times). A signal from another clock domain has no idea when your clock ticks, so sooner or later it changes right inside that window. When it does, the flip-flop output can hang at an in-between voltage for a while before it snaps to 0 or 1, and which way it snaps is anyone's guess. That in-between state is metastability.
A foreign signal d changes right at a clock edge (the dashed line). The flip-flop output q can hang between 0 and 1 before it resolves. You cannot stop this from happening.
You cannot prevent metastability, because the inputs are asynchronous by definition. What you can do is make it astronomically unlikely to matter.
Single-bit crossings: the two-flop synchronizer
Give the metastable value time to settle before anything reads it. That is the whole idea of the two-flop synchronizer: sample the foreign signal into one flip-flop, then pass it through a second flip-flop in the destination clock, all before any logic uses it.
Two flip-flops in the destination clock. The first (meta) is allowed to go metastable; it gets a full cycle to settle before the second one samples it, so sync_out is clean.
// Two-flop synchronizer. Safe for SINGLE-BIT signals (or Gray-coded buses).
module sync_2ff #(parameter WIDTH = 1) (
input wire dst_clk,
input wire rst_n,
input wire [WIDTH-1:0] async_in, // crosses in from another clock
output reg [WIDTH-1:0] sync_out
);
reg [WIDTH-1:0] meta; // stage 1: may go metastable
always @(posedge dst_clk or negedge rst_n)
if (!rst_n) begin
meta <= {WIDTH{1'b0}};
sync_out <= {WIDTH{1'b0}};
end else begin
meta <= async_in;
sync_out <= meta; // stage 2: clean
end
endmodule
The first flip-flop (meta) is allowed to go metastable. It gets a full destination clock cycle to resolve before the second flip-flop samples it, so sync_out is clean. The probability that metastability survives a whole cycle falls exponentially with the settling time, which is why two stages take the mean time between failures from "hours" to "longer than you will run the design" at typical clocks. At very high frequencies, or for safety-critical paths, add a third stage.
There is one rule you must never break: only ever synchronize a signal that is safe to sample one bit at a time. Which brings us to the trap.
The multi-bit trap
It is tempting to take a bus, drop a two-flop synchronizer on every bit, and call it done. Do not. The bits of a bus never change at exactly the same instant: routing delays differ, and near a clock edge each synchronizer independently resolves its own bit high or low. So for one cycle the destination can latch any mix of old and new bits.
The three bits of 3 -> 4 (011 -> 100) do not switch at the same instant. Sampling on the red edge catches 110 = 6, a value that never existed. This is exactly why you never put a synchronizer on each bit of a bus.
Now imagine that garbage value is a FIFO pointer, or an address. This is not a rare corner case, it is the default behaviour of the naive approach.
Multi-bit crossings that actually work
Three patterns. Pick by what you are crossing.
Gray code, for counters and pointers. In Gray code, consecutive values differ by exactly one bit, so even if the crossing samples mid-transition the worst case is reading the old value or the new value, never garbage. This is precisely why asynchronous FIFOs cross their read and write pointers in Gray code.
// Binary <-> Gray. Consecutive values differ by one bit, so a crossing
// caught mid-step reads either the old or the new value, never garbage.
function automatic [W-1:0] bin2gray (input [W-1:0] b);
bin2gray = b ^ (b >> 1);
endfunction
function automatic [W-1:0] gray2bin (input [W-1:0] g);
integer i;
begin
gray2bin = g;
for (i = 1; i < W; i = i + 1)
gray2bin = gray2bin ^ (g >> i);
end
endfunction
A handshake, for arbitrary data. Put the data in a register in the source domain and hold it steady. Cross a single-bit request through a synchronizer; the destination captures the (now stable) data and crosses a single-bit acknowledge back. Only the two control bits ever cross, and each is a safe single-bit signal. It costs a few cycles of latency per word, but it moves any data you like.
An asynchronous FIFO, for streams. For continuous data use a dual-clock FIFO: it combines Gray-coded pointers with a dual-port RAM so you write in one domain and read in another at full rate. Sizing one is its own topic, covered in how to size an async FIFO, and the FIFO depth calculator does the arithmetic.
Crossing a pulse
A single-cycle pulse is a special headache: if the destination clock is slower than the source, the pulse can be gone before the destination ever sees it. The fix is to turn the pulse into a level that toggles, synchronize the level, then edge-detect on the far side.
// Cross a single-cycle pulse safely, even to a slower clock:
// toggle a level in the source domain, synchronize it, edge-detect.
module pulse_cdc (
input wire src_clk, input wire src_pulse,
input wire dst_clk, input wire dst_rst_n,
output wire dst_pulse
);
reg toggle = 1'b0;
always @(posedge src_clk)
if (src_pulse) toggle <= ~toggle;
reg [2:0] sync = 3'b0;
always @(posedge dst_clk or negedge dst_rst_n)
if (!dst_rst_n) sync <= 3'b0;
else sync <= {sync[1:0], toggle};
assign dst_pulse = sync[2] ^ sync[1]; // one output pulse per toggle edge
endmodule
The five-point checklist
- Never let a raw cross-domain signal reach logic. Synchronize it first.
- Two flip-flops minimum, three for very fast clocks or safety-critical paths.
- Never bus-synchronize. Use Gray code, a handshake, or an async FIFO.
- Constrain it. Tell the tools these paths are asynchronous (
set_false_path, orset_max_delay -datapath_only) so timing analysis does not lie to you and the synchronizer flip-flops are not merged or moved apart. - Keep the two stages close. Most tools have an
ASYNC_REG(or equivalent) attribute that packs the synchronizer flip-flops tightly to maximise settling time.
Get these five right and multi-clock design stops being scary.
Want boilerplate you can paste? The CDC synchronizer generator writes a correct two- or three-flop synchronizer for any width, you can try any of this in the Verilog playground, and the glossary entry on metastability has the one-paragraph theory.