Coverage closure
Measure RTL simulation code coverage (statement / branch / condition) and iteratively close it toward an explicit target by adding targeted testbench stimulus. Use when the user asks "what is my code coverage", "how much is covered", "coverage closure", "improve coverage to N%", "which lines aren't tested", "are there untested branches", or wants a sign-off-grade coverage report before tape-out / merge. Requires the SynthPilot MCP server with Vivado + the Tcl server running (xsim/xelab/xcrg, validated on Vivado 2024.2). PRO-tier tool sim_get_coverage. This is a methodology playbook, not code: it tells the AI which real SynthPilot tools to call, in what order, and how to decide what to do with the numbers.From its SKILL.md
npx -y skills add LNC0831/oh-my-fpga --skill coverage-closureAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 16 stars16 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
12.4 KB, as published. Nobody here has run it
Coverage Closure (Simulation Code Coverage)
Measure statement / branch / condition coverage from an xsim run, find the
exact file:line:type that the testbench never exercised, and close the gap by
adding stimulus — not by deleting code, lowering the bar, or pragma-ing the
problem away. Coverage is a necessary signoff signal, not a sufficient one:
100% code coverage with a weak testbench is still a weak testbench. This skill
makes the number honest.
When to use
- "What's my code coverage?" / "How much of the design is covered?"
- "Get coverage to 90% statement / 85% branch before I merge."
- "Which lines / branches are never hit by the testbench?"
- You need a downloadable coverage report for a code review or tape-out gate.
- A regression passes but you suspect it isn't exercising the whole design.
When NOT to use
- FSM state/transition coverage — xsim code coverage has no FSM
dimension (verified). Use SVA
cover property/ directed tests; that is out of scope for this skill. Say so explicitly rather than faking an FSM number. - Functional / assertion coverage (covergroups, SVA cover) —
sim_get_coveragereports code coverage only. Do not present code coverage as functional coverage. - Toggle coverage as a gate — toggle is reported (percentage only) but is routinely dragged to single digits by reserved/constant-zero bits. Do not gate on it.
- "Is timing met / CDC clean / lint clean?" — wrong skill (timing-closure, cdc-analysis, lint-cleanup). Coverage says nothing about those.
- The user wants you to delete untested RTL to raise the percentage — refuse; see Safety rails.
Prerequisites (verify first)
Run these before measuring. Do not assume state.
test_connection— confirm the Tcl server is live inside Vivado. If it fails, stop and tell the user to start Vivado + the Tcl server.get_license_status—sim_get_coverageis a PRO-tier tool. If FREE, stop and report that coverage requires a Pro license.get_project_info— confirm a project is open (oropen_projectfirst).list_simulation_files— confirm the testbench and DUT are in the sim fileset. Coverage is only as good as the TB that drives it.set_simulation_top("<tb_top>")— mandatory.sim_compileresolves the compile order relative to the sim-fileset top. If you skip this, xelab fails withCannot find design unit work.<tb>. (Pitfall #1, verified.)
Only after all five succeed do you instrument and run.
Methodology
One change-class per iteration so cause→effect stays attributable. Always measure with a tool before and after acting.
1. Establish the target (ask if unstated)
Coverage closure is meaningless without a target. If the user gave none, ask, or propose conventional pre-signoff gates and confirm:
- statement ≥ 90%, branch ≥ 85%, condition ≥ 80% (typical).
Record which metrics gate and which modules are in scope (third-party /
generated IP,
glbl, and verification-only modules are normally excluded).
2. Instrument the compile
set_simulation_top("<tb_top>") # step 5 above, re-assert
sim_compile(top_module="<tb_top>", coverage_types="sbc") # s=stmt b=branch c=cond
- Use
"sbc"for gating metrics. Addtonly if the user explicitly wants toggle visibility — never to gate. - If compile fails, read
sim_get_compile_log(stage="compile"), fix the RTL/TB or fileset/order issue, and recompile. A coverage number from a partial compile is invalid.
3. Run the simulation (coverage flushes automatically)
- Short TB (< ~30 s expected):
sim_run(). - Long TB:
sim_run_async()then pollsim_get_sim_status()untilCOMPLETED. Do not fetch coverage while it is stillRUNNING.
No special flag is needed to flush coverage — the normal run/quit path records it (verified). Confirm the run actually completed and the TB passed before trusting coverage; a TB that aborts early under-counts coverage and the number is a lie. If the TB has failures, fix those first — coverage of a broken run is meaningless.
4. Measure — get structured coverage
sim_get_coverage(detail_level=2, uncovered_limit=200)
Returns JSON:
summary— global statement/branch/condition (and toggle if compiled).per_module— per source module,{covered, total, pct}for each metric. Counts are a cross-instance union: a line counts as hit if any instance hit it (verified). Parameterized modules appear asname(PARAM=val).uncovered—[{file, line, type, context}]: the exact lines/branches/ conditions never exercised. This is your worklist.report_path—dashboard.htmlfor human review (download for PR/signoff).
Report the baseline before touching anything (Show your work).
5. Classify each gap before writing any stimulus
Read each uncovered entry against the source (read_file_lines) and classify
it using the table below. Do not write a single line of TB code until every
in-scope gap is classified. The fix is determined by the class, not by the
percentage.
6. Close the gap — smallest safe action first
In priority order:
- Reachable-but-untested → add directed stimulus to the testbench (recommend the edit; show the new case to the user before/while applying).
- Reachable-only-from-another-config → add a parameterization / second TB instance that exercises that branch.
- Genuinely unreachable defensive code / reserved bits → do not fake it.
xsim ignores inline
(* coverage_off *)and// xilinx coverage off(both verified to have zero effect), so you cannot pragma it away. Surface it as a documented exclusion for the user's CI exclusion list (file:line:type), stated as an explicit assumption — never silently drop it.
Apply one class of change per iteration (e.g., "added reset-deassert case"), then re-measure, so you can attribute the delta.
7. Re-measure (mandatory after every change)
Re-run steps 2→4 (sim_compile → run → sim_get_coverage). A coverage claim is
only valid against fresh sim_get_coverage output from the current TB+RTL.
Never report a projected or inferred percentage.
Classification table (uncovered → cause → safe action)
uncovered symptom | Likely cause | Smallest safe action |
|---|---|---|
Statement in else/error branch never hit | TB never drives the error/edge input | Add directed TB case that forces that input; re-measure |
Branch hit one direction only (e.g. if taken, else not) | Missing complementary stimulus | Drive both polarities of the condition in the TB |
| Condition term never both-T/both-F | Sub-expression of a compound &&/|| never toggled | Add stimulus toggling that specific operand |
| Reset/de-assert path uncovered | TB asserts reset but never exercises de-assert sequence | Extend TB reset sequence |
| Whole module 0% | DUT instance not driven, or TB top wrong | Re-check set_simulation_top, fileset, and that the module is instantiated/driven |
MISSING ELSE with line: null | if with no else (synthetic branch) | Normal; add else-direction stimulus or document as N/A |
| Reserved/constant-zero bits, dead defensive default | Genuinely unreachable | Document as exclusion for the user's CI list — do not pragma, do not gate; ask before excluding if reachability is uncertain |
glbl / generated IP / verification module uncovered | Out-of-scope module | Exclude from scope (state it); do not chase |
| Toggle very low | Reserved/idle bits | Expected; do not gate toggle |
Loop + STOP conditions
measure (sim_get_coverage)
└─ classify every in-scope uncovered entry
└─ pick ONE class of reachable gap
└─ add TB stimulus (recommend / apply)
└─ re-compile + re-run + re-measure
└─ compare before/after → repeat
STOP and hand back to the human when:
- All gating metrics meet the target on fresh
sim_get_coverageoutput — done. - Remaining uncovered items are all classified unreachable / out-of-scope, and meeting the literal target would require deleting code or excluding reachable paths — present the exclusion list + costs; do not force the number green.
- Two consecutive iterations add stimulus but the gating metric does not move (thrash) — stop, present the residual gaps and options, ask for direction.
- A gap can only be closed by an RTL change (genuinely dead/unreachable logic that the user may want removed) — recommend it; do not silently edit RTL.
- The TB itself is failing/aborting — coverage is invalid; escalate the TB bug before any coverage work.
Never loop silently. Each iteration reports before→after numbers and what changed.
Safety rails (do not violate)
- No fake-pass. Never raise the percentage by deleting RTL, narrowing scope to dodge a real path, lowering the agreed target, or excluding a reachable line/branch. Exclusions are only for provably-unreachable code, stated as an explicit assumption, and confirmed with the user when reachability is uncertain.
- Pragmas don't work here — don't pretend they do. xsim ignores
(* coverage_off *)and// xilinx coverage off(verified, zero effect). Do not insert them to "handle" reserved bits; that is a silent fake-pass. Exclude in the CI list instead. - Evidence before claims. "Coverage met" / "target reached" REQUIRES fresh
sim_get_coverageoutput from the current TB+RTL on a passing run. Never infer, never reuse a stale number, never project. - Coverage ≠ correctness. High code coverage does not mean the design is correct, timing-clean, or functionally verified. State this when reporting.
- Smallest safe change first. Close gaps by adding testbench stimulus before touching DUT RTL. Recommend RTL/architectural edits; do not apply them silently.
- One change-class per iteration so each delta is attributable.
- Don't gate toggle. Report it if asked; never let it block.
- FSM / functional coverage are out of scope — say so; do not substitute code coverage for them.
- Real tools only. Use exactly:
test_connection,get_license_status,get_project_info,open_project,list_simulation_files,set_simulation_top,sim_compile,sim_run,sim_run_async,sim_get_sim_status,sim_get_compile_log,sim_get_coverage,read_file_lines,update_file/replace_in_file(TB edits). If a needed capability (e.g. FSM coverage) doesn't exist, say so — never invent a tool.
Output
Report, in this shape:
- Scope & target — gating metrics + thresholds; modules in/out of scope.
- Baseline (with evidence) —
summarypercentages + per-metriccovered/totalfrom the firstsim_get_coverage; note the run passed. - Gap analysis — table of in-scope
uncoveredentries:file:line:type→ classification (reachable / config-only / unreachable / out-of-scope) → action. - Iterations — for each: what stimulus was added (one class), and the before→after delta on the gating metric (fresh tool output each time).
- Final state (with evidence) — final
sim_get_coveragesummary+ per-modulecovered/total; explicit PASS/FAIL vs each target. Linkreport_path(dashboard.html) for human review. - Documented exclusions — any unreachable items proposed for the CI
exclusion list (
file:line:type+ one-line justification), flagged as assumptions for the user to ratify. - Caveats — restate: code coverage ≠ functional correctness; no FSM coverage; toggle not gated. Surface any residual gaps and the options/costs if the literal target wasn't honestly reachable.