SystemVerilog · Module 11
Introduction to Functional Coverage
Why functional coverage is the only honest verification-closure metric, the four goals every plan must satisfy, the CRV feedback loop, and the trap of 100% code coverage with 0% functional coverage.
Module 11 · Page 11.1
What Functional Coverage Measures
Functional coverage is a user-defined, specification-driven metric that counts how many of the scenarios you declared interesting have actually been exercised by your tests. You write the coverage model. The simulator counts the hits. The percentage answers the only question that matters at the end of a verification project: have we verified what the spec says we must?
It is the opposite of code coverage. Code coverage is structural and automatic — the EDA tool inspects RTL and reports which lines, branches, and expressions a simulation executed. Functional coverage is intentional and manual — you tell the simulator which values, transitions, and combinations of signals encode "real verification work."
Why Code Coverage Is Not Enough
Two teams report "1000 tests passing, 100% line coverage." One team hit every corner case in a 200-row verification plan. The other team hit ten scenarios a hundred times each. Code coverage cannot distinguish these outcomes. Functional coverage can.
| Aspect | Functional Coverage | Code Coverage |
|---|---|---|
| Author | Verification engineer | EDA tool |
| Source | The verification plan and the spec | The RTL source as written |
| Measures | Whether features were verified | Whether code was executed |
| Setup effort | High — requires plan-to-coverpoint translation | None — -cov flag on the simulator |
| 100% achievable? | Yes, with ignore_bins for unreachable scenarios | Often not — dead code, synthesis-only branches |
| Silent failure | Engineer forgets to write the coverpoint | Code runs with safe values, real bug never triggered |
| Use at sign-off | The closure number that gates tape-out | Diagnostic — finds unexercised RTL paths |
The Four Coverage Goals
Every verification plan organised around functional coverage must satisfy four distinct goals. They are not synonyms for each other; a model that covers one but omits another leaves real scenarios unverified.
1 — Completeness
Every feature in the verification plan has at least one coverpoint. If a feature has no coverage, no test result tells you whether it was exercised. The goal is: zero uncovered features in the plan, full stop.
2 — Corner Cases
Boundary values, min/max payloads, empty/full FIFOs, simultaneous events, illegal opcodes. Dedicated bins for the corners that random traffic almost never hits — without them, an "85% coverage" report can be the easy 85% while the hard 15% sits at zero.
3 — Scenario Coverage
Cross coverage. A READ is well-tested in isolation; a maximum payload is well-tested in isolation; "READ with maximum payload" might never have been combined. The intersection is where most silicon-stage bugs live.
4 — Regression Quality
Cumulative coverage across regression runs, merged into a single database. If a design change drops merged coverage, the regression suite has quietly lost reach — and you know immediately, not at silicon.
A Concrete Example — Covering APB Transactions
Concepts hold up best against working code. Here is a minimal covergroup for an AMBA APB transaction. It tracks the address space, the read/write split, and — crucially — the cross of the two, because a fault in PWRITE decoding inside a specific address region is not visible from PWRITE alone or PADDR alone.
// ── Transaction class with embedded coverage ───────────────────────
class apb_txn;
rand bit [31:0] paddr;
rand bit pwrite; // 1 = write, 0 = read
rand bit [31:0] pwdata;
// Sampling event — fired explicitly when the monitor publishes
// a completed transaction. See Module 11.9 for event details.
covergroup cg_apb;
cp_addr: coverpoint paddr {
bins low_regs = {[32'h0000_0000:32'h0000_00FF]}; // CSR region
bins ram = {[32'h1000_0000:32'h1FFF_FFFF]}; // mapped RAM
bins boundary[] = {32'h0000_0000, 32'h0000_00FF, // explicit
32'h1000_0000, 32'h1FFF_FFFF}; // corners
illegal_bins reserved = {[32'hF000_0000:32'hFFFF_FFFF]};
}
cp_dir: coverpoint pwrite {
bins read_xn = {1'b0};
bins write_xn = {1'b1};
}
// The intersection — scenario coverage.
cr_addr_dir: cross cp_addr, cp_dir;
endgroup
function new();
cg_apb = new(); // covergroup must be constructed
endfunction
function void sample_cov();
cg_apb.sample(); // monitor calls this after each transaction
endfunction
endclassWhat this code does. Declares a transaction class, embeds a covergroup that tracks two coverpoints and their cross, constructs the covergroup in new() (forgetting this is the most common bug — see the Debug Lab below), and exposes a sampling hook.
How to simulate it. Compile under any IEEE 1800-compatible simulator (vcs -sverilog -cm line+cond+fsm+tgl+branch+cov for VCS, vsim -coverage for Questa, xrun -coverage all for Xcelium). Run a test that creates, randomises, and samples ≥ 1000 transactions, then dump coverage with the simulator's UCDB or urg/imc reporter.
Expected output (truncated urg-style report).
Group cg_apb coverage 78.6%
cp_addr low_regs 100.0% ( 412 hits )
ram 100.0% ( 388 hits )
boundary[0] (32'h0000_0000) 100.0% ( 12 hits )
boundary[1] (32'h0000_00FF) 100.0% ( 9 hits )
boundary[2] (32'h1000_0000) 0.0% ( 0 hits ) ← gap
boundary[3] (32'h1FFF_FFFF) 0.0% ( 0 hits ) ← gap
reserved ILLEGAL ( 0 hits ) ← good
cp_dir read_xn 100.0% ( 408 hits )
write_xn 100.0% ( 412 hits )
cr_addr_dir 62.5% ( 10/16 bins )What to read out of this. Two address-corner bins (32'h1000_0000 and 32'h1FFF_FFFF) are at zero — the random distribution favoured mid-range addresses. The cross is at 62.5% because the corner-address × write-direction combinations never occurred. This is the coverage report telling you the next directed test to write. No code-coverage tool reports this gap.
The Coverage-Driven Verification Loop
Functional coverage is the measurement instrument inside a four-stage feedback loop called Coverage-Driven Verification (CDV). The loop runs until the mandatory coverage targets are met.
1 — Author the coverage model
Translate the verification plan into covergroups and coverpoints. Decide bin boundaries, illegal bins, ignore bins, and per-bin at_least thresholds. Pair each plan item with a measurable bin.
2 — Run randomised regressions
Use constrained-random stimulus (Module 10) to drive the DUT. The solver produces diverse legal transactions; coverage bins accumulate hits across every simulation in the regression.
3 — Read the coverage report
Identify bins at zero or below threshold. Decide for each: is the scenario reachable, and if so, why has random stimulus not hit it? Is it actually unreachable and a candidate for ignore_bins?
4 — Close the gap
Write directed tests or tighten constraints to steer CRV traffic into the uncovered region. Add justified ignore_bins for unreachable scenarios. Re-run regression. Repeat until every mandatory bin has hit its threshold.
Inside Module 11 — What You'll Build
The rest of this module builds the coverage vocabulary in detail. Each subtopic targets one construct or one closure problem.
| Construct | What it does | Lesson |
|---|---|---|
covergroup / coverpoint | The container and the per-variable tracker | 11.2 |
| Automatic bins | The default behaviour when no bins are declared | 11.3 |
| Named bins | Explicit bins/bins[] for declared value sets | 11.4 |
wildcard_bins / illegal_bins / ignore_bins | Pattern matching, error flagging, and unreachable scoping | 11.5 |
| Transition coverage | => operator across sampled values | 11.6 |
| Cross coverage | Scenario coverage between coverpoints | 11.7 |
option.at_least / option.weight / option.goal | Threshold, importance, and closure target | 11.8 |
| Sampling events | When the simulator commits a sample to the database | 11.9 |
| Instance vs type coverage | Per-object tracking vs class-wide aggregation | 11.10 |
Common Pitfalls — The Three Coverage Traps
Most coverage failures are not modelling problems — they are sampling problems. The covergroup exists, the bins are correct, but the report says 0%.
class my_txn;
covergroup cg_data;
cp_d: coverpoint data { bins all[] = {[0:255]}; }
endgroup
// ── Missing: cg_data = new(); inside the constructor ──
endclass
// At simulation time:
// my_txn t = new();
// t.cg_data.sample(); // SEGFAULT — null handle dereference (VCS/Questa)
// // Or silently no-op (some flows). Report: 0%.class env;
apb_txn t1, t2;
task run();
forever begin
mbx.get(t1); // monitor publishes a fresh transaction
t2.sample_cov(); // SAMPLES t2 — the wrong object — always
// stale or null fields. Coverage looks
// healthy *for the wrong scenarios*.
end
endtask
endclassclass apb_mon;
task collect();
apb_txn t = new();
@(vif.pclk); // wait one cycle
t.sample_cov(); // PADDR/PWRITE still default 0
// ...then drive fields ...
t.paddr = vif.paddr;
t.pwrite = vif.pwrite;
endtask
endclass
// The sample happened on the *initial* values, not the captured ones.
// Coverage shows everything hitting the "address 0, read" bin only.The shape of the underlying mistake is consistent: the sample fires on a value the engineer did not intend. The fix is always upstream — construct the covergroup, sample the right handle, sample after the fields are valid.
Debug Lab — Coverage at 0% While All Tests Pass
A real failure mode. The DV team reports green: every test in the regression returns PASS. The verification lead requests the coverage report. It shows 0% functional coverage. Both statements are true, and both are tracking the same fact from different angles.
class apb_txn;
rand bit [31:0] paddr;
rand bit pwrite;
covergroup cg_apb;
cp_addr: coverpoint paddr { bins all = {[0:$]}; }
cp_dir: coverpoint pwrite;
endgroup
// ── BUG: no constructor for cg_apb ─────────────────────────
endclass
class apb_driver;
task run();
apb_txn t = new();
repeat (10_000) begin
assert(t.randomize());
drive(t);
t.cg_apb.sample(); // null-handle dereference, or silently
// no-op, depending on tool flow
end
endtask
endclassEvery transaction passes. The driver runs ten thousand transactions per test, the regression returns green, the scoreboard reports zero mismatches. urg / imc report: cg_apb 0% (0/2 coverpoints sampled). No assertion failure. No simulator warning unless +UVM_VERBOSITY=UVM_HIGH is set — and in some flows, no warning at all.
The covergroup was declared but never constructed. SystemVerilog covergroups behave like class objects: declaration reserves the type, but new() must be called to instantiate the storage. On the offending line t.cg_apb.sample(), cg_apb is a null handle. VCS and Questa typically null-dereference (the simulator may swallow the error or report it only at high verbosity); some simulators silently no-op. Either way, the bin counters never increment. Tests pass — they were never sampling.
Construct the covergroup explicitly in the constructor:
class apb_txn;
rand bit [31:0] paddr;
rand bit pwrite;
covergroup cg_apb;
cp_addr: coverpoint paddr { bins all = {[0:$]}; }
cp_dir: coverpoint pwrite;
endgroup
function new();
cg_apb = new(); // ── the missing line ──
endfunction
endclassThe discipline rule that prevents recurrence: every class that embeds a covergroup must construct that covergroup in new(), and the construction line must appear in the same commit as the covergroup declaration. Code review should flag the omission. Lint tools (Spyglass, JasperGold lint) detect it as COVERGROUP_NOT_CONSTRUCTED in default rule sets.
Industry Context — Where Coverage Sign-Off Actually Happens
Functional coverage is the document of record for tape-out readiness at every modern ASIC and large-FPGA project.
- ARM Cortex-A / Cortex-M IP teams maintain coverage plans with thousands of bins per CPU subsystem; release gates require 100% closure on the mandatory plan items before a tag ships to integration partners.
- PCI-SIG compliance flows publish coverage plans for LTSSM states, TLP types, DLLP cases, and credit return scenarios — the test labs check both protocol-compliance tests and coverage of the underlying scenarios.
- DDR4/DDR5 controller verification uses coverage to ensure every refresh interaction, every training sequence, and every rank/bank crossing has been hit before the controller IP ships to SoC teams.
- UVM-based VIP (AMBA, Ethernet, USB, JTAG vendors) ships with coverage models alongside the agents; the coverage model is the spec promise the VIP holds to the integrator.
Coverage closure rates are a leading indicator of project schedule risk: a project at 60% functional coverage two weeks before tape-out is in a different reality from one at 95%, no matter how green the regression is.
Interview Q&A — Ten Questions You Will Be Asked
A user-defined metric, derived from the verification plan, that measures which spec-driven scenarios the simulator has actually exercised — independent of whether tests passed and independent of which RTL lines ran.
Coding Guidelines — Six Rules for Coverage Discipline
- Every covergroup is constructed in
new(). No exceptions. Declaration without construction is the single most common reason a coverage report reads 0%; code review should reject any class that omits the construction line. - Coverage models trace one-to-one to the verification plan. Each plan row maps to a named bin or a named cross. A covergroup with bins that do not appear in the plan is dead weight; a plan row with no bin is an audit failure.
illegal_binsfor "must never happen";ignore_binsfor "legal but uninteresting / unreachable." Confusing the two either masks real bugs (illegal turned into ignore) or floods the regression with false errors (ignore turned into illegal). The distinction is intent, not value.- Sample on a precise, well-named event. A covergroup tied to
@(posedge clk)without qualification samples noise; tie it to a monitor'stransaction_committedevent or callsample()explicitly when the published transaction is final. The sampling event is part of the coverage model. - Coverage closure is plan-driven, not percentage-driven. "100% coverage" with a missing mandatory bin is not sign-off; "92% coverage" with the rest in justified ignore bins is. Track closure per plan row, not per total-bin denominator.
- A green regression without coverage is not verification done. Sign-off gates require both: scoreboard cleanliness and coverage closure on the mandatory plan. Teams that ship on regressions alone find the gaps in the lab, where they cost an order of magnitude more.
Summary
Functional coverage is the only honest answer to "have we verified what the spec requires?" — code coverage proves the simulator executed the RTL, scoreboard cleanliness proves the transactions that were tested matched the reference, and functional coverage proves the testbench hit the scenarios the verification plan promised. Module 11 is the construct-by-construct build of that model: covergroups, coverpoints, bins, crosses, options, and the sampling discipline that makes the report trustworthy. The rest of the module assumes the framing from this page — coverage as the document of record for verification completeness, and CRV as the engine that fills the coverage database.