A covergroup declaration takes a formal argument list like a function. Arguments bind at new time and can shape bins, set options, and name the instance.
One definition then serves N differently configured instances. The alternative is N copy-pasted variants that drift apart.
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
// Value arguments shape bins and options per instance
covergroup addr_cg (string nm, int unsigned lo, int unsigned hi)
@(posedge clk iff valid);
option.per_instance = 1;
option.name = nm;
cp_addr : coverpoint addr {
bins in_range = {[lo:hi]};
bins below = {[0:lo-1]};
bins above = {[hi+1:32'hFFFF_FFFF]};
}
endgroup
// Same definition, three differently bounded instances.
// Keep 0 < lo and hi < 32'hFFFF_FFFF so the edge bins stay correct:
// at lo = 0, lo-1 wraps to 32'hFFFF_FFFF and below covers the whole
// space (always hit); at max hi, hi+1 wraps to 0 the same way.
addr_cg sram_cov = new("sram_cov", 32'h0000_1000, 32'h0000_FFFF);
addr_cg peri_cov = new("peri_cov", 32'h4000_0000, 32'h4001_FFFF);
addr_cg ddr_cov = new("ddr_cov", 32'h8000_0000, 32'hBFFF_FFFF);Bounds are frozen per instance at construction. Later changes to the passed variables never retune the bins.
Kind | Bound when | Used for |
|---|---|---|
Value, the default | Copied at | Bin bounds, names, modes, option settings |
| Aliased at | The variable to sample, read live at each sampling event |
A ref argument lets each instance watch a different signal. 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
// ref argument: the instance samples whichever variable it was bound to
covergroup len_cg (ref logic [7:0] len_sig, input string nm)
@(posedge clk iff valid);
option.per_instance = 1;
option.name = nm;
cp_len : coverpoint len_sig {
bins s = {[1:4]}; bins m = {[5:64]}; bins l = {[65:255]};
}
endgroup
logic [7:0] rx_len, tx_len;
len_cg rx_cov = new(rx_len, "rx_len_cov");
len_cg tx_cov = new(tx_len, "tx_len_cov");One definition watches two signals. The referenced signal must outlive the covergroup instance, or sampling reads a dangling alias.
In class benches the same idea appears as an embedded covergroup, built once per object with that object's configuration. 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
class port_monitor;
string mon_name;
int max_len;
covergroup cg (string nm, int maxl) with function sample(int len, bit bad);
option.per_instance = 1;
option.name = nm;
cp_len : coverpoint len {
bins small = {[1:8]};
bins big = {[9:maxl]}; // per-instance upper bound
}
cp_bad : coverpoint bad { bins ok = {0}; bins seen = {1}; }
x_lb : cross cp_len, cp_bad;
endgroup
function new(string name_arg, int max_len_arg);
mon_name = name_arg;
max_len = max_len_arg;
cg = new(mon_name, max_len); // embedded covergroup: construct once, here
endfunction
function void observe(int len, bit bad);
cg.sample(len, bad);
endfunction
endclass
// port0 allows long packets, port1 is a narrow control port:
port_monitor m0 = new("port0_cov", 1500);
port_monitor m1 = new("port1_cov", 64);An embedded covergroup is constructed in the class constructor, once and only there.
Value arguments are a snapshot at new. Expecting bins to track the source variable is the classic bug.
Bins built from arguments are fixed at construction. Parameterization is per-instance, not dynamic.
new. Do the bins change?No. Value arguments are copied at construction. Only a ref argument follows a live variable, and it supplies sampled values, not bounds.
Once, in the class constructor. Anywhere else risks tool errors or silently lost coverage.
The bench gives the bins model a constructor with the lesson's arguments (nm, lo, hi), builds three instances, and changes the variable that supplied sram's upper bound after new. Addresses are 14 bits here so the model stays small, and the constructor has the guards a real covergroup needs: no below bin when lo is 0.
hi_bound drops from 255 to 100 after sram was built. How many in-range hits does sram count?4: 0, 1, 255 and 7. The bound was copied at new, so 255 still counts. peri counts 3 and ddr counts 3 from the same addresses.
// uses: covmodel
// One parameterized definition, three instances: covergroup addr_cg (string nm, int lo, int hi).
// Addresses are 14 bits here so the model's bins stay small.
class addr_cov;
cm_point cp_addr;
function new(string nm, int lo, int hi); // value arguments: copied now, frozen after
cp_addr = new(nm);
cp_addr.add("in_range", lo, hi);
if (lo > 0) cp_addr.add("below", 0, lo - 1); // the guards a real covergroup needs too:
if (hi < 16383) cp_addr.add("above", hi + 1, 16383); // lo - 1 and hi + 1 must not wrap
endfunction
endclass
module tb_range_args;
addr_cov sram, peri, ddr;
initial begin
int hi_bound = 255;
sram = new("sram_cov", 0, hi_bound);
peri = new("peri_cov", 1024, 2047);
ddr = new("ddr_cov", 2048, 8191);
hi_bound = 100; // too late: sram's bins were built with 255
begin
int addrs[12] = '{0, 1, 255, 256, 1024, 1500, 2047, 2048, 4096, 8191, 8192, 7};
foreach (addrs[i]) begin sram.cp_addr.sample(addrs[i]); peri.cp_addr.sample(addrs[i]); ddr.cp_addr.sample(addrs[i]); end
end
sram.cp_addr.report(); peri.cp_addr.report(); ddr.cp_addr.report();
if (sram.cp_addr.bin_list[0].hits != 4 || peri.cp_addr.bin_list[0].hits != 3 || ddr.cp_addr.bin_list[0].hits != 3 ||
sram.cp_addr.n_bins() != 2) $fatal(1, "FAIL: per-instance bins");
$display("PASS: one definition counted 4 sram, 3 peri and 3 ddr hits; changing hi_bound after new changed nothing");
$finish;
end
endmodule coverpoint sram_cov: 100.00% (2 of 2 bins)
in_range 4
above 8
coverpoint peri_cov: 100.00% (3 of 3 bins)
in_range 3
below 5
above 4
coverpoint ddr_cov: 100.00% (3 of 3 bins)
in_range 3
below 8
above 1
PASS: one definition counted 4 sram, 3 peri and 3 ddr hits; changing hi_bound after new changed nothingCounted 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.
Three instances counted 4, 3 and 3 in-range hits from the same twelve addresses, and changing the bound variable after new() changed nothing.