Coverage in a class
Put a covergroup inside a monitor class, guard its samples with iff, choose the moment it samples, and read per-instance and merged coverage for several links.
- Lesson
- C6
- Time
- 30 min
- Runs on
- Coverage build
- Check
- 5 questions
A chip had two packet links, and the regression reported 66.67% coverage for the link monitor class for weeks. Each link alone was at 66.67%, but together they had sent every kind at every length; the report averaged the two instead of combining them. Nobody could say whether the remaining third was a real hole or just the way the numbers were added.
One column per rising edge, with values sampled as they were just before the edge. The guard admits only transfers.
A covergroup inside the monitor
In a class-based testbench, coverage belongs with the monitor, the component that sees each transaction. A covergroup declared inside a class is an embedded covergroup. It must be constructed in the class constructor, with cg = new();, and each object of the class then has its own instance.
Declare it with function sample(packet p, bit first_try) and it takes its values from arguments, not from variables that happen to be in scope. The monitor calls cg.sample(p, first_try) once per observed packet, and the coverpoints read p.kind and p.len directly.
Keeping coverage in the monitor also means every test gets it for free. A test only creates stimulus, and the monitor that is always present counts what that stimulus achieved.
Guards with iff
An iff after a coverpoint expression is a guard: the sample counts for that coverpoint only when the condition is true. coverpoint p.kind iff (first_try) ignores retried packets, so a packet that had to be sent twice is counted once.
A guard is also the second way to fix the idle-bus problem of lesson C2. A covergroup with the clocking event @(posedge clk) and the guard iff (valid && ready) samples every edge but counts only transfers, as the trace shows.
A guard is evaluated at each sample, with the same values the coverpoint sees, so it can use any argument of sample() or any variable in scope.
A guard can also make a model too strict. A guard that excludes too much hides real cases, so state the reason for each guard in a comment, just as for an ignore_bins.
Use guards for conditions that belong to the model, such as "first attempt only". Leave conditions about when a transaction exists to the code that calls sample(), where they are easier to see.
When a clocked covergroup samples
A covergroup with a clocking event samples when that event happens, in the same time step as the design's always blocks. A signal that the design updates with a nonblocking assignment at that edge is still read with its old value. That is the value that was valid during the cycle that just ended.
A signal written with a blocking assignment by another process at the same edge is a race: the covergroup may see either the old or the new value. Sample from values that are stable at the edge, such as the outputs of a clocking block, or call sample() from the monitor after it has captured the transaction.
Per-instance and type coverage
With two links there are two monitor objects, and so two instances of the covergroup. Setting option.per_instance = 1 keeps a separate record for each, and get_inst_coverage() reads one instance. At its default of 0, reports show coverage only for the type; turn it on when you need to know which link left a hole.
get_coverage() reports the coverage of the covergroup type. By default it is the average of the instances' coverage. With type_option.merge_instances = 1, the type coverage is instead computed from all the instances' hits combined, as if they were one.
| Measure | Link A | Link B | Type |
|---|---|---|---|
| kinds seen | READ, WRITE | CONFIG, READ | all three |
| length classes seen | one, short | one, long | all three |
| instance coverage | 66.67% | 66.67% | - |
| type coverage, averaged (default) | - | - | 66.67% |
| type coverage, merged | - | - | 100.00% |
The opening story in numbers, from the example. Neither link is complete; together they are.
One covergroup type per monitor class is the usual shape. Every link then uses the same model, so their reports line up bin for bin and can be compared or merged.
Merging is set with type_option, because it describes how all instances combine. Options written option. belong to each instance, and options written type_option. belong to the covergroup type as a whole.
Merged coverage answers "did the chip see every case?", which is usually the question for a regression. Per-instance coverage answers "did each link see every case?", which matters when the links differ in hardware.
Try it
Link A sends short reads and writes; its long WRITE is a retry, so the iff (first_try) guard leaves it out. Link B sends a CONFIG and long reads. Each instance covers 2 of 3 kinds and 2 of 3 length classes, and the average agrees, but merged they cover everything.
Change the retry flag of link A's last packet to 1 and predict all four numbers.
link A 66.67%, link B 66.67% type coverage, averaged 66.67%, merged 100.00%Check yourself
Remember
- Embed the covergroup in the monitor class and construct it in the constructor.
- Use
with function sample(...)so the covergroup takes its values from the transaction. - An
iffguard decides whether a sample counts for that coverpoint. - A clocked covergroup reads values from before the edge; sample only values that are stable there.
- Type coverage averages instances by default;
merge_instancescombines their hits instead.