A single missed clock domain crossing can cost $2–5 million in silicon re-spins. This checklist is the systematic defence every chip team must execute before tapeout. Organised into five phases — RTL, constraints, tool sign-off, simulation, and physical design — it covers all 20 items that separate a clean CDC closure from a catastrophic field failure.
CDC verification is the only major sign-off category where neither RTL simulation nor static timing analysis catches all violations by default. Simulation uses a single-state model — metastability never appears. STA cannot flag asynchronous paths it does not know about. That leaves a gap which only a structured, tool-assisted review fills.
The consequences of gaps are severe:
SpyGlass and Questa CDC are excellent, but they do not automatically detect: (1) false synchronizers — two cascaded flops that are not in the same standard-cell column and therefore have hold risk, (2) protocol violations when a sender changes data too fast for a handshake to gate it, (3) power-domain interactions that create glitches on gated clocks. The checklist covers these blind spots explicitly.
Phase 1 is the foundation. If the RTL architecture has structural CDC mistakes, no amount of constraint tuning or tool waivering will save the chip. Each item below corresponds to a class of real tapeout failures.
Before any tool runs, the design team must produce a CDC path list — a spreadsheet or database entry identifying every signal that crosses from one clock domain to another. This list must cover: source domain, destination domain, signal name, synchronization method used, and waiver status. If a crossing is not in this list, it will not be reviewed.
Passing a multi-bit bus directly between clock domains is the most common and most dangerous CDC mistake. Individual bits of the bus can arrive at the destination flop at different times relative to the destination clock. Even if each individual bit avoids metastability, the bus as a whole can be sampled in a partially updated state — half old value, half new value.
The only safe solutions for multi-bit buses are: gray code encoding (only one bit changes per transition), FIFO synchronization (full asynchronous FIFO), or handshake protocol (request/acknowledge with data held stable during crossing).
A 32-bit address bus crossing from a 100 MHz CPU domain to a 200 MHz memory controller domain. The CPU changes the address from 0x0000_0000 to 0xFFFF_FFFF. The memory controller samples the bus mid-transition and reads 0x0000_FFFF — a completely wrong address. The memory write corrupts the wrong location. This passes simulation (simulation is deterministic) and fails randomly in silicon.
The number of synchronizer stages required depends on the destination clock frequency:
| Destination clock frequency | Minimum stages | Rationale |
|---|---|---|
| Up to 250 MHz | 2 stages | Standard 2-FF synchronizer. Each stage gives one clock period for metastability to resolve. At 250 MHz, 4 ns per stage is sufficient. |
| 251 MHz to 500 MHz | 3 stages | Shorter clock period (2 ns at 500 MHz) means less resolution time per stage. Adding a third stage reduces MTBF from unacceptable to >1000 years. |
| Above 500 MHz | 3+ stages, custom cell | Standard synchronizer cells may not be characterised above 500 MHz. Use foundry-provided high-speed synchronizer macros with library-level metastability characterisation. |
A synchronizer output is only safe to use after it has been registered in the destination domain. Feeding the synchronizer output directly into combinational logic (e.g., an AND gate or a mux select) before registering creates a glitch path. If the synchronizer output takes more than one destination clock cycle to settle in an extreme metastability event, the combinational logic can produce a glitch that propagates through the design before the synchronizer actually resolves.
The rule: always register first, then decode. Never use a synchronizer output combinationally.
Asynchronous FIFOs are the standard solution for data flow across clock domains, but their read and write pointers must be gray coded before they cross. A binary counter changes multiple bits simultaneously at each increment. A gray code counter changes exactly one bit per increment, which means a synchronizer on each bit is safe — even if the destination samples the pointer mid-transition, it will read either the old pointer or the new pointer, never a corrupted combination.
// Write pointer: binary counter -> gray code -> synchronise to read domain
// Rule: ALWAYS sync the gray pointer, never the binary pointer
module ptr_gray_sync #(parameter PTR_W = 4) (
input logic rclk, rrst_n,
input logic [PTR_W-1:0] wptr_bin, // from write domain
output logic [PTR_W-1:0] rptr_gray_sync // to read domain logic
);
logic [PTR_W-1:0] wptr_gray;
logic [PTR_W-1:0] sync_s1, sync_s2;
// Binary to gray conversion (write domain, happens before CDC)
assign wptr_gray = (wptr_bin >> 1) ^ wptr_bin;
// Two-stage synchroniser in read domain
always_ff @(posedge rclk or negedge rrst_n) begin
if (!rrst_n) begin
sync_s1 <= '0;
sync_s2 <= '0;
end else begin
sync_s1 <= wptr_gray; // stage 1: metastability can occur here
sync_s2 <= sync_s1; // stage 2: resolved value propagates here
end
end
assign rptr_gray_sync = sync_s2;
endmodule
Resets are asynchronous signals that can themselves cause metastability. Every clock domain must have its own reset synchronizer that de-asserts reset synchronously with the local clock. Asserting reset (going to 0) can be asynchronous — it is safe to assert reset at any time. But de-asserting reset must be synchronous to prevent logic from waking up in different cycles across a distributed design.
// Reset synchroniser: async assert, synchronous de-assert
// Instantiate one per clock domain
module reset_sync (
input logic clk,
input logic rst_n_async, // raw async reset from system
output logic rst_n_sync // synchronised reset for this domain
);
logic [1:0] sync_chain;
// Async assert: rst_n_async going low forces chain to 0 immediately
// Sync de-assert: chain only propagates 1 on clock edges
always_ff @(posedge clk or negedge rst_n_async) begin
if (!rst_n_async)
sync_chain <= 2'b00;
else
sync_chain <= {sync_chain[0], 1'b1};
end
assign rst_n_sync = sync_chain[1];
endmodule
Clock-gating enables are among the most dangerous CDC signals because their metastability directly corrupts a clock. If a gating enable signal crosses a clock domain boundary without a synchronizer, a metastable enable can produce a glitched clock — one that has a short runt pulse. This runt pulse can trigger flip-flops downstream at the wrong time, creating state corruption that is indistinguishable from random hardware faults.
Rule: any signal used as input to an ICG (integrated clock gate) must be generated in, or synchronised to, the domain whose clock it controls.
Incorrect constraints are the silent killer of CDC verification. A tool that does not know about a crossing cannot check it. A tool that sees an incorrect clock definition will produce false violations and false passes simultaneously.
Every clock in the design must be defined using create_clock or create_generated_clock with accurate period and waveform. Missing clocks are the most common cause of undetected CDC paths — if the tool does not know a clock exists, it cannot trace the timing of signals in that domain.
# Define all independent clocks with accurate periods create_clock -name CLK_CPU -period 4.0 [get_ports clk_cpu] ;# 250 MHz create_clock -name CLK_MEM -period 2.5 [get_ports clk_mem] ;# 400 MHz create_clock -name CLK_IO -period 10.0 [get_ports clk_io] ;# 100 MHz create_clock -name CLK_PCIE -period 4.0 [get_ports clk_pcie] ;# 250 MHz # Generated clocks from PLLs create_generated_clock -name CLK_DSP \ -source [get_ports clk_cpu] \ -divide_by 1 -multiply_by 3 \ [get_pins pll_inst/clk_dsp_out] ;# 750 MHz
set_clock_groups -asynchronous is the critical constraint that tells the tool two clocks have no phase relationship. Without it, the tool attempts timing analysis across the CDC path — which always passes (because the tool picks the best-case phase relationship) giving completely false confidence that the crossing is timed.
# Declare all asynchronous clock domain pairs
# Every combination of independent clocks must appear here
set_clock_groups -asynchronous \
-group {CLK_CPU} \
-group {CLK_MEM} \
-group {CLK_IO} \
-group {CLK_PCIE}
# If CLK_DSP is derived from CLK_CPU via a PLL with known ratio,
# do NOT put them in the same asynchronous group.
# Instead, use set_max_delay with correct margin:
# set_max_delay -datapath_only 4.0 -from [get_clocks CLK_CPU] \
# -to [get_clocks CLK_DSP]
For intentional CDC paths that have been properly synchronized, the constraint must explicitly tell STA not to time the combinational path between source and destination flops. Use set_max_delay -datapath_only rather than set_false_path when you still want hold checking on the path — which you always want for synchronizer stage 2.
Run the CDC tool's clock audit report and verify zero undefined clocks. Any unconstrained register is invisible to CDC analysis. Common sources of missing clocks: test clocks added late in the flow, gated clock outputs not constrained with create_generated_clock, and board-level clocks that enter through I/O cells.
Phase 3 is the formal certification step. The team must run a qualified CDC static analysis tool (SpyGlass CDC, Questa Formal CDC, JasperGold CDC) and achieve a clean report — not just a low-violation-count report.
The target is zero open violations — not "violations reviewed and suppressed." A violation that has been suppressed without a waiver justification is an open violation. Every finding the tool raises must be in one of three states: fixed in RTL, covered by a properly reviewed waiver, or escalated for management sign-off.
Waivers are not approvals to ignore violations. Each waiver document must contain:
Acceptable: "Signal pcie_cfg_done is a one-time static signal set during power-on initialization before any CDC transfers begin. It never changes after the first clock edge of clk_cpu. Verified by simulation test cdc_init_tb.sv lines 45–120 confirming the signal is stable for all subsequent CDC operations."
Rejected: "Signal assumed stable." or "Protocol ensures safety." without citing specific verification evidence.
For safety-critical or security-critical CDC paths, static analysis is not sufficient. Run a formal equivalence or property check that proves the synchronizer protocol is implemented correctly. JasperGold CDC or VC Formal can verify properties such as: "data at destination is always a valid snapshot of data at source" and "no glitch exists on synchronizer output."
Most CDC tools can generate a coverage report showing which CDC crossings have been exercised by the testbench. All CDC paths must appear in the coverage report as exercised. An uncovered CDC path is a path that was never stress-tested — even if the tool reports no structural violations, an unexercised path may have a protocol bug that only appears under a specific transaction sequence.
CDC simulation goes beyond standard functional testing. It requires dedicated tests that specifically target the timing uncertainty of asynchronous crossings.
Questa CDC and Synopsys VCS both support metastability injection mode — where the simulator randomly delays the resolution of synchronizer outputs by one additional cycle. This simulates a worst-case metastability event. Run your full regression suite in metastability injection mode. Any test that fails in injection mode but passes normally has found a real CDC protocol bug.
# Questa CDC -- enable metastability injection for CDC sign-off sim
# This randomly adds 1-cycle delay to synchronizer outputs
# Any test failure in this mode = real CDC protocol bug
vsim -cdc_inject_meta 1 \
-cdc_meta_seed 42 \
work.tb_top_cdc
# Run for at least 10x the normal regression duration
# Metastability injection requires more cycles to expose bugs
run -all
# Check the injection report
cdc report -meta_injections
A CDC design that works at a 2:1 clock ratio may fail at a 3:1 or 7:3 ratio. The handshake timing and FIFO depth assumptions may be correct at one ratio but fail at another. Verify all combinations:
| Test scenario | Why it matters |
|---|---|
| Source faster than destination (e.g. 2:1) | Destination may miss transitions if data changes faster than sync latency allows |
| Source slower than destination (e.g. 1:3) | FIFO may drain faster than it fills; empty condition must be handled correctly |
| Nearly identical frequencies (e.g. 99:100) | Worst case for gray code pointer crossing — maximum phase drift accumulates over time |
| Min/max PVT frequency variation | Frequency tolerance affects crossing timing at corner conditions |
In a 4-state simulation (0, 1, X, Z), an unprotected CDC crossing will produce X at the destination. Run the full simulation with X-propagation checking enabled and verify that no X values appear on destination-domain signals after reset de-assertion. Any X on a data bus, control signal, or state machine input that originated from a CDC crossing is a direct indicator of a missing synchronizer.
Phase 5 is often overlooked in logical verification — but a synchronizer that is correct in RTL can fail in silicon if it is placed incorrectly by the physical design tool.
The two (or three) flip-flops of a synchronizer must be placed physically close together — ideally in the same standard-cell column or at least within a few micrometers of each other. This is critical for two reasons:
Implementation: add a dont_touch attribute and a placement constraint (DEF fence or Tcl set_dont_touch_placement) on every synchronizer instance before running placement.
# Constrain synchronizer flop placement in Cadence Innovus
# Prevents the placer from separating synchronizer stages
# Mark all sync flops as dont_touch (do not move post-placement)
set sync_insts [get_cells -hierarchical -filter "is_synchronizer == true"]
foreach inst $sync_insts {
set_dont_touch $inst true
}
# Create a placement fence around each synchronizer pair
# Ensures stage1 and stage2 flops are within 2 microns of each other
foreach_in_collection sync_grp [get_synchronizer_groups] {
set bbox [get_sync_group_bbox $sync_grp]
create_place_fence -name "fence_$sync_grp" \
-bbox $bbox \
-type hard \
-cells [get_cells -of_objects $sync_grp]
}
After placement and routing, run a targeted hold timing check specifically on the output of synchronizer stage 2. A hold violation here is not just a timing issue — it means the synchronizer is functionally broken. The stage-2 flip-flop samples both the metastable output of stage 1 (which may still be resolving) and the next value of stage 1 in the same cycle. This produces exactly the kind of glitch that the synchronizer was designed to prevent.
Hold violations on synchronizers must be fixed with buffer insertion on the stage-1 to stage-2 path, not waived. There is no acceptable waiver for a hold violation on a synchronizer.
Use this table as a sign-off document. Every row must show a status of PASS before the CDC closure is complete.
| # | Phase | Checklist item | Risk if missed | Verified by |
|---|---|---|---|---|
| 01 | RTL | All CDC paths identified and documented in path list | HIGH | CDC path spreadsheet review |
| 02 | RTL | No direct multi-bit bus crossing without synchronization | HIGH | SpyGlass rule cdc_sample |
| 03 | RTL | Synchronizer stage count correct (<=250 MHz: 2, >250 MHz: 3) | HIGH | RTL code review + tool check |
| 04 | RTL | No combinational logic on synchronized signals before re-registering | HIGH | SpyGlass rule cdc_combo_logic |
| 05 | RTL | Gray code used for all multi-bit FIFO pointer crossings | HIGH | RTL review + simulation |
| 06 | RTL | Reset synchronizer present for each clock domain | MED | CDC path list + tool rule |
| 07 | RTL | No clock-gating enable crossing without synchronization | HIGH | SpyGlass rule cdc_glitch |
| 08 | SDC | All clocks defined with correct frequency and waveform | HIGH | SDC audit report: 0 undefined clocks |
| 09 | SDC | set_clock_groups -asynchronous for all independent clock pairs | HIGH | SDC review + STA report |
| 10 | SDC | false_path or set_max_delay -datapath_only for all intentional CDC paths | MED | STA report: no unconstrained CDC |
| 11 | SDC | Zero missing clock definitions in CDC tool clock audit | HIGH | Tool clock audit report |
| 12 | Tool | SpyGlass / Questa CDC: 0 open violations | HIGH | Tool report sign-off sheet |
| 13 | Tool | All waivers reviewed and signed by senior engineer | HIGH | Waiver database with signatures |
| 14 | Tool | Formal CDC proof complete for all critical paths | MED | JasperGold / VC Formal report |
| 15 | Tool | CDC coverage report shows 100% of crossings exercised | MED | Tool coverage report |
| 16 | Sim | Metastability injection simulation run with no failures | HIGH | Questa -cdc_inject_meta regression |
| 17 | Sim | All clock ratio combinations and corner frequencies tested | MED | Regression matrix sign-off |
| 18 | Sim | Zero X propagation from CDC crossings post-reset | HIGH | X-propagation sim report |
| 19 | PD | Synchronizer cells placed in same column (placement fence) | HIGH | Layout review + placement report |
| 20 | PD | No hold violation on synchronizer stage-2 output | HIGH | Post-route hold timing report |
Every waiver in the CDC tool represents a violation that the team has decided not to fix in RTL. This decision must be rigorous. Use the following criteria to evaluate each waiver before sign-off:
| Criterion | Required evidence |
|---|---|
| Path is static during operation | Simulation log showing signal has not changed value for the entire duration after initialization. Formal proof that the signal is constant in all reachable states after reset. |
| Protocol guarantees data stability | Handshake waveform showing data is held stable for at least N destination clock cycles before and after enable assertion. N must be >= synchronizer latency + 1. |
| Signal is a one-way status flag | Architecture document confirming the flag transitions at most once per power cycle (e.g., a PLL lock signal). Simulation showing the flag is never sampled during its transition. |
| Path is in a scan-only context | Confirm the crossing only exists in DFT scan mode and is not reachable in functional mode. DFT analysis report confirming isolation. |
The following waiver justifications are never acceptable and will be rejected on review:
The CDC closure report is the official sign-off document that must be attached to the tapeout checklist. It must contain all of the following sections:
These questions appear regularly in senior VLSI design and verification interviews:
| Question | Key points in your answer |
|---|---|
| How do you know when CDC is closed? | Zero open tool violations, all waivers signed off by senior engineer, metastability injection sim passed, physical placement verified, formal proof complete for critical paths, closure report issued |
| What is set_clock_groups and why is it mandatory? | Declares clocks asynchronous to each other. Without it, STA times across the crossing using the best-case phase relationship, always passing, giving false confidence on an unprotected path |
| Can you waive a hold violation on a synchronizer? | Never. A hold violation on synchronizer stage 2 means the synchronizer is functionally broken. It must be fixed with buffer insertion. |
| What does metastability injection simulation test? | It simulates a worst-case metastability event by adding one extra cycle of delay to synchronizer outputs randomly. Any test that fails reveals a protocol bug — data was used before it was stable. |
| Why must gray code be used for FIFO pointers? | Binary counters change multiple bits simultaneously. A single-bit-change-per-transition gray code means even if the destination samples mid-transition, it reads either the old or new pointer — never a corrupted combination. |
| What is the risk of combinational logic on a synchronizer output? | If extreme metastability causes stage 2 to take more than one cycle to resolve, combinational logic driven by the stage 2 output produces a glitch that propagates before resolution. Always register first. |
Missing set_clock_groups for asynchronous pairs in the SDC. If the tool does not know two clocks are asynchronous, it tries to meet timing across the CDC path instead of flagging it — giving false confidence that the path is timed correctly when it is completely unprotected.
Three stages. The rule is two stages for up to 250 MHz, three stages above 250 MHz. At 400 MHz the clock period is 2.5 ns per stage, leaving insufficient resolution time with only two stages. The third stage exponentially reduces MTBF to an acceptable level.
A waiver is acceptable when it identifies the exact signal path, explains why the crossing is safe (for example: signal is static during crossing, or a protocol handshake guarantees stability for at least N cycles), names the senior engineer who reviewed it, and cites a specific simulation test or formal proof as verification. Blanket suppression of a rule class without path-specific justification is never acceptable.