From oh-my-fpga
Author a correct Vivado XDC timing constraint set from scratch — primary clocks, generated (derived) clocks, input/output delays, and timing exceptions (clock groups / false paths / multicycle paths) — for a design that has no constraints yet, or whose constraints are incomplete (unconstrained ports, missing generated clocks, unrelated async clocks reported as failing paths). Invoke when the user says: "write constraints", "author xdc", "set up timing constraints", "no constraints yet", "constrain my clocks", "add input/output delays", "my clocks are asynchronous / I need clock groups", or whenever check_timing / report_timing_summary reports unconstrained clocks, ports, or combinational endpoints. This is the FIRST step before any timing-closure or signoff work — WNS/TNS numbers are meaningless until the design is correctly constrained. Requires the SynthPilot MCP server connected to an OPEN Vivado session with the Tcl server running (test_connection must pass) and a project that elaborates and synthesizes.
How this skill is triggered — by the user, by Claude, or both
Slash command
/oh-my-fpga:constraints-authoringThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
A methodology playbook for writing a **correct, complete, and honest** XDC
A methodology playbook for writing a correct, complete, and honest XDC constraint set. The goal of constraints is to tell the timing engine the truth about the design's real timing requirements — not to make WNS look good. A constraint that lies (a too-loose clock, a false_path on a real path) produces a green report that does not survive on silicon. Correctness first, optimism never.
This skill stops at a timed, sane, signed-off constraint set plus a clear list of any assumptions you had to make. It does NOT attempt timing closure (that is the timing-closure skill's job). It hands off once timing is defined, not met.
check_timing / report_timing_summary reports unconstrained clocks,
ports, or combinational endpoints (e.g. "There are N input/output ports with
no input/output delay specified").set_clock_groups.set_input_delay / set_output_delay)
for a board interface.set_clock_groups silences the cross-domain timing report; it
does not make the crossing safe. Use the cdc-analysis skill to verify
synchronizers exist before/after you group clocks. (This skill will tell you
when to stop and call it.)create_io_constraint; that part is in-scope here but if there is no timing
question at all this skill is overkill.Run these BEFORE writing anything. Do not skip — a wrong period or a clock on a non-existent port wastes a full synthesis.
test_connection — confirm the MCP↔Vivado Tcl bridge is alive. If this
fails, stop: nothing below will work.get_project_info — confirm a project is open and know the target part
(speed grade matters; constraints are part-agnostic but I/O delay budgets are
not).list_source_files + set_top_module (if not set) — you must constrain the
top-level ports. Constraining ports that aren't on top is a silent no-op.list_constraint_files — find what already exists. If there is an XDC, you
are editing, not creating from scratch; read_file it first and do not
duplicate clock definitions.read_file on the top
module: every clock port and its frequency, which clocks are generated (PLL/
MMCM/divider), which clock domains are asynchronous to each other, and the
board-level I/O timing (source-synchronous vs system-synchronous). If any of
these is unknown, ask the user — do not guess clock periods.You author constraints at elaboration/RTL level (against port and pin names), but you VERIFY them against a real synthesized netlist where pin paths (
pll/CLKOUT0,reg[*]/C) actually exist. Authoring uses port names; verification needsopen_synthesized_design.
Author in dependency order: clocks → generated clocks → I/O delays → exceptions. Each later layer references names defined by an earlier one, so order is not optional. One constraint class per step, then re-measure, so you can attribute every change in the timing report to exactly one edit.
check_timing — the single most important diagnostic. It reports unconstrained
clocks, missing input/output delays, combinational loops, and (critically)
appends the full port inventory so you can cross-reference which ports still
lack constraints. Read it carefully; it is your worklist.get_all_clocks — what clocks (if any) Vivado has already inferred. Empty =
truly from scratch. Non-empty = some clocks exist (maybe from IP); do not
redefine them.get_clock_info(<name>) per clock to see period and
source — confirm they match intent before building generated clocks on top.If
check_timingcannot run because nothing is elaborated, runrun_synthesis_asynconce to get a netlist, pollget_run_status, thenopen_synthesized_design. You need a netlist to see real clock pins anyway.
For every physical clock input port (an oscillator/board clock entering the
FPGA), one create_clock_constraint:
create_clock_constraint(clock_port="<top clock port>", period_ns=<period>, name="<clk_name>")1000 / freq_MHz. Use the real board frequency from intent, not a
hopeful one. A 100 MHz oscillator is period_ns=10.0.name so later steps and reports are readable.After defining all primary clocks, get_all_clocks to confirm each appears with
the intended period.
Any clock derived inside the FPGA needs an explicit generated clock so the engine knows the true frequency/phase relationship:
create_generated_clock(name="<clk_out>", source="<pll>/CLKIN1", target="<pll>/CLKOUT0", multiply_by=M, divide_by=D)
— M/D from the IP's actual configuration (e.g. 100→200 MHz is multiply_by=2).create_generated_clock(name="clk_div2", source="<clk_in port>", target="<div_reg>/Q", divide_by=2).phase=<deg> (e.g. a 90° clock for source-sync DDR).invert=True.multiply_by/divide_by for simple ratios; edges/edge_shift only for
genuinely non-integer or asymmetric waveforms.Source/target are PIN paths, not ports — they only exist on the synthesized
netlist. Author them, then prove them in Step 7 with report_clock_networks /
get_all_clocks. If a generated clock shows up with the wrong period after
synthesis, the M/D or the target pin is wrong — fix it, don't paper over it.
Many IP cores (Clocking Wizard, MIG, transceivers) ship their OWN generated clocks in their
.xdc. Checkreport_clock_networks/ the IP's constraints first; do not redefine a clock the IP already constrains, or you create a conflicting double definition.
Now that all clocks exist, declare their relationships — this is where most "failing paths" on a fresh design come from, and where the biggest honesty risk lives.
set_clock_groups(group1="<clkA>", group2="<clkB>", relationship="asynchronous").
This tells timing not to analyze paths between them.
HARD GATE: only do this for a crossing that is genuinely asynchronous AND
has a proper CDC synchronizer. Grouping clocks does not synchronize them — it
hides the report. See the classification table and Safety rails. If unsure
whether a synchronizer exists, stop and ask / run cdc-analysis; do not
group blindly.relationship="exclusive"
(or logically_exclusive/physically_exclusive) — not asynchronous.set_clock_uncertainty(<jitter_ns>, clock="<clk>") to model PLL jitter/
board skew if you have a real number; otherwise leave the tool's default.For every top-level data port that crosses the chip boundary and isn't a clock, add a delay so timing knows the external budget:
set_input_delay(port_name="<port>", delay_ns=<board_delay>, clock_name="<launch clk>").set_output_delay(port_name="<port>", delay_ns=<board_delay>, clock_name="<capture clk>").create_io_constraint(port_name, pin, io_standard) if the board pinout isn't
already in a pin XDC.Exceptions tell the tool to relax analysis. Each one is a claim about the design's behavior. Every exception requires a one-line justification, and if the path could be real, ask the user before adding it. Prefer the narrowest tool.
set_false_path(from_signal="<rst>", from_type="port").
Justification: "reset is recovered through a synchronizer; the async assert edge
has no timing requirement." If there is NO reset synchronizer, this is a bug —
do not false_path it; flag it.set_multicycle_path(N, from_signal="<src_reg>[*]/C", to_signal="<dst_reg>[*]/D")
with a justification of why N cycles are guaranteed by the protocol. Hold MCP is
auto-adjusted; verify it.set_max_delay(<period_ns>, from_node, to_node, datapath_only=True)
(and/or set_bus_skew for a multi-bit bus through a handshake/gray-code). This
is safer than set_clock_groups because it still bounds the path.set_clock_groups relationship="logically_exclusive" over a pile of false paths.Never add an exception merely because a path is failing setup/hold. A failing real path is a timing-closure problem, not a constraints problem.
Constraints written against RTL are unproven until checked against real clock networks and pins. Do this every time:
save_constraints(file_name="timing.xdc") then ensure it's in the project
(add_constraint_file / list_constraint_files). Or author the file directly
with create_constraint_file(file_name, content) for full control of ordering.run_synthesis_async → poll get_run_status until done → open_synthesized_design.check_timing — the unconstrained-objects count must drop to the intended
set (zero, except objects you deliberately left as no-timing async). Any
remaining "no clock"/"no input delay" is an unfinished constraint, not a pass.get_all_clocks + report_clock_networks — confirm every clock (primary
and generated) appears with the correct period and source. A generated
clock at the wrong frequency = wrong M/D.report_clock_interaction — the clock-pair matrix. Confirm: related clocks are
Timed, intentionally-async pairs are Ignored/partial because you grouped
them on purpose, and no pair you expected to be timed is silently ignored. This
is the honesty check on Step 4.report_timing_summary — sanity only. At this stage you care that paths are
being analyzed correctly, not that WNS is positive. Do not declare success
on WNS here; that's closure/signoff.save_constraints the verified file; confirm it's the one in the project.Use this to decide which constraint, not just that a constraint is needed.
| Symptom (from check_timing / clock reports) | Likely cause | Smallest correct fix | Honesty gate |
|---|---|---|---|
| "Clock has no create_clock" | Primary clock undefined | create_clock_constraint with real period | Period must be the real board freq, not a wish |
| PLL/MMCM output clock missing or at source period | Generated clock undefined | create_generated_clock(multiply_by/divide_by from IP config) | M/D must match the IP's actual config |
| N input/output ports "no input/output delay" | I/O unbudgeted | set_input_delay/set_output_delay from interface spec | Numbers from datasheet/board — never fabricated |
| Inter-clock paths failing between unrelated clocks | Async domains analyzed as if related | set_clock_groups(... asynchronous) | ONLY if a real CDC synchronizer exists — else CDC bug, do not group |
| Mux-selected clocks both timed against each other | Tool can't infer mutual exclusivity | set_clock_groups(... logically_exclusive) | The mux must truly make them mutually exclusive |
| Path from slow config reg to fast logic fails setup | Real multicycle relationship | set_multicycle_path(N, ...) | N must be guaranteed by the protocol, justified |
| Async reset/async input path fails | Async assert with sync recovery | set_false_path(from_signal=rst, from_type=port) | A reset synchronizer must exist; else flag bug |
| Generated clock at wrong frequency after synth | Wrong M/D or wrong target pin | Fix create_generated_clock args | Re-verify with report_clock_networks — do not waive |
| Combinational loop reported | RTL bug, not a constraint | Do not constrain around it | Report to user — this is an RTL fix |
Constraints are correct only when proven on a netlist. Loop one constraint class at a time:
check_timing + report_clock_interaction + report_clock_networks.
Record: unconstrained-object count, which clock pairs are Timed/Ignored,
generated-clock periods.run_synthesis_async → get_run_status →
open_synthesized_design → repeat Step 1's measurements.check_timing shows zero unintended unconstrained objects; every
clock present at correct period/source in report_clock_networks; every clock
pair in report_clock_interaction is Timed-or-intentionally-Ignored with a
stated reason. → Finalize (Step 8). Note: this is constraints complete, not
timing met.set_false_path,
set_clock_groups(asynchronous), set_multicycle_path, or
waive_lint_violation to make a real path's violation disappear. An exception
is a factual claim about the hardware; if it could be false, ask the user
first and record the assumption. WNS going green because you cut a path is not
closure — it's a hidden bug.set_clock_groups(asynchronous) removes the
cross-domain path from analysis; it does NOT add a synchronizer. Only group
domains that already have proper CDC structures. When you add any async group,
you MUST flag it for cdc-analysis in the Output.set_max_delay(datapath_only=True) rather than wholesale set_clock_groups
when you only need a skew bound.create_clock_constraint on a PLL/
MMCM output or a derived clock — that double-defines and corrupts analysis. Those
are always create_generated_clock.report_clock_networks / its XDC), do not add
a conflicting definition.check_timing + report_clock_networks + report_clock_interaction
output in this session — never inferred from the RTL or from a prior run. Any
signoff-level timing claim (timing met) requires post-implementation
evidence and belongs to the timing-closure/signoff flow, not here.Return a concise, evidence-backed report:
report_clock_networks (proves it, doesn't just assert it).set_clock_groups / uncertainty, with the
relationship and a one-line reason; the relevant rows of
report_clock_interaction showing the matrix is as intended.check_timing unconstrained-object count
(before → after, ideally → 0 unintended), confirmation all clocks appear
correctly, and that the clock-pair matrix matches intent. Quote the numbers.Honesty footer to include verbatim when relevant: "Constraints are complete and verified against the synthesized netlist. This means timing is now correctly DEFINED — it does not mean timing is MET. Any async clock groups I added assume a working CDC synchronizer exists; verify with cdc-analysis before signoff."
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.