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 .

diagram
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 control
systemverilog
covergroup 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());
endtask

Adaptive 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.

systemverilog
// 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());
endtask

start() / stop() — gating collection windows

systemverilog
// 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();
end
  • stop()/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.