A covergroup samples automatically on a declared clocking event, or explicitly when code calls sample(). Clocked sampling suits white-box signal coverage inside modules. Explicit sampling suits transaction testbenches, where each completed transaction is sampled once.
Model | Trigger | Best for | Risk |
|---|---|---|---|
Clocked |
| White-box signal coverage in a module | Idle cycles and reset garbage get bucketed |
Explicit | A | Transaction coverage from a monitor | Forgotten calls leave silent holes |
The clocked trap is over-counting: an idle bus hits one bin millions of times and teaches nothing. Explicit sampling buys control at the price of vigilance.
Coverpoints normally reference variables at the declaration scope, which forces a copy-then-sample dance. The with function sample(...) form turns sample() into a function with typed arguments.
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.
// not-run: Verilator 5.048 ignores covergroups (COVERIGN) and Icarus 13 has none; the bins model counts it
typedef enum logic [1:0] { OKAY, EXOKAY, SLVERR, DECERR } resp_e;
class bus_txn;
rand bit write;
rand bit [7:0] len;
resp_e resp;
endclass
class bus_coverage;
covergroup cg with function sample(bit wr, bit [7:0] len, resp_e rsp);
cp_dir : coverpoint wr {
bins read = {0};
bins write = {1};
}
cp_len : coverpoint len {
bins zero = {0};
bins single = {1};
bins short_b = {[2:4]};
bins long_b = {[5:254]};
bins max_len = {255};
}
cp_resp : coverpoint rsp; // enum: one named bin per literal
endgroup
function new();
cg = new();
endfunction
// Called by the monitor once per completed transaction
function void write_txn(bus_txn t);
cg.sample(t.write, t.len, t.resp);
endfunction
endclassCoverpoints reference the sample() arguments directly, so the call site passes each field in one step. The length bins partition all 256 values: 1 plus 1 plus 3 plus 250 plus 1, with no gap and no overlap.
Sample from the monitor on observed behavior, once per completed transaction. Driver-side sampling records intent, and per-beat sampling inflates counts without adding scenarios.
Each coverpoint accepts an iff (expression) guard. A false guard skips that one coverpoint sample: no bin hits, and no illegal-bin fires.
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.
// not-run: Verilator 5.048 ignores covergroups (COVERIGN) and Icarus 13 has none; the bins model counts it
module fifo_cov (input logic clk, rst_n,
input logic [4:0] count,
input logic push, pop);
covergroup cg @(posedge clk);
// Skip every coverpoint sample while in reset
cp_count : coverpoint count iff (rst_n) {
bins empty = {0};
bins mid = {[1:30]};
bins full = {31};
}
// Guard can be per-coverpoint and condition-specific:
cp_push_full : coverpoint push iff (rst_n && count == 31) {
bins push_when_full = {1}; // overflow-attempt scenario
}
endgroup
cg cg_inst = new();
endmoduleThe group still samples its other coverpoints normally. The classic use is gating out reset, where signal values are meaningless.
iff guards one coverpoint; the rest of the group samples normally.
Gate clocked covergroups with iff (rst_n) so reset-phase values never reach bins.
Scenario conditions such as push-when-full also fit in iff, though complex cases read better as expressions.
iff guard and the test starts in reset. Which bins suffer?Every coverpoint samples reset-phase values, including propagated unknowns. Those hits pollute real bins and fake closure progress.
The driver records intent. The monitor records what the design did. Coverage must measure observed behavior, or a misbehaving design still scores well.
The bench counts cp_len three ways over the same four transactions: once per completed transaction from the monitor, on every clock, and on every clock with iff (rst_n).
The per-transaction one. Clocked sampling fills the zero bin with every idle and reset cycle, and even the guarded version counts idle cycles; neither says anything about transactions.
// uses: covmodel
// bus_coverage sampled three ways: once per transaction, every clock, and every clock with iff (rst_n).
module tb_sampling;
logic clk = 0, rst_n = 0;
always #5 clk = ~clk;
logic busy = 0;
logic [7:0] len = 0;
cm_point per_txn = new("cp_len (sample once per txn)");
cm_point per_clk = new("cp_len (clocked, no guard)");
cm_point guarded = new("cp_len (clocked, iff rst_n)");
function automatic void len_bins(cm_point cp);
cp.add("zero", 0, 0); cp.add("single", 1, 1); cp.add("short_b", 2, 4);
cp.add("long_b", 5, 254); cp.add("max_len", 255, 255);
endfunction
// Clocked sampling: every posedge, whatever the bus is doing.
always @(posedge clk) begin
per_clk.sample(len);
if (rst_n) guarded.sample(len); // iff (rst_n): skipped during reset
end
// The monitor: one sample when a transaction completes.
task automatic txn(logic [7:0] l, int beats);
@(negedge clk) busy = 1; len = l;
repeat (beats) @(negedge clk);
busy = 0;
per_txn.sample(l);
len = 0;
@(negedge clk);
endtask
initial begin
len_bins(per_txn); len_bins(per_clk); len_bins(guarded);
repeat (4) @(negedge clk); // reset: len sits at 0
rst_n = 1;
txn(8'd1, 1); txn(8'd3, 3); txn(8'd40, 6); txn(8'd255, 8);
repeat (6) @(negedge clk); // idle bus
per_txn.report(); per_clk.report(); guarded.report();
$display("zero bin: %0d hits clocked, %0d with iff (rst_n), %0d per transaction",
per_clk.bin_list[0].hits, guarded.bin_list[0].hits, per_txn.bin_list[0].hits);
if (per_txn.n_covered() != 4 || per_txn.bin_list[0].hits != 0 || per_clk.bin_list[0].hits <= guarded.bin_list[0].hits)
$fatal(1, "FAIL: sampling counts");
$display("PASS: per-transaction sampling counted 4 transactions once each; clocked sampling filled the zero bin with idle and reset cycles");
$finish;
end
endmodule coverpoint cp_len (sample once per txn): 80.00% (4 of 5 bins) below goal 100
zero 0 <- hole
single 1
short_b 1
long_b 1
max_len 1
coverpoint cp_len (clocked, no guard): 100.00% (5 of 5 bins)
zero 18
single 1
short_b 3
long_b 6
max_len 8
coverpoint cp_len (clocked, iff rst_n): 100.00% (5 of 5 bins)
zero 14
single 1
short_b 3
long_b 6
max_len 8
zero bin: 18 hits clocked, 14 with iff (rst_n), 0 per transaction
PASS: per-transaction sampling counted 4 transactions once each; clocked sampling filled the zero bin with idle and reset cyclesThe per-transaction report has one hole, zero: no zero-length transaction was ever sent, which is a real stimulus gap. The clocked reports show that bin full, filled by idle cycles, and hide the gap.
Counted 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 bench counts the same four transactions both ways: per-transaction sampling counted each once, and clocked sampling filled the zero bin from idle cycles.