Skip to content

SystemVerilog · Module 11

Cross Coverage

The Cartesian product of two or more coverpoints' bins — measuring feature combinations rather than features in isolation. Two-way and N-way cross, binsof / intersect bin selection, illegal and ignore bins inside crosses, the cross-bin explosion trap, and why crossing auto-binned coverpoints is a project-killer.

Module 11 · Page 11.7

Why Cross Coverage Exists

Individual coverpoints answer "did each feature get exercised?" Cross coverage answers "did the features get exercised together?" An opcode coverpoint at 100% proves every opcode was sampled. An operand coverpoint at 100% proves every operand class was sampled. Neither tells you whether the dangerous combinations — DIV with operand zero, MUL with maximum operand, WRITE to the reserved region — were ever exercised in the same transaction.

Protocol bugs at silicon almost never live inside a single feature. They live in the intersections: burst-type WRAP combined with size doubleword; write-strobe 1100 combined with the unaligned address; back-pressure asserted concurrently with last-beat. The function of cross coverage is to make those intersections measurable, named, and reportable as their own verification-plan rows.

Syntax — The cross Keyword

A cross declaration appears inside a covergroup, after the coverpoints it references. Both (or all) coverpoints must be labelled — cross references coverpoints by name.

SystemVerilog — basic cross coverage syntax
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
covergroup cg_basic;
    // Step 1 — define labelled coverpoints
    cp_opcode: coverpoint opcode;
    cp_mode:   coverpoint mode;
 
    // Step 2 — cross them; one bin per combination
    cross cp_opcode, cp_mode;
endgroup
 
// The cross above is implicitly named "cp_opcode X cp_mode".
// To set options on it or use it in binsof, label the cross too:
covergroup cg_named;
    cp_opcode: coverpoint opcode;
    cp_mode:   coverpoint mode;
 
    // Labelled cross — required for option.* and for binsof references
    cr_op_mode: cross cp_opcode, cp_mode;
endgroup

The reading rule: a labelled cross is the cross-coverage equivalent of a named bin. All the named-bin discipline from Module 11.4 applies. The cross's label is the verification-plan row name; the per-cross option.comment cites the plan section; per-cross option.weight/option.at_least/option.goal apply to the cross as a whole.

A Working Example — ALU Two-Way and Three-Way Cross

The pattern below shows a complete ALU coverage model with the dangerous combinations made measurable.

SystemVerilog — ALU cross coverage, two-way and three-way
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
typedef enum bit [2:0] {
    ADD = 3'b000, SUB = 3'b001, MUL = 3'b010, DIV = 3'b011
} opcode_e;
 
class alu_txn;
    rand opcode_e   opcode;
    rand bit [7:0]  operand_a;
    rand bit [7:0]  operand_b;
    rand bit [1:0]  mode;
 
    covergroup cg_alu;
        // ── Coverpoints with explicit named bins ──────────────────
        cp_opcode: coverpoint opcode {
            bins arith = {ADD, SUB};   // grouped arithmetic
            bins mul   = {MUL};
            bins div   = {DIV};
        }
        cp_op_a: coverpoint operand_a {
            bins zero   = {0};
            bins small  = {[1:15]};
            bins medium = {[16:127]};
            bins large  = {[128:255]};
        }
        cp_mode: coverpoint mode {
            bins normal   = {2'b00};
            bins saturate = {2'b01};
            bins overflow = {2'b10};
        }
 
        // ── Two-way cross — 3 × 4 = 12 bins ────────────────────────
        cr_op_opa:   cross cp_opcode, cp_op_a;
 
        // ── Two-way cross — 3 × 3 = 9 bins ─────────────────────────
        cr_op_mode:  cross cp_opcode, cp_mode;
 
        // ── Three-way cross — 3 × 4 × 3 = 36 bins ──────────────────
        cr_all:      cross cp_opcode, cp_op_a, cp_mode;
 
        option.comment = "vplan §8 — ALU feature crosses";
    endgroup
 
    function new();
        cg_alu = new();
    endfunction
 
    function void post_randomize();
        cg_alu.sample();
    endfunction
endclass

What this code does. Defines a transaction class with three coverpoints, each carrying explicit named bins (per Module 11.4 discipline). Three crosses: opcode × operand-class (12 bins), opcode × mode (9 bins), and the three-way opcode × operand × mode (36 bins). The three-way cross is the audit document for "every combination of operation, operand class, and mode was exercised together."

How to simulate it. Drive 5000 randomised alu_txn objects under any IEEE 1800-compatible simulator with coverage enabled.

Expected output (urg-style, partial).

cg_alu coverage report
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Group  cg_alu                                  coverage   77.8%
 
  cp_opcode                                     100.0%
  cp_op_a                                       100.0%
  cp_mode                                       100.0%
 
  cr_op_opa   <arith,zero>                      100.0%   (  92 hits )
              <arith,small>                     100.0%   ( 387 hits )
              <arith,medium>                    100.0%   ( 412 hits )
              <arith,large>                       0.0%   (   0 hits )   ← gap
              <mul,zero>                        100.0%   (  31 hits )
              <mul,small>                       100.0%   ( 198 hits )
              <mul,medium>                        0.0%   (   0 hits )   ← gap
              <mul,large>                         0.0%   (   0 hits )   ← gap
              <div,zero>                          0.0%   (   0 hits )   ← gap (DIV-by-0 — critical!)
              <div,small>                       100.0%   (  87 hits )
              <div,medium>                      100.0%   ( 102 hits )
              <div,large>                       100.0%   (  41 hits )
 
  cr_op_mode                                    100.0%   (9/9 bins)
  cr_all      ... 24/36 bins hit ...             66.7%

What to read out of this. Every individual coverpoint is at 100% — by isolated-feature measurement, the regression looks complete. But cr_op_opa is at 66.7% with four named gaps, the most critical of which is <div,zero> — the divide-by-zero scenario that the verification plan explicitly calls out as mandatory. Code coverage and individual coverpoint coverage both reported "done" while the canonical bug class went untested. The cross report is the only metric that surfaces the gap, which is the entire point of cross coverage.

Bin Selection — binsof and intersect

By default, cross creates one bin for every combination of every bin in every crossed coverpoint. Often the verification plan needs only a subset — specific bin combinations, with the rest excluded or grouped. The binsof construct names the subset.

binsof syntax

SystemVerilog — binsof for cross-bin filtering
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
covergroup cg_filter;
    cp_opcode: coverpoint opcode {
        bins read_ops  = {READ, READ_BURST};
        bins write_ops = {WRITE, WRITE_BURST};
    }
    cp_burst: coverpoint burst_len {
        bins single = {1};
        bins burst  = {[2:16]};
    }
 
    // Only count read × burst-length combinations; ignore write × burst.
    cr_read_only: cross cp_opcode, cp_burst {
        bins read_with_any = binsof(cp_opcode.read_ops) && binsof(cp_burst);
    }
endgroup
 
// Without binsof: 4 cross bins (read × single, read × burst, write × single, write × burst)
// With binsof:    2 cross bins (read × single, read × burst)
// The write_ops × * combinations are excluded from this cross entirely.

The semantic rule. binsof(cp.bin_name) selects one specific bin from a coverpoint. binsof(cp) selects all bins from a coverpoint. The && operator combines selections from different coverpoints into a single cross-bin definition. Each bins name = expr; inside a cross body creates one named cross bin whose value space is the combination described by the expression.

intersect — selecting bins by value rather than by name

intersect lets you select bins by the values they cover rather than by their declared names. Useful when you need to scope a cross-bin filter to a specific value or range without referring to a bin name.

SystemVerilog — intersect for value-based cross selection
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
covergroup cg_intersect;
    cp_opcode: coverpoint opcode {
        bins add = {ADD}; bins sub = {SUB};
        bins mul = {MUL}; bins div = {DIV};
    }
    cp_size: coverpoint operand_size {
        bins byte_sz  = {8};
        bins half_sz  = {16};
        bins word_sz  = {32};
        bins large_sz = {[33:64]};
    }
 
    // ── Exclude the architecturally-impossible DIV-with-large combination
    cr_op_size: cross cp_opcode, cp_size {
        ignore_bins div_large = binsof(cp_opcode) intersect {DIV} &&
                                  binsof(cp_size.large_sz);
    }
endgroup

Reading rule. binsof(cp_opcode) intersect {DIV} means "the bin in cp_opcode that contains the value DIV" — which is the bin named div. The form is functionally equivalent to binsof(cp_opcode.div) in this case, but intersect is more flexible: it works against any value set, not just a named bin, and it composes with ranges (intersect {[ADD:SUB]}).

The two selection mechanisms compose. Use binsof(cp.name) when you have a bin name; use binsof(cp) intersect {…} when you want to select by value or range. Production environments use both, depending on whether the filter is "this specific named scenario" or "any value in this range."

Illegal and Ignore Bins Inside Crosses

The same illegal_bins / ignore_bins discipline from Module 11.5 applies inside crosses. The semantics are the same — illegal cross bins fire runtime violations when hit, ignore cross bins are silently excluded from the cross's denominator — but the value space is a cross combination, not a single value.

SystemVerilog — illegal and ignore bins inside a cross
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
covergroup cg_protocol;
    cp_cmd: coverpoint cmd {
        bins rd  = {CMD_READ};
        bins wr  = {CMD_WRITE};
        bins rfr = {CMD_REFRESH};
    }
    cp_addr_region: coverpoint addr_region {
        bins csr       = {REGION_CSR};
        bins ram       = {REGION_RAM};
        bins reserved  = {REGION_RESERVED};
    }
 
    cr_cmd_region: cross cp_cmd, cp_addr_region {
        // ── Reserved region must never be accessed by anything ────
        illegal_bins illegal_to_reserved =
            binsof(cp_addr_region.reserved);
 
        // ── REFRESH can only target RAM; other combinations are
        //     architecturally impossible — out of scope, not violation
        ignore_bins rfr_not_ram =
            binsof(cp_cmd.rfr) && !binsof(cp_addr_region.ram);
    }
endgroup

What this does. Three cmd bins × three region bins = nine total combinations. The illegal cross bin covers every cmd × reserved combination (three combinations: rd × reserved, wr × reserved, rfr × reserved); if any of those fire, the simulator raises a violation. The ignore cross bin covers every rfr × non-ram combination (two combinations: rfr × csr, rfr × reserved); those are excluded from the cross's denominator entirely.

Net counted bins. Nine total – three illegal – two ignored = four counted cross bins (rd × csr, rd × ram, wr × csr, wr × ram). The cross's 100% calculation uses only those four.

Cross Options and Weights

A labelled cross supports the same per-coverpoint options as a coverpoint: weight, at_least, goal, and comment.

SystemVerilog — options on cross coverage
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
covergroup cg;
    cp_addr: coverpoint addr {
        bins csr = {[0:127]};
        bins ram = {[128:255]};
    }
    cp_data: coverpoint data {
        bins zero    = {0};
        bins nonzero = {[1:255]};
    }
    cp_write: coverpoint write;
 
    // ── Critical cross — high weight, 5 hits per cross bin required
    cr_addr_data: cross cp_addr, cp_data {
        option.weight   = 10;
        option.at_least = 5;
        option.goal     = 100;
        option.comment  = "vplan §3.1 — addr × data combinations, "
                           + "critical for power-on access ordering";
    }
 
    // ── Standard cross — default options
    cr_addr_write: cross cp_addr, cp_write {
        option.comment = "vplan §3.2 — addr × write direction";
    }
endgroup

The option.weight = 10 makes cr_addr_data contribute ten times the influence of cr_addr_write to the headline covergroup percentage. The option.at_least = 5 raises the per-bin closure bar — a cross bin in cr_addr_data reports "covered" only after five hits, not one. This is the right shape for crosses the verification plan flags as critical: the dashboard feels the gap when a critical cross is incomplete.

The Cross-Bin Explosion Problem

Cross coverage scales as the product of the crossed coverpoints' bin counts. The growth is unforgiving.

CrossBins eachTotal cross binsAchievability
Two-way A × B3, 39Easy
Two-way A × B4, 416Easy
Two-way A × B8, 864Moderate
Three-way A × B × C3, 3, 327Moderate
Three-way A × B × C4, 4, 464Hard — needs ignore_bins discipline
Three-way A × B × C8, 8, 8512Likely-impossible at sign-off
Two-way, auto-binned 8-bit fields64, 644,096Effectively impossible
Two-way, auto-binned 32-bit fields64, 644,096Effectively impossible

The single most expensive verification mistake in cross coverage is crossing coverpoints that have unnecessarily many bins. The fix is upstream, in the coverpoint design: explicit named bins that group values into spec-meaningful regions, before the cross is declared.

Four discipline rules for managing cross size

SystemVerilog — managing cross-bin explosion
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Rule 1 — Cross only EXPLICIT-binned coverpoints ─────────────────
covergroup cg_good;
    cp_addr: coverpoint paddr {
        bins csr  = {[32'h0000_0000 : 32'h0000_00FF]};   // 1 bin
        bins ram  = {[32'h1000_0000 : 32'h1FFF_FFFF]};   // 1 bin
        bins mmio = {[32'h2000_0000 : 32'h2FFF_FFFF]};   // 1 bin
    }
    cp_op: coverpoint cmd {
        bins rd = {CMD_READ}; bins wr = {CMD_WRITE};
    }
    cr_addr_op: cross cp_addr, cp_op;   // 3 × 2 = 6 bins — tractable
endgroup
 
// ── Rule 2 — NEVER cross auto-binned coverpoints ───────────────────
covergroup cg_bad;
    cp_addr: coverpoint paddr;          // ← auto bins on 32-bit → 64 bins
    cp_data: coverpoint pwdata;         // ← auto bins on 32-bit → 64 bins
    cross cp_addr, cp_data;             // ← 64 × 64 = 4096 cross bins
                                          //   Effectively impossible to close
endgroup
 
// ── Rule 3 — Use binsof / ignore_bins to scope the cross ───────────
covergroup cg_scoped;
    cp_op: coverpoint cmd {
        bins rd = {READ}; bins wr = {WRITE};
        bins broadcast = {BROADCAST};   // exists, but not in this cross
    }
    cp_addr: coverpoint addr_region {
        bins csr = {CSR}; bins ram = {RAM};
    }
    // Cross excludes the broadcast operation — keeps the bin count down
    cr_op_addr: cross cp_op, cp_addr {
        ignore_bins drop_broadcast = binsof(cp_op.broadcast);
    }
endgroup
 
// ── Rule 4 — Prefer two-way crosses; reserve three-way for critical
covergroup cg_disciplined;
    cp_op: coverpoint cmd;          // 4 bins
    cp_addr: coverpoint region;      // 4 bins
    cp_burst: coverpoint burst_len;  // 4 bins
 
    // Two two-way crosses (4×4 + 4×4 = 32 bins total)
    cr_op_addr:   cross cp_op, cp_addr;
    cr_op_burst:  cross cp_op, cp_burst;
    // NOT a three-way cross unless the spec specifically calls out
    // the op × addr × burst combination as a critical scenario.
endgroup

The pattern that scales: every coverpoint that participates in a cross has fewer than 10 bins, and those bins are explicit-named and spec-aligned. Three-way crosses appear only when the spec calls out the three-feature interaction as a critical scenario, never as a "completeness gesture." Auto-binned coverpoints never appear in a cross at all.

Debug Lab — "The Cross Bin Is at 0% but Both Coverpoints Are at 100%"

A failure mode that catches every team at least once: each axis of the cross is fully covered, but the specific combination is still empty. The expectation "if A and B are each fully exercised, every (A, B) combination must be too" is structurally wrong.

Buggy code
SystemVerilog — both axes at 100%, cross at 0%
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class apb_txn;
    rand bit [31:0] paddr;
    rand bit         pwrite;
 
    constraint c_addr {
        paddr inside { [32'h0000_0000 : 32'h0000_00FF],
                       [32'h1000_0000 : 32'h1FFF_FFFF],
                       [32'h2000_0000 : 32'h2FFF_FFFF] };
    }
 
    constraint c_dir {
        // ── BUG: write biased to 99%, read to 1%; reads will only
        //         realistically appear with mid/high addresses
        pwrite dist { 1'b1 := 99, 1'b0 := 1 };
    }
 
    covergroup cg;
        cp_addr: coverpoint paddr {
            bins csr  = {[32'h0000_0000 : 32'h0000_00FF]};
            bins ram  = {[32'h1000_0000 : 32'h1FFF_FFFF]};
            bins mmio = {[32'h2000_0000 : 32'h2FFF_FFFF]};
        }
        cp_dir: coverpoint pwrite {
            bins read  = {1'b0};
            bins write = {1'b1};
        }
        cr_addr_dir: cross cp_addr, cp_dir;
    endgroup
endclass
 
// After 5000 transactions:
//   cp_addr        100.0%   (csr, ram, mmio all hit hundreds of times)
//   cp_dir         100.0%   (read hit ~50 times, write hit ~4950 times)
//   cr_addr_dir     50.0%   (csr × write, ram × write, mmio × write at 100%;
//                              csr × read at 100% — only because the few
//                              reads happened to land there;
//                              ram × read at 0%, mmio × read at 0%)
Symptom

The covergroup report shows cp_addr 100%, cp_dir 100%. The verification engineer believes the individual features are fully covered. The cross cr_addr_dir reports 50% — three of six combinations are empty. The natural first reaction is "the cross definition is wrong" — but the cross is correct, the coverpoints are correct, and the gap is in the joint distribution the test produces, not in the model.

Root cause

The constraint c_dir biases pwrite heavily toward 1 (write). The constraint c_addr distributes addresses uniformly across three regions. The joint distribution that the constraint solver produces is therefore: ~99% of transactions are writes (spread across all three address regions), ~1% are reads (spread thinly across the same three regions). With 5000 transactions, ~50 reads — far too few to reliably hit all three address regions. The csr × read combination got lucky; ram × read and mmio × read did not.

The conceptual mistake is the implicit assumption "100% on A and 100% on B implies 100% on the cross." It does not — a cross of M × N bins needs each combination to be sampled at least option.at_least times. A skewed joint distribution can leave most combinations empty while both single coverpoints fill quickly. This is precisely why cross coverage exists as a separate metric.

Fix

Two valid fixes, depending on the verification plan's intent.

Fix A — if the read/write distribution should be balanced for coverage closure: rebalance the constraint to give reads enough mass to hit every address region.

SystemVerilog — Fix A: rebalance the joint distribution
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
constraint c_dir {
    pwrite dist { 1'b1 := 50, 1'b0 := 50 };   // 50/50 read vs write
}

Fix B — if the read distribution should remain skewed but the cross must still close: add directed tests or a coverage-driven sequence that targets the empty cross bins specifically.

SystemVerilog — Fix B: directed sequence for cross-bin closure
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class cross_closure_sequence extends uvm_sequence #(apb_txn);
    task body();
        apb_txn t;
        // Three directed reads — one per region — to close the cross gap
        foreach (regions[i]) begin
            `uvm_do_with(t, {
                paddr inside { regions[i] };
                pwrite == 1'b0;
            })
        end
    endtask
endclass

The discipline rule that prevents recurrence: treat coverpoint percentages as necessary but insufficient evidence of cross-coverage completeness. The cross has its own closure number; don't infer it from the per-coverpoint numbers. For every cross in the model, run a regression-level audit that compares the cross's percentage against the product-of-individual-percentages — a large gap (cross 50% with both axes 100%) signals a joint-distribution skew that needs either a constraint rebalance or directed-test coverage.

Industry Context — Where Cross Coverage Lives

  • AXI / AHB / APB verification. Every shipping AMBA VIP includes a cross of cmd × addr-region, cmd × burst-length, cmd × strobe-shape, and burst-type × burst-length. These are the combinations where protocol bugs concentrate; the cross report is the audit document for "every documented combination was exercised."
  • Memory controller verification. Cross of cmd × bank × rank for DDR/HBM; cross of refresh-state × read-state for refresh interaction. The verification-plan sign-off rows directly map to these crosses.
  • Cache coherence verification. Cross of transaction-type × cache-state × snoop-response for MESI/MOESI protocols. Three-way cross is justified here because every (txn, state, snoop) tuple is a spec-defined coherence scenario.
  • Cryptographic IP verification. Cross of mode × key-length × payload-class for AES/SHA/RSA cores. Cross gaps here often map directly to NIST CAVP certification holes.
  • UVM scoreboard coverage. Cross of transaction-direction × expected-vs-actual-status to verify every direction got compared against every status outcome — a structural audit of the scoreboard's reach.
  • DO-254 / ARP4754A flows. Crosses are the canonical way to render "feature interaction" verification-plan rows; the traceability matrix maps cross labels to spec sections, and the cross's option.comment carries the citation.

The structural fact behind these uses: in any IP whose behaviour is governed by feature interactions (and most non-trivial IP is), individual-feature coverage cannot prove the spec was met. Cross coverage is the only metric that can.

Interview Q&A — Ten Questions You Will Be Asked

Combinations of features rather than features in isolation. A cross of two coverpoints with M and N bins creates M × N cross bins, one per (a-bin, b-bin) combination. The cross is "covered" when every combination has been hit at least option.at_least times. Where individual coverpoints answer "was each feature exercised," cross coverage answers "were the features exercised together."

Coding Guidelines — Seven Rules for Cross-Coverage Discipline

  1. Label every cross. Unlabelled crosses cannot be referenced by binsof, cannot carry option.* settings, and report under tool-generated names. The cost is one word; the payoff is that the cross is now a first-class participant in the coverage discipline.
  2. Never cross auto-binned coverpoints. Wide-field auto bins create dozens of bins per axis, and the cross becomes effectively impossible to close. Every coverpoint that feeds a cross has explicit named bins matched to the verification plan.
  3. Two-way crosses by default; three-way only with a spec citation. Three-way crosses scale by multiplication and consume regression budget exponentially. Reserve them for combinations the spec explicitly calls out as critical, and cite the spec section in option.comment.
  4. Use ignore_bins to drop architecturally-impossible combinations — with a documented reason. Every cross ignore_bins declaration cites the architectural constraint that makes the combination impossible. Without the citation, the ignore form becomes the gap-hiding tool the dashboard reads but the spec audit doesn't.
  5. Use illegal_bins inside crosses for "must never happen" combinations. The cross illegal-bin form is the canonical tool for documenting spec-forbidden combinations (cmd × reserved-region, op × illegal-mode). The violation report names both offending values — more informative than a single-coverpoint illegal bin.
  6. Weight critical crosses higher than routine ones. option.weight controls how much each item contributes to the covergroup's headline number; weighting the spec-critical crosses higher makes the dashboard feel the gap when they're incomplete. Combined with option.at_least, the weighting expresses the spec's priorities directly in the coverage model.
  7. A cross at 0% with both axes at 100% is a distribution problem, not a cross problem. Don't remove the cross bin or relax the closure rule. Either rebalance the constraint, add solve before to fix satisfying-set skew, or write a directed sequence that targets the empty combinations. The cross is doing its job — surfacing the gap.

Summary

Cross coverage measures the Cartesian product of crossed coverpoints' bins — turning "each feature was exercised" into "every spec-defined feature combination was exercised together." The construct is straightforward (cross cp_a, cp_b;); the discipline is structural — every cross is labelled, every participating coverpoint has explicit named bins, binsof and ignore_bins scope the cross to the verification plan's relevant combinations, illegal cross bins document spec-forbidden combinations, and per-cross weights reflect spec priorities. The single most common failure mode is reading 100% on the individual axes as "cross-coverage done" — a skewed joint distribution can leave most cross combinations empty even when each coverpoint is fully covered. Module 11.8 builds on this with the coverage-options vocabulary — at_least, weight, goal, comment, per_instance, and the other knobs that tune coverage models toward sign-off.