Part 5 · Functional Coverage · Intermediate
Simulator Coverage Control
Coverage databases, $set_coverage_db_name, performance switches, hierarchy exclusion, and the per-seed merge flow.
Where coverage actually lives
Bin hit counts accumulate in simulator memory during the run and are written to a coverage database file at the end (UCDB for Questa, VDB for VCS, and so on — formats differ, concepts do not). The database, not the simulation log, is the artifact: it is what gets merged across seeds, ranked, reported, and archived for sign-off.
// LRM-standard control of the database name from inside the test:
initial begin
// make this run's database identifiable by seed
$set_coverage_db_name($sformatf("cov_seed_%0d", seed));
end
// Companion system functions (LRM 'Coverage system tasks'):
// $load_coverage_db("name") — load a previously saved database
// $get_coverage() — overall functional coverage %Most flows set the database name from the run script rather than the test, but $set_coverage_db_name is the portable in-language hook — and naming databases by seed/test is what makes the merge step automatable.
The regression merge flow
No single seed closes a real coverage model. Each seed writes its own database; a merge tool unions the bin hits; the merged database is what closure and hole analysis read. Merging is a union of hits , never an average of percentages.
REGRESSION MERGE FLOW
test_smoke seed 1 ──► cov_seed_1.db ──┐
test_random seed 2 ──► cov_seed_2.db ──┤
test_random seed 3 ──► cov_seed_3.db ──┼──► MERGE (union of bin hits)
test_burst seed 4 ──► cov_seed_4.db ──┤ │
test_err seed 5 ──► cov_seed_5.db ──┘ ▼
merged.db
│
┌──────────────┼──────────────┐
▼ ▼ ▼
closure % hole report test ranking
vs goals (empty bins) (which tests
│ added bins?)
▼
new directed tests / waivers
│
▼
next regression (loop)
seed A: bin X hit 3, bin Y hit 0
seed B: bin X hit 0, bin Y hit 7 merge → X:3, Y:7 (union, not average)Merge is incremental in most tools — yesterday's merged.db plus today's new seeds is a normal flow.
Test ranking reads the merged data to find which runs contributed unique bins — the rest are pruning candidates for the regression list.
Per-instance coverage and option.name survive into the database; consistent naming is what makes cross-seed instance merging coherent.
Performance: turning coverage off where it buys nothing
Coverage collection costs simulation speed — bin updates on every sample, database writes at exit. Flows therefore disable it where it adds nothing: performance-characterization runs, debug re-runs of a failing seed, and hierarchies outside the verification scope (a vendor PHY model never closes and never needs to).
COVERAGE ON/OFF DECISION
run type functional cov? why
──────────────────────────────────────────────────────────────
nightly regression seeds ON that's the product
debug re-run of failed seed OFF need speed; cov known
performance benchmark run OFF cov skews timing runs
gate-level sims usually OFF different sign-off basis
vendor IP / PHY hierarchy EXCLUDED can't close, don't try
Mechanisms (tool-specific flags, portable concepts):
- compile/run switches enable coverage kinds (functional, line, toggle)
- exclusion files remove hierarchies/instances from collection
- cg.stop()/start() gates one instance from inside the languageInterview angle: "Walk me through how 300 random seeds become one coverage number" is a flow question disguised as a tool question: per-seed databases named by seed, merge as a union of hits, holes triaged from the merged view, ranking to prune the test list, exclusions documented for out-of-scope hierarchy. Saying "averages" anywhere in that answer is the classic fail — merging unions hits.
Key takeaways
The coverage database file, not the sim log, is the artifact — name it per seed/test so merging is automatable.
$set_coverage_db_name / $load_coverage_db / $get_coverage are the LRM-portable hooks; richer control is tool-side.
Merge unions bin hits across seeds; closure and hole analysis read only the merged database.
Disable or exclude coverage where it cannot pay: debug re-runs, perf runs, vendor hierarchies.
Common pitfalls
Letting every seed write the same default database name — runs overwrite each other and the merge is silently partial.
Averaging per-seed percentages instead of merging databases — mathematically wrong, always pessimistic or just false.
Collecting coverage on vendor IP hierarchies — permanent un-closeable holes that pollute every report.
Re-running a failing seed with coverage on while debugging — paying the slowdown for data you already have.