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_coverage
reports 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_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.
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. Add t only 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 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.
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 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).
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_coverage output — 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_coverage output 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) —
summary percentages + per-metric covered/total
from the first sim_get_coverage; note the run passed.
- Gap analysis — table of in-scope
uncovered entries: 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_coverage summary +
per-module covered/total; explicit PASS/FAIL vs each target. Link
report_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.
1---2name: coverage-closure3description: 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.4---56# Coverage Closure (Simulation Code Coverage)78Measure statement / branch / condition coverage from an xsim run, find the9exact `file:line:type` that the testbench never exercised, and close the gap by10**adding stimulus** — not by deleting code, lowering the bar, or pragma-ing the11problem away. Coverage is a *necessary* signoff signal, not a *sufficient* one:12100% code coverage with a weak testbench is still a weak testbench. This skill13makes the number honest.1415## When to use1617- "What's my code coverage?" / "How much of the design is covered?"18- "Get coverage to 90% statement / 85% branch before I merge."19- "Which lines / branches are never hit by the testbench?"20- You need a downloadable coverage report for a code review or tape-out gate.21- A regression passes but you suspect it isn't exercising the whole design.2223## When NOT to use2425- **FSM state/transition coverage** — xsim code coverage has **no FSM26 dimension** (verified). Use SVA `cover property` / directed tests; that is out27 of scope for this skill. Say so explicitly rather than faking an FSM number.28- **Functional / assertion coverage** (covergroups, SVA cover) — `sim_get_coverage`29 reports *code* coverage only. Do not present code coverage as functional coverage.30- **Toggle coverage as a gate** — toggle is reported (percentage only) but is31 routinely dragged to single digits by reserved/constant-zero bits. Do not gate on it.32- "Is timing met / CDC clean / lint clean?" — wrong skill (timing-closure,33 cdc-analysis, lint-cleanup). Coverage says nothing about those.34- The user wants you to *delete untested RTL* to raise the percentage — refuse;35 see Safety rails.3637## Prerequisites (verify first)3839Run these before measuring. Do not assume state.40411. `test_connection` — confirm the Tcl server is live inside Vivado. If it42 fails, stop and tell the user to start Vivado + the Tcl server.432. `get_license_status` — `sim_get_coverage` is a **PRO-tier** tool. If FREE,44 stop and report that coverage requires a Pro license.453. `get_project_info` — confirm a project is open (or `open_project` first).464. `list_simulation_files` — confirm the **testbench and DUT are in the sim47 fileset**. Coverage is only as good as the TB that drives it.485. `set_simulation_top("<tb_top>")` — **mandatory.** `sim_compile` resolves the49 compile order relative to the sim-fileset top. If you skip this, xelab fails50 with `Cannot find design unit work.<tb>`. (Pitfall #1, verified.)5152Only after all five succeed do you instrument and run.5354## Methodology5556One change-class per iteration so cause→effect stays attributable. Always57*measure with a tool* before and after acting.5859### 1. Establish the target (ask if unstated)6061Coverage closure is meaningless without a target. If the user gave none, ask, or62propose conventional pre-signoff gates and confirm:63- statement ≥ 90%, branch ≥ 85%, condition ≥ 80% (typical).64Record which **metrics** gate and which **modules** are in scope (third-party /65generated IP, `glbl`, and verification-only modules are normally excluded).6667### 2. Instrument the compile6869```70set_simulation_top("<tb_top>") # step 5 above, re-assert71sim_compile(top_module="<tb_top>", coverage_types="sbc") # s=stmt b=branch c=cond72```73- Use `"sbc"` for gating metrics. Add `t` only if the user explicitly wants74 toggle visibility — never to gate.75- If compile fails, read `sim_get_compile_log(stage="compile")`, fix the RTL/TB76 or fileset/order issue, and recompile. A coverage number from a partial77 compile is invalid.7879### 3. Run the simulation (coverage flushes automatically)8081- Short TB (< ~30 s expected): `sim_run()`.82- Long TB: `sim_run_async()` then poll `sim_get_sim_status()` until `COMPLETED`.83 Do not fetch coverage while it is still `RUNNING`.8485No special flag is needed to flush coverage — the normal run/quit path records it86(verified). **Confirm the run actually completed and the TB passed** before87trusting coverage; a TB that aborts early under-counts coverage and the number is88a lie. If the TB has failures, fix those first — coverage of a broken run is meaningless.8990### 4. Measure — get structured coverage9192```93sim_get_coverage(detail_level=2, uncovered_limit=200)94```95Returns JSON:96- `summary` — global statement/branch/condition (and toggle if compiled).97- `per_module` — per source module, `{covered, total, pct}` for each metric.98 Counts are a **cross-instance union**: a line counts as hit if *any*99 instance hit it (verified). Parameterized modules appear as `name(PARAM=val)`.100- `uncovered` — `[{file, line, type, context}]`: the exact lines/branches/101 conditions never exercised. This is your worklist.102- `report_path` — `dashboard.html` for human review (download for PR/signoff).103104Report the baseline before touching anything (Show your work).105106### 5. Classify each gap before writing any stimulus107108Read each `uncovered` entry against the source (`read_file_lines`) and classify109it using the table below. **Do not write a single line of TB code until every110in-scope gap is classified.** The fix is determined by the class, not by the111percentage.112113### 6. Close the gap — smallest safe action first114115In priority order:1161. **Reachable-but-untested** → add directed stimulus to the **testbench**117 (recommend the edit; show the new case to the user before/while applying).1182. **Reachable-only-from-another-config** → add a parameterization / second TB119 instance that exercises that branch.1203. **Genuinely unreachable defensive code / reserved bits** → do **not** fake it.121 xsim ignores inline `(* coverage_off *)` and `// xilinx coverage off` (both122 verified to have zero effect), so you cannot pragma it away. Surface it as a123 **documented exclusion** for the user's CI exclusion list (file:line:type),124 stated as an explicit assumption — never silently drop it.125126Apply **one class of change per iteration** (e.g., "added reset-deassert case"),127then re-measure, so you can attribute the delta.128129### 7. Re-measure (mandatory after every change)130131Re-run steps 2→4 (`sim_compile` → run → `sim_get_coverage`). A coverage claim is132only valid against **fresh** `sim_get_coverage` output from the current TB+RTL.133Never report a projected or inferred percentage.134135## Classification table (uncovered → cause → safe action)136137| `uncovered` symptom | Likely cause | Smallest safe action |138|---|---|---|139| Statement in `else`/error branch never hit | TB never drives the error/edge input | Add directed TB case that forces that input; re-measure |140| Branch hit one direction only (e.g. `if` taken, `else` not) | Missing complementary stimulus | Drive both polarities of the condition in the TB |141| Condition term never both-T/both-F | Sub-expression of a compound `&&`/`\|\|` never toggled | Add stimulus toggling that specific operand |142| Reset/de-assert path uncovered | TB asserts reset but never exercises de-assert sequence | Extend TB reset sequence |143| Whole module 0% | DUT instance not driven, or TB top wrong | Re-check `set_simulation_top`, fileset, and that the module is instantiated/driven |144| `MISSING ELSE` with `line: null` | `if` with no `else` (synthetic branch) | Normal; add `else`-direction stimulus or document as N/A |145| 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 |146| `glbl` / generated IP / verification module uncovered | Out-of-scope module | Exclude from scope (state it); do not chase |147| Toggle very low | Reserved/idle bits | Expected; do not gate toggle |148149## Loop + STOP conditions150151```152measure (sim_get_coverage)153 └─ classify every in-scope uncovered entry154 └─ pick ONE class of reachable gap155 └─ add TB stimulus (recommend / apply)156 └─ re-compile + re-run + re-measure157 └─ compare before/after → repeat158```159160**STOP and hand back to the human when:**161- All gating metrics meet the target on **fresh** `sim_get_coverage` output — done.162- Remaining uncovered items are all classified **unreachable / out-of-scope**, and163 meeting the literal target would require deleting code or excluding reachable164 paths — present the exclusion list + costs; do not force the number green.165- Two consecutive iterations add stimulus but the gating metric does not move166 (thrash) — stop, present the residual gaps and options, ask for direction.167- A gap can only be closed by an RTL change (genuinely dead/unreachable logic168 that the user may want removed) — recommend it; do not silently edit RTL.169- The TB itself is failing/aborting — coverage is invalid; escalate the TB bug170 before any coverage work.171172Never loop silently. Each iteration reports before→after numbers and what changed.173174## Safety rails (do not violate)175176- **No fake-pass.** Never raise the percentage by deleting RTL, narrowing scope177 to dodge a real path, lowering the agreed target, or excluding a *reachable*178 line/branch. Exclusions are only for provably-unreachable code, stated as an179 explicit assumption, and confirmed with the user when reachability is uncertain.180- **Pragmas don't work here — don't pretend they do.** xsim ignores181 `(* coverage_off *)` and `// xilinx coverage off` (verified, zero effect). Do182 not insert them to "handle" reserved bits; that is a silent fake-pass. Exclude183 in the CI list instead.184- **Evidence before claims.** "Coverage met" / "target reached" REQUIRES fresh185 `sim_get_coverage` output from the current TB+RTL on a **passing** run. Never186 infer, never reuse a stale number, never project.187- **Coverage ≠ correctness.** High code coverage does not mean the design is188 correct, timing-clean, or functionally verified. State this when reporting.189- **Smallest safe change first.** Close gaps by adding **testbench stimulus**190 before touching DUT RTL. Recommend RTL/architectural edits; do not apply them191 silently.192- **One change-class per iteration** so each delta is attributable.193- **Don't gate toggle.** Report it if asked; never let it block.194- **FSM / functional coverage are out of scope** — say so; do not substitute code195 coverage for them.196- **Real tools only.** Use exactly: `test_connection`, `get_license_status`,197 `get_project_info`, `open_project`, `list_simulation_files`,198 `set_simulation_top`, `sim_compile`, `sim_run`, `sim_run_async`,199 `sim_get_sim_status`, `sim_get_compile_log`, `sim_get_coverage`,200 `read_file_lines`, `update_file`/`replace_in_file` (TB edits). If a needed201 capability (e.g. FSM coverage) doesn't exist, say so — never invent a tool.202203## Output204205Report, in this shape:2062071. **Scope & target** — gating metrics + thresholds; modules in/out of scope.2082. **Baseline (with evidence)** — `summary` percentages + per-metric `covered/total`209 from the first `sim_get_coverage`; note the run passed.2103. **Gap analysis** — table of in-scope `uncovered` entries: `file:line:type` →211 classification (reachable / config-only / unreachable / out-of-scope) → action.2124. **Iterations** — for each: what stimulus was added (one class), and the213 before→after delta on the gating metric (fresh tool output each time).2145. **Final state (with evidence)** — final `sim_get_coverage` `summary` +215 per-module `covered/total`; explicit PASS/FAIL vs each target. Link216 `report_path` (`dashboard.html`) for human review.2176. **Documented exclusions** — any unreachable items proposed for the CI218 exclusion list (`file:line:type` + one-line justification), flagged as219 assumptions for the user to ratify.2207. **Caveats** — restate: code coverage ≠ functional correctness; no FSM221 coverage; toggle not gated. Surface any residual gaps and the options/costs if222 the literal target wasn't honestly reachable.