Generate Code Coverage Report with QuestaSim
Code coverage answers a single question: was every part of my RTL exercised by my tests? Not was it correct — that's checked by the testbench. Coverage just tells you whether stimulus reached each statement, branch, and toggle. A passing test suite with 60% line coverage means there's a substantial chunk of your design that nothing has ever poked. This guide is the QuestaSim flow for collecting it, merging it across regressions, and finding the holes.
Coverage flow at a glance
flowchart LR A[RTL + TB] -->|vlog +cover| B[Compiled DB] B -->|vsim -coverage| C[run.ucdb] C -->|vcover merge| D[merged.ucdb] D -->|vcover report -html| E[HTML report] D -->|vcover ranking| F[Test ranking] E --> G[Holes analysis] G --> H[Add tests / exclude legitimate dead code] H --> A
Coverage types — what you're actually measuring
| Flag | Type | What it counts | Why it matters |
|---|---|---|---|
s | Statement | Lines of RTL executed at least once | Cheapest signal that a chunk of code is reachable at all |
b | Branch | Taken/not-taken arms of if, case | Two tests can hit every line but miss half the branches |
c | Condition | Each operand of a multi-term boolean | Catches when only part of a complex if condition is exercised |
e | Expression | RHS expressions covered with all input combos | Stronger than condition; useful for arithmetic and decoders |
f | FSM | States visited and state-to-state transitions | The single most useful type for control-heavy designs |
t | Toggle | Every signal bit went 0→1 and 1→0 | Catches dead bits — buses partially driven, unused decode |
The minimum runnable flow
# 1. Create work library
vlib work
# 2. Compile with full coverage instrumentation
vlog -coveropt 3 +cover +acc mod16_counter.v mod16_tb.v
# 3. Simulate, collect coverage, save to UCDB on exit
vsim -coverage -vopt work.mod16_tb -c \
-do "coverage save -onexit -directive -codeAll mod16_cov; run -all; exit"
# 4. Generate HTML report
vcover report -html mod16_cov
The covhtmlreport/ folder it produces is what you open in a browser. Statement, branch, condition, FSM, and toggle metrics all show with drill-down to the offending line.
What each flag means
| Option | Effect |
|---|---|
-coveropt 3 | Optimization level 3 — preserves enough info for accurate condition/expression coverage |
+cover | Enable all coverage types: s,b,c,e,f,t |
+cover=sbf | Enable just statement, branch, FSM (cheaper; useful for nightly regressions) |
+acc | Preserve design hierarchy access for debug; required for accurate coverage |
-coverage | Collect coverage during simulation |
-onexit -directive -codeAll | Save full UCDB on exit (rather than only the last collection) |
Merging coverage across a regression
One simulation run almost never hits everything. The realistic flow is to run a regression of N tests, each producing a UCDB, then merge them:
vcover merge merged.ucdb test1.ucdb test2.ucdb test3.ucdb ...
vcover report -html -htmldir final_report merged.ucdb
Use shell expansion when you have many tests:
vcover merge regression.ucdb runs/*/coverage.ucdb
Excluding legitimate dead code
Not every uncovered line is a missing test. Synthesis-only branches (`ifdef SYNTHESIS), DFT logic that only fires in test mode, and assertion-only code paths legitimately aren't exercised by functional sims. Mark them with an exclusion file so they don't pollute the metric:
# exclusions.do
coverage exclude -src dut/dft.v -line 45-72
coverage exclude -scope /tb_top/dut/sram_inst -toggle
vcover report -html -excludefile exclusions.do merged.ucdb
Two rules of thumb to avoid abuse: (1) the exclusion file should live in version control next to the RTL, (2) every exclusion should have a one-line comment justifying it.
Test ranking — which tests carry the load
Once a regression is merged, you can rank tests by their unique contribution. Useful for trimming a slow regression or discovering redundant tests:
vcover ranking -output ranking.txt regression.ucdb runs/*/coverage.ucdb
The output sorts tests by incremental coverage — the top of the list is the smallest set you'd rerun if you only had time for a fraction of the regression.
Holes analysis — closing the gap
The HTML report's Missed bins view is where you spend your time once total coverage stalls. Typical patterns:
- Untaken default arm — your
casehas no test for the illegal-state branch. Either add a directed test or exclude it as defensive code. - One-hot bit never set — toggle coverage flags it. Often points at a feature that's wired but never enabled by config.
- Condition coverage gap with full statement coverage — a complex
if (a && b && c)always evaluates the same way. Need stimulus that varies each operand independently. - FSM transitions missing — one of the most actionable. Drives directed tests for specific state→state edges.
Common pitfalls
- Compiling with
-coveropt 0or1. Saves compile time but degrades condition/expression coverage accuracy. Use3for sign-off,1only for quick smoke runs. - Forgetting
+acc. Coverage works without it, but design hierarchy is opaque, so the report can't drill down to the right scope. - Merging UCDBs from different RTL builds. If a signal was renamed between runs, the merge silently drops it. Always rebuild and rerun all tests when RTL changes.
- Treating 100% as the goal. 100% code coverage with thin checks is meaningless. The right metric pair is code coverage + functional coverage — only together do they say the design was both reached and behaved correctly.
Reference example: Mod-16 Up-counter with Coverage
Comments (0)
Leave a Comment