UVM
Cross Coverage
Measuring combinations, not just features in isolation — the cross: the Cartesian product of coverpoints' bins, why bugs hide in combinations that per-coverpoint coverage is blind to, the combinatorial explosion, and how to control it with coarse bins and pruning (binsof / intersect, ignore and illegal bins) so a cross measures the meaningful combinations rather than an unclosable product.
Functional Coverage · Module 19 · Page 19.5
The Engineering Problem
Coverpoints (Module 19.3) measure each feature in isolation — every operation was seen, every size was seen, every mode was seen. But a design's bugs often live not in any single feature but in the combination of features: a WRITE at the maximum size, a read in low-power mode, an error response during a burst. A testbench can reach 100% on every coverpoint — every operation and every size individually exercised — while never once exercising WRITE × max-size together, the exact combination where the bug lives. Per-coverpoint coverage is blind to combinations: it checks that each axis was visited, not that each cell was. The construct that measures combinations is the cross — and it is the most powerful coverage construct and the most dangerous, because the combination space explodes multiplicatively, and crossing the wrong coverpoints yields a cross with millions of bins that can never close — a project-killer. The problem this chapter solves is cross coverage: measuring the combinations that matter, and controlling the explosion so the cross stays meaningful and closable.
A cross measures combinations of coverpoints — the Cartesian product of their bins — answering "was every combination exercised?", not just "was every value of each exercised?". cross cp_op, cp_size creates a cross bin for every pair (op-bin × size-bin); the cross is covered when every meaningful combination is hit. Crosses matter because bugs hide in combinations that per-coverpoint coverage cannot see — each coverpoint at 100% does not imply every combination occurred. But the cross-bin count is the product of the coverpoints' bin counts, so the space explodes: crossing two auto-binned coverpoints (64 bins each) is 4096 bins; a third makes it 262144 — unclosable. The art is control: cross only the coverpoints whose interaction is functionally significant, give them coarse bins (few each), and prune the meaningless and illegal combinations with bin selection (binsof / intersect), ignore_bins, and illegal_bins inside the cross. This chapter is cross coverage: combinations as the Cartesian product, why bugs hide in cells, the explosion, and the pruning that keeps a cross meaningful and closable.
What is cross coverage — how does it measure combinations of coverpoints as a Cartesian product, why do bugs hide in combinations that per-coverpoint coverage is blind to, how does the combination space explode, and how do you control it with coarse bins and pruning so the cross measures the meaningful combinations rather than an unclosable product?
Motivation — why combinations need their own coverage
Per-coverpoint coverage cannot see combinations, and combinations are where interaction bugs live. The reasons crosses are essential — and dangerous:
- Bugs hide in combinations, not isolated features. A design handles each operation correctly and each size correctly, but fails at a specific combination (WRITE at max size) where two features interact. Isolated coverage misses it; only the cross measures whether that combination occurred.
- Per-coverpoint 100% is not combination 100%. Every op seen and every size seen does not mean every op×size seen — you could hit READ-small and WRITE-large and reach 100% on both coverpoints while never hitting WRITE-small or READ-large. The margins can be full while cells are empty.
- The cross is the deepest argument for functional coverage. Interaction coverage is exactly what simpler metrics (code coverage, per-signal toggle) cannot provide — it's intent about how features combine, which only a deliberately-authored cross expresses.
- But the space explodes multiplicatively. Cross bins = product of bin counts. A few coverpoints with many bins → thousands or millions of cross bins — most meaningless, the cross never closing. Crossing carelessly is a project-killer.
- Control is the whole skill. A useful cross is a controlled one — few axes, coarse bins, aggressive pruning of meaningless and illegal combinations. The value of cross coverage is unlocked only by managing the explosion.
The motivation, in one line: bugs hide in combinations of features that per-coverpoint coverage is blind to (every coverpoint at 100% ≠ every combination exercised), so the cross — measuring the Cartesian product — is essential to catch interaction bugs; but the combination space explodes multiplicatively, so the skill is control: cross only the functionally-significant interactions, with coarse bins, pruning the meaningless and illegal combinations, so the cross stays meaningful and closable.
Mental Model
Hold a cross as a grid of cells — per-coverpoint coverage fills the margins, the cross fills the cells, and bugs hide in the cells the margins can't see:
A cross is a grid: one coverpoint's bins are the rows, another's are the columns, and each cell is a combination. Per-coverpoint coverage only checks that every row was visited and every column was visited — the margins — but a margin can be full while interior cells stay empty. The cross checks every cell, and bugs hide in the cells the margins can't see. The catch is that the grid grows multiplicatively, so you keep it small by crossing few axes with coarse divisions and blanking out the cells that can't or shouldn't occur. Picture a checkerboard grid. The rows are one coverpoint's bins (say
op: read, write, RMW); the columns are another's (size: small, large, max). Each cell is a combination — write × max, read × small, and so on. Now, per-coverpoint coverage only looks at the margins: it checks that every row was visited at least once (every op occurred somewhere) and every column was visited (every size occurred somewhere). But here's the trap: the margins can be completely full while interior cells are empty. If you ran read-small and write-large and RMW-max, every row has a tally and every column has a tally — 100% per-coverpoint — yet six of the nine cells are empty: you never ran write-small, read-max, and so on. A bug that only manifests at write × max lives in an empty cell that the full margins completely hide. The cross fills in the grid itself: it checks every cell, so the write × max cell must be visited before the cross closes. That's the power. The danger is that the grid grows multiplicatively: 3 rows × 3 columns = 9 cells is fine, but 64 rows × 64 columns = 4096 cells, and adding a third axis (a cube) makes it 262144 cells — most empty, most meaningless, the grid never filling. So you keep the grid small: cross few axes (2 or 3, not 6), with coarse divisions (3–4 bins per coverpoint, not 64), and blank out the cells that can't occur (an ignore — write × size-only-applies-to-read) or shouldn't occur (an illegal — a forbidden combination). A good cross is a small grid of meaningful cells, all reachable.
So a cross is a grid of cells: per-coverpoint coverage fills the margins (every row, every column visited), but the cross fills the cells (every combination), and bugs hide in the empty cells the full margins hide. The grid grows multiplicatively, so you keep it small — few axes, coarse divisions — and blank out the unreachable (ignore) and forbidden (illegal) cells. Cross the few axes whose interaction matters, keep the grid small, and check every cell that can legally occur.
Visual Explanation — the cross as a grid of combinations
The defining picture is the grid: two coverpoints become rows and columns, and the cross is the cells — every combination.
The figure shows the cross as a grid. Coverpoint cp_op (read, write, RMW) forms the rows; cp_size (small, large, max) forms the columns. The cross (cross cp_op, cp_size, the warning-colored center — warning because it's the explosive construct) creates a bin for every cell — every op×size combination (9 cells for 3×3). The crucial reading is the margins-versus-cells distinction. Per-coverpoint coverage checks only the margins: that every row was visited (cp_op at 100% = read, write, RMW each occurred somewhere) and every column was visited (cp_size at 100% = small, large, max each occurred somewhere). The cross checks every cell: the write × max combination (the default-colored node) is its own cell that must be exercised, even when cp_op and cp_size are already at 100%. This is why the cross adds information no amount of per-coverpoint coverage can: you can have full margins (every op, every size) with empty cells (write-max never co-occurred). The success-colored "9 combination cells" is what the cross requires — and the cross is covered only when all (meaningful) cells are hit. The diagram is the anatomy of interaction coverage: coverpoints (rows, columns) → cross (the grid) → cells (combinations) — and the cross measures what the coverpoints alone cannot: that every combination, not just every value, was exercised. The write × max cell standing alone is the whole point: interaction coverage requires the specific combination, because that's where the interaction bug lives. A cross measures the cells; the coverpoints alone measure only the margins.
RTL / Simulation Perspective — declaring and controlling a cross
In code, a cross is one line (cross a, b) — but the control (selecting, ignoring, forbidding cross bins) is where the skill is. The example shows a cross with coarse coverpoints, pruned with binsof/intersect, ignore_bins, and illegal_bins.
covergroup txn_cg with function sample(bus_txn t);
// cross ONLY coarsely-binned coverpoints — few bins each, so the product stays small
cp_op: coverpoint t.op { bins rd = {READ}; bins wr = {WRITE}; bins rmw = {RMW}; } // 3 bins
cp_size: coverpoint t.size { bins small = {[1:4]}; bins large = {[5:63]}; bins max = {64}; } // 3 bins
// the CROSS: 3 × 3 = 9 combination bins — manageable and meaningful
x_op_size: cross cp_op, cp_size {
// IGNORE meaningless combinations: suppose RMW is never issued at max size (spec-disallowed by config)
ignore_bins rmw_max = binsof(cp_op.rmw) && binsof(cp_size.max);
// FORBID combinations that must never occur: a WRITE at size 0 would be a protocol violation
// (size 0 isn't even a bin here, but illustrate forbidding a combination)
illegal_bins wr_illegal = binsof(cp_op.wr) && binsof(cp_size) intersect {0};
}
// ✗ ANTI-PATTERN: crossing AUTO-binned coverpoints → explosion, never closes
// cp_addr: coverpoint t.addr; // ~64 auto bins
// cp_data: coverpoint t.data; // ~64 auto bins
// x_bad: cross cp_addr, cp_data; // 64 × 64 = 4096 mostly-meaningless bins → UNCLOSABLE
endgroup
// the cross measures COMBINATIONS; binsof/intersect/ignore/illegal PRUNE it to the meaningful onesThe code shows a controlled cross. The crossed coverpoints (cp_op, cp_size) are coarsely binned — 3 bins each — so the cross is 3 × 3 = 9 combination bins, manageable and meaningful. The cross (x_op_size: cross cp_op, cp_size) creates those 9 bins, then prunes: ignore_bins removes a meaningless combination (rmw_max — binsof(cp_op.rmw) && binsof(cp_size.max) — suppose RMW is never issued at max size, so that cell can't occur and shouldn't block closure); illegal_bins forbids a combination that must never occur (wr_illegal — a WRITE at a forbidden size — sampling it fails the test). The binsof() operator selects cross bins by which coverpoint bin they involve, and intersect {...} filters by value — together they express which cells to ignore or forbid. The anti-pattern (commented) is the project-killer: crossing auto-binned coverpoints (cp_addr, cp_data, ~64 bins each) → 64 × 64 = 4096 mostly-meaningless bins → unclosable. The shape to carry: a cross is trivial to declare (cross a, b) but the skill is control — cross coarsely-binned coverpoints (small product), and prune with binsof/intersect/ignore_bins/illegal_bins to the meaningful combinations. The ignore_bins inside a cross remove the unreachable cells (so the cross can close); the illegal_bins inside a cross forbid the impossible cells (so a forbidden combination fails the test). The coarse binning is the first defense against explosion; the pruning is the second. Declare the cross in one line, but bin coarsely and prune aggressively, or it never closes.
Verification Perspective — the explosion and how pruning tames it
The defining hazard of crosses is the multiplicative explosion — and the defining skill is taming it with coarse bins and pruning. Seeing the numbers makes the danger concrete.
The figure shows the explosion and its taming. The cross-bin count is the product of the coverpoints' bin counts. Coarse × coarse (3 × 3 bins) → 9 cross bins — meaningful, closable. Auto × auto (64 × 64, or × 64 for a third) → 4096 (or 262144) → explosion, unclosable. Taming it has two levers. Coarse binning (fewer bins per coverpoint) shrinks the product — the first and most powerful defense (going from 64 to 4 bins per coverpoint turns 4096 into 16). Pruning (ignore_bins removes unreachable combinations, illegal_bins forbids impossible ones, binsof/intersect selects which cells to keep) removes cells. The verification insight is that both levers attack the explosion but differently: coarse binning attacks the product (the multiplicative growth) at its source — fewer bins per axis means a dramatically smaller grid; pruning attacks the remaining grid by removing the cells that can't (ignore) or shouldn't (illegal) occur. The warning-colored explosion is what careless crossing produces; the brand-colored levers transform it into the success-colored small, reachable set of meaningful combinations. The crucial discipline is that coarse binning comes first: you cannot ignore_bins your way out of a 64 × 64 cross (you'd need to list thousands of cells) — you must re-bin the coverpoints coarsely so the product is small to begin with, then prune the few remaining meaningless cells. So the art is: cross coarsely-binned coverpoints (small product), then prune the unreachable and forbidden cells — turning an unclosable product into a small set of meaningful, reachable combinations. The figure is the taming: explosion (product of bin counts) → coarse bins (shrink the product) + prune (remove cells) → meaningful, closable cross. Coarse-bin first to shrink the product, then prune the cells that can't or shouldn't occur.
Runtime / Execution Flow — how a cross bin gets hit
At run time, a cross bin is hit only when all its coverpoints' bins are hit on the same sample — the combination must co-occur. The flow shows it.
The flow shows how a cross bin gets hit. Sample (step 1): one transaction carries values for all the crossed fields at once (op and size and mode together). Map (step 2): each coverpoint maps its value to a bin — cp_op maps op to its bin, cp_size maps size to its bin — the row and the column for this transaction. Cell (step 3): the cross bin at that (op-bin, size-bin) cell is marked hit — the combination that co-occurred. Covered (step 4): the cross closes only when every meaningful combination has co-occurred on some transaction. The runtime insight is the co-occurrence requirement: a cross bin is hit only when its component bins are hit together, on the same transaction — not when they're hit separately on different transactions. This is exactly why per-coverpoint coverage can't substitute: exercising every op (on some transactions) and every size (on some transactions) fills the coverpoints but not the cross cells — the cross cell requires a single transaction where the specific op and the specific size occur together. So to close a cross, the stimulus must produce the combinations — which is harder than producing each value, and is why crosses drive the most targeted constrained-random work (Module 19.1's feedback loop). The flow is the cross-hit mechanic: sample (all fields together) → map (each to a bin, the row and column) → cell (the intersection bin hit) → covered (when every meaningful cell co-occurred). The co-occurrence is the essence: the cross measures whether the combinations actually happened together, which is what isolated coverage cannot tell you. A cross cell needs its values to co-occur on one transaction — separate exercise of each value never fills it.
Waveform Perspective — combinations co-occurring over time
A cross is visible as combinations co-occurring on individual transactions: a cross cell is hit when its values appear together on one transaction — and a combination that never co-occurs is a cross gap. The waveform shows it.
A cross cell is hit when its values co-occur on one transaction; a never-co-occurring combination is a gap
12 cyclesThe waveform shows combinations co-occurring. Each transaction carries an op and a size together. Transaction 1 is read-small (cell read×small hit, x_rd_sm). Transaction 2 is write-large (cell write×large hit, x_wr_lg). Transaction 3 is read-large (cell read×large hit). The crucial reading is the co-occurrence and the gap. Across the run, op covers read and write, and size covers small and large — both coverpoints look well-exercised (the margins are full). But the cell write×small never occurs: no transaction is both write and small, so that cross bin stays empty (x_wr_sm flat) — a combination gap, invisible to the per-coverpoint margins. This is the visual proof of the chapter's thesis: the op coverpoint and the size coverpoint can both read 100% (read and write both occurred; small and large both occurred) while a specific combination (write-small) never co-occurred — an empty cell the full margins completely hide. The cross is what exposes it: x_wr_sm staying empty is a visible gap that demands a transaction where write and small occur together. The picture to carry is that a cross cell is hit only when its values appear on the same transaction (x_rd_sm fires because txn1 is read AND small), and a never-co-occurring combination is a gap that per-coverpoint coverage cannot see. Reading the waveform this way — which combinations co-occurred? which cell is still empty despite full margins? — is reading the cross. The write×small cell staying empty while op and size are both fully covered is the signature of an interaction gap — the exact blind spot that makes cross coverage necessary. A cross cell needs co-occurrence; full margins can hide an empty cell, and that empty cell is where the interaction bug hides.
DebugLab — the cross that could never close
A regression chasing 100% on a cross with 16384 mostly-impossible bins
A team added a cross of address and data coverpoints to "thoroughly" cover the datapath, and the project's coverage cratered: the cross sat at 3% and would not move, week after week, despite the regression growing to thousands of seeds. The coverage report showed the cross had over 16000 bins. The team kept adding stimulus and constraints to "close the cross", burning weeks of compute and engineering — and the number crawled from 3% to 4% to 5%. Closure was projected to take years. Meanwhile, meaningful coverage work stalled because the unclosable cross dominated the coverage metric and the team's attention.
The cross was between two auto-binned coverpoints — 128 bins × 128 bins ≈ 16384 cross bins — most of them meaningless or impossible combinations that no stimulus would ever produce. The cross was mathematically unclosable, and no amount of stimulus could fix a binning problem:
✗ CROSS of two AUTO-binned coverpoints → multiplicative explosion:
cp_addr: coverpoint t.addr; // ~128 auto bins (meaningless raw partition)
cp_data: coverpoint t.data; // ~128 auto bins
x_addr_data: cross cp_addr, cp_data; // 128 × 128 ≈ 16384 bins — MOSTLY impossible combos
// most cells are raw-value combinations no stimulus targets → cross stuck at ~3%, never closes
// adding stimulus can't help: the problem is the BINNING, not the stimulus
✓ COARSE-BIN first, then CROSS only the meaningful interaction, then PRUNE:
cp_addr_region: coverpoint t.addr { bins low={[0:'h3FF]}; bins io={['hF000:'hFFFF]}; bins other=default; }
cp_data_kind: coverpoint t.data { bins zero={0}; bins ones={'hFFFFFFFF}; bins normal=default; }
x_region_kind: cross cp_addr_region, cp_data_kind { // 3 × 3 = 9 MEANINGFUL bins
ignore_bins io_ones = binsof(cp_addr_region.io) && binsof(cp_data_kind.ones); // can't occur
}
// now 9 (minus ignored) reachable, meaningful combinations → closable in a normal regressionThis is the cross-explosion bug — the project-killer of cross coverage. The team crossed two auto-binned coverpoints (cp_addr, cp_data — ~128 raw-value bins each), producing a cross of ~16384 bins, most of which are meaningless raw-value combinations that no stimulus would ever deliberately target. The cross was mathematically unclosable: with thousands of impossible or pointless cells, no feasible regression could fill them all, so coverage crawled near 3% forever. The fatal error is adding stimulus to fix a binning problem — the unclosable-ness is baked into the binning (auto bins → huge product), so no amount of stimulus or constraint can close it; the team burned weeks attacking the wrong layer. The fix is re-binning, not more stimulus: coarse-bin the coverpoints first (cp_addr_region with a few meaningful regions — low memory, IO, other; cp_data_kind with a few meaningful kinds — zero, all-ones, normal), then cross only the meaningful interaction (x_region_kind = 3 × 3 = 9 bins), then ignore_bins the impossible combinations (io_ones — suppose the IO region can't carry all-ones data). The result is a small set of reachable, meaningful combinations — closable in a normal regression. The general lesson, and the chapter's thesis: crossing auto-binned or finely-binned coverpoints multiplies into an unclosable space of mostly-meaningless combinations, and no amount of stimulus can close a binning problem — so coarse-bin the coverpoints first (few meaningful bins each, shrinking the product at its source), cross only the interactions that are functionally significant, and prune the unreachable (ignore_bins) and forbidden (illegal_bins) cells — so the cross measures a small set of meaningful, reachable combinations rather than an unclosable product. Before you cross two coverpoints, count the product of their bins; if it isn't small, coarse-bin them first — a cross is a multiplication, and you control the result by controlling the factors.
The tell is a cross stuck at very low coverage with a huge bin count. Diagnose a cross explosion:
- Count the cross bins. Multiply the crossed coverpoints' bin counts; thousands or more means an explosion, not a stimulus gap.
- Check for auto-binned crossed coverpoints. A cross of any auto-binned or wide coverpoint is almost always the cause.
- Inspect whether the cells are meaningful. If most cross bins are raw-value combinations no plan targets, the binning is wrong, not the stimulus.
- Confirm stimulus can't help. If the product is unclosable by construction, more seeds and constraints won't move it — the fix is re-binning.
Control the cross before declaring it:
- Coarse-bin the coverpoints before crossing. Few meaningful bins each keeps the product small at its source — the most powerful lever.
- Cross only functionally-significant interactions. Don't cross everything with everything; cross the few pairs where the combination matters.
- Prune meaningless and illegal cells. Use ignore_bins for unreachable combinations and illegal_bins for forbidden ones, with binsof/intersect to select.
- Estimate the bin count up front. Multiply the factors before writing the cross; if it isn't small and meaningful, fix the binning first.
The one-sentence lesson: crossing auto-binned or finely-binned coverpoints multiplies into an unclosable space of mostly-meaningless combinations, and no amount of stimulus can close a binning problem — so coarse-bin the coverpoints first, cross only the functionally-significant interactions, and prune the unreachable and forbidden cells, so the cross measures a small set of meaningful, reachable combinations rather than an unclosable product.
Common Mistakes
- Crossing auto-binned or wide coverpoints. The product explodes into thousands of meaningless bins that never close; coarse-bin first.
- Adding stimulus to close an unclosable cross. The problem is the binning, not the stimulus; re-bin the coverpoints coarsely instead of burning compute.
- Crossing everything with everything. Cross only the few interactions that are functionally significant; most pairs don't need a cross.
- Not pruning meaningless combinations. Unreachable cells block closure; remove them with ignore_bins (and forbid impossible ones with illegal_bins).
- Assuming per-coverpoint 100% covers combinations. Full margins can hide empty cells; a cross is needed to require the combinations.
- Going N-way when targeted 2-way crosses suffice. Each axis multiplies the space; prefer several targeted 2-way or 3-way crosses over one giant N-way.
Senior Design Review Notes
Interview Insights
Cross coverage measures combinations of coverpoints — the Cartesian product of their bins — and it's needed because bugs hide in combinations of features that per-coverpoint coverage is completely blind to. A coverpoint measures one variable in isolation: every operation was seen, every size was seen. But a design often handles each feature correctly while failing at a specific combination — a write at the maximum size, a read in low-power mode — where two features interact. Per-coverpoint coverage can't see this. You could reach 100% on the operation coverpoint and 100% on the size coverpoint while never once exercising write at max size together. The mental model is a grid: one coverpoint's bins are the rows, another's are the columns, and each cell is a combination. Per-coverpoint coverage only checks the margins — that every row was visited and every column was visited — but the margins can be completely full while interior cells stay empty. If you ran read-small, write-large, and RMW-max, every row and every column has a tally, so both coverpoints read 100%, yet most of the nine cells are empty. A bug that only manifests at write-max lives in an empty cell the full margins hide. The cross checks every cell, so write-max becomes its own bin that must be exercised before the cross closes. This is the deepest argument for functional coverage: interaction coverage is exactly what simpler metrics like code coverage can't provide, because it's deliberate intent about how features combine. The key mechanic is co-occurrence: a cross bin is hit only when its component bins are hit together on the same transaction, not separately on different ones. So exercising every value of each coverpoint never fills a cross cell — the values must co-occur on one transaction, which is harder and is what makes the cross measure something the coverpoints alone cannot.
Because the cross-bin count is the product of the coverpoints' bin counts, so crossing two auto-binned coverpoints — which can have 64 or more bins each — produces thousands of cross bins, most of them meaningless combinations that no stimulus will ever target, making the cross mathematically unclosable. Auto bins create roughly one bin per value, capped at auto_bin_max which defaults to 64. So a cross of two auto-binned coverpoints is something like 64 times 64, around 4096 bins, and crossing a wide variable can be far worse — 128 times 128 is over 16000. A third auto-binned axis makes it hundreds of thousands. Most of these cells are raw-value combinations with no functional meaning, combinations no test plan targets and many that can't physically occur. The cross then sits at a few percent and won't move, because filling all those cells would take an infeasible number of transactions. The fatal trap is that teams respond by adding stimulus and constraints to close the cross, burning weeks of compute, when the unclosable-ness is baked into the binning, not the stimulus. No amount of stimulus can close a binning problem. The cross dominates the coverage metric and the team's attention while real coverage work stalls. The fix is re-binning, not more stimulus: coarse-bin each coverpoint first into a few meaningful bins — address into regions like low memory, IO, other; data into kinds like zero, all-ones, normal — so the product is small, like 3 times 3 equals 9. Then cross only that meaningful interaction, and prune the impossible combinations with ignore_bins. The principle is that a cross is a multiplication, and you control the result by controlling the factors. Before writing any cross, you multiply the bin counts; if the product isn't a small, meaningful number, you coarse-bin the coverpoints before crossing. Coarse binning is the first and most powerful defense because it attacks the multiplicative growth at its source.
You control a cross with two levers: coarse binning to shrink the product at its source, and pruning to remove the cells that can't or shouldn't occur. The first lever, coarse binning, is the most powerful because the cross-bin count is the product of the crossed coverpoints' bin counts. If each coverpoint has just a few meaningful bins instead of dozens, the product stays small — three bins times three bins is nine, versus 64 times 64 being 4096. So before crossing, you bin each coverpoint coarsely into the functionally-distinct cases that matter for the interaction, not raw values. You attack the multiplication by controlling its factors. The second lever, pruning, removes cells from the resulting grid. There are three tools. ignore_bins inside the cross removes combinations that can't occur — for instance if a particular mode is never issued at a particular size, that cell is unreachable and would block closure, so you ignore it, removing it from the denominator. illegal_bins inside the cross forbids combinations that must never occur, so that if a forbidden combination is ever sampled, the test fails — turning the cross into a checker for illegal interactions. And binsof with intersect is the selection operator: binsof picks cross bins by which coverpoint bin they involve, and intersect filters by value, so together they express exactly which cells to keep, ignore, or forbid. The discipline is that coarse binning comes first — you can't ignore_bins your way out of a 64-by-64 cross because you'd have to list thousands of cells; you must re-bin so the product is small to begin with, then prune the few remaining meaningless cells. Beyond these, you also control dimensionality: prefer several targeted two-way or three-way crosses over one giant N-way cross, because each additional axis multiplies the space again and the high-order combinations are usually less meaningful. So the recipe is: cross only functionally-significant interactions, coarse-bin the coverpoints first, prune the unreachable and forbidden cells, and keep the dimensionality low.
binsof and intersect are the operators for selecting which cross bins to act on — they let you name a subset of the cross's cells so you can ignore them, make them illegal, or define custom cross bins. When you cross two coverpoints, you get a cell for every combination of their bins, but you frequently need to refer to a group of those cells — say all the cells where the operation is RMW, or all the cells where the size is the maximum. binsof does this selection: binsof of a coverpoint's bin refers to all cross cells that involve that bin. So binsof of cp_op.rmw refers to every cell in the RMW row, and binsof of cp_size.max refers to every cell in the max column. You combine them with logical operators: binsof of cp_op.rmw and binsof of cp_size.max refers to the single cell at their intersection, RMW-max. With or, you'd get the union — the whole row plus the whole column. intersect adds value-based selection: binsof of a coverpoint with intersect and a set of values selects the cells where that coverpoint's value falls in the set, which is handy when you want to select by raw value ranges rather than by named bin. Together they form expressions that identify exactly the cells you care about. You then use those expressions in three ways. As ignore_bins, to exclude unreachable combinations so the cross can close — ignore_bins of the RMW-max cell if RMW is never issued at max size. As illegal_bins, to forbid combinations that must never occur, so sampling one fails the test. And to define explicit named cross bins, grouping cells into a meaningful combined bin. So binsof and intersect are the addressing mechanism for the cross grid — they let you point at specific rows, columns, cells, or value-defined regions, which is what makes precise pruning and forbidding possible. Without them you could only treat the cross as an all-or-nothing product; with them you sculpt it down to the meaningful, reachable combinations.
You should use a cross when two or more features genuinely interact, such that a bug could hide in a specific combination even when each feature works alone — and you should not cross features that don't meaningfully interact or coverpoints that are finely or auto-binned. The case for a cross is interaction: when the design's behavior at a combination isn't just the independent behavior of each feature. Operation crossed with size, because the datapath might mishandle a write specifically at the max size. Mode crossed with state, because a transition might be buggy only in a particular mode. Transaction type crossed with which port, because arbitration might fail for a specific type on a specific port. In these cases, per-coverpoint coverage genuinely can't substitute, because the bug lives in a cell, not a margin, and only the cross requires the combination to co-occur. The case against a cross is several. First, when the features don't interact — crossing two independent attributes just to be thorough creates cells with no functional meaning and an unclosable obligation. Second, when the coverpoints are auto-binned or finely-binned — the product explodes, and you must coarse-bin first or not cross at all. Third, when the dimensionality is high — a four- or five-way cross multiplies the space enormously and the high-order combinations are rarely meaningful, so several targeted two-way crosses are almost always better than one giant N-way. The practical test before writing a cross is to ask two questions: do these features actually interact in a way a bug could exploit, and what is the product of their bin counts? If the interaction is real and the product is small and meaningful, write the cross and prune the unreachable cells. If the interaction is dubious or the product is large, don't — either coarse-bin until it's small, drop to fewer axes, or skip the cross. Crosses are the most powerful coverage construct but also the most explosive, so they reward deliberate, sparing use over blanket application.
Exercises
- Margins versus cells. Give a concrete example where both coverpoints read 100% but a cross cell is empty, and name the bug that could hide there.
- Estimate the explosion. For a cross of three coverpoints with 8, 16, and 4 bins, compute the cross-bin count and decide whether it's acceptable.
- Prune a cross. For a cross of op and size, write the ignore_bins for a combination that can't occur and the illegal_bins for one that must never occur.
- Coarse-bin to close. A cross of two 32-bit fields won't close. Describe how to re-bin them into a small, meaningful, closable cross.
Summary
- A cross measures combinations of coverpoints — the Cartesian product of their bins — answering "was every combination exercised?", because bugs hide in combinations (write × max size) that per-coverpoint coverage is blind to: every coverpoint at 100% does not imply every combination occurred.
- A cross is a grid of cells: per-coverpoint coverage fills the margins (every row, every column visited), but the cross fills the cells (every combination); a cross bin is hit only when its component bins co-occur on the same transaction.
- The cross-bin count is the product of the coverpoints' bin counts, so the space explodes multiplicatively — crossing auto-binned coverpoints (64 × 64 = 4096, × 64 = 262144) is unclosable, a project-killer, and no stimulus can close a binning problem.
- Control is the skill: coarse-bin the coverpoints first (shrink the product at its source), cross only the functionally-significant interactions, and prune the unreachable (
ignore_bins) and forbidden (illegal_bins) cells, selecting withbinsof/intersect. - The durable rule of thumb: a cross is a multiplication — its bin count is the product of the crossed coverpoints' bin counts — so before you cross, count the product, and if it isn't a small, meaningful number, coarse-bin the coverpoints first; cross only the interactions where features genuinely interact, prune the cells that can't or shouldn't occur, and prefer several targeted 2-way crosses to one giant N-way, so the cross measures the meaningful combinations rather than an unclosable product.
Next — Coverage Analysis: with covergroups, coverpoints, bins, and crosses in place, the question becomes how to read the result — the coverage report. How to interpret coverage numbers, find the meaningful gaps, distinguish a stimulus gap from an unreachable bin, merge coverage across many regression runs, and turn the report into the next actions that drive toward closure.