Part 5 · Functional Coverage · Intermediate
option.per_instance & Instance Options
Type-level vs instance-level accumulation, option.name and option.comment, and when per-instance coverage matters.
Two accumulation models
By default, every instance of a covergroup type pours its hits into one shared type-level bucket . Construct the same covergroup in four port monitors and the report shows a single number — the union of everything any port saw. Setting option.per_instance = 1 makes each instance keep its own hit counts and appear as its own row in the report, alongside the type-level rollup.
TYPE-LEVEL (default) PER-INSTANCE (per_instance = 1)
mon[0].cg ─┐ mon[0].cg ──► port0_cov : 97%
mon[1].cg ─┼──► one merged mon[1].cg ──► port1_cov : 95%
mon[2].cg ─┤ type bucket mon[2].cg ──► port2_cov : 96%
mon[3].cg ─┘ "my_cg : 98%" mon[3].cg ──► port3_cov : 3% ◄── !!
│
port3 broken/idle? INVISIBLE. type rollup still reported too.
Other ports' hits mask it. The dead port is one glance away.The diagram is the whole argument: with type-level accumulation, three healthy ports hide a dead one. Per-instance coverage exists to make per-unit health visible.
Naming instances so reports are readable
With per-instance mode on, the report needs to tell instances apart. option.name sets the per-instance report label (otherwise you get tool-generated hierarchy strings), and option.comment attaches free-text that shows up next to the entry — typically the plan section the group covers.
covergroup port_cg (string inst_name) @(posedge clk iff valid);
option.per_instance = 1;
option.name = inst_name; // report row label
option.comment = "vplan 3.2 per-port traffic shapes";
cp_kind : coverpoint pkt_kind { bins data = {DATA}; bins ctrl = {CTRL}; }
cp_size : coverpoint pkt_size { bins s = {[1:64]}; bins m = {[65:512]};
bins l = {[513:1500]}; }
x_ks : cross cp_kind, cp_size;
endgroup
// One definition, four named instances:
port_cg cov0 = new("port0_cov");
port_cg cov1 = new("port1_cov");
port_cg cov2 = new("port2_cov");
port_cg cov3 = new("port3_cov");Instance options you set most often
option.per_instance = 1 — keep per-instance hit counts and report rows; must be set, conceptually, before sampling begins.
option.name — human-readable instance label; pass it through a constructor argument so each instance differs.
option.comment — free text for the report; use it for plan traceability references.
option.weight / option.goal — also settable per instance; covered in the next sub-lesson.
When per-instance matters — and when it does not
Decision guide
Per-port / per-channel monitors — YES. Each unit must individually prove traffic coverage; symmetry bugs hide in the merge.
Same covergroup on N identical DMA channels — YES if channels can be configured differently; arguably type-level if they are provably symmetric and stimulus is round-robin.
One covergroup instantiated exactly once — irrelevant; type and instance views coincide (though option.name still tidies the report).
Hundreds of instances (one per packet flow) — be careful: per-instance multiplies database size and report rows; coarser grouping may serve the plan better.
// Reading both views at runtime:
real type_pct = cov0.get_coverage(); // merged across ALL instances
real inst_pct = cov0.get_inst_coverage(); // this instance only
initial begin
// end-of-test per-port health check
if (cov3.get_inst_coverage() < 50.0)
$display("WARNING: port3 coverage %0.1f%% — dead port?",
cov3.get_inst_coverage());
endInterview angle: "You have four identical ports and the covergroup reports 98% — are you done?" is a classic. The expected answer names the trap (type-level merge hides an idle port), the fix (option.per_instance = 1 plus option.name per port), and the check (compare get_inst_coverage across instances). Bonus points for noting the database-size cost of per-instance mode at large instance counts.
Key takeaways
Default accumulation is type-level: all instances merge into one number, which hides per-unit failures.
option.per_instance = 1 gives each instance its own counts and report row; option.name makes the rows readable.
get_coverage() reads the type-level merge; get_inst_coverage() reads one instance — different questions, different numbers.
Use per-instance for per-port/per-channel symmetry checking; skip it when instances are provably equivalent.
Common pitfalls
Trusting a type-level 98% across N monitors — one dead monitor is invisible in the merge.
Forgetting option.name with per_instance — the report fills with unreadable auto-generated hierarchy paths.
Enabling per_instance on hundreds of instances without need — database bloat and report noise.
Calling get_coverage() expecting this instance's number — that is get_inst_coverage(); the two diverge exactly when per-instance matters.