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 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. Pruning Crosses: ignore_bins & illegal_bins

Part 5 · Functional Coverage · Intermediate

Pruning Crosses: ignore_bins & illegal_bins

Pruning impossible cells with ignore_bins, flagging forbidden cells with illegal_bins, and goal math.

On this page

    Why this matters

    A cross sat at 66.7% for weeks and the team kept writing tests for it. The two empty cells were reads with error injection, which the environment cannot produce.

    After this lesson you can: prune unreachable cells with ignore_bins, turn forbidden cells into checks with illegal_bins, and show the goal math before and after.

    Why crosses need pruning

    The full product almost always contains combinations the design or the environment makes impossible. Left in the cross, those cells sit at zero hits forever and cap the achievable percentage below 100%.

    ignore_bins removes such cells from the denominator. illegal_bins goes further: it removes them from the goal and fires a runtime error if a sample ever lands there, turning the cross into a checker.

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

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // not-run: Verilator 5.048 ignores covergroups (COVERIGN) and Icarus 13 has none; the bins model counts it
    covergroup err_cg @(posedge clk iff txn_done);
      cp_dir : coverpoint is_write { bins rd = {0}; bins wr = {1}; }
      cp_err : coverpoint err_mode { bins none   = {2'b00};
                                     bins parity = {2'b01};
                                     bins crc    = {2'b10}; }
    
      x_dir_err : cross cp_dir, cp_err {
        // Environment fact: error injection only exists on the write path.
        // read x parity and read x crc are IMPOSSIBLE -> remove from goal.
        ignore_bins rd_no_inject =
          binsof(cp_dir.rd) && (binsof(cp_err.parity) || binsof(cp_err.crc));
      }
    endgroup

    none

    parity

    crc

    rd

    Reachable

    Unreachable: ignored

    Unreachable: ignored

    wr

    Reachable

    Reachable

    Reachable

    Before pruning the goal is 6 cells with 4 reachable, so the best possible score is 4 of 6, 66.7%. After ignore_bins the goal is the 4 reachable cells, and 100% is possible.

    The goal math, worked

    Cross coverage percentage is hit cells divided by goal cells. Pruning changes the denominator, so identical simulation results read very differently before and after.

    Same regression, same hits

    Hit

    Goal

    Coverage

    Without pruning

    4

    6

    66.7%: looks like a hole problem

    With ignore_bins

    4

    4

    100%: honest closure

    The 33.3% gap was never a stimulus gap. It was a modeling bug: the coverage model demanded combinations the design cannot produce.

    Hole triage therefore starts by asking whether a cell is reachable, before asking which test hits it. An unreachable cell belongs in ignore_bins with a comment citing the spec line that forbids it, not in a directed-test backlog.

    Check yourself The same regression reads 66.7% before pruning and 100% after, with zero new tests. Did stimulus improve?

    No. The denominator shrank from 6 to 4 when two unreachable cells moved to ignore_bins. The 33.3% gap was a modeling artifact, never a stimulus gap.

    illegal_bins as a checker

    Use illegal_bins when a combination is forbidden by the spec rather than merely outside the plan. A hit means something is broken, in the DUT or in the testbench, and the simulator reports the failing combination at the offending sample.

    The listing below is not run in this course's checks, for the same reason.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // not-run: Verilator 5.048 ignores covergroups (COVERIGN) and Icarus 13 has none; the bins model counts it
    x_mode_size : cross cp_mode, cp_size {
      // Spec 4.3.1: WRAP bursts must be 2, 4, 8 or 16 beats.
      // WRAP x size-1 is a protocol violation, not a coverage hole.
      illegal_bins wrap1 = binsof(cp_mode.wrap) && binsof(cp_size.b1);
    
      // Environment limitation (not a spec violation): exclude silently.
      ignore_bins no_fixed_long =
        binsof(cp_mode.fixed) && binsof(cp_size.b16);
    }

    Aspect

    ignore_bins

    illegal_bins

    Goal effect

    Cell leaves the denominator

    Cell leaves the denominator

    Hit at runtime

    No error, by the standard

    Simulator raises a runtime error

    Use when

    The combination cannot or need not happen

    The combination must never happen

    Review needs

    Comment with the spec or environment reason

    Comment with the spec section number

    The decision rule fits one line: if a hit means a bug gets filed, it is illegal_bins. If the response would be a shrug, it is ignore_bins. Either way, uncommented pruning is the first thing a coverage review flags.

    Testbench

    The bench counts both crosses from this lesson with the bins model: the direction-by-error cross with and without the ignore_bins, then the mode-by-size cross with its illegal WRAP × 1 cell and a last sample that lands there.

    Predict first The same six samples hit every reachable direction-by-error cell. What do the two versions read, and what happens to the WRAP × 1 sample?

    4 of 6 without pruning and 4 of 4 with it, from the same hits. The WRAP × 1 sample is reported at once and adds nothing to the mode-by-size score, which reads 7 of 7.

    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
    // uses: covmodel
    // Pruning a cross: the same hits before and after ignore_bins, then an illegal cell as a checker.
    module tb_cross_prune;
      cm_point cp_dir = new("cp_dir"), cp_err = new("cp_err"), cp_mode = new("cp_mode"), cp_size = new("cp_size");
      cm_cross x_raw, x_pruned, x_mode_size;
      initial begin
        cp_dir.add("rd", 0, 0); cp_dir.add("wr", 1, 1);
        cp_err.add("none", 0, 0); cp_err.add("parity", 1, 1); cp_err.add("crc", 2, 2);
        x_raw    = new("x_dir_err (no pruning)", cp_dir, cp_err);
        x_pruned = new("x_dir_err (pruned)", cp_dir, cp_err);
        // ignore_bins rd_no_inject = binsof(cp_dir.rd) && (binsof(cp_err.parity) || binsof(cp_err.crc));
        x_pruned.ignore(x_pruned.sel_and(x_pruned.sel("rd", ""), x_pruned.sel_or(x_pruned.sel("", "parity"), x_pruned.sel("", "crc"))));
        // A regression that hits every reachable combination: error injection exists on writes only.
        begin
          int dirs[6] = '{0, 0, 1, 1, 1, 1};
          int errs[6] = '{0, 0, 0, 1, 2, 1};
          foreach (dirs[i]) begin cp_dir.sample(dirs[i]); cp_err.sample(errs[i]); x_raw.sample(); x_pruned.sample(); end
        end
        x_raw.report(); x_pruned.report();
        // Spec 4.3.1: WRAP bursts are 2, 4, 8 or 16 beats, so WRAP x size-1 must never happen.
        cp_mode.add("incr", 0, 0); cp_mode.add("wrap", 1, 1); cp_mode.add("fixed", 2, 2);
        cp_size.add("b1", 1, 1); cp_size.add("b4", 4, 4); cp_size.add("b16", 16, 16);
        x_mode_size = new("x_mode_size", cp_mode, cp_size);
        x_mode_size.illegal(x_mode_size.sel("wrap", "b1"));          // illegal_bins wrap1
        x_mode_size.ignore(x_mode_size.sel("fixed", "b16"));         // ignore_bins no_fixed_long: the bench never sends it
        begin
          int modes[8] = '{0, 0, 0, 1, 1, 2, 2, 1};
          int sizes[8] = '{1, 4, 16, 4, 16, 1, 4, 1};                // the last sample is WRAP x 1
          foreach (modes[i]) begin cp_mode.sample(modes[i]); cp_size.sample(sizes[i]); x_mode_size.sample(); end
        end
        x_mode_size.report();
        if (x_raw.n_bins() != 6 || x_raw.n_covered() != 4 || x_pruned.get_coverage() != 100.0 || x_mode_size.n_bins() != 7 ||
            x_mode_size.get_coverage() != 100.0 || x_mode_size.illegal_hits != 1) $fatal(1, "FAIL: pruning math");
        $display("PASS: the same hits read 4 of 6 unpruned and 4 of 4 pruned; the illegal WRAP x 1 was reported");
        $finish;
      end
    endmodule
    Terminal
      cross x_dir_err (no pruning): 66.67% (4 of 6 bins)
        hole: <rd, parity>
        hole: <rd, crc>
      cross x_dir_err (pruned): 100.00% (4 of 4 bins)
      [cov] x_mode_size: illegal cross bin <wrap, b1> hit
      cross x_mode_size: 100.00% (7 of 7 bins)
    PASS: the same hits read 4 of 6 unpruned and 4 of 4 pruned; the illegal WRAP x 1 was reported

    Interview

    “Your cross is stuck at 75% — what do you do?” Strong answers triage reachability first: compute which cells are zero, check each against the spec, move the impossible ones to ignore_bins with the percentage math shown, make the forbidden ones illegal_bins, and only then write stimulus for the rest. Jumping straight to “write more tests” is the junior tell.

    Check yourself A combination is legal for the DUT but the bench never produces it. Which pruning, if any?

    ignore_bins with an environment-limitation comment, or nothing at all. illegal_bins would turn legal DUT behavior into a simulation failure.

    Common pitfall

    Leaving impossible combinations in the cross plateaus the percentage and sends the team chasing phantom holes. Pruning merely hard-to-hit cells instead hides a stimulus gap. And pruning at deadline to push 92% to 100% reads as goal-rigging: reviewers diff the exclusion list, so bulk late pruning is a red flag.

    Remember

    • Pruning changes the denominator, not the hits.

    • Triage reachability before writing stimulus.

    • A spec NO becomes illegal_bins, with the section cited.

    Rule of thumb: comment every pruned cell with its 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.

    The same six samples read 4 of 6 unpruned and 4 of 4 pruned, and the WRAP × 1 sample was reported as an illegal cross bin.

    Related topics

    • binsof & intersect

    • Coverpoint illegal and ignore bins

    Previous

    151 / 259

    Next: Managing Cross Explosion

    Up next

    Up nextManaging Cross Explosion

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Cross Basics
    • Cross Coverage Patterns