Functional coverage is built from two closely related pieces: coverpoint, which does the actual work of tracking what's been exercised, and covergroup, which is the container that holds one or more coverpoints together. This article starts with the coverpoint, since that's the piece that actually answers the "have we tested everything?" question, then shows how covergroups bring several of them together into one coverage model.

8 min read | Intermediate Level

What You'll Learn

  • What a coverpoint is and why it's the fundamental unit of functional coverage
  • How to declare a coverpoint, and how its legal values get grouped into bins
  • What a covergroup is and how it brings multiple coverpoints together
  • How a covergroup gets sampled, and how to read its reported coverage percentage

What Is a Coverpoint, and Why Does It Matter?

A coverpoint watches a single integral variable or expression and records which of its legal values have actually occurred during the test. Every time it's sampled, a coverpoint checks the current value of whatever it's watching and marks that value as "hit."

This is the piece that does the real measuring in functional coverage — it's the difference between assuming your random stimulus was thorough and actually knowing it was. Each value (or group of values) a coverpoint tracks lives in a bin. By default, SystemVerilog automatically creates one bin per legal value of the variable being covered — a 2-bit variable gets 4 automatic bins, one per possible value. SystemVerilog Coverpoint Bins covers how to group values into custom bins yourself, which matters once a variable is too wide for one bin per value to be practical.

How a Coverpoint Is Written

A coverpoint can optionally be given a name (a label) before a colon:


[label :] coverpoint <variable_or_expression>;

Parameters:

  • label — an optional name for the coverpoint, followed by a colon
  • variable_or_expression — the signal, bit-slice, or expression the coverpoint tracks

A coverpoint never appears on its own, though — it always lives inside a covergroup, which is the container for the overall coverage model.

What Is a Covergroup?

A covergroup is a user-defined type that encapsulates a coverage model — it can be defined once and instantiated multiple times through its new() function, just like a class. A covergroup can be defined inside a package, module, program, interface, or class, and typically brings together:

  • One or more coverpoints — the individual signals or expressions being tracked
  • Cross coverage between coverage points — tracking combinations of values across two or more coverpoints, rather than each one in isolation (covered in its own article later in this chapter)
  • An event that defines when the covergroup is sampled — a clock edge or any other SystemVerilog event, deciding how often every coverpoint inside it checks its current value
  • Other options to configure the coverage object — things like per-coverpoint weight or a target coverage goal

How a Covergroup Is Written

A minimal covergroup declaration wraps one or more coverpoints between covergroup and endgroup:


covergroup <covergroup_name> [(arguments)] [@ (sampling_event)];
	[label :] coverpoint <variable_or_expression>;
	...
endgroup

// Instantiate the covergroup, similar to instantiating a class
<covergroup_name> <instance_name> = new();

Parameters:

  • <covergroup_name> — the name of the covergroup type being defined
  • (arguments) — optional arguments passed in when the covergroup is instantiated (optional)
  • @ (sampling_event) — the event that triggers sampling, such as a clock edge (optional, but almost always used)

A Single Coverpoint in Action

Here's that skeleton filled in with a minimal working example — one covergroup with one coverpoint tracking the variable mode. The group is sampled on every posedge clk, while mode and cfg are assigned fresh random values on every negedge clk — so by the time each sample happens, mode already holds that cycle's random value:


module tb;
  
  // Declare some variables that can be "sampled" in the covergroup
  bit [1:0] mode;
  bit [2:0] cfg;
  
  // Declare a clock to act as an event that can be used to sample
  // coverage points within the covergroup
  bit clk;
  always #20 clk = ~clk;
  
  // "cg" is a covergroup that is sampled at every posedge clk
  covergroup cg @ (posedge clk);
    coverpoint mode;
  endgroup
  
  // Create an instance of the covergroup
  cg  cg_inst;
  
  initial begin
    // Instantiate the covergroup object similar to a class object
    cg_inst= new();
  
    // Stimulus : Simply assign random values to the coverage variables
    // so that different values can be sampled by the covergroup object
    for (int i = 0; i < 5; i++) begin
      @(negedge clk);
      mode = $random;
      cfg  = $random;
      $display ("[%0t] mode=0x%0h cfg=0x%0h", $time, mode, cfg);
    end
  end
  
  // At the end of 500ns, terminate test and print collected coverage
  initial begin
    #500 $display ("Coverage = %0.2f %%", cg_inst.get_inst_coverage());
    $finish;
  end
endmodule

Output

Output
ncsim> run
[40] mode=0x0 cfg=0x1
[80] mode=0x1 cfg=0x3
[120] mode=0x1 cfg=0x5
[160] mode=0x1 cfg=0x2
[200] mode=0x1 cfg=0x5
Coverage = 50.00 %
Simulation complete via $finish(1) at time 500 NS + 0

mode is 2 bits wide, so it has 4 legal values (0 through 3), and by default a coverpoint creates one automatic bin per legal value, as described earlier. Across the 5 samples shown, only 0x0 and 0x1 ever appeared — 2 out of 4 possible values — which is exactly why get_inst_coverage() reports 50.00%.

Quick Check

Q: If a 3-bit coverpoint variable has hit 5 of its 8 possible values by the end of a test, what coverage percentage would you expect?

Show Answer

62.5% — 5 out of 8 possible values, the same "values hit ÷ total legal values" calculation used in the example above.

Multiple Coverpoints in One Covergroup

A covergroup can contain more than one coverpoint, and each one can optionally be given a name followed by a colon. mode can take any value from 0 to 3, and cfg can take any value from 0 to 7 — the question functional coverage answers is: out of all the values each of these could legally take, how many actually showed up during the test?


module tb;
  
  bit [1:0] mode;
  bit [2:0] cfg;
  
  bit clk;
  always #20 clk = ~clk;
  
  // "cg" is a covergroup that is sampled at every posedge clk
  // This covergroup has two coverage points, one to cover "mode"
  // and the other to cover "cfg". Mode can take any value from
  // 0 -> 3 and cfg can take any value from 0 -> 7
  covergroup cg @ (posedge clk);
  
    // Coverpoints can optionally have a name before a colon ":"
    cp_mode    : coverpoint mode;
    cp_cfg_10  : coverpoint cfg[1:0];
    cp_cfg_lsb : coverpoint cfg[0];
    cp_sum     : coverpoint (mode + cfg);
  endgroup
  
  cg  cg_inst;
  
  initial begin
    cg_inst= new();

    for (int i = 0; i < 5; i++) begin
      @(negedge clk);
      mode = $random;
      cfg  = $random;
      $display ("[%0t] mode=0x%0h cfg=0x%0h", $time, mode, cfg);
    end
  end
  
  initial begin
    #500 $display ("Coverage = %0.2f %%", cg_inst.get_inst_coverage());
    $finish;
  end
endmodule

Output

Output
ncsim> run
[40] mode=0x0 cfg=0x1
[80] mode=0x1 cfg=0x3
[120] mode=0x1 cfg=0x5
[160] mode=0x1 cfg=0x2
[200] mode=0x1 cfg=0x5
Coverage = 68.75 %
Simulation complete via $finish(1) at time 500 NS + 0

Note that a coverpoint doesn't have to track a plain variable — cp_cfg_10 covers a bit-slice (cfg[1:0]), cp_cfg_lsb covers a single bit (cfg[0]), and cp_sum covers an entire expression (mode + cfg). get_inst_coverage() reports the overall coverage across all four coverpoints combined, which is why the second run's 68.75% differs from the single-coverpoint example's 50.00% — it's now an aggregate across every coverpoint in the group.

Where Is Functional Coverage Used?

Functional coverage is how verification teams decide when random testing has actually done its job — tracking which opcodes, address ranges, or protocol states have been exercised lets you spot gaps a purely random test plan might otherwise miss for a long time, and coverage reports are commonly part of the sign-off criteria before a design is considered adequately verified.

Common Beginner Mistakes

Mistake #1: Forgetting the sampling event

A covergroup only updates when its sampling event occurs — @ (posedge clk) in the examples above. Why? If that event never fires (a clock that's never toggled, or an event that's misspelled), the covergroup silently reports 0% coverage forever, with no error to warn you something's wrong.

Mistake #2: Assuming high coverage means the design is bug-free

Coverage only measures which values or scenarios your stimulus produced — it says nothing about whether the design responded correctly to them. Why? A test can reach 100% functional coverage on a coverpoint while a completely broken scoreboard or missing checker lets every incorrect result slip through unnoticed.

Mistake #3: Expecting automatic bins to scale to wide variables

By default, a coverpoint creates one bin per legal value, which works fine for a 2-bit or 3-bit variable like the examples above. Why? For something wider, like a 32-bit address, that default would try to create billions of bins — you need to explicitly group values into ranges, which is exactly what SystemVerilog Coverpoint Bins covers next.

Practice Exercise

Challenge: Add a Coverpoint for cfg

Starting from the single-coverpoint example, add a second coverpoint that tracks cfg directly (not a bit-slice). Given the sampled values in the output log (cfg = 0x1, 0x3, 0x5, 0x2, 0x5), how many of the 8 possible values were hit, and what coverage percentage would that coverpoint alone report?

Hint

List out the distinct cfg values from the log first, then divide the count of distinct values by 8.

Solution

covergroup cg @ (posedge clk);
  coverpoint mode;
  coverpoint cfg;
endgroup

The sampled cfg values are 0x1, 0x3, 0x5, 0x2, and 0x5 — four distinct values (0x1, 0x2, 0x3, 0x5) out of 8 possible. That coverpoint alone would report 4/8 = 50.00% coverage.

Summary

In this tutorial, you learned:

  • A coverpoint is the piece that actually measures coverage — it tracks one variable or expression and records which legal values have occurred
  • Each value (or group of values) a coverpoint tracks lives in a bin; by default, one automatic bin is created per legal value
  • A covergroup is the container that groups one or more coverpoints together, along with a sampling event, optional cross coverage, and configuration options
  • A covergroup is sampled whenever its declared event occurs; if that event never fires, coverage stays at 0%
  • get_inst_coverage() reports the aggregate coverage across every coverpoint in the group
  • High coverage confirms stimulus diversity, not correctness — it's not a substitute for checking results

Continuing the Functional Coverage Chapter

Continue with SystemVerilog Functional Coverage for a broader look at the coverage flow, Coverpoint Bins for grouping wide ranges of values together, and Cross Coverage for tracking combinations between multiple coverpoints.

Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5