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.

A guard and a clock edge decide what is counted
012345validreadykindWRITECONFIGREADCONFIGREADcounted (iff valid && ready)-WRITE-CONFIGREADa transfer

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.

MeasureLink ALink BType
kinds seenREAD, WRITECONFIG, READall three
length classes seenone, shortone, longall three
instance coverage66.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.

RUNVerilator (coverage build) ยท simulator service
Edit the code, then run it
Expected: link A 66.67%, link B 66.67% type coverage, averaged 66.67%, merged 100.00%

Check yourself

Predict the outputTwo instances of the same covergroup type are sampled. Predict the exact output.
class mon;
  bit [1:0] v;
  covergroup cg;
    option.per_instance = 1;
    cp: coverpoint v { bins b[] = {[0:3]}; }
  endgroup
  function new(); cg = new(); endfunction
endclass

module tb;
  mon m1 = new(), m2 = new();
  initial begin
    m1.v = 0; m1.cg.sample();
    m2.v = 1; m2.cg.sample();
    m2.v = 2; m2.cg.sample();
    m2.v = 3; m2.cg.sample();
    $display("%0.2f %0.2f %0.2f", m1.cg.get_inst_coverage(), m2.cg.get_inst_coverage(), m1.cg.get_coverage());
  end
endmodule
Spot the bugEach option is the kind coverpoint of the link monitor. Retried packets must not be counted. Link A sends READ and WRITE first tries, plus a retried CONFIG. Kind coverage for link A must be 66.67%. Which option counts the retry?
ConceptA covergroup is declared inside a monitor class, but the constructor never runs cg = new();. What happens when the monitor calls cg.sample(p, 1)?
ConceptA clocked covergroup reads count, which the design updates with count <= count + 1; at the same rising edge. Which value does it sample?
ConceptTwo identical links each reach 80% coverage, and the type coverage with default options is also 80%. Can the chip as a whole be at 100%?

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 iff guard 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_instances combines their hits instead.