Two engineers bring job_cg numbers to the same review, from the same dma_check_test hits. One says 41.7%, the other 37.5%. A third, who raised at_least "for robustness", says 25%. Nobody ran a different test. And at the subsystem level the type coverage reads 33.3% in one report and 50% in another, again from identical hits.
Covergroup options are the scoring rules, and they change the number without changing what happened. Which option moves which number, and which ones should a regression fix once and never touch again?
Think of an exam. `at_least` is the pass mark for one question: how many times a bin must be hit before it counts as covered. `weight` is how many marks a question carries in the total. `goal` is the target grade that marks the paper as met; it does not change the score. `per_instance` decides whether each student's paper is graded separately, and `merge_instances` decides whether the class result is the average of the papers or the best combined paper. Set the rules before the exam, and keep them fixed for the whole regression.
Option | Default | Set on | What it changes |
|---|---|---|---|
| 1 | covergroup, coverpoint, cross | Its share of the parent's weighted average. 0 removes it from the score. |
| 100 | covergroup, coverpoint, cross | The target used to mark it met. The percentage itself is unchanged. |
| 1 | covergroup, coverpoint, cross | Hits a bin needs before it counts as covered. |
| 0 | covergroup | Keep and report coverage for each instance separately. |
| 64 | covergroup, coverpoint | The most automatic bins a coverpoint gets; values are grouped when there are more. |
| very large | covergroup, cross | The most automatic cross cells. Tool support for 0 varies. |
| 0 | covergroup | Type coverage as the weighted average of instances (0) or the union of their bins (1). |
| 1 | covergroup | The covergroup type's share of the overall score. |
auto_bin_max deserves a word of its own, because it silently decides what an unbinned coverpoint means. For an integral coverpoint with more possible values than the limit, values are divided into that many equal ranges. An enum coverpoint ignores the limit and gets one bin per named value, which is usually what you want. And cross_auto_bin_max only caps the automatic cells; it does nothing for a cross that the plan should have pruned in the first place.
Here is job_cg with the options this lesson discusses written in (a variant, not bench code). option settings apply per instance; type_option settings apply to the covergroup type and must be constants.
// snippet: job_cg with explicit options (a variant, not in the bench)
covergroup job_cg with function sample(bit [15:0] len, bit irq);
option.per_instance = 1; // report each instance on its own
option.goal = 100;
type_option.merge_instances = 1; // type coverage = union of the instances
cp_len: coverpoint len {
option.at_least = 1;
bins zero = {0}; bins one = {1}; bins few = {[2:4]};
bins many = {[5:15]}; bins max_pkt = {16}; bins over = {[17:$]};
}
cp_irq: coverpoint irq { option.weight = 0; } // counted only through the cross
len_x_irq: cross cp_len, cp_irq;
endgroupget_inst_coverage() returns the coverage of the instance it is called on; get_coverage() returns the type coverage across all instances. Both can also return the covered and total bin counts through two optional ref int arguments. dma_coverage calls get_inst_coverage() without setting per_instance; how a tool answers that varies, so set option.per_instance = 1 whenever you report per-instance numbers you intend to trust.
Illegal bins are not an option, but they belong in this list of things that change a run's outcome. They are excluded from the score, and a hit raises a simulator run-time error that the UVM report server never counts (bins lesson).
Verilator cannot print real covergroup percentages, so the plain-SystemVerilog model below does the arithmetic the standard defines, on dma_check_test's real hits (jobs of length 10, 1 and 3, all with IRQ_EN set). A coverpoint's score is covered bins over total bins; the covergroup's score is the weighted average of its coverpoints and crosses.
// How a covergroup score is computed: bins hit (at_least), coverpoint and cross
// percentages, weights, and the type score for two instances.
class cp_score;
int unsigned hits[$];
int unsigned at_least = 1;
function real pct();
int unsigned covered = 0;
foreach (hits[i]) if (hits[i] >= at_least) covered++;
return 100.0 * covered / hits.size();
endfunction
endclass
group: 41.7% default, 37.5% with cp_irq weight 0, 25.0% with at_least 2
type over two instances: 33.3% averaged, 50.0% merged
PASS: weights, at_least and merge_instances change the number, not the hits41.7%: cp_len 3 of 6 (50%), cp_irq 1 of 2 (50%), the cross 3 of 12 (25%), averaged with equal weights.
37.5%: weight 0 takes cp_irq out of the average. Its hole is still there; it just no longer counts.
25.0%: with at_least = 2, no LEN bin (each hit once) counts, while the cross keeps its own at_least of 1.
33.3% versus 50.0%: two instances that each saw two LEN classes. Averaged, the type looks like one instance; as a union it shows the three classes the subsystem really exercised.
The subsystem review is the classic case. Two DMAs, two coverage instances, and a dashboard that shows type coverage. With merge_instances at its default of 0 the type number is the average, so a LEN class run only on dma1 counts half. Someone switches it to 1 to "fix" the dashboard, and now a class run only on dma1 counts fully, which hides that dma0 never saw it. Neither number is wrong. The mistake is not deciding, in the plan, which question the number answers: "did each instance see every case?" needs per-instance numbers; "did the subsystem see every case anywhere?" needs the union.
Options must not change mid-project. A merge across builds with different bins or auto_bin_max lines up different bins, and tools either refuse or merge by their own rules.
`at_least` applies to merged counts. A bin hit once in each of two tests reaches at_least = 2 only after the merge, so per-test reports and the merged report disagree by design.
Weight 0 is for coverpoints that exist only to build a cross, not for hiding a hole that is hard to close.
cp_irq gets option.weight = 0. Does len_x_irq still have 12 cells?Yes. Weight affects only how a coverpoint's score enters the covergroup's average. The coverpoint still exists, still has its bins, and the cross is still built from them.
cp_len has option.at_least = 2, and two tests each run one 16-word job. Is max_pkt covered?Not in either test's own report (one hit each), but yes in the merged database, where the hit counts add up to 2. Per-test and merged reports disagree on purpose.
cp_len, leaving cp_len: coverpoint len;. What does the report show after one job of length 1?LEN is 16 bits, so 65,536 values are grouped into at most 64 automatic bins of 1,024 values each. Lengths 0 to 1023 share auto[0], so zero, one, few and many collapse into one bin that a single length-1 job covers. That is why plan-driven coverpoints use explicit bins.
Next: some plan rows are not values at all but sequences over time. SVA cover property vs Covergroups covers the DMA's stalls and wait states.