From oh-my-fpga
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.
How this skill is triggered — by the user, by Claude, or both
Slash command
/oh-my-fpga:coverage-closureThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Measure statement / branch / condition coverage from an xsim run, find the
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.
cover property / directed tests; that is out
of scope for this skill. Say so explicitly rather than faking an FSM number.sim_get_coverage
reports code coverage only. Do not present code coverage as functional coverage.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_coverage is a PRO-tier tool. If FREE,
stop and report that coverage requires a Pro license.get_project_info — confirm a project is open (or open_project first).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_compile resolves the
compile order relative to the sim-fileset top. If you skip this, xelab fails
with Cannot find design unit work.<tb>. (Pitfall #1, verified.)Only after all five succeed do you instrument and run.
One change-class per iteration so cause→effect stays attributable. Always measure with a tool before and after acting.
Coverage closure is meaningless without a target. If the user gave none, ask, or propose conventional pre-signoff gates and confirm:
glbl, and verification-only modules are normally excluded).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
"sbc" for gating metrics. Add t only if the user explicitly wants
toggle visibility — never to gate.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.sim_run().sim_run_async() then poll sim_get_sim_status() until COMPLETED.
Do not fetch coverage while it is still RUNNING.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.
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 as name(PARAM=val).uncovered — [{file, line, type, context}]: the exact lines/branches/
conditions never exercised. This is your worklist.report_path — dashboard.html for human review (download for PR/signoff).Report the baseline before touching anything (Show your work).
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.
In priority order:
(* 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.
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.
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 |
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:
sim_get_coverage output — done.Never loop silently. Each iteration reports before→after numbers and what changed.
(* 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.sim_get_coverage output from the current TB+RTL on a passing run. Never
infer, never reuse a stale number, never project.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.Report, in this shape:
summary percentages + per-metric covered/total
from the first sim_get_coverage; note the run passed.uncovered entries: file:line:type →
classification (reachable / config-only / unreachable / out-of-scope) → action.sim_get_coverage summary +
per-module covered/total; explicit PASS/FAIL vs each target. Link
report_path (dashboard.html) for human review.file:line:type + one-line justification), flagged as
assumptions for the user to ratify.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.