A single coverpoint tells you whether a variable took a particular value at some point during the test. It says nothing about which values happened together. Cross coverage fills that gap. It tracks combinations of bins across two or more coverpoints, which is exactly what you need to catch corner cases that only appear when several conditions line up at once.
8 min read | Intermediate Level
What You'll Learn
- What cross coverage measures and how its automatic bins are created
- The syntax of a
crossstatement and the rules for what it can contain - How to build custom cross bins with
binsofand useintersectcorrectly - How
illegal_binsandignore_binschange the cross coverage total
What Is Cross Coverage?
Cross coverage observes the combination of states between two or more coverpoints. Instead of asking "did master_id ever equal 2?" it asks "did master_id equal 2 while slave_id equalled 3?" This is essential for verifying interactions such as every opcode with every addressing mode, or every command type with every address region.
Syntax
Cross coverage is declared with the cross keyword inside a covergroup:
[label :] cross item1, item2 [, itemN] [iff (expression)] [{
bins bin_name = select_expression;
ignore_bins bin_name = select_expression;
illegal_bins bin_name = select_expression;
}];
- label (optional): names the cross and creates a scope for its custom bins
- item1, item2 ... (required, two or more): coverpoint names, or plain variables in the same covergroup
iff (expression)(optional): the cross is sampled only when the expression is true- body (optional): user-defined, ignored and illegal cross bins
Some rules to remember:
- When a plain variable is listed in a
cross, SystemVerilog creates an implicit coverpoint for it with automatic bins. - An expression such as
a + bcannot be crossed directly. Define it as a named coverpoint first, then cross the coverpoint. - All items in a
crossmust belong to the same covergroup.
A Basic Cross Coverage Example
Imagine a system where different masters can initiate transactions with different slaves. Individual coverpoints on master_id and slave_id are useful, but they do not tell you whether master 0 ever actually talked to slave 1. A cross answers exactly that:
covergroup master_slave_interaction @(posedge clk);
master_id_cp: coverpoint master_id {
bins master0 = {0};
bins master1 = {1};
bins master2 = {2};
bins master3 = {3};
}
slave_id_cp: coverpoint slave_id {
bins slave0 = {0};
bins slave1 = {1};
bins slave2 = {2};
bins slave3 = {3};
}
ms_cross: cross master_id_cp, slave_id_cp; // Cross coverage declaration
endgroup
ms_cross automatically creates one cross product bin for every combination of the two coverpoints' bins. Since both coverpoints have 4 bins, the cross produces 4 x 4 = 16 bins: <master0,slave0>, <master0,slave1>, and so on up to <master3,slave3>. Each bin counts one specific master-slave interaction.
The label before the colon becomes the cross's name and the scope for any custom bins. If you omit it, the tool generates a name for reporting, but that name cannot be referenced in your code.
Label every cross. It makes coverage reports readable and lets you refer to the cross from coverage options and custom bins.
Quick Check
Q: If master_id_cp had 5 bins and slave_id_cp had 3 bins, how many automatic cross bins would the cross create?
Show Answer
15. The number of automatic cross bins is the product of the participating coverpoints' bin counts, 5 x 3 in this case.
Selecting Cross Products with binsof and intersect
Custom cross bins are built from select expressions. The binsof construct picks bins from one coverpoint, and the optional intersect clause narrows that pick to bins whose values overlap a given value or range list in braces.
| Select expression | Selects cross products where... |
|---|---|
binsof(cp.b) | bin b of cp is part of the combination |
binsof(cp) intersect {[0:1]} | the bin of cp contains at least one value in 0 to 1 |
!binsof(cp) intersect {[0:1]} | the bin of cp contains no value in 0 to 1 |
sel1 && sel2 | both selections are true |
sel1 || sel2 | either selection is true |
The right-hand side of intersect is always a brace-enclosed value or range list, never another binsof(). To combine conditions on two different coverpoints, join two select expressions with && or ||.
Defining User-Defined Cross Coverage Bins
User-defined bins group one or more cross products under a single name. Say you care about master 0 accessing slave 1, and separately about any master accessing slave 3:
covergroup master_slave_interaction_refined @(posedge clk);
master_id_cp: coverpoint master_id {
bins master0 = {0};
bins master1 = {1};
bins master2 = {2};
bins master3 = {3};
}
slave_id_cp: coverpoint slave_id {
bins slave0 = {0};
bins slave1 = {1};
bins slave2 = {2};
bins slave3 = {3};
}
ms_cross: cross master_id_cp, slave_id_cp {
bins master0_slave1 = binsof(master_id_cp.master0) && binsof(slave_id_cp.slave1);
bins any_master_slave3 = binsof(slave_id_cp.slave3);
// Selects : 4 cross products grouped into one bin
}
endgroup
- master0_slave1 combines two
binsofselections with&&, so it matches only the single product<master0,slave1>. - any_master_slave3 uses
binsof(slave_id_cp.slave3)on its own. Because no condition is placed on master_id_cp, every master paired with slave 3 is selected. The bin is covered as soon as any one of those four combinations is hit.
Here is the same idea written with intersect, to cover only the low-numbered masters talking to slave 3:
ms_cross: cross master_id_cp, slave_id_cp {
// master bins whose values overlap 0..1 (master0, master1), paired with slave3
bins low_masters_slave3 = binsof(master_id_cp) intersect {[0:1]} &&
binsof(slave_id_cp) intersect {3};
}
Any cross product not included in a user-defined bin still gets its own automatic bin. With the two bins in the refined example above, the cross reports 2 user-defined bins plus 11 automatic bins (16 - 1 - 4).
Where Is Cross Coverage Used?
Cross coverage is a natural fit for protocol verification: confirming every command type was issued to every address region, every burst length was used with every data width, or every opcode was executed in every privilege mode. It is also used in stress testing to confirm that high-traffic or boundary combinations were hit, not just each field in isolation.
Specifying Illegal and Ignore Cross Products
Not every combination is valid or relevant. Two special bin types handle these cases:
illegal_bins: combinations that should never occur. Hitting one produces a run-time error, and these products are excluded from the coverage total.ignore_bins: combinations that are valid but not part of your coverage goals. They are excluded from the coverage total.
covergroup master_slave_interaction_with_exceptions @(posedge clk);
...
ms_cross: cross master_id_cp, slave_id_cp {
bins master0_slave1 = binsof(master_id_cp.master0) && binsof(slave_id_cp.slave1);
bins any_master_slave3 = binsof(slave_id_cp.slave3);
illegal_bins bad_master_slave_config = binsof(master_id_cp.master0) && binsof(slave_id_cp.slave0); // Master 0 should not talk to Slave 0
ignore_bins unused_master_slave_combo = binsof(master_id_cp.master3) && binsof(slave_id_cp.slave2); // Valid, but not critical to cover
}
endgroup
If master 0 accesses slave 0, the simulator reports an error. The combination of master 3 and slave 2 simply does not contribute to the coverage percentage. If a product falls into both an illegal_bins and an ignore_bins selection, it is still treated as illegal and the error is reported.
The diagram below shows how all 16 cross products of ms_cross are classified:
Quick Check
Q: In the example above, how many bins count toward the coverage percentage of ms_cross?
Show Answer
11. Out of 16 products, 1 belongs to master0_slave1, 4 to any_master_slave3, 1 is illegal and 1 is ignored. The remaining 9 become automatic bins, giving 2 user-defined + 9 automatic = 11 bins.
Cross Coverage in Design and Verification
Covergroups are a verification construct and are not synthesizable. In a testbench they are usually placed in a monitor or subscriber that samples transaction fields, which is the most common use of cross coverage.
Design engineers can still use cross coverage to capture design intent, for example crossing FSM state with an input request to confirm every request was seen in every state. A covergroup can be declared inside an RTL module or interface, or attached with bind, as long as it is excluded from synthesis.
`ifndef SYNTHESIS
covergroup fsm_cg @(posedge clk);
state_cp : coverpoint state; // enum-typed FSM state
req_cp : coverpoint req;
state_x_req : cross state_cp, req_cp;
endgroup
fsm_cg fsm_cg_inst = new();
`endif
Leaving a covergroup outside a synthesis guard can cause errors or warnings in synthesis tools. Always wrap design-embedded coverage in `ifndef SYNTHESIS or keep it in a separate bind file.
Design-Side Coverage in Practice
RTL designers often add small covergroups or cover property statements next to complex control logic such as arbiters and FIFO controllers. Crossing arbiter grant with requester count, for example, shows whether the fairness logic was exercised under contention, not just in idle conditions.
Common Beginner Mistakes
Mistake #1: Trying to cross an expression directly
cross only accepts coverpoint names or plain variables. Why? To cross something like mode + cfg, first define a named coverpoint for the expression, then reference that coverpoint in the cross.
Mistake #2: Expecting intersect to select only bins fully inside the range
binsof(cp) intersect {3} selects every bin of cp that contains the value 3, even if the bin also holds other values. A bin defined as {[0:7]} is selected. Why? intersect checks for overlap, not containment. If you need exact selection, refer to the bin directly with binsof(cp.bin_name).
Mistake #3: Assuming invalid combinations are excluded automatically
A cross creates automatic bins for every combination, whether or not it makes sense for the design. Why? The tool has no knowledge of your protocol rules. Unreachable combinations must be marked with illegal_bins or ignore_bins, otherwise coverage can never reach 100%.
Practice Exercise
Challenge: Cross Two 2-Bin Coverpoints
Given a coverpoint cp_dir with 2 bins (read, write) and a coverpoint cp_region with 2 bins (low, high), write a labeled cross between them. How many automatic cross bins does it create? As a bonus, add a user-defined bin that collects all writes using intersect.
Hint
Follow the label: cross cp1, cp2; pattern and multiply the bin counts. For the bonus, write is the bin holding the value 1.
Solution
covergroup cg @ (posedge clk);
cp_dir : coverpoint dir {
bins read = {0};
bins write = {1};
}
cp_region : coverpoint region {
bins low = {0};
bins high = {1};
}
dir_region_cross : cross cp_dir, cp_region;
endgroup
2 bins x 2 bins = 4 automatic cross bins: <read,low>, <read,high>, <write,low> and <write,high>.
Bonus: give the cross a body with bins all_writes = binsof(cp_dir) intersect {1};. It groups <write,low> and <write,high> into one bin, leaving 2 automatic bins for the reads.
Summary
In this tutorial, you learned:
- Cross coverage tracks combinations of bins across two or more coverpoints, not each one in isolation
- A
crossaccepts coverpoints or plain variables from the same covergroup; expressions need their own coverpoint first - The number of automatic cross bins is the product of the participating coverpoints' bin counts
binsofselects bins of one coverpoint, andintersectnarrows that selection to bins overlapping a value or range list in braces- Conditions on different coverpoints are combined with
&&and||; products not in any user-defined bin still get automatic bins illegal_binsflag combinations that must never happen;ignore_binsdrop valid but irrelevant ones; both are excluded from the coverage total- Covergroups are not synthesizable, but designers can embed cross coverage in RTL behind a synthesis guard