timing-closure
Close FPGA timing on a Vivado design driven by the SynthPilot MCP — iterate synthesis/implementation, diagnose critical paths, and apply the safest constraint/strategy fix until worst negative slack (
Files1
View on GitHubSKILL.mdskills/timing-closure/SKILL.md
GitHubTiming Closure
A disciplined loop that drives a Vivado design to timing closure using the SynthPilot MCP tools. You are the conductor: measure → diagnose → classify → apply the safest fix → re-measure → repeat. Never make a number go green by hiding a real violation.
When to use
- "close timing", "fix WNS/TNS", "why doesn't this meet timing", "resolve setup violations"
- After synthesis or implementation reports negative slack.
When NOT to use
- No project open / no design to analyze → set the project up first (or use
full-flow-demo). - The user wants a one-shot report only, not iteration → just call
report_timing_summary. - Hold (min-delay) violations dominate and routing is already done → these usually need Vivado's own hold-fix; surface that rather than looping.
Prerequisites (verify first)
test_connectionsucceeds (Vivado + Tcl server up).- A project is open (
get_project_info). If only RTL exists, run synthesis first.
The loop
Step 0 — Baseline (measure, don't guess)
- Ensure a timed design exists. If none,
run_synthesis(orrun_synthesis_async+ pollget_run_statusfor large designs). Synthesis WNS is optimistic — true signoff is post-implementation, but synth WNS is a fast first signal. - Read the numbers:
report_timing_summary, thenextract_timing_metricsfor structured WNS/TNS/WHS/THS, failing-endpoint count, and per-clock breakdown. - Record the baseline (WNS, TNS, #failing endpoints, clocks involved). Every later iteration is compared against this. State it to the user.
If WNS ≥ 0 post-implementation: report closed, show the margin, stop. (Post-synth
positive is necessary but not sufficient — confirm with run_implementation.)
Step 1 — Diagnose the worst paths
analyze_critical_pathsandreport_worst_timing_paths→ the top failing paths.- For the worst path,
report_timing_path/report_datapath_delayto split delay into logic vs net vs clock-skew/uncertainty. report_high_fanout_nets→ fanout-driven net delay.get_all_clocks/analyze_clock_domain_timing/report_clock_interaction→ is the failing path intra-clock, or a cross-clock / async path that shouldn't be timed at all?
Step 2 — Classify the failure (this decides the fix)
Map the worst paths to one of these, in this priority order:
| # | Signature | Likely cause | Safe fix |
|---|---|---|---|
| A | Failing path crosses unrelated/async clocks, or is a config/quasi-static reg | Missing exception | set_false_path or set_clock_groups (async) / set_multicycle_path (multi-cycle) — only if genuinely valid |
| B | I/O path failing, no/incorrect input/output delay | Missing I/O constraint | set_input_delay / set_output_delay from the real interface spec |
| C | Big net delay, high fanout | Placement/fanout | implementation strategy (Performance_*), or recommend register duplication / max_fanout |
| D | Big logic delay, deep combinational path, intra-clock | Real logic depth | recommend RTL pipelining/retiming — do NOT fake-pass with exceptions |
| E | Small negative, close to met | Last-mile | set_implementation_strategy Performance_Explore / phys-opt; tighten nothing |
Step 3 — Apply the smallest safe change
- Constraints (A/B): apply the specific exception, then
save_constraints. Before anyset_false_path/set_multicycle_path, state the assumption ("treating CDC path reg_a→reg_b as async; valid only if a synchronizer exists") and get confirmation if the path could be real. - Strategy (C/E):
set_implementation_strategy(e.g. Performance_ExplorePostRoutePhysOpt) /set_synthesis_strategy. Cheapest lever, try before touching RTL. - RTL (D): describe the pipelining/retiming change and the expected slack gain; recommend, don't silently rewrite — hand back to the user to approve RTL edits.
- Apply one class of change per iteration so cause→effect is attributable.
Step 4 — Re-measure
- Re-run the affected stage (
run_implementationfor signoff;run_synthesisif you changed RTL/synth strategy). Use the async variants +get_run_statusfor long runs. - Re-read WNS/TNS via
extract_timing_metrics. Compare to baseline and to the previous iteration. Did it improve, regress, or stall?
Step 5 — Loop or stop
Repeat Steps 1–4 until one stop condition:
- ✅ Closed: WNS ≥ 0 (and WHS ≥ 0) post-implementation → report margin, list the changes applied, save constraints, stop.
- ⛔ No safe move left: remaining failures are class D (real logic depth) or require risky exceptions → stop, present the options + trade-offs, hand to the human.
- ⛔ Stalled: 2 consecutive iterations with no WNS improvement → stop, don't thrash.
- ⛔ Budget: a caller-specified max iterations reached.
Safety rails (do not violate)
- No fake-pass. Never apply
set_false_path/set_multicycle_path/waivers to a path that could be real just to clear a violation. If unsure it's safe, ask. - Evidence before claims. "Timing met" requires a fresh post-implementation
extract_timing_metricsshowing WNS ≥ 0 — never infer it. - Show your work. Each iteration: report what you changed, why (which class), and the before/after WNS/TNS. Keep a running summary table.
- One change class per loop so a regression is traceable.
- Hands off RTL unless the user approves the specific edit.
Output (each iteration + final)
Iter N | change: <what> (class <X>) | WNS <before> → <after> ns | TNS <before> → <after> | failing <n>→<m>
Final: closed/not-closed verdict, the ordered list of changes that worked, remaining risks, and (if not closed) the recommended RTL/architectural next step.