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

FlagTypeWhat it countsWhy it matters
sStatementLines of RTL executed at least onceCheapest signal that a chunk of code is reachable at all
bBranchTaken/not-taken arms of if, caseTwo tests can hit every line but miss half the branches
cConditionEach operand of a multi-term booleanCatches when only part of a complex if condition is exercised
eExpressionRHS expressions covered with all input combosStronger than condition; useful for arithmetic and decoders
fFSMStates visited and state-to-state transitionsThe single most useful type for control-heavy designs
tToggleEvery signal bit went 0→1 and 1→0Catches 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

OptionEffect
-coveropt 3Optimization level 3 — preserves enough info for accurate condition/expression coverage
+coverEnable all coverage types: s,b,c,e,f,t
+cover=sbfEnable just statement, branch, FSM (cheaper; useful for nightly regressions)
+accPreserve design hierarchy access for debug; required for accurate coverage
-coverageCollect coverage during simulation
-onexit -directive -codeAllSave 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 case has 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 0 or 1. Saves compile time but degrades condition/expression coverage accuracy. Use 3 for sign-off, 1 only 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

Author
Mayur Kubavat
VLSI Design and Verification Engineer sharing knowledge about SystemVerilog, UVM, and hardware verification methodologies.

Comments (0)

Leave a Comment