Home Knowledge Base Clock domain crossing (CDC)

Clock domain crossing (CDC) is the interface where a signal passes between two parts of a chip running on different clocks — a point where metastability can corrupt data and cause silent, intermittent failures that are nearly impossible to debug in silicon. Every modern SoC has dozens of clock domains (CPU cores at one frequency, memory controller at another, PCIe PHY at a third, always-on power management at a fourth), and every signal that crosses between them is a potential CDC bug. CDC verification consumes 5–15% of total design verification effort and is the #1 source of post-silicon functional bugs that escape pre-silicon simulation.

Why CDC is dangerous — metastability. When a flip-flop samples a signal that is changing (violating setup or hold time), the output can enter a metastable state — an unstable voltage between 0 and 1 that eventually resolves to one value, but takes an unpredictable amount of time. If downstream logic reads the output before it resolves, the "0 or 1" uncertainty propagates as data corruption. Since clocks in different domains are asynchronous (no fixed phase relationship), any signal crossing a domain boundary will inevitably violate timing at some point — metastability is not a maybe, it's a certainty.

The mean time between failures (MTBF) for a single synchronizer:

$$\text{MTBF} = \frac{e^{t_r / \tau}}{f_s \cdot f_d \cdot T_0}$$

where $t_r$ is the resolution time (slack available for metastability to resolve), $\tau$ is the metastability time constant of the flip-flop (~20–50 ps at 5 nm), $f_s$ is the source clock frequency, $f_d$ is the destination clock frequency, and $T_0$ is a technology-dependent constant. A two-flip-flop synchronizer provides $t_r$ equal to one full destination clock period — giving MTBF of thousands of years. A single flip-flop with no margin gives MTBF of seconds to minutes.

The standard solution — synchronizer circuits:

Crossing typeCircuitLatencyWhen to use
Single bit (level)2-FF synchronizer (double-flop)2 destination clocksControl signals, enables, flags
Single bit (pulse)Pulse synchronizer (toggle + 2-FF)2–3 clocksEvent pulses, interrupts
Multi-bit (bus)Gray-code FIFO (async FIFO)2–4 clocksData buses, streaming interfaces
Multi-bit (register)MUX-based handshake (req/ack)4–8 clocksConfiguration registers, slow updates
Multi-bit (memory)Dual-clock FIFO with gray pointers2–4 clocksHigh-throughput data paths (DMA, NoC)
Full bus (protocol)Credit-based or valid/ready with syncVariableNoC links, AXI async bridge

The two-flip-flop synchronizer is the fundamental building block: two back-to-back flip-flops on the destination clock. The first flip-flop may go metastable, but has a full clock period to resolve before the second flip-flop samples it. This reduces the probability of failure exponentially. Three-flop synchronizers are used for higher reliability (automotive, aerospace).

Async FIFO — crossing multi-bit data safely. You cannot simply synchronize each bit of a bus independently (different bits might resolve to different values on different cycles — data corruption). The async FIFO solves this: write data into a dual-port RAM using the source clock, read it using the destination clock, and synchronize only the read/write pointers (encoded in Gray code, so only one bit changes at a time — safe to synchronize bit-by-bit).

CDC verification — finding bugs before silicon:

<svg viewBox="0 0 960 380" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,Segoe UI,Roboto,sans-serif">
<rect width="960" height="380" rx="12" fill="#1a1a17"/>
<text x="480" y="28" fill="#e5e7eb" font-size="16" font-weight="700" text-anchor="middle">Clock domain crossing — 2-FF synchronizer and async FIFO</text>

<!-- Left: 2-FF synchronizer -->
<rect x="30" y="50" width="440" height="150" rx="8" fill="#111318" stroke="#3a3a37" stroke-width="1"/>
<text x="250" y="72" fill="#c9c3f2" font-size="12" text-anchor="middle" font-weight="600">2-FF synchronizer (single-bit crossing)</text>

<!-- Source domain -->
<rect x="50" y="90" width="80" height="40" rx="4" fill="#14312a" stroke="#2f6d55" stroke-width="1.2"/>
<text x="90" y="114" fill="#8fe3bd" font-size="9" text-anchor="middle">Source FF</text>
<text x="90" y="140" fill="#6fbf6f" font-size="8" text-anchor="middle">CLK_A</text>

<!-- Arrow -->
<path d="M130 110 L165 110" stroke="#6f6f6a" stroke-width="1.5" marker-end="url(#cdarr)"/>

<!-- Sync FF 1 (may go metastable) -->
<rect x="170" y="90" width="80" height="40" rx="4" fill="#3a3a28" stroke="#e8d44d" stroke-width="1.5"/>
<text x="210" y="107" fill="#e8d44d" font-size="9" text-anchor="middle">Sync FF1</text>
<text x="210" y="122" fill="#bf4040" font-size="7" text-anchor="middle">(may metastable)</text>
<text x="210" y="140" fill="#e0913a" font-size="8" text-anchor="middle">CLK_B</text>

<!-- Arrow -->
<path d="M250 110 L285 110" stroke="#6f6f6a" stroke-width="1.5" marker-end="url(#cdarr)"/>

<!-- Sync FF 2 (safe output) -->
<rect x="290" y="90" width="80" height="40" rx="4" fill="#2a3a2a" stroke="#6fbf6f" stroke-width="1.5"/>
<text x="330" y="107" fill="#bff0bf" font-size="9" text-anchor="middle">Sync FF2</text>
<text x="330" y="122" fill="#6fbf6f" font-size="7" text-anchor="middle">(resolved)</text>
<text x="330" y="140" fill="#6fbf6f" font-size="8" text-anchor="middle">CLK_B</text>

<!-- Arrow to destination -->
<path d="M370 110 L405 110" stroke="#6f6f6a" stroke-width="1.5" marker-end="url(#cdarr)"/>

<rect x="410" y="95" width="45" height="30" rx="4" fill="#2a2a3a" stroke="#6f6fbf" stroke-width="1"/>
<text x="432" y="114" fill="#c9c3f2" font-size="8" text-anchor="middle">Dest</text>

<!-- Domain boundary -->
<line x1="155" y1="85" x2="155" y2="185" stroke="#bf4040" stroke-width="1.5" stroke-dasharray="4,3"/>
<text x="155" y="195" fill="#bf4040" font-size="8" text-anchor="middle">domain boundary</text>

<!-- Right: Async FIFO -->
<rect x="490" y="50" width="440" height="150" rx="8" fill="#111318" stroke="#3a3a37" stroke-width="1"/>
<text x="710" y="72" fill="#c9c3f2" font-size="12" text-anchor="middle" font-weight="600">Async FIFO (multi-bit crossing)</text>

<!-- Write side -->
<rect x="510" y="95" width="70" height="35" rx="4" fill="#14312a" stroke="#2f6d55" stroke-width="1"/>
<text x="545" y="116" fill="#8fe3bd" font-size="9" text-anchor="middle">Write ptr</text>
<text x="545" y="138" fill="#6fbf6f" font-size="8" text-anchor="middle">CLK_A</text>

<!-- FIFO memory -->
<rect x="610" y="85" width="120" height="55" rx="6" fill="#2a2a3a" stroke="#6f6fbf" stroke-width="1.5"/>
<text x="670" y="108" fill="#c9c3f2" font-size="10" text-anchor="middle">Dual-port</text>
<text x="670" y="123" fill="#c9c3f2" font-size="10" text-anchor="middle">RAM</text>

<!-- Read side -->
<rect x="760" y="95" width="70" height="35" rx="4" fill="#2a3a2a" stroke="#6fbf6f" stroke-width="1"/>
<text x="795" y="116" fill="#bff0bf" font-size="9" text-anchor="middle">Read ptr</text>
<text x="795" y="138" fill="#6fafaf" font-size="8" text-anchor="middle">CLK_B</text>

<!-- Gray-code sync arrows -->
<path d="M760 105 Q740 80 670 80 Q600 80 580 105" stroke="#e8d44d" stroke-width="1" fill="none" stroke-dasharray="3,2" marker-end="url(#cdarr)"/>
<text x="670" y="75" fill="#e8d44d" font-size="7" text-anchor="middle">gray-code ptr sync</text>

<!-- Arrows in/out -->
<path d="M500 112 L510 112" stroke="#6f6f6a" stroke-width="1.2" marker-end="url(#cdarr)"/>
<path d="M830 112 L850 112" stroke="#6f6f6a" stroke-width="1.2" marker-end="url(#cdarr)"/>
<text x="502" y="102" fill="#8a8a86" font-size="7">data in</text>
<text x="852" y="102" fill="#8a8a86" font-size="7">data out</text>

<!-- Domain boundary -->
<line x1="670" y1="82" x2="670" y2="185" stroke="#bf4040" stroke-width="1.5" stroke-dasharray="4,3"/>
<text x="670" y="195" fill="#bf4040" font-size="8" text-anchor="middle">domain boundary</text>

<!-- Bottom: CDC verification flow -->
<rect x="30" y="215" width="900" height="150" rx="8" fill="#111318" stroke="#3a3a37" stroke-width="1"/>
<text x="480" y="238" fill="#e5e7eb" font-size="12" text-anchor="middle" font-weight="600">CDC verification flow (find bugs before silicon)</text>

<rect x="60" y="255" width="180" height="45" rx="5" fill="#2a3a2a" stroke="#6fbf6f" stroke-width="1"/>
<text x="150" y="275" fill="#bff0bf" font-size="9" text-anchor="middle" font-weight="600">Structural CDC check</text>
<text x="150" y="290" fill="#8a8a86" font-size="8" text-anchor="middle">SpyGlass / Conformal CDC</text>

<path d="M240 277 L270 277" stroke="#6f6f6a" stroke-width="1.2" marker-end="url(#cdarr)"/>

<rect x="275" y="255" width="180" height="45" rx="5" fill="#3a3a28" stroke="#e8d44d" stroke-width="1"/>
<text x="365" y="275" fill="#e8d44d" font-size="9" text-anchor="middle" font-weight="600">Formal CDC proof</text>
<text x="365" y="290" fill="#8a8a86" font-size="8" text-anchor="middle">Exhaustive, all interleavings</text>

<path d="M455 277 L485 277" stroke="#6f6f6a" stroke-width="1.2" marker-end="url(#cdarr)"/>

<rect x="490" y="255" width="180" height="45" rx="5" fill="#3a2a3a" stroke="#bf6faf" stroke-width="1"/>
<text x="580" y="275" fill="#dfafdf" font-size="9" text-anchor="middle" font-weight="600">Async simulation</text>
<text x="580" y="290" fill="#8a8a86" font-size="8" text-anchor="middle">Randomized clock ratios</text>

<path d="M670 277 L700 277" stroke="#6f6f6a" stroke-width="1.2" marker-end="url(#cdarr)"/>

<rect x="705" y="255" width="180" height="45" rx="5" fill="#14312a" stroke="#2f6d55" stroke-width="1.5"/>
<text x="795" y="275" fill="#8fe3bd" font-size="9" text-anchor="middle" font-weight="600">CDC clean ✓</text>
<text x="795" y="290" fill="#8a8a86" font-size="8" text-anchor="middle">Ready for tape-out</text>

<text x="480" y="335" fill="#8a8a86" font-size="9" text-anchor="middle">Missing CDC synchronizer = intermittent data corruption in silicon — nearly impossible to debug post-fab</text>
<text x="480" y="352" fill="#6f6f6a" font-size="8" text-anchor="middle">#1 source of post-silicon functional bugs that escape pre-silicon simulation</text>

<defs><marker id="cdarr" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"><path d="M0,0 L6,3 L0,6" fill="none" stroke="#6f6f6a" stroke-width="1"/></marker></defs>
</svg>

CDC in AI chips — many domains, high bandwidth. A modern GPU or AI accelerator has 30–50 independent clock domains: each compute cluster has its own frequency (for per-core DVFS), the HBM PHY runs at a different rate, the PCIe/NVLink SerDes has its own recovered clock, and the power management unit runs on a low-frequency always-on clock. Every data path between these domains — thousands of crossings on a large chip — must have correct synchronization. A single missed crossing can cause silent data corruption during AI training, producing wrong model weights that are almost impossible to trace back to a hardware bug.

CDC and the CFS platform. The CFS clock-tree keyword covers the clock distribution within a single domain; CDC covers the boundaries between them. The verification keyword covers the formal and simulation methods used to prove CDC correctness. Together they represent the timing-integrity side of chip design — ensuring signals arrive when expected, across every clock boundary, for the lifetime of the product.

clock domain crossingcdcsynchronizertwo flopgray codemetastabilitymtbf

Explore 500+ Semiconductor & AI Topics

From EUV lithography to CUDA optimization — search the full knowledge base or chat with our AI assistant.