Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here6
  • SystemVerilog Language Foundations35
  • Object-Oriented SystemVerilog for Verification35
  • Constraint Randomization — Interview-Critical Deep Dive66
  • SystemVerilog Assertions — Interview-Critical Deep Dive63
  • Functional Coverage in SystemVerilog28
    • Covergroups & Coverpoints
    • Bins: Explicit, Auto, Illegal & Ignore
    • Cross Coverage
    • Coverage Options & Methods
      • option.per_instance & Instance Options
      • Weights, Goals & at_least
      • get_coverage(), sample() & Friends
      • Parameterizing Covergroups
      • Simulator Coverage Control
    • Coverage Closure & Sign-Off
  • Testbench Architecture in SystemVerilog38
  • Advanced Language & Integration30
  • Senior & Interview Preparation34
  • Principal-Level Verification28
Loading lesson

‹Coverage Options & Methods
  1. Topics
  2. SystemVerilog
  3. Functional Coverage in SystemVerilog
  4. Coverage Options & Methods
  5. option.per_instance & Instance Options

Part 5 · Functional Coverage · Intermediate

option.per_instance & Instance Options

Type-level versus per-instance accumulation, instance naming, and when separate report rows matter.

On this page

    Why this matters

    Four port monitors shared one covergroup and the report read 98%. Port 3 had been tied off by a bad config for a month; its own row would have read 0%.

    After this lesson you can: choose type-level or per-instance coverage, name instances, and read both views at run time.

    Two accumulation models

    Every covergroup type has one type-level number for all its instances. type_option.merge_instances picks how it is formed. The default, 0, takes the weighted average of the instances' coverage. With 1, the instances' bins are merged as a union, as if one instance had seen every sample.

    Setting option.per_instance to 1 also keeps each instance's own hit counts and gives it its own report row, alongside the type-level number. Set it in the covergroup body; it cannot be changed procedurally.

    Instance

    Type-level only

    Per-instance (per_instance = 1)

    mon[0].cg

    One type number for all four:

    port0_cov: 97%

    mon[1].cg

    union (merge_instances = 1): 98%

    port1_cov: 95%

    mon[2].cg

    average (merge_instances = 0): 73%

    port2_cov: 96%

    mon[3].cg

    Port 3 broken or idle: invisible

    port3_cov: 3%, one glance away

    The table is the whole argument. The union reads 98% because the healthy ports hit the bins the dead one missed. The default average reads about 73%, which looks like a stimulus gap, not a dead port. Either way the type number cannot say which port is broken. Per-instance coverage exists to make per-unit health visible.

    Naming instances

    With per-instance mode on, the report must tell instances apart. The following listing is not run in this course's checks: the open simulators do not count covergroups.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // not-run: Verilator 5.048 ignores covergroups (COVERIGN) and Icarus 13 has none; the bins model counts it
    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 = {0}; bins ctrl = {1}; }
      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");

    option.name labels the row and option.comment carries plan traceability. Pass the name through a constructor argument so each instance differs.

    • option.per_instance = 1 keeps per-instance counts and rows. Its default is 0: only the type-level number is reported.

    • type_option.merge_instances sets how the type-level number is formed: 0 (default) averages the instances, 1 takes the union of their bins.

    • option.name replaces tool-generated hierarchy strings with a readable label.

    • option.comment holds free text, typically the plan section the group covers.

    • option.weight and option.goal are also settable per instance. The next lesson covers them.

    When separate rows matter

    Situation

    Accumulation to use

    Per-port or per-channel monitors

    Per-instance. Each unit must prove its own traffic; symmetry bugs hide in the merge.

    One covergroup instantiated exactly once

    Either. Type and instance views coincide, though a name still tidies the report.

    Hundreds of instances, one per flow

    Type-level or coarser grouping. Per-instance rows multiply database size and report noise.

    Channels provably symmetric with round-robin stimulus

    Type-level is defensible. Per-instance still helps during debug.

    Both views are readable at runtime. The following listing is not run in this course's checks: the open simulators do not count covergroups.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // not-run: Verilator 5.048 ignores covergroups (COVERIGN) and Icarus 13 has none; the bins model counts it
    port_cg cov0 = new("port0_cov");
    port_cg cov1 = new("port1_cov");
    port_cg cov2 = new("port2_cov");
    port_cg cov3 = new("port3_cov");
    
    initial begin
      // End-of-test per-port health check, inside procedural code:
      if (cov3.get_inst_coverage() < 50.0)
        $display("WARNING: port3 coverage low, dead port?");
    end

    get_coverage reads the type-level number and get_inst_coverage reads one instance. They answer different questions and diverge exactly when per-instance mode matters.

    Common pitfall

    Trusting a merged 98% across N monitors hides one dead monitor. Forgetting option.name fills the report with unreadable generated paths. Enabling per-instance mode on hundreds of instances bloats the database.

    Interview

    “Four identical ports report 98% — are you done?” expects the trap (the merge hides an idle port), the fix (option.per_instance plus a name per port), and the check (compare per-instance numbers). Bonus points for noting the database cost at large instance counts.

    Check yourself What is the difference between get_coverage and get_inst_coverage?

    get_coverage returns the type-level number across all instances: the average by default, or the union with merge_instances = 1. get_inst_coverage returns one instance only. With a single instance the two coincide.

    Testbench

    The bench builds four instances of port_cg with the bins model. Ports 0 to 2 each miss a different kind-and-size pair and port 3 sees nothing. A fifth instance sees every sample, which is what merge_instances = 1 computes.

    Predict first Each live port misses one of its six cross cells. What do the per-instance rows, the average and the union read?

    Each live port reads 94.44%: both coverpoints full, the cross 5 of 6. Port 3 reads 0%. The average is 70.83%, and the union is 100%, because each pair one port missed, another port hit.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // uses: covmodel
    // Four instances of port_cg: the type-level average, the union of their bins, and the per-instance rows.
    class port_cov;                                     // covergroup port_cg (string inst_name), with per_instance = 1
      cm_group cg;
      cm_point cp_kind, cp_size;
      cm_cross x_ks;
      function new(string inst_name);
        cg = new(inst_name);                            // option.name = inst_name
        cp_kind = new("cp_kind"); cp_size = new("cp_size");
        cp_kind.add("data", 0, 0); cp_kind.add("ctrl", 1, 1);
        cp_size.add("s", 1, 64); cp_size.add("m", 65, 512); cp_size.add("l", 513, 1500);
        x_ks = new("x_ks", cp_kind, cp_size);
        cg.points.push_back(cp_kind); cg.points.push_back(cp_size); cg.crosses.push_back(x_ks);
      endfunction
      function void sample(int kind, int size);
        cp_kind.sample(kind); cp_size.sample(size); x_ks.sample();
      endfunction
    endclass
    
    Terminal
      port0_cov: 94.44%
      port1_cov: 94.44%
      port2_cov: 94.44%
      port3_cov: 0.00%
      type level, average (merge_instances = 0): 70.83%
      type level, union   (merge_instances = 1): 100.00%
      port3 coverage low: dead port?
    PASS: the union read 100% and the average 70.83%; only the per-instance rows showed port3 at 0

    Remember

    • merge_instances = 0 averages instances; 1 takes the union.

    • per_instance = 1 gives every instance its own row.

    • get_inst_coverage reads one instance; get_coverage the type.

    Rule of thumb: symmetric units get per-instance rows.

    How this was checked

    Counted with the course's bins model (rich/coverage/cov_model.sv, listed on the Coverage hub) on Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown. Verilator 5.048 parses covergroups with a COVERIGN warning and ignores their bins, and Icarus 13 has none, so each covergroup listing is marked not run and its bench counts the same bins with the model.

    Three live ports read 94.44% each and the dead one 0%; the average read 70.83% and the union 100%, and only the rows named port 3.

    Related topics

    • Covergroup basics

    • Coverage closure

    Previous

    223 / 363

    Next: Weights, Goals & at_least

    Up next

    Up nextWeights, Goals & at_least

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • get_coverage(), sample() & Friends
    • Parameterizing Covergroups
    • Simulator Coverage Control