One coverpoint with three bins is the whole idea at its smallest. The coverpoint watches the ALU opcode. Each bin is a plan row: this opcode must happen at least once.
The next listing is not run in this course's checks: the open simulators do not count covergroups. The bench at the end of the lesson counts the same bins with the course's bins model, listed on the Bins Model page of this topic.
// not-run: Verilator 5.048 ignores covergroups (COVERIGN) and Icarus 13 has none; the bins model counts it
covergroup cg_alu with function sample(bit [1:0] op);
cp_op : coverpoint op {
bins add = {0}; // plan row 1: an ADD happened
bins sub = {1}; // plan row 2: a SUB happened
bins shl = {2}; // plan row 3: a shift happened
}
endgroupThe bins model counts the same three bins with plain classes: cp_op.add("add", 0, 0) is bins add = {0};. Its source and its mapping to covergroup syntax are on The Bins Model page. Every bench in this part starts with // uses: covmodel, so paste it below that listing.
The bench samples five transactions, none of them a shift, and prints the report. It then sends one directed shift and prints it again.
// uses: covmodel
// The smallest covergroup, counted with the bins model: three bins on one coverpoint.
// covergroup cg; cp_op : coverpoint op { bins add = {0}; bins sub = {1}; bins shl = {2}; } endgroup
module tb_three_bins;
cm_point cp_op = new("cp_op");
initial begin
int ops[5] = '{0, 1, 0, 0, 1}; // five ALU transactions: no shift among them
cp_op.add("add", 0, 0); cp_op.add("sub", 1, 1); cp_op.add("shl", 2, 2);
foreach (ops[i]) cp_op.sample(ops[i]);
$display("after five transactions:");
cp_op.report();
if (cp_op.get_coverage() > 67.0 || cp_op.n_covered() != 2) $fatal(1, "FAIL: expected the shl bin to be the hole");
cp_op.sample(2); // one directed shift
$display("after one shift:");
cp_op.report();
if (cp_op.get_coverage() != 100.0) $fatal(1, "FAIL: the shift did not close the hole");
$display("PASS: three bins counted, the unhit shl bin was the named hole and one SHL closed it");
$finish;
end
endmoduleafter five transactions:
coverpoint cp_op: 66.67% (2 of 3 bins) below goal 100
add 3
sub 2
shl 0 <- hole
after one shift:
coverpoint cp_op: 100.00% (3 of 3 bins)
add 3
sub 2
shl 1
PASS: three bins counted, the unhit shl bin was the named hole and one SHL closed itTwo of three bins hit, 66.67%, and the shl bin marked as a hole with a count of 0.
after five transactions:
coverpoint cp_op: 66.67% (2 of 3 bins) below goal 100
add 3
sub 2
shl 0 <- hole
after one shift:
coverpoint cp_op: 100.00% (3 of 3 bins)
add 3
sub 2
shl 1
PASS: three bins counted, the unhit shl bin was the named hole and one SHL closed itRead it line by line. The first line is the coverage: bins hit divided by bins in the goal. Each line below it is a bin with its hit count, and the arrow marks a bin that has not reached at_least, which is 1 unless you change it. The report names the missing scenario, and the fix is a test, not more of the same traffic: five more adds would leave the number where it is.
A real model starts from a short plan, one sentence per scenario, and gives every sentence a bin named after it. The next lesson does that for a six-row ALU plan, and the one after it explains why a model that reads 100% can still sit beside a verification gap.
// uses: covmodel
// PacketFIFO step 1: the kind field of the header, counted with the bins model.
// covergroup cg_kind; cp_kind : coverpoint hdr[7:6] { bins data = {0}; bins ctrl = {1}; bins err = {2}; } endgroup
module tb_pkt_kind_cov;
cm_point cp_kind = new("cp_kind");
initial begin
byte hdrs[4] = '{8'h22, 8'h52, 8'h13, 8'h24}; // headers of four packets: kind is bits 7:6
cp_kind.add("data", 0, 0); cp_kind.add("ctrl", 1, 1); cp_kind.add("err", 2, 2);
foreach (hdrs[i]) cp_kind.sample(hdrs[i][7:6]);
cp_kind.report();
if (cp_kind.n_covered() != 2 || cp_kind.bin_list[2].hits != 0) $fatal(1, "FAIL: expected err to be the hole");
cp_kind.sample(8'h92 >> 6); // one ERR packet header
if (cp_kind.get_coverage() != 100.0) $fatal(1, "FAIL: the ERR packet did not close the hole");
$display("PASS: kind has three bins; no ERR packet had been sent, and one closed the hole");
$finish;
end
endmodulecoverpoint cp_kind: 66.67% (2 of 3 bins) below goal 100
data 3
ctrl 1
err 0 <- hole
PASS: kind has three bins; no ERR packet had been sent, and one closed the holeClosure in one page: what you just did, name the hole and write the test that fills it, is the loop that Coverage Closure & Sign-Off lays out. You can read that page now.
Nothing. The add count grows, but the percentage divides bins hit by bins in the goal, and shl is still unhit. Only a shift moves it.
Counted with the course's bins model (rich/coverage/cov_model.sv, listed on The Bins Model page) 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.
Five transactions with no shift read 66.67% with shl as the hole, and one directed shift closed it at 100%.
PacketFIFO step 1: a three-bin kind coverpoint read 2 of 3 until one ERR header was sampled.