A covergroup percentage is a weighted average of its coverpoints and crosses. option.weight sets each element's share, with a default of 1.
The LRM defines the rollup as the weight-averaged coverage of the items: each element contributes its coverage times its weight, divided by the counted weight sum. Tool reports may add other views, such as bin-count totals.
The following listing is not run in this course's checks: the open simulators do not count covergroups.
// not-run: Verilator 5.048 ignores covergroups (COVERIGN) and Icarus 13 has none; the bins model counts it
covergroup proto_cg @(posedge clk iff txn_done);
cp_dir : coverpoint is_write { option.weight = 1;
bins rd = {0}; bins wr = {1}; }
cp_resp : coverpoint resp { option.weight = 3; // plan-critical
bins okay = {0}; bins exok = {1};
bins slverr = {2}; bins decerr = {3}; }
cp_id : coverpoint txn_id { option.weight = 0; // feeds the cross only
bins ids[4] = {[0:15]}; }
x_id_resp : cross cp_id, cp_resp { option.weight = 2; }
endgroupElement | Coverage | Weight | Contribution |
|---|---|---|---|
| 100% | 1 | 100 × 1 = 100 |
| 50% | 3 | 50 × 3 = 150 |
| 100% | 0 | Excluded |
| 25% | 2 | 25 × 2 = 50 |
Covergroup | 1 + 3 + 2 = 6 | (100 + 150 + 50) / 6 = 50% |
With every weight left at 1 the same simulation reads (100 + 50 + 100 + 25) / 4 = 68.75%, 18.75 points higher. Weights are the policy.
Weight 0 removes bookkeeping points from the math without removing them from crosses. Leaving every weight at 1 says all elements matter equally, which is rarely the plan view.
option.goal declares the target percentage at which an element counts as done. Its default is 100. A goal only flags reports; it never changes the returned percentage.
option.at_least sets how many hits a bin needs before it counts as covered. Its default is 1. One hit can be noise, so plan-critical corners ask for repeats.
The following listing is not run in this course's checks: the open simulators do not count covergroups.
// not-run: Verilator 5.048 ignores covergroups (COVERIGN) and Icarus 13 has none; the bins model counts it
covergroup err_cg @(posedge clk iff txn_done);
option.goal = 90; // plan accepts 90% on this group
cp_err : coverpoint err_kind {
option.at_least = 4; // a bin needs 4 hits to count
bins parity = {0};
bins timeout = {1};
bins crc = {2};
}
endgroup
// With at_least = 4, a bin hit twice is still uncovered.
// Stimulus must exercise each error repeatedly, not by luck.A corner cell hit once may have landed on the only cycle where checking was masked. Repeats make the evidence statistical.
Logic that misbehaves on the Nth occurrence needs N occurrences. A threshold above 1 forces them.
Thresholds raise the closure bar. Apply them to plan-critical points, not globally.
Every knob so far was option, which configures one instance. type_option is the type-scoped sibling: one value shared by all instances. In the covergroup body it takes constant expressions only.
Property |
|
|
|---|---|---|
Scope | One instance | The covergroup type |
Set where | Body, or at runtime through an instance handle (except | Body, with constant expressions |
Affects | Instance coverage | Type-level coverage |
| Yes | Yes |
| Yes | No, instance only |
| Yes, body only | No, meaningless at type scope |
| No | Yes: 0 (default) averages instances, 1 takes the union of bins |
The following listing is not run in this course's checks: the open simulators do not count covergroups.
// not-run: Verilator 5.048 ignores covergroups (COVERIGN) and Icarus 13 has none; the bins model counts it
covergroup cg @(posedge clk);
type_option.weight = 2; // this type counts double in the scope rollup
type_option.comment = "vplan section 4";
option.per_instance = 1; // instance-level knob
cp : coverpoint mode { bins m[4] = {[0:3]}; }
endgroupoption.goal raise the coverage percentage?No. The goal only marks elements as done in reports. The percentage comes from bin hits, weights, and thresholds.
Yes. Weight 0 only removes the coverpoint from the percentage math. The cross still samples it normally.
The bench builds proto_cg with the bins model, samples four transactions that produce exactly the table above, and reads the group twice: with the lesson's weights and with every weight at 1. Then it applies at_least = 4 and goal = 90 to the error coverpoint.
cp_err sees parity 4 times, timeout twice and crc 5 times, with at_least = 4 and goal = 90. What does it read?66.67%, 2 of 3 bins: timeout was seen but is still a hole. The report flags the coverpoint as below its goal of 90; the goal does not change the percentage.
// uses: covmodel
// The weighted rollup from this lesson, then at_least and goal on an error coverpoint.
module tb_weights;
cm_group proto = new("proto_cg");
cm_point cp_dir = new("cp_dir"), cp_resp = new("cp_resp"), cp_id = new("cp_id"), cp_err = new("cp_err");
cm_cross x_id_resp;
initial begin
real weighted, unweighted;
cp_dir.add("rd", 0, 0); cp_dir.add("wr", 1, 1); // option.weight = 1
cp_resp.add("okay", 0, 0); cp_resp.add("exok", 1, 1); cp_resp.add("slverr", 2, 2); cp_resp.add("decerr", 3, 3);
cp_resp.weight = 3; // plan-critical
cp_id.add_split("ids", 0, 15, 4); cp_id.weight = 0; // feeds the cross only
x_id_resp = new("x_id_resp", cp_id, cp_resp); x_id_resp.weight = 2;
proto.points.push_back(cp_dir); proto.points.push_back(cp_resp); proto.points.push_back(cp_id);
proto.crosses.push_back(x_id_resp);
begin
int dirs[4] = '{0, 1, 0, 1};
int ids[4] = '{0, 4, 8, 12};
int resps[4] = '{0, 1, 0, 1}; // no error responses yet
foreach (ids[i]) begin cp_dir.sample(dirs[i]); cp_id.sample(ids[i]); cp_resp.sample(resps[i]); x_id_resp.sample(); end
end
proto.report();
weighted = proto.get_coverage();
cp_dir.weight = 1; cp_resp.weight = 1; cp_id.weight = 1; x_id_resp.weight = 1; // every weight left at 1
unweighted = proto.get_coverage();
$display(" weighted %0.2f%%, unweighted %0.2f%%: same simulation", weighted, unweighted);
// option.at_least = 4 and option.goal = 90 on the error coverpoint.
cp_err.add("parity", 0, 0); cp_err.add("timeout", 1, 1); cp_err.add("crc", 2, 2);
cp_err.at_least = 4; cp_err.goal = 90;
repeat (4) cp_err.sample(0);
repeat (2) cp_err.sample(1); // seen, but not 4 times
repeat (5) cp_err.sample(2);
cp_err.report();
if (weighted != 50.0 || unweighted != 68.75 || cp_err.n_covered() != 2) $fatal(1, "FAIL: rollup math");
$display("PASS: weighted 50%% against unweighted 68.75%%; timeout hit twice is still a hole at at_least 4");
$finish;
end
endmodulecovergroup proto_cg: 50.00% (goal 100)
coverpoint cp_dir: 100.00% (2 of 2 bins)
rd 2
wr 2
coverpoint cp_resp: 50.00% (2 of 4 bins) below goal 100
okay 2
exok 2
slverr 0 <- hole
decerr 0 <- hole
coverpoint cp_id: 100.00% (4 of 4 bins)
... 11 more lines ...
hole: <ids[2], exok>
hole: <ids[2], slverr>
hole: <ids[2], decerr>
hole: <ids[3], okay>
hole: <ids[3], slverr>
hole: <ids[3], decerr>
weighted 50.00%, unweighted 68.75%: same simulation
coverpoint cp_err: 66.67% (2 of 3 bins) below goal 90
parity 4
timeout 2 <- hole
crc 5
PASS: weighted 50% against unweighted 68.75%; timeout hit twice is still a hole at at_least 4Counted with the course's bins model (rich/coverage/cov_model.sv, listed on the Coverage hub) on Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown. Verilator 5.048 parses covergroups with a COVERIGN warning and ignores their bins, and Icarus 13 has none, so each covergroup listing is marked not run and its bench counts the same bins with the model.
The same four samples read 50% weighted and 68.75% unweighted, and with at_least = 4 a bin hit twice stayed a hole.