SystemVerilog Series · SV-20

SystemVerilog Series — SV-20: Coverage — VLSI Trainers
SystemVerilog Series · SV-20

Coverage

Functional coverage concepts, the covergroup construct as a user-defined type, coverage points, cross coverage, clocking and block events for sampling, formal arguments including ref, and embedding covergroups inside classes for tightly-bound property coverage.

📈 Functional Coverage

Functional coverage is a user-defined metric that measures how much of the design specification has been exercised by simulation. Unlike code coverage (which is inferred automatically from design code), functional coverage is written explicitly to reflect the intent of the design — what scenarios, corner cases, and invariants must be tested.

  • User-specified — not automatically inferred from the design code or its structure.
  • Specification-based — tied to the design intent, independent of implementation details.
  • Used to measure whether interesting scenarios, corner cases, and required protocol sequences have been observed and validated.

Functional coverage is used as a guide: when coverage is high, the verification team can redirect simulation effort to areas not yet exercised rather than running redundant tests.

📋 Code vs Functional Coverage

Code Coverage

  • Automatically extracted from design source
  • Measures lines, branches, expressions, FSM states hit
  • Independent of design intent
  • Does not tell you if interesting scenarios were exercised
  • 100% code coverage does not mean 100% of specification exercised

Functional Coverage

  • User-written coverage model
  • Directly tied to the verification plan
  • Measures specific values, transitions, combinations
  • Can cover corner cases, protocol sequences, error conditions
  • Requires up-front effort but yields higher confidence

The SV functional coverage constructs enable: coverage of variables and expressions; automatic and user-defined coverage bins; bins for values, transitions, and cross products; filtering conditions; event- and sequence-triggered sampling; and procedural activation and query of coverage data.

📄 The covergroup Construct

A covergroup is a user-defined type that encapsulates an entire coverage model. The type is defined once, and any number of instances can be created via new(). It can be defined inside a module, program, interface, or class.

// Define the covergroup type
covergroup cg;
  // ... coverage points, cross, options
endgroup

// Create an instance
cg cg_inst = new;

// Optional closing name — must match opening name
covergroup my_cov;
  // ...
endgroup : my_cov

// Multiple instances of the same covergroup type
cg inst_a = new;
cg inst_b = new;   // independent coverage tracking per instance
covergroup is a type, not an instance. Just like a class, declaring a covergroup creates a type. The actual coverage tracking only starts when you create an instance with new(). Multiple instances track coverage independently — useful when the same coverage model applies to multiple bus channels, ports, or objects.

📋 Five Components of a Covergroup

Clocking event
Optional — synchronises sampling. If omitted, call sample() manually.
Coverage points
Variables or expressions to track. Each point has associated bins.
Cross coverage
Cartesian product of two or more coverage points.
Formal arguments
Optional — parameterise the covergroup with values or ref variables.
Coverage options
Control binning behaviour, goal, weight, per-instance tracking.
// Showing all five components
covergroup g2 @(posedge clk);         // ① clocking event
  Hue:    coverpoint pixel_hue;       // ② coverage point (labeled)
  Offset: coverpoint pixel_offset;   // ② coverage point (labeled)
  AxC:    cross color, pixel_adr;    // ③ cross (2 implicit coverpoints)
  all:    cross color, Hue, Offset;  // ③ cross (1 var + 2 explicit points)
  option.per_instance = 1;           // ⑤ coverage option
endgroup

📋 Type and Instance — new()

The covergroup type is defined once. Instances are created with new(). Instance options like name and comment can differ per instance; type options like weight and goal apply to all instances.

// Simple type and instance
covergroup cg; ... endgroup
cg cg_inst = new;

// Instance options can be set after creation
cg_inst.option.comment = "Bus A coverage";
cg_inst.a.option.weight = 3;  // weight for coverpoint 'a' of this instance

// Type options set with :: — affect all instances
gc::type_option.comment = "Global coverage model";
gc::a::type_option.weight = 3;

Clocking Events

A clocking event specifies when coverage points are sampled automatically. Without one, the covergroup must be sampled procedurally by calling .sample().

// Sample on every posedge clk
covergroup cg1 @(posedge clk);
  coverpoint state;
endgroup

// Sample on any change of member m_z (event-based)
covergroup cg2 @m_z;
  coverpoint data;
endgroup

// No clocking event — must call sample() manually
covergroup cg3;
  coverpoint addr;
endgroup
cg3 c = new;
initial begin
  @(posedge clk);
  c.sample();   // trigger sampling explicitly
end
Multiple triggers per time step: by default, if the clocking event fires multiple times in one simulation time step, coverage is sampled each time. Set type_option.strobe = 1 to restrict sampling to the Postponed region — once per time slot regardless of multiple triggers.

📋 Block Event Expressions

Instead of a clocking signal, a covergroup can trigger on the start or end of execution of a named block, task, function, or class method.

// Trigger on BEGIN of task "write_burst"
covergroup cg_begin @@(begin write_burst);
  coverpoint burst_len;
endgroup

// Trigger on END of task "write_burst"
covergroup cg_end @@(end write_burst);
  coverpoint status;
endgroup

// OR: fire on end of either of two functions
covergroup cg_or @@(end read_bus or end write_bus);
  coverpoint return_code;
endgroup
  • @@(begin task_name) — fires immediately before the task’s first statement.
  • @@(end task_name) — fires immediately after the task’s last statement.
  • The end-of-execution trigger does not fire if the block is disabled.
  • Multiple conditions can be OR-ed: @@(end f1 or end f2).

📋 Coverage Points

A coverage point samples an integral variable or expression at each clocking event. It can have an optional label (becoming the point’s name) and an optional iff guard condition.

// Labeled coverage point on an enum — 3 auto bins (red, green, blue)
enum { red, green, blue } color;
covergroup g1 @(posedge clk);
  c: coverpoint color;  // label 'c' creates the coverpoint name
endgroup

// Guard condition: sample s0 only when reset is inactive
covergroup g4;
  coverpoint s0 iff(!reset);  // coverage ignored when reset is true
endgroup

// Explicit bins: named ranges covering a 10-bit variable
bit [9:0] v_a;
covergroup cg @(posedge clk);
  coverpoint v_a {
    bins a       = { [0:63], 65 };       // one bin for this value set
    bins b[]     = { [127:150],[148:191] }; // one bin per value (65 bins)
    bins c[]     = { 200, 201, 202 };      // three individual bins
    bins d       = { [1000:$] };           // $ = maximum value of v_a
    bins others[] = default;              // catch-all: one bin per uncovered value
  }
endgroup

Bin forms

SyntaxEffect
bins name = {range_list}One bin for the entire value set
bins name[] = {range_list}One bin per value in the set (array of bins)
bins name[N] = {range_list}N fixed bins; values distributed uniformly
bins name = defaultOne bin per value not covered by other bins (not counted in coverage %)
ignore_bins name = {values}Exclude these values from coverage entirely
illegal_bins name = {values}Exclude and report runtime error if seen
wildcard bins name = {4’b11??}X/Z/? treated as 0 or 1 wildcard (2-state match)
default bin is excluded from the coverage percentage. The default bin captures unexpected values for diagnostic purposes, but it does not count toward the coverage goal. Use it to catch design bugs (unexpected state values) without inflating the coverage score.

Automatic bin creation

  • If no bins are defined, SV automatically creates bins.
  • For enums: one bin per enumeration value.
  • For other integral types: N = min(2ᴹ, auto_bin_max) bins, where M = bit-width. Default auto_bin_max = 64.
  • If N < 2ᴹ, values are distributed uniformly across N bins; the last bin absorbs any remainder.
  • Automatic bins are named auto[value] or auto[low:high].
  • X and Z values are excluded from automatic bins.

Cross Coverage

Cross coverage tracks the Cartesian product of two or more coverage points. Every combination of bins from the participating points becomes a cross-product bin. When a variable (not a coverpoint) is listed in a cross, SV implicitly creates a coverpoint for it.

// Simple 2-variable cross: 16×16 = 256 cross-product bins
bit [3:0] a, b;
covergroup cov @(posedge clk);
  aXb : cross a, b;   // implicit coverpoints for a and b
endgroup

// Expression coverpoint crossed with a variable
covergroup cov2 @(posedge clk);
  BC: coverpoint b+c;    // explicit coverpoint for expression
  aXb: cross a, BC;     // a implicitly created; BC already explicit
endgroup

// Explicit cross bins using select expressions
covergroup cg @(posedge clk);
  a: coverpoint v_a { bins a1={[0:63]}; bins a2={[64:127]}; }
  b: coverpoint v_b { bins b1={0}; bins b2={[1:84]}; }
  c: cross v_a, v_b {
    // cross products where a does NOT intersect [100:200] → 4 products
    bins c1 = !binsof(a) intersect {[100:200]};
    // cross products in a2 OR in b2 → 7 products
    bins c2 = binsof(a.a2) || binsof(b.b2);
    // cross products in a1 AND in b4 → 1 product
    bins c3 = binsof(a.a1) && binsof(b.b2);
    // ignore: exclude specific cross products from coverage
    ignore_bins foo = binsof(a) intersect {5, [1:3]};
  }
endgroup
binsof operators: binsof(cp) yields all bins of a coverpoint. binsof(cp.bin_name) selects a specific named bin. binsof(cp) intersect {range} selects only bins whose values overlap the range. The negated form !binsof(cp) intersect {range} selects bins whose values do NOT overlap. Combine with && and || for complex cross bin selection.

📋 Coverage Options

Options control binning behaviour, goals, weights, and reporting. Instance options (option.name) are per-instance; type options (type_option.name) apply to all instances of the covergroup type.

Instance optionDefaultWhat it controls
weight1Weight of this point/cross toward overall coverage
goal90Target coverage percentage for this instance/point
nameuniqueExplicit name for this instance in coverage reports
comment“”Comment stored in coverage database and reports
at_least1Minimum hits per bin to count as covered
auto_bin_max64Max automatic bins for coverpoints without explicit bins
cross_auto_bin_maxunboundedMax automatic cross product bins
detect_overlap0Warn if bin ranges overlap within a coverpoint
per_instance0Track coverage individually per instance (in addition to type-level)
Type optionDefaultWhat it controls
weight1Weight toward the simulation-wide cumulative coverage
goal90Target for the covergroup type overall
comment“”Comment for the covergroup type in reports
strobe01 = sample in Postponed region (one sample per time slot)
// Options inline in the covergroup definition
covergroup g1(int w, string instComment) @(posedge clk);
  option.per_instance = 1;
  option.comment      = instComment;
  type_option.strobe = 1;     // sample once per time slot
  a: coverpoint a_var {
    option.auto_bin_max = 128;  // 128 bins for this point
  }
  b: coverpoint b_var {
    option.weight       = w;    // instance option set from argument
    type_option.weight  = 5;    // type option — must be a constant
  }
endgroup

📋 Formal Arguments and ref

Covergroups can accept arguments, allowing generic coverage models reusable across different variables and ranges. A ref argument causes the covergroup to track the variable — sampling its current value at each clock tick. An input argument captures the value once at new() time.

// Generic covergroup with ref variable and range arguments
covergroup gc(ref int ra, int low, int high) @(posedge clk);
  coverpoint ra {                   // ra sampled by reference at each clock
    bins good  = { [low : high] };  // range from input args
    bins bad[] = default;
  }
endgroup

int va, vb;
gc c1 = new(va, 0,   50);   // c1 tracks va; good = [0:50]
gc c2 = new(vb, 120, 600);  // c2 tracks vb; good = [120:600]

// ref vs input distinction:
// ref int ra  → ra is sampled at every clocking event (tracks variable)
// int low     → low is evaluated once when new() is called (snapshot)

📋 Covergroups in Classes

Embedding a covergroup inside a class tightly binds coverage to the class properties — including local and protected members that would otherwise be inaccessible from outside. The embedded covergroup becomes part of the class’s type definition.

// Simple embedded covergroup
class xyz;
  bit [3:0] m_x;
  int       m_y;
  bit       m_z;

  covergroup cov1 @m_z;     // samples on change of m_z
    coverpoint m_x;
    coverpoint m_y;
  endgroup

  function new(); cov1 = new; endfunction   // MUST instantiate in new()
endclass

// Multiple covergroups in one class — different sampling events
class MC;
  logic [3:0] m_x;
  local logic  m_z;   // local — only embedded covergroup can access
  bit          m_e;
  covergroup cv1 @(posedge clk); coverpoint m_x; endgroup
  covergroup cv2 @m_e;           coverpoint m_z; endgroup
  // cv2 covers local m_z — impossible from outside the class
endclass

📋 Embedded Covergroup Rules

  • When a covergroup is defined in a class without an explicit variable declaration, a variable with the same name as the covergroup is implicitly declared.
  • Each class contains exactly one variable of each embedded covergroup type. Declaring multiple variables of the same embedded covergroup is a compile error.
  • The embedded covergroup must be explicitly instantiated in the class’s new() method by assigning cg_name = new(...). If it is not, the covergroup is never created and no data is sampled.
  • Embedded covergroups can access any class member — including local and protected — without changing the class’s data encapsulation.

📋 Advanced Embedded Patterns

Covergroup referencing a member of a nested object

class Helper;
  int m_ev;
endclass

class MyClass;
  Helper m_obj;
  int    m_a;

  covergroup Cov @(m_obj.m_ev);   // trigger on m_obj.m_ev change
    coverpoint m_a;
  endgroup

  function new();
    m_obj = new;     // MUST create m_obj before Cov!
    Cov   = new;     // Cov references m_obj.m_ev — m_obj must exist first
  endfunction
endclass

Covergroup argument used to set an option

class C1;
  bit [7:0] x;
  covergroup cv(int arg) @(posedge clk);
    option.at_least = arg;  // each bin must be hit 'arg' times to count
    coverpoint x;
  endgroup

  function new(int p1);
    cv = new(p1);   // pass in minimum hit count at construction time
  endfunction
endclass

initial begin
  C1 obj = new(4);   // each bin must be hit at least 4 times
end
Why embed covergroups in classes? A standalone covergroup can only access variables in its enclosing scope via hierarchical references. An embedded covergroup automatically sees all class members — including private ones — without any hierarchical paths or argument passing. This is the cleanest way to write coverage models for transactors, monitors, and scoreboards in object-oriented testbenches.

📋 Quick Reference

Covergroup structure

ItemSyntaxNotes
Define typecovergroup name; … endgroupType only — no sampling yet
Instantiatename inst = new(args)Sampling begins at construction
Clock triggercovergroup cg @(posedge clk)Auto-samples at every trigger
Block triggercovergroup cg @@(end task_name)Samples at start/end of named block
Manual sampleinst.sample()Required when no clocking event
Coverage pointlabel: coverpoint expr;Label becomes coverpoint name
Guard conditioncoverpoint x iff(!reset)Skip sample when condition false
Crosslabel: cross cp1, cp2;Cartesian product of all bins

Key rules

  • Covergroup is a type — instance must be created with new().
  • Without a clocking event, call .sample() procedurally.
  • The default bin catches uncovered values but does not count toward coverage %.
  • Automatic bins: enum → one per value; others → min(2ᴹ, auto_bin_max) bins.
  • X/Z values excluded from automatic bins and wildcard bins.
  • Embedded covergroups access all class members including local and protected.
  • Embedded covergroups must be instantiated in new() — or they never collect data.
  • Object dependencies (e.g. m_obj.m_ev) must be created before the covergroup is instantiated.
  • type_option values must be constants; option values can be expressions evaluated at new() time.
  • per_instance can only be set in the covergroup definition, not procedurally afterward.
Coming next: SV-21 covers Parameters — typed parameters, type parameters (parameter type), localparam in generate blocks, the $ token for unbounded ranges, $isunbounded(), and parameter dependencies.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top