name: cdc-audit description: >- Audit and resolve clock-domain-crossing (CDC) risks on a design: enumerate every clock crossing, classify each by structure (synchronizer present/absent, single-bit vs bus, async reset, reconvergence), and apply the smallest safe fix (synchronizer RTL, set_clock_groups / set_max_delay -datapath_only / set_bus_skew constraints) without ever waiving a real crossing. Invoke when the user says "cdc analysis", "check clock domain crossings", "metastability review", "are my async crossings safe", "audit clock crossings", "fix CDC violations", "report_cdc is showing critical", "do I need a synchronizer here", or asks about gray-code, handshake, async FIFO, or reset-domain-crossing (RDC) safety. Requires the SynthPilot MCP server with Vivado open and the Tcl server running (TCP:9999); CDC reporting needs an elaborated or synthesized design.
CDC Audit (Clock-Domain-Crossing Risk Review & Resolution)
A methodology playbook for finding, classifying, and safely resolving clock-domain crossings using the SynthPilot MCP tools that drive Vivado. CDC bugs do not show up as timing failures — a metastable crossing can pass STA and still fail silently in silicon. This skill treats CDC as a structural correctness problem first and a constraint problem second.
The governing rule for this entire skill: a CDC constraint does not fix a CDC bug.
set_clock_groups, set_false_path, and set_max_delay -datapath_only only tell
the timing engine to stop reporting a crossing — they do nothing to add the
synchronizer hardware that actually prevents metastability propagation. Constraints
are applied only after the crossing is confirmed to be structurally safe, or
explicitly waived with a stated assumption.
When to use
- User asks for a "CDC analysis", "check clock domain crossings", "metastability review", or "reset domain crossing (RDC) check".
- A design has two or more asynchronous (unrelated) clocks and you need to prove every crossing is either synchronized or safely a quasi-static/false path.
report_cdc/check_cdc_lintis reporting Critical or Warning severity items and the user wants them triaged and resolved.- Pre-tapeout / pre-release signoff that must include a clean CDC report.
- A clock-crossing FIFO, handshake, or gray-code counter was just added and the user wants it verified.
- Intermittent / "works on the bench sometimes" hardware bugs where metastability is a suspect.
When NOT to use
- Single-clock design (or all clocks synchronous/derived from one source with a
known phase relationship). There are no async crossings to audit — confirm this
with
get_all_clocks/report_clock_interactionand stop. - Setup/hold timing failures within a single domain → use the
timing-closureskill. CDC is about unrelated clocks; intra-domain WNS violations are a different problem. - Functional logic bugs that happen to be near a crossing → simulate first
(
sim_run); do not blame CDC without evidence of a real domain crossing. - Pure lint/style cleanup with no async clocks → use the
lintskill. - You only have an RTL file and no project / no Vivado connection — CDC structural analysis needs an elaborated or synthesized netlist. Set the project up first.
Prerequisites (verify first)
Run these before any analysis. Do not proceed on assumption.
- MCP/Vivado link is live —
test_connection. If it fails, the Tcl server inside Vivado is not running; stop and tell the user to start it. Optionallyvivado_versionto record the tool version in the report. - A project is open with the design loaded —
get_project_infoandlist_source_files. If nothing is open,open_project(orcreate_project+add_source_file+set_top_module). - Clocks are actually defined —
get_all_clocks. CDC analysis is meaningless without clock constraints; if clocks are missing (everything shows as one inferred clock, orreport_cdclater says "no inter-clock paths"), the timing environment is incomplete. Note this and, before drawing conclusions, ensure primary clocks are constrained viacreate_clock_constraint/create_generated_clock. A missing clock definition silently hides real crossings — treat an incomplete clock set as a blocker, not a pass. - A design exists to report on.
report_cdcneeds an elaborated or synthesized design open. The cheapest entry point is structural lint (check_cdc_lint), which works on RTL. For the authoritative crossing-by-crossing report you need a synthesized design — if none exists, runrun_synthesis_asyncand pollget_run_status(see Methodology step 2). Thenopen_synthesized_design.
State explicitly which of these are satisfied before continuing.
Methodology (measure → classify → smallest-safe-fix → re-measure)
Work the crossings in severity order (Critical → Warning). Change one crossing-class per iteration so cause and effect stay attributable.
1. Inventory the clock topology (understand the battlefield)
get_all_clocks— list every clock, its period, and source.get_clock_info— per-clock detail (waveform, generated-vs-primary) where needed.report_clock_networks— physical clock network / buffering view.report_clock_interaction— the key map: which clock pairs have paths between them and whether each pair is timed, partial-false-path, or asynchronous. This is your ground truth for "which crossings exist." Note every pair flagged asynchronous or with inter-clock paths.
From this, build the set of clock-pair crossings you must account for. A crossing not on this list does not exist; a crossing on this list must be classified before you may close the audit.
2. Get an authoritative CDC report
- RTL-level first pass (fast, no synth):
check_cdc_lintand/orrun_full_lint_check→get_lint_violations. This catches missing synchronizers and obvious structural issues early and cheaply. - Netlist-level authoritative pass: ensure a synthesized design is open. If not:
run_synthesis_async, then pollget_run_statusuntil complete (multi-minute — do not block; use the async + poll pattern). On completion checkcheck_synthesis_issues/get_synthesis_warnings.open_synthesized_design.report_cdc— the full crossing-by-crossing CDC report with severity (Critical / Warning / Info) and endpoint detail.
- For large reports, narrow with
report_cdc_filtered(e.g., by clock pair or severity) and drill into specific endpoints withreport_cdc_details.
Record the raw counts (Critical / Warning / Info) as the audit baseline. Do not editorialize them yet.
3. Classify every Critical/Warning crossing (classification table below)
For each flagged crossing, identify: source clock, destination clock, signal
width (single-bit vs bus), whether a synchronizer is present, whether it is a
control vs data path, and whether reconvergence or reset-domain-crossing
is involved. Use report_cdc_details and, where you need to see the RTL,
read_file / read_file_lines on the relevant source. Map each to a structure type
using the table below.
4. Apply the smallest safe fix for that crossing class
Follow the fix-precedence ladder strictly:
- Already-safe → constrain only. If a correct synchronizer already exists, the
crossing is structurally fine; the only action is to make the timing tool treat it
correctly (
set_clock_groups -asynchronous, orset_max_delay -datapath_only/set_bus_skewfor buses — see table). This is a constraint, not a design change. - Missing/incorrect synchronizer → recommend RTL. Do not silence it. Propose
the specific RTL fix (2-FF synchronizer for single-bit control; async FIFO or
gray-code + handshake for buses; reset synchronizer for RDC) and present it to the
user. Recommend the edit; do not silently apply RTL. If the user approves, use
read_file→replace_in_file/update_file(orcreate_source_filefor a new synchronizer module), then add it to the project and re-elaborate. - Genuinely a non-crossing (quasi-static, same effective domain, tied constant) →
waive with a stated assumption. Only here may you use
set_false_path/set_clock_groups/waive_lint_violation, and only after explicitly stating the assumption (e.g., "config register is written once at reset, never while the destination clock is active"). If the path could be real and you are unsure, ask the user before waiving.
Apply exactly one crossing-class of change, then save_constraints (for constraint
changes).
5. Re-measure on the same evidence source
Re-run the same tool you measured with: report_cdc (and report_clock_interaction
to confirm the pair is now correctly treated) for netlist changes;
check_cdc_lint / get_lint_violations for RTL changes. Confirm the targeted
crossing's severity dropped and that no new Critical/Warning appeared elsewhere.
RTL changes require re-synthesis (step 2) before the netlist CDC report is valid
again — synthesis warnings alone are necessary-not-sufficient.
6. (Signoff) Prove it on the implemented design
For a signoff claim that "CDC is clean," constraints must hold through place &
route. Run run_implementation_async, poll get_run_status, then
open_implemented_design and:
report_cdc(final, post-implementation) — the authoritative clean/not-clean verdict.report_clock_interaction— confirm every async pair is correctly grouped.report_drcandreport_methodology— catch CDC-adjacent methodology violations (e.g.,CDC-*,TIMING-*rule hits) that the CDC report alone may not surface.- For bus crossings constrained with
set_bus_skew, confirm the skew constraint passed inreport_timing_summary(a skew constraint that fails routing is not a fix).
A success claim requires fresh post-implementation tool output, never inference from RTL or synthesis.
Classification table (symptom → structure → safe fix)
| # | Symptom from report_cdc / report_cdc_details | Structural cause | Smallest safe fix (in precedence order) |
|---|---|---|---|
| C1 | 1-bit control crossing, no synchronizer (direct reg→reg between async clocks) | Missing 2-FF synchronizer — true metastability risk | RTL: recommend a 2-flop synchronizer on the destination clock. Do NOT false-path. After fix + re-synth, optionally set_max_delay -datapath_only src→sync-FF1. |
| C2 | 1-bit crossing WITH a recognized 2-FF synchronizer | Already safe; tool just needs to be told it's async | Constraint only: set_clock_groups -asynchronous between the two clocks (or set_max_delay -datapath_only on the input to FF1). No RTL change. |
| C3 | Multi-bit bus crossing, all bits sampled by a synchronizer | Bus bits can skew → recombine to a wrong value (data coherency hazard) | RTL: recommend gray-code (counters), or async FIFO, or a handshake-gated MUX-capture (req/ack). A per-bit 2-FF synchronizer on a bus is a BUG, not a fix — call it out. |
| C4 | Bus crossing via async FIFO / handshake-qualified capture | Already coherent by construction | Constraint: set_bus_skew on the bus (bound inter-bit skew) and/or set_max_delay -datapath_only; set_clock_groups -asynchronous for the clock pair. No RTL change. |
| C5 | Reset asserted in one domain, deasserted into another (RDC) | Reset deassertion not synchronized → metastable release, partial reset | RTL: recommend an async-assert / sync-deassert reset synchronizer per destination domain. Verify with check_async_reset. |
| C6 | Reconvergence: two synchronized signals from the same source recombine downstream | Independent 2-FF synchronizers resolve on different cycles → glitch/illegal combo | RTL: recommend re-architecting to cross a single encoded bus (gray/handshake) once, then decode in the destination domain. Constraints cannot fix this. |
| C7 | "Quasi-static" / config-register crossing (written once, static thereafter) | Not a dynamic crossing — but only if the "written once" assumption holds | Waive with stated assumption: set_false_path (or waive_lint_violation). State the assumption explicitly; ask the user to confirm the register is truly static during destination-clock operation. |
| C8 | Clocks reported as crossing but actually synchronous/related (common divider, known phase) | Not an async crossing at all | Constraint: define the relationship (create_generated_clock) so STA times it normally; do NOT set_clock_groups -asynchronous (that would hide a real timed path). |
| C9 | Crossing into/out of an IP (clk-wiz, async FIFO IP, AXI clock converter) | IP handles CDC internally if configured correctly | Verify IP config (get_fifo_config / get_clocking_wizard_config / report_ip_status); apply the IP's recommended set_* constraints. Don't hand-synchronize an IP boundary that's already safe. |
Rule of thumb: single-bit control → 2-FF synchronizer; multi-bit data → FIFO / gray-code / handshake; reset → async-assert+sync-deassert. A bank of independent 2-FF synchronizers on a bus is itself a defect (C3), not a remedy.
Resolution loop & STOP conditions
for each crossing in (Critical first, then Warning), one class at a time:
measure → report_cdc / report_cdc_details (or check_cdc_lint for RTL)
classify → map to C1..C9 above
fix → smallest safe change in the precedence ladder
(constraint-only > recommend-RTL > waive-with-assumption)
re-measure→ same tool; confirm THIS crossing dropped AND nothing new appeared
(RTL change ⇒ re-synthesize before the netlist CDC report is valid again)
STOP and hand back to the human when ANY of:
- Clean:
report_cdcon the implemented design shows 0 Critical, 0 unexplained Warning, every remaining waiver has a written assumption, andreport_methodologyshows no openCDC-*violations. Report the fresh evidence. - RTL change required: the safe fix is a synchronizer / FIFO / re-architecture. Present the exact recommended RTL and wait for user approval before editing. Never silently rewrite RTL to make a number go green.
- Ambiguous quasi-static waiver: a crossing might be real and you cannot prove it is static from the design alone — present it and ask the user before waiving.
- Missing clock definitions: crossings are hidden because clocks aren't constrained. Stop, report the gap, and ask for / propose the clock constraints first — do not declare "clean" on an under-constrained design.
- Constraint can't make a bus crossing meet skew even though the structure is
correct (C4): present the options (tighter floorplan, deeper FIFO, slower crossing
clock) with their costs and stop. Do not relax/remove the
set_bus_skewjust to pass. - Two adversarial iterations with no net reduction in Critical/unexplained Warning: stop thrashing; summarize remaining items with options and trade-offs.
Do not loop forever and do not "resolve" by deletion of evidence (removing waivers' assumptions, false-pathing real crossings, disabling lint rules).
Safety rails (do not violate)
- No fake-pass. Never apply
set_false_path,set_clock_groups -asynchronous,set_max_delay,disable_lint_rule, orwaive_lint_violationto a crossing that could be a real, unsynchronized metastability path just to clear the report. A constraint silences STA; it adds no hardware. Waivers/false-paths are allowed ONLY for confirmed non-crossings (C7/C8) with a written assumption, and if there's any doubt the path is real, ask the user first. - Constraint ≠ correctness.
set_clock_groups -asynchronousis correct ONLY when a real synchronizer already protects the crossing (C2/C4). Applying it to an unsynchronized crossing (C1/C3) hides a live bug. Confirm the synchronizer exists (report_cdc_details/read_file) before grouping. - Smallest safe change first. Constraints/strategy before RTL. Only escalate to RTL when no safe constraint exists, and then recommend, do not silently apply.
- Recommend RTL, never sneak it. Synchronizer insertion, FIFO swap-in, and reset re-architecture change design behavior. Present the diff and get approval.
- One crossing-class per iteration so cause→effect is attributable; re-measure after each.
- Evidence before claims. "CDC clean," "metastability-safe," and "crossings
resolved" each REQUIRE fresh tool output — and for signoff, fresh
post-implementation
report_cdc+report_methodology. Synthesis-level or lint-level results are necessary-not-sufficient. Never infer cleanliness from a source edit. - A bus is not single bits. Never recommend per-bit 2-FF synchronizers on a multi-bit data bus (C3) — that is a coherency bug. Use FIFO / gray-code / handshake.
- Don't hide real timing. Don't
set_clock_groups -asynchronouson clocks that are actually related (C8); define the generated-clock relationship instead so the timed path stays timed. - Bounded effort. When safe options are exhausted, surface the remaining items with trade-offs and hand back. Do not thrash.
Output (report format)
Produce a CDC audit report with:
- Environment — Vivado version, project, top module, clock list (from
get_all_clocks), and which design stage the verdict is based on (RTL-lint / synthesized / implemented). State it plainly if no post-implementation evidence was collected — then the verdict is "preliminary," not "signoff." - Crossing inventory — table of clock-pair crossings from
report_clock_interaction, each marked async / synchronous / handled-by-IP. - Baseline counts — Critical / Warning / Info from the first
report_cdc(orcheck_cdc_lint), quoted from tool output. - Per-crossing findings — for each Critical/Warning: source→dest clock, width, classification (C1–C9), structural diagnosis, and the action taken or recommended.
- Changes applied — constraints added (with the exact
set_*rationale) and any RTL edits the user approved, each tied to its crossing. - Waivers — every
set_false_path/set_clock_groups/waive_lint_violationwith its explicit stated assumption and whether the user confirmed it. - Final verdict with fresh evidence — post-implementation
report_cdccounts +report_methodologyCDC-rule status (andreport_timing_summaryfor anyset_bus_skew). Quote the numbers. If post-implementation evidence is absent, say "NOT signed off" and list exactly what remains. - Open items / trade-offs — anything requiring a human decision (RTL approval, ambiguous waiver, structural re-architecture), with options and costs.