Functional Coverage¶
Why coverage, and why functional coverage specifically¶
Code coverage (line/branch/toggle/FSM) answers "did the simulation execute this RTL?" It says nothing about whether the scenarios that matter to the spec were exercised. Functional coverage is written against the verification plan, not the RTL — it answers "did we actually test the cases we said we would?" A DUT can hit 100% code coverage while never once testing back-to-back writes to the same address, a specific error-injection sequence, or a corner-case FIFO-full condition — functional coverage is how you make those specific intents measurable.
Covergroups and coverpoints¶
class txn;
rand bit [3:0] len;
rand bit [1:0] burst_type;
endclass
covergroup cg_txn with function sample(txn t);
cp_len: coverpoint t.len {
bins small = {[1:4]};
bins medium = {[5:12]};
bins large = {[13:15]};
}
cp_burst: coverpoint t.burst_type {
bins fixed = {0};
bins incr = {1};
bins wrap = {2};
illegal_bins reserved = {3};
}
endgroup
binsgroup values into named buckets — without explicit bins, SystemVerilog auto-bins every discrete value, which is rarely what you want for a 4-bit field, let alone a 32-bit one.illegal_binsflags a value as a coverage/protocol violation if it's ever sampled — combine with an assertion for the same condition so the violation is caught immediately, not just noticed later when reviewing the coverage report.
Cross coverage¶
The interesting bugs are usually at combinations of conditions, not single fields in isolation:
covergroup cg_txn with function sample(txn t);
cp_len: coverpoint t.len { bins small = {[1:4]}; bins large = {[13:15]}; }
cp_burst: coverpoint t.burst_type { bins wrap = {2}; bins incr = {1}; }
cx_len_burst: cross cp_len, cp_burst; // every (len-bin, burst-bin) combination
endgroup
A cross of two coverpoints with N and M bins produces N × M cross-bins by default — this grows fast. Use bins ... = binsof(...) intersect {...} to cherry-pick only the combinations that are actually meaningful (e.g. "large transfer + wrapping burst" is a real corner case worth tracking; "small transfer + fixed burst" combined with every other field might just be noise).
Coverage closure in practice¶
Writing the covergroup is the easy part. The actual work of "closing coverage" is:
- Randomize with a bias toward corners, not uniformity. Pure uniform random rarely hits edge values (
0, max, exact boundary) often enough in a reasonable sim budget — useconstraintweighting (dist) to over-sample interesting regions. - Read the holes, not just the percentage. A 95% functional coverage number with the missing 5% concentrated in one untested error-injection mode is a much bigger risk than a report showing scattered, individually-low-value misses.
- Directed tests for anything random can't reasonably reach. Some cross-bins (a specific back-to-back sequence combined with a specific reset-timing window) have vanishingly small probability under constrained-random and are better hit with a short directed test than by hoping randomization eventually finds them.
Coverage is a proxy, not a guarantee
100% functional coverage means every scenario you thought to write a covergroup for was exercised. It says nothing about scenarios you didn't think of — the verification plan itself can be incomplete. Coverage closure closes out your plan; it doesn't validate the plan was complete.