From oh-my-fpga
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.
How this skill is triggered — by the user, by Claude, or both
Slash command
/oh-my-fpga:cdc-auditThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
A methodology playbook for finding, classifying, and safely resolving clock-domain
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.
report_cdc / check_cdc_lint is reporting Critical or Warning severity
items and the user wants them triaged and resolved.get_all_clocks / report_clock_interaction and stop.timing-closure
skill. CDC is about unrelated clocks; intra-domain WNS violations are a different
problem.sim_run); do not blame CDC without evidence of a real domain crossing.lint skill.Run these before any analysis. Do not proceed on assumption.
test_connection. If it fails, the Tcl server
inside Vivado is not running; stop and tell the user to start it. Optionally
vivado_version to record the tool version in the report.get_project_info and
list_source_files. If nothing is open, open_project (or create_project +
add_source_file + set_top_module).get_all_clocks. CDC analysis is meaningless
without clock constraints; if clocks are missing (everything shows as one
inferred clock, or report_cdc later says "no inter-clock paths"), the timing
environment is incomplete. Note this and, before drawing conclusions, ensure
primary clocks are constrained via create_clock_constraint /
create_generated_clock. A missing clock definition silently hides real
crossings — treat an incomplete clock set as a blocker, not a pass.report_cdc needs 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, run run_synthesis_async and poll
get_run_status (see Methodology step 2). Then open_synthesized_design.State explicitly which of these are satisfied before continuing.
Work the crossings in severity order (Critical → Warning). Change one crossing-class per iteration so cause and effect stay attributable.
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.
check_cdc_lint and/or
run_full_lint_check → get_lint_violations. This catches missing synchronizers
and obvious structural issues early and cheaply.run_synthesis_async, then poll get_run_status until complete (multi-minute —
do not block; use the async + poll pattern). On completion check
check_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.report_cdc_filtered (e.g., by clock pair or
severity) and drill into specific endpoints with report_cdc_details.Record the raw counts (Critical / Warning / Info) as the audit baseline. Do not editorialize them yet.
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.
Follow the fix-precedence ladder strictly:
set_clock_groups -asynchronous, or set_max_delay -datapath_only /
set_bus_skew for buses — see table). This is a constraint, not a design change.read_file → replace_in_file / update_file (or create_source_file for a new
synchronizer module), then add it to the project and re-elaborate.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).
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.
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_drc and report_methodology — catch CDC-adjacent methodology violations
(e.g., CDC-*, TIMING-* rule hits) that the CDC report alone may not surface.set_bus_skew, confirm the skew constraint
passed in report_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.
| # | 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.
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:
report_cdc on the implemented design shows 0 Critical, 0
unexplained Warning, every remaining waiver has a written assumption, and
report_methodology shows no open CDC-* violations. Report the fresh evidence.set_bus_skew just to
pass.Do not loop forever and do not "resolve" by deletion of evidence (removing waivers' assumptions, false-pathing real crossings, disabling lint rules).
set_false_path, set_clock_groups -asynchronous,
set_max_delay, disable_lint_rule, or waive_lint_violation to 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.set_clock_groups -asynchronous is 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.report_cdc + report_methodology. Synthesis-level or
lint-level results are necessary-not-sufficient. Never infer cleanliness from a
source edit.set_clock_groups -asynchronous on clocks that
are actually related (C8); define the generated-clock relationship instead so the
timed path stays timed.Produce a CDC audit report with:
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."report_clock_interaction, each marked async / synchronous / handled-by-IP.report_cdc
(or check_cdc_lint), quoted from tool output.set_* rationale) and any
RTL edits the user approved, each tied to its crossing.set_false_path / set_clock_groups / waive_lint_violation
with its explicit stated assumption and whether the user confirmed it.report_cdc counts +
report_methodology CDC-rule status (and report_timing_summary for any
set_bus_skew). Quote the numbers. If post-implementation evidence is absent,
say "NOT signed off" and list exactly what remains.npx claudepluginhub lnc0831/oh-my-fpga --plugin oh-my-fpgaGuides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.