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.
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 coverageThe 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.
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
endclassWhere 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.
driver ──► DUT ──► monitor ──► coverage.write_txn(t)
(intent) (observed │
reality) └── cg.sample(...) ← HERE
once per completed txnGuarding 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.
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();
endmoduleiff 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.