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

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here6
  • SystemVerilog Language Foundations28
  • Object-Oriented SystemVerilog for Verification30
  • Constraint Randomization — Interview-Critical Deep Dive36
  • SystemVerilog Assertions — Interview-Critical Deep Dive40
  • Functional Coverage in SystemVerilog22
    • Covergroups & Coverpoints
    • Bins: Explicit, Auto, Illegal & Ignore
    • Cross Coverage
      • Cross Basics
      • binsof & intersect
      • Pruning Crosses: ignore_bins & illegal_bins
      • Managing Cross Explosion
      • Cross Coverage Patterns
    • Coverage Options & Methods
    • Coverage Closure & Sign-Off
  • Testbench Architecture in SystemVerilog23
  • Advanced Language & Integration25
  • Senior & Interview Preparation21
  • Principal-Level Verification28
Loading lesson

‹Cross Coverage
  1. Topics
  2. SystemVerilog
  3. Functional Coverage in SystemVerilog
  4. Cross Coverage
  5. Managing Cross Explosion
On this page

    Part 5 of 10, lesson 12 of 22Part 5 of 10/Functional Coverage/Lesson 12 of 22Intermediate

    Managing Cross Explosion

    Why cross bins multiply, and how coarsening, naming, and splitting keep the goal closable.

    A 16 × 16 × 8 cross sat at 61% after 500 seeds, and the plan was to add seeds. Most of its 2048 cells were combinations nobody had asked for.

    After this lesson you can: estimate a cross's size, and shrink it by coarsening sources, naming only target cells, or splitting by concern.

    The multiplication

    Cross bin counts multiply, and the numbers get away fast. Three coverpoints that each look reasonable in isolation can produce a cross no regression will ever close, and every empty cell costs triage time at sign-off.

    Bins

    cp_addr_region: address map regions

    16

    cp_burst_len: 1 to 16, one bin each

    16

    cp_qos: QoS levels

    8

    cross cp_addr_region, cp_burst_len, cp_qos

    16 × 16 × 8 = 2048

    Restructured: region (4 coarse) × length (3) × QoS (2 groups)

    24

    Plus named precision bins for the plan-critical exact combinations

    + 6 = 30

    A regression of 100 seeds at 50,000 transactions each gives 5 million samples, about 2,441 per cell on average. The average misleads: random stimulus clusters, corner cells may see one sample per million, and the last 5% of cells dominate the closure schedule. The 30-bin version closes in days and reviews in minutes.

    The fix is never more seeds. Restructure the cross so the goal holds only cells the plan actually demands.

    Three strategies

    The listings below are not run in this course's checks: the open simulators do not count covergroups, so the lesson's bench counts them with the bins model.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // not-run: Verilator 5.048 ignores covergroups (COVERIGN) and Icarus 13 has none; the bins model counts it
    // STRATEGY 1: coarser source bins — shrink the factors
    cp_len : coverpoint burst_len {
      bins single = {1};
      bins short_b = {[2:4]};
      bins long_b  = {[5:15]};
      bins max_b   = {16};        // 16 bins -> 4 bins
    }
    
    // STRATEGY 2: keep sources fine, but grade only the named bins
    // the plan demands. Named bins absorb only the cells they select;
    // every other auto cell stays in the goal. The ignore_bins
    // catch-all is REQUIRED to make the goal exactly these two bins.
    x_len_qos : cross cp_len, cp_qos {
      bins max_hiqos  = binsof(cp_len.max_b) && binsof(cp_qos) intersect {[6:7]};
      bins single_any = binsof(cp_len.single);
      ignore_bins rest = (!binsof(cp_len.max_b) || !binsof(cp_qos) intersect {[6:7]})
                         && !binsof(cp_len.single);
    }

    Strategy 3 splits by verification concern. One covergroup crossing everything against everything is a smell; small two-dimensional crosses stay reviewable, with option.weight keeping the rollup meaningful.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // not-run: Verilator 5.048 ignores covergroups (COVERIGN) and Icarus 13 has none; the bins model counts it
    covergroup perf_cg @(posedge clk iff txn_done);
      cp_qos : coverpoint qos      { bins lo = {[0:3]}; bins hi = {[4:7]}; }
      cp_len : coverpoint burst_len{ bins s = {1}; bins m = {[2:8]}; bins l = {[9:16]}; }
      x_perf : cross cp_qos, cp_len;            // 6 cells
    endgroup
    
    covergroup addrmap_cg @(posedge clk iff txn_done);
      cp_reg : coverpoint region   { bins r[4] = {[0:15]}; }   // 4 coarse bins
      cp_dir : coverpoint is_write { bins rd = {0}; bins wr = {1}; }
      x_map  : cross cp_reg, cp_dir;            // 8 cells
    endgroup
    // 6 + 8 = 14 goal cells instead of one 2048-cell monster.

    Strategy

    What shrinks

    Keep in mind

    Coarsen source bins

    Each factor: 16 len values become 4 bins

    Stay coarse enough to still separate the plan's corners

    Name only target bins

    The product: 2048 auto cells become a few named bins

    Unselected auto cells stay in the goal unless an ignore_bins catch-all removes them

    Split covergroups by concern

    Dimensionality: one 3-D cross becomes two 2-D crosses

    Rebalance option.weight so the rollup reflects priorities

    What reviewers flag

    • Any cross over about 50 cells without a written justification line in the plan.

    • Crosses of 3 or more coverpoints where no design logic examines all three fields together.

    • An auto product accepted wholesale, with no named bins and no pruning, on any non-trivial cross.

    • A coverpoint with per-value bins feeding a cross, where the factor explodes silently.

    • Goal cells at zero hits across three consecutive regressions: either unreachable and prunable, or unplanned and untested.

    Testbench

    The bench sends the same 1,000 clustered samples (70% of bursts 1 to 4 beats, 80% low QoS) to three crosses built with the bins model: 16 length bins × 8 QoS bins, the coarse 4 × 2 version, and the fine sources with only two named target bins and a catch-all.

    Predict first 1,000 samples over 128 fine cells is almost 8 per cell. Does the fine cross close?

    No. It reads 115 of 128: the clustering starves the long, high-QoS cells, and 13 stay empty. The 8 coarse cells and the 2 named bins all close on the same samples.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    // uses: covmodel
    // Cross explosion: fine bins, coarse bins and named target bins over the same 1000 skewed samples.
    module tb_cross_explosion;
      cm_point len_fine = new("len (16 bins)"), qos_fine = new("qos (8 bins)");
      cm_point len_coarse = new("len (4 bins)"), qos_coarse = new("qos (2 bins)");
      cm_cross x_fine, x_coarse, x_named;
      int unsigned r = 32'd2026;
      function int unsigned rnd(int unsigned n);             // a fixed generator, so every simulator sees the same stream
        r = r * 32'd1103515245 + 32'd12345;
        return (r >> 8) % n;
      endfunction
      initial begin
        len_fine.add_each("len", 1, 16);  qos_fine.add_each("qos", 0, 7);
        len_coarse.add("single", 1, 1); len_coarse.add("short_b", 2, 4); len_coarse.add("long_b", 5, 15); len_coarse.add("max_b", 16, 16);
        qos_coarse.add("lo", 0, 3); qos_coarse.add("hi", 4, 7);
        x_fine   = new("x_fine", len_fine, qos_fine);
        x_coarse = new("x_coarse", len_coarse, qos_coarse);
        // Fine sources, but only the two combinations the plan names, and a catch-all for the rest.
        x_named  = new("x_named", len_fine, qos_fine);
        x_named.add_bin("max_hiqos", x_named.sel_and(x_named.sel("len[16]", ""), x_named.sel_values('{6, 7}, 1)));
        x_named.add_bin("single_any", x_named.sel("len[1]", ""));
        x_named.ignore(x_named.sel_not(x_named.sel_or(x_named.nb_cells[0], x_named.nb_cells[1])));
        // Random traffic clusters: 70% of bursts are 1 to 4 beats, 80% of them low QoS.
        repeat (1000) begin
          int len, qos;
          len = rnd(10) < 7 ? 1 + rnd(4) : 5 + rnd(12);
          qos = rnd(10) < 8 ? rnd(4) : 4 + rnd(4);
          len_fine.sample(len); qos_fine.sample(qos); len_coarse.sample(len); qos_coarse.sample(qos);
          x_fine.sample(); x_coarse.sample(); x_named.sample();
        end
        $display("  cross x_fine: %0.2f%% (%0d of %0d bins), %0d holes", x_fine.get_coverage(), x_fine.n_covered(), x_fine.n_bins(),
                 x_fine.n_bins() - x_fine.n_covered());
        x_coarse.report(); x_named.report();
        if (x_fine.n_bins() != 128 || x_fine.get_coverage() == 100.0 || x_coarse.get_coverage() != 100.0 || x_named.n_bins() != 2)
          $fatal(1, "FAIL: explosion math");
        $display("PASS: 1000 samples left %0d of 128 fine cells empty; the 8 coarse cells and 2 named bins closed",
                 x_fine.n_bins() - x_fine.n_covered());
        $finish;
      end
    endmodule
    Terminal
      cross x_fine: 89.84% (115 of 128 bins), 13 holes
      cross x_coarse: 100.00% (8 of 8 bins)
      cross x_named: 100.00% (2 of 2 bins)
        max_hiqos      2
        single_any     147
    PASS: 1000 samples left 13 of 128 fine cells empty; the 8 coarse cells and 2 named bins closed

    “You inherit a covergroup with a 16×16×8 cross at 61% after a 500-seed regression. What now?” The expected shape: quantify (2048 cells, about 800 empty), classify empties by reachability, coarsen or split so the goal matches intent, keep a handful of named precision bins for the spec-critical exact combinations, re-baseline, and present the before-and-after goal math. Saying you would not just add seeds is most of the answer.

    Check yourself When is a three-coverpoint cross justified?

    When design logic examines all three fields together. Without shared logic across all three, split the concern into smaller crosses.

    Check yourself Coarsening removed the only bin separating two plan-critical corners. What fixes it without restoring the explosion?

    Keep the coarse product and add named precision bins for the exact critical combinations. The goal stays small while the corners stay graded.

    Per-value array bins feeding a cross multiply out of sight. Answering explosion with more seeds fails because random stimulus clusters and rare cells stay rare. And splitting covergroups while leaving every weight at default silently rewrites the rollup priorities.

    • Cross bin counts multiply.

    • Clustered random traffic starves corner cells.

    • Coarsen, name targets, or split: never just add seeds.

    Rule of thumb: a cross over about 50 cells needs a written reason.

    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.

    1,000 clustered samples left 13 of 128 fine cells empty, while the 8 coarse cells and the 2 named target bins closed.

    Related topics

    • Pruning Crosses: ignore_bins & illegal_bins

    • Hole analysis

    Previous

    152 / 259

    Next: Cross Coverage Patterns

    Up next

    Up nextCross Coverage Patterns

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Cross Basics
    • binsof & intersect