Part 5 · Functional Coverage · Intermediate
Parameterizing Covergroups
Covergroup constructor arguments, ref arguments for sampled variables, and one definition reused across N differently-configured instances.
Covergroups take constructor arguments
A covergroup declaration can have a formal argument list, exactly like a function. Arguments are bound at new() time and can shape bins, set options, and select what gets sampled. This is how one covergroup definition serves N instances with different ranges, names, and modes — instead of N copy-pasted variants that drift apart.
// 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]}; // bounds from constructor args
bins below = {[0:lo-1]};
bins above = {[hi+1:32'hFFFF_FFFF]};
}
endgroup
// Same definition, three differently-bounded instances:
addr_cg sram_cov = new("sram_cov", 32'h0000_0000, 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);Value vs ref arguments
ARGUMENT KIND bound when used for
──────────────────────────────────────────────────────────────
value (default) copied at new() bin bounds, names, modes,
option settings
ref aliased at new() THE VARIABLE TO SAMPLE —
covergroup reads it live at
each sampling event
module port_mon; ┌──────────────┐
logic [7:0] len0, len1; │ len_cg type │
len_cg c0 = new(len0); ──ref──►│ samples len0 │
len_cg c1 = new(len1); ──ref──►│ samples len1 │
endmodule └──────────────┘
One definition. Each instance watches a DIFFERENT signal.// 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");The reuse pattern in a class-based bench
In class-based environments the same idea appears as an embedded covergroup constructed with per-object configuration. One monitor class, instantiated per port, builds its covergroup with port-specific name and bounds — the covergroup definition is written once.
class port_monitor;
string name;
int max_len;
covergroup cg (string nm, int maxl) with function sample(int len, bit err);
option.per_instance = 1;
option.name = nm;
cp_len : coverpoint len {
bins small = {[1:8]};
bins big = {[9:maxl]}; // per-instance upper bound
}
cp_err : coverpoint err { bins ok = {0}; bins bad = {1}; }
x_le : cross cp_len, cp_err;
endgroup
function new(string name, int max_len);
this.name = name;
this.max_len = max_len;
cg = new(name, max_len); // embedded covergroup: construct EXACTLY once
endfunction
function void observe(int len, bit err);
cg.sample(len, err);
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);Rules that keep this safe
An embedded (in-class) covergroup is constructed in the class constructor with cg = new(args) — once, and only there.
Value arguments are frozen at new(): later changes to the variable you passed do not retune the bins.
ref arguments alias a variable for sampling; the referenced signal must outlive the covergroup instance.
Bins built from arguments (like [9:maxl]) are fixed at construction — parameterization is per-instance, not dynamic.
Interview angle: "How do you cover five ports with different legal length ranges without five covergroups?" — constructor arguments for the bounds and name, ref arguments (or a with function sample signature) for the data, per_instance for separate report rows. A frequent follow-up probes whether you know value args are bound at construction time, not sampled live — the classic bug is passing a variable and expecting bins to track it.
Key takeaways
Covergroups accept constructor arguments — bin bounds, names, and options can differ per instance.
Value arguments are copied at new(); ref arguments alias the actual variable to sample.
Embedded covergroups in classes are constructed once in the class constructor, with per-object arguments.
One parameterized definition beats N drifting copies — the bins stay reviewable in one place.
Common pitfalls
Expecting a value argument to track its source variable — it is a snapshot taken at new().
Constructing an embedded covergroup outside the class constructor, or twice — tool errors or lost coverage.
Binding a ref argument to a variable that goes out of scope — sampling a dangling alias.
Parameterizing bounds but hard-coding option.name — per-instance reports become indistinguishable again.