The short answer: Functional coverage measures which features and scenarios from the spec your tests have actually exercised. In SystemVerilog you write covergroups with coverpoints, bins (explicit, automatic, ranges, transitions, illegal and ignore bins) and crosses, and sample them at the right moments; cover properties capture temporal scenarios. Together with code coverage, it tells you when verification is done, and which constraints or tests to add when it isn’t.
Random tests are only useful if you know what they hit. Functional coverage is how you find out. In this post I show how to write coverage that answers real questions, and how to use it to close verification. It combines my older posts on assertions and functional coverage and on coverage analysis.
For assertions themselves, see SystemVerilog assertions.
- Coverage closure
- Code coverage vs functional coverage
- A covergroup, step by step
- Transition and sequence coverage
- Where and when to sample
- Closing coverage
Table of Contents
Part 1: Coverage closure

Part 2: Code coverage vs functional coverage
| Code coverage | Functional coverage | |
|---|---|---|
| Written by | The simulator, automatically | You, from the verification plan |
| Measures | Lines, branches, conditions, toggles, FSM states | Features, values, combinations, sequences |
| Tells you | Which RTL was executed | Which spec scenarios were tested |
| Misses | Missing features and unchecked results | Code you didn’t think to cover |
You need both. 100% code coverage with poor functional coverage means untested features; full functional coverage with low code coverage means dead code or a weak plan.
Part 3: A covergroup, step by step
covergroup txn_cg with function sample(bus_txn t);
cp_kind: coverpoint t.kind; // one bin per enum value
cp_len: coverpoint t.len {
bins one = {1};
bins small = {[2:7]};
bins medium = {[8:15]};
bins max = {16};
illegal_bins zero = {0}; // error if ever seen
}
cp_addr: coverpoint t.addr[15:12] {
bins region[] = {[0:15]}; // 16 bins, one per 4 KB region
}
x_kind_len: cross cp_kind, cp_len {
ignore_bins idle_long = binsof(cp_kind) intersect {IDLE} &&
!binsof(cp_len.one);
}
endgroup
txn_cg cg = new();
// in the monitor, after a transaction completes:
cg.sample(t);
Part 4: Transition and sequence coverage
cp_state: coverpoint state {
bins start = (IDLE => RUN);
bins restart = (DONE => IDLE => RUN);
bins long_run = (RUN [*4:8]); // stays in RUN 4 to 8 samples
}
c_b2b: cover property (@(posedge clk) push ##1 push ##1 push);
Part 5: Where and when to sample
- Sample in the monitor, on completed transactions, not on the stimulus you intended to send. Coverage must reflect what really happened on the DUT’s pins.
- For internal states, sample on a clock with an explicit event:
covergroup fsm_cg @(posedge clk iff !rst);. - Don’t sample during reset.
Part 6: Closing coverage
- Merge coverage from all seeds and tests, then look at the holes, not the percentage.
- For each hole, ask: is it unreachable (exclude it with a reason), not stimulated (adjust constraints) or a real gap (write a directed test)?
- Watch for crosses that explode into thousands of bins; only cross what matters.
- Review exclusions with the designer; an excluded bin is a claim that the scenario can’t happen.
Practise this on VLSI Forge
I built VLSI Forge so you can write RTL in your browser, run it on a real simulator and check every signal in the waveform. Free, nothing to install.
Design Verification problems · Daily challenge
Previous in my SystemVerilog series: SystemVerilog assertions. Next: Testbench architecture.
FAQ
What is the difference between code coverage and functional coverage?
Code coverage is collected automatically and shows which RTL was exercised. Functional coverage is written by you and shows which spec features and scenarios were tested.
What are illegal_bins and ignore_bins?
illegal_bins report an error if the value occurs; ignore_bins remove values from coverage because they don’t matter or can’t occur.
Where should I sample covergroups?
Usually in the monitor, on transactions observed at the DUT interface, so coverage reflects what really happened.
Is 100% functional coverage enough?
Only if the coverage model is good and every check passes. Coverage measures stimulus, not correctness, so it must be paired with checkers.
