Part 5 · Functional Coverage · Intermediate
get_coverage(), sample() & Friends
The covergroup method API — sample, get_coverage vs get_inst_coverage, start/stop, $get_coverage, and adaptive-stimulus patterns.
The method API at a glance
Covergroups are not write-only. Every instance exposes methods to trigger sampling procedurally, query the current percentage, and pause collection — which turns coverage from a post-simulation report into a runtime signal your test can react to .
COVERGROUP METHOD API
cg.sample() trigger a sampling event procedurally
cg.sample(args...) with covergroup ... with function sample(...)
cg.get_coverage() type-level % (merged over all instances)
cg.get_inst_coverage() this instance's %
cp.get_coverage() per-coverpoint/per-cross queries also exist
cg.stop() pause collection on this instance
cg.start() resume collection
cg.set_inst_name("s") set instance name procedurally
system-wide:
$get_coverage() overall functional coverage % (all groups)
$set_coverage_db_name() next lesson — database controlcovergroup txn_cg with function sample(bit wr, logic [3:0] len);
option.per_instance = 1;
cp_wr : coverpoint wr { bins rd = {0}; bins wr_ = {1}; }
cp_len : coverpoint len { bins s = {1}; bins m = {[2:8]}; bins l = {[9:15]}; }
x_wl : cross cp_wr, cp_len;
endgroup
txn_cg cg = new();
task collect(bit wr, logic [3:0] len);
cg.sample(wr, len); // explicit, argument-passing sample
$display("inst=%0.1f%% type=%0.1f%%",
cg.get_inst_coverage(), cg.get_coverage());
endtaskAdaptive tests — stop stimulus when the goal is reached
The highest-value use of the query API: let the test watch its own coverage and stop, redirect, or escalate when a goal is met. The classic pattern caps random stimulus by achievement instead of by a fixed transaction count.
// Pattern: run until coverage goal OR transaction budget, whichever first
task run_random_phase(int max_txns = 10_000);
int n = 0;
while (n < max_txns) begin
drive_random_txn(); // stimulus + cg.sample inside
n++;
if ((n % 100) == 0) begin // poll every 100 txns, not every txn
if (cg.get_inst_coverage() >= cg.option.goal) begin
$display("goal hit after %0d txns — stopping random phase", n);
break;
end
end
end
if (cg.get_inst_coverage() < cg.option.goal)
$display("budget exhausted at %0.1f%% — holes need directed tests",
cg.get_inst_coverage());
endtaskstart() / stop() — gating collection windows
// Exclude reset and error-recovery windows from coverage
initial begin
cg.stop(); // nothing counts yet
wait (rst_n == 1'b1);
repeat (10) @(posedge clk); // settle cycles
cg.start(); // begin honest collection
end
always @(posedge fatal_err) begin
cg.stop(); // recovery traffic is not plan traffic
wait (recovered);
cg.start();
endstop()/start() gate an instance — samples during the stopped window are simply not counted.
set_inst_name() does procedurally what option.name does declaratively — useful when names are computed.
$get_coverage() returns the overall functional-coverage number across all covergroups — coarse, but handy for a single end-of-test gate.
Per-coverpoint queries (cp_len.get_coverage()) let adaptive tests steer at finer grain than the whole group.
Costs and judgement
Coverage queries are not free — each call walks bin state — and per-sample polling can dominate runtime on hot paths. Poll on a decimated schedule (every N transactions, or on a timer), and remember that in-test numbers are single-seed numbers : a test that stops itself at 100% has closed this seed's view, while sign-off closure still comes from the merged regression database.
Interview angle: "How would you make a random test smarter about coverage?" expects exactly the poll-and-stop pattern above, plus its caveats: poll cheaply, and never confuse single-run get_coverage() with merged closure. A sharper follow-up — "get_coverage() vs get_inst_coverage()?" — wants type-merge vs this-instance, and the observation that they differ only when multiple instances exist.
Key takeaways
sample() triggers collection procedurally; the with function sample(...) form passes values explicitly.
get_inst_coverage() is this instance; get_coverage() is the type-level merge; $get_coverage() is everything.
stop()/start() exclude reset and recovery windows so closure reflects plan traffic only.
Adaptive tests poll coverage on a decimated schedule and stop on goal — but sign-off still uses the merged database.
Common pitfalls
Polling get_coverage() every transaction — bin-state walks on hot paths slow the simulation measurably.
Treating an in-test 100% as closure — it is one seed's view, not the regression merge.
Forgetting cg.start() after a stop() — the rest of the test silently collects nothing.
Calling cg.sample() procedurally on a covergroup that also has a clocking event — double counting every cycle.