Covergroups and coverpoints

Write a covergroup with coverpoints, sample it at exactly the moments that count, and read what get_coverage() reports.

Lesson
C2
Time
25 min
Runs on
Coverage build
Check
5 questions

A link's coverage report showed all three packet kinds at 100% after a test that had never sent a single READ. The covergroup sampled the kind field at every clock edge, including idle cycles, and an idle bus reads 0, which is the encoding of READ. The coverage model was right; it was simply counting moments when no packet existed.

Sampling at every edge counts the idle bus
0123456validreadykindREADWRITEREADCONFIGREADsampled on transfer-WRITE-CONFIG-sampled every edgeREADWRITEREADCONFIGREADfirst transfer

One column per rising edge. Only edges with valid and ready both 1 carry a packet.

A covergroup counts values into bins

A covergroup is a type, declared much like a class, that holds a coverage model. Inside it, each coverpoint names a value to watch, and each coverpoint sorts the values it sees into bins. A bin is covered once at least one sampled value has landed in it.

A covergroup type, one instance, and one sample.
covergroup cg_xfer;
  cp_kind: coverpoint kind;   // one bin per value of kind_e
endgroup

cg_xfer xfer = new();         // a covergroup must be constructed
...
xfer.sample();                // count the current value of kind

A covergroup can hold several coverpoints, each with its own bins. Coverpoints usually carry labels, such as cp_kind:, so that reports and crosses can refer to them by name.

A covergroup counts nothing until it is constructed with new(). After that, each call to sample() reads every coverpoint's value at that moment and adds one hit to the matching bin.

Automatic bins

If a coverpoint lists no bins, the simulator creates automatic bins. An enum gets one bin per named value, so kind gets three: READ, WRITE and CONFIG. A plain vector gets one bin per value, as long as there are at most 64 values.

Beyond 64 values, the values are shared out evenly across 64 bins, the default limit called auto_bin_max. An 8-bit field therefore gets 64 bins of 4 values each, and sampling 0, 3 and 4 covers just 2 of them.

Samples so farREAD binWRITE binCONFIG binCoverage
none0000%
WRITE01033.33%
WRITE, CONFIG01166.67%
WRITE, CONFIG, WRITE02166.67%

The cp_kind bins filling in as the example's three transfers arrive.

Coverage only grows during a run. Once a bin is covered it stays covered, so the number can only rise as a test goes on.

Automatic bins are a quick start, but they count every value of the type, including values the specification forbids. Lesson C3 replaces them with bins you choose.

Sample at the moments that count

A covergroup can be sampled in two ways. You can call sample() yourself, or give the covergroup a clocking event, as in covergroup cg_every @(posedge clk);, which makes it sample at every such edge.

For a handshake, a packet exists only at an edge where valid and ready are both 1. The example calls sample() from always @(posedge clk) if (valid && ready), so it counts transfers and nothing else. The clocked covergroup counts idle and stalled cycles too, and the idle bus value 0 reads as READ, so it reports 100% for packets that never existed.

Sampling once per transfer also keeps the counts honest: a packet stalled for three cycles is one packet, not four. Lesson C6 shows the other fix, a guard written into the covergroup itself.

sample() is an ordinary method call, so it can sit anywhere: in the monitor, in the scoreboard or in a callback. What matters is that it runs exactly once for each event you want to count.

Reading get_coverage()

get_coverage() returns a percentage as a real. For one coverpoint it is the number of covered bins divided by the number of bins. For a covergroup with several coverpoints, it is the average of their percentages, weighted equally unless you say otherwise.

Called on a covergroup instance, get_coverage() reports the coverage of the covergroup type, combined over every instance of it. With one instance, as here, that is the same number; lesson C6 explains what changes with several.

Print the coverage at the end of every test, next to the pass or fail message. A number in the log makes a sudden drop visible at once, long before anyone opens a detailed report.

Try it

The first WRITE waits two cycles for ready and moves at t=35. Each transfer is sampled once, and READ never moves, so the per-transfer covergroup stays at 66.67%. The clocked covergroup also saw the idle bus, read its 0 as READ, and reached 100%.

Change the idle value in send() from READ to CONFIG and predict both percentages.

RUNVerilator (coverage build) ยท simulator service
Edit the code, then run it
Expected: t=35 transfer 1 (WRITE): coverage 33.33% t=55 transfer 2 (CONFIG): coverage 66.67% t=75 transfer 3 (WRITE): coverage 66.67% transfers=3: per-transfer 66.67%, every edge 100.00%

Check yourself

Predict the outputPredict the exact output.
module tb;
  bit [2:0] x;
  covergroup cg;
    cp: coverpoint x;
  endgroup
  cg c = new();
  initial begin
    x = 1; c.sample();
    x = 5; c.sample();
    x = 1; c.sample();
    x = 6; c.sample();
    $display("coverage=%0.2f", c.get_coverage());
  end
endmodule
Spot the bugEach option decides when the transfer covergroup xfer is sampled in the example's link, where only WRITE and CONFIG packets move. The result must be 66.67%. Which option reports a packet kind that never moved?
ConceptHow many automatic bins does coverpoint addr; create for a bit [7:0] addr, with default options?
ConceptA covergroup has two coverpoints, one at 100% and one at 50%. What does get_coverage() return, with default options?
ConceptA testbench declares cg_xfer xfer; and calls xfer.sample() at every transfer, but never calls new(). What happens?

Remember

  • A covergroup holds coverpoints; each coverpoint sorts sampled values into bins.
  • Construct every covergroup with new(); nothing is counted before that.
  • Without explicit bins, an enum gets one bin per value and a vector gets up to 64 bins.
  • Sample exactly when the thing you are counting happens, such as a transfer, never on idle cycles.
  • get_coverage() is covered bins over total bins, averaged across coverpoints.