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.

systemverilog
// 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.

diagram
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).

diagram
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 language

Interview 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.