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.
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.
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 kindA 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 far | READ bin | WRITE bin | CONFIG bin | Coverage |
|---|---|---|---|---|
| none | 0 | 0 | 0 | 0% |
| WRITE | 0 | 1 | 0 | 33.33% |
| WRITE, CONFIG | 0 | 1 | 1 | 66.67% |
| WRITE, CONFIG, WRITE | 0 | 2 | 1 | 66.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.
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
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.