Part 5 · Functional Coverage · Intermediate

Sampling: Clocked, Event & sample()

Clocking-event sampling vs explicit sample() calls, sample() with function arguments, where to sample in a testbench, and iff guards.

Two sampling models

A covergroup samples in one of two ways: automatically , on a clocking event declared in its header, or explicitly , when your code calls .sample(). Clocked sampling suits white-box signal coverage inside modules and interfaces — every clock edge is a measurement. Explicit sampling suits transaction-level testbenches — you sample once per completed transaction, exactly when the data is meaningful.

diagram
WHEN DOES THE COVERGROUP SAMPLE?

  Clocked (declarative)              Explicit (procedural)
  ─────────────────────              ─────────────────────
  covergroup cg @(posedge clk);      covergroup cg;
    ...                                ...
  endgroup                           endgroup

  every posedge clk                  only when code runs
       │                                  cg.sample();
       ▼                                  │
  samples whether or not             ▼
  the value is meaningful            samples at a chosen,
  (X during reset, idle cycles,      meaningful moment
   mid-burst garbage...)             (end of transaction)

  Best for: module/interface         Best for: class-based TB,
  white-box signal coverage          monitor-driven coverage

The trap with clocked sampling is over-counting: an idle bus sampled every cycle hits the “idle” bin millions of times while telling you nothing new, and during reset it cheerfully buckets X-propagated garbage. Explicit sampling gives you control; clocked sampling gives you convenience.


sample() with function arguments

By default, coverpoints reference variables visible at the covergroup's declaration scope, which forces an awkward copy-then-sample dance. The with function sample(...) form turns sample() into a function with typed arguments — coverpoints reference the arguments directly, and the call site passes the transaction fields in one step.

systemverilog
typedef enum logic [1:0] { OKAY, EXOKAY, SLVERR, DECERR } resp_e;

class bus_txn;
  rand bit        write;
  rand bit [7:0]  len;
  resp_e          resp;
endclass

class bus_coverage;
  covergroup cg with function sample(bit wr, bit [7:0] len, resp_e rsp);
    cp_dir : coverpoint wr {
      bins read  = {0};
      bins write = {1};
    }
    cp_len : coverpoint len {
      bins single = {1};
      bins short_b = {[2:4]};
      bins long_b  = {[5:255]};
    }
    cp_resp : coverpoint rsp;        // enum → automatic named bins
  endgroup

  function new();
    cg = new();
  endfunction

  // Called by the monitor once per completed transaction
  function void write_txn(bus_txn t);
    cg.sample(t.write, t.len, t.resp);
  endfunction
endclass

Where to call sample() in a testbench

Sample from the monitor , on observed DUT behavior, once per complete transaction. Sampling from the driver records what you intended to send, not what the DUT actually did — if the DUT errors, drops, or transforms the transaction, driver-side coverage lies. Sampling per beat of a burst inflates hit counts without adding scenarios.

diagram
driver ──► DUT ──► monitor ──► coverage.write_txn(t)
  (intent)            (observed     │
                       reality)     └── cg.sample(...) ← HERE
                                        once per completed txn

Guarding coverpoints with iff

Each coverpoint accepts an iff (expression) guard: when the guard is false at the sampling moment, that coverpoint simply skips the sample — no bin is hit, and (importantly) no illegal_bins fire. The classic use is gating out reset, where signal values are meaningless.

systemverilog
module fifo_cov (input logic clk, rst_n,
                 input logic [4:0] count,
                 input logic       push, pop);

  covergroup cg @(posedge clk);
    // Skip every coverpoint sample while in reset
    cp_count : coverpoint count iff (rst_n) {
      bins empty = {0};
      bins mid   = {[1:30]};
      bins full  = {31};
    }
    // Guard can be per-coverpoint and condition-specific:
    cp_push_full : coverpoint push iff (rst_n && count == 31) {
      bins push_when_full = {1};   // overflow-attempt scenario
    }
  endgroup

  cg cg_inst = new();
endmodule
  • iff guards the sample of one coverpoint; the covergroup still samples other coverpoints normally.

  • Use iff (rst_n) on clocked covergroups — otherwise reset-phase X/garbage values pollute bins.

  • iff can encode scenario conditions (push when full) — but complex conditions often read better as expression coverpoints.

  • Interview angle: “how do you keep reset values out of coverage?” — iff guard or gating the sample() call are both accepted answers; know both.

Key takeaways

  • Clocked sampling is declarative and fires every event; explicit sample() fires only when you decide the data is meaningful.

  • with function sample(...) gives sample() typed arguments — the clean pattern for transaction coverage.

  • Sample from the monitor on completed transactions — never from the driver, never per beat.

  • iff guards skip a coverpoint's sample when the condition is false — the standard reset-exclusion tool.

Common pitfalls

  • Clocked sampling on an idle bus — millions of meaningless hits on the same bin, slowing simulation.

  • Sampling during reset without iff or a gate — X values land in real bins and fake closure progress.

  • Calling sample() from the driver — measures stimulus intent, not DUT-visible behavior.

  • Copying fields into class variables then sampling, when with function sample() would do it in one atomic step.