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.

systemverilog
// 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

diagram
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.
systemverilog
// 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.

systemverilog
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.