What coverage measures
Tell apart "the tests passed" from "the tests exercised what matters", and describe what a functional coverage model must count.
- Lesson
- C1
- Time
- 20 min
- Runs on
- Verilator
- Check
- 5 questions
A packet link passed 200 random tests every night for a quarter, and its first long write in the lab corrupted memory. A constraint left over from bring-up, kind == WRITE -> len <= 4, had quietly kept every write short. Every test passed because no test ever sent the packet that failed.
One column per packet. Nothing fails, and nothing ever tests a long write.
Passing and exercising are different claims
A passing test says that whatever happened was correct. It says nothing about what did not happen. A scoreboard can only find bugs in traffic that reaches the design, so a regression is only as good as the situations its stimulus produces.
Functional coverage measures those situations directly. You list the cases the design must be tested in, such as every packet kind at every length, and the testbench counts how often each one occurred. A case that never occurred is a hole: a part of the design that the regression has not tested, however green it looks.
The gap is easy to miss because a passing regression produces nothing worth reading. Failures demand attention, but a test that never sends a long write prints nothing at all, which looks exactly like success.
Code coverage and functional coverage
Simulators can also measure code coverage: which lines, branches and toggles of the design ran. It needs no extra work and finds dead code, but it only knows the code that exists. It cannot tell you that a combination of inputs never arrived, or that a feature the specification requires was never written at all.
| Code coverage | Functional coverage | |
|---|---|---|
| Measures | lines, branches and toggles of the code that ran | situations from the specification that occurred |
| Written by | the simulator, automatically | you, as a coverage model |
| Finds | code no test reached | combinations and features no test reached |
| Cannot see | missing features and untested combinations | code nobody listed in the model |
The two kinds of coverage answer different questions, so a project uses both.
In the opening story, code coverage could well have been 100%. The write path and the long-packet path each ran; only their combination, a long write, never did. Functional coverage names that combination as a case, so its absence shows up as a hole.
A coverage model comes from the specification
The list of cases is called a coverage model. Build it from the specification and the test plan, never from the design's code. The reason is the same as for a scoreboard's reference model: a model copied from the code would only ask about cases the code already handles.
For the packet link, the specification says there are three kinds of packet, lengths from 1 to 8 beats, and CONFIG packets of exactly one beat. Lengths fall into three classes that matter to the design: a single beat, a short burst of 2 to 4, and a long burst of 5 to 8. The model asks for every kind in every legal length class.
| Kind | one beat | 2 to 4 beats | 5 to 8 beats |
|---|---|---|---|
| READ | required | required | required |
| WRITE | required | required | required |
| CONFIG | required | impossible | impossible |
The coverage model for the packet link: 7 required cells and 2 impossible ones.
Review the model with the people who wrote the specification. They know which combinations matter, such as long writes, and which can never happen, such as long CONFIG packets.
Writing the model down before running anything also catches gaps in the specification. If nobody can say whether a CONFIG packet may be long, the model forces that question early.
Counting by hand, then with covergroups
The example counts the model's cells by hand, with an array, for 200 random packets. Every packet passes, and the table shows a zero in the WRITE row under 5 to 8 beats: the hole from the opening story.
A hole is information, not a failure. Here it points straight at the leftover constraint, the very bug the regression had been hiding for a quarter.
Counting by hand works for one table, but it grows quickly and every testbench would invent its own format. SystemVerilog has a language feature for this job, the covergroup, which counts, groups values into bins and reports a percentage. Lesson C2 replaces the array with one.
A coverage number is only meaningful next to the model it measures. A model that leaves out long writes can reach 100% and say nothing about them, so review the model itself as carefully as the tests. The rest of this track builds such models with the language's own tools, and ends by testing a model itself.
Try it
All 200 packets pass, and the table makes the hole obvious: no WRITE packet ever had 5 to 8 beats. The inline constraint left from bring-up removed them, and no check could notice, because nothing was wrong with the packets that were sent.
Delete the with { ... } part of the randomize() call and predict which cells change. The counts per cell depend on the simulator and seed; whether a cell is zero does not.
200 packets sent, 200 passed len 1 len 2-4 len 5-8 READ 2 27 134 WRITE 7 22 0 CONFIG 8 0 0Check yourself
Remember
- A passing test shows that what happened was correct; coverage shows what happened at all.
- Code coverage records the code that ran; functional coverage records situations from the specification.
- Build the coverage model from the specification, and leave out cases that cannot occur.
- A hole is a required case that never occurred: a part of the design no test has checked.
- A coverage percentage means nothing without the model behind it.