Part 5 · Functional Coverage · Intermediate
Cross Basics
Product-of-bins semantics, auto cross bin naming, crossing more than two coverpoints, and when crosses encode real intent.
What a cross actually generates
A cross names two or more coverpoints (or directly, variables — the tool builds implicit coverpoints) and generates one cross bin for every combination of the source bins . It does not cross raw values; it crosses bins. If you bucketed an 8-bit length field into 3 named bins, the cross sees 3 things, not 256.
covergroup op_cg @(posedge clk iff valid);
cp_op : coverpoint opcode {
bins add = {OP_ADD};
bins sub = {OP_SUB};
bins mul = {OP_MUL};
bins div = {OP_DIV};
}
cp_len : coverpoint burst_len {
bins single = {1};
bins short_b = {[2:4]};
bins long_b = {[5:16]};
}
// Full product: 4 op bins x 3 len bins = 12 cross bins
x_op_len : cross cp_op, cp_len;
endgroupThe product matrix
CROSS = PRODUCT OF BINS (cp_op × cp_len)
cp_len.single cp_len.short_b cp_len.long_b
┌───────────────┬────────────────┬───────────────┐
cp_op.add │ <add,single> │ <add,short_b> │ <add,long_b> │
├───────────────┼────────────────┼───────────────┤
cp_op.sub │ <sub,single> │ <sub,short_b> │ <sub,long_b> │
├───────────────┼────────────────┼───────────────┤
cp_op.mul │ <mul,single> │ <mul,short_b> │ <mul,long_b> │
├───────────────┼────────────────┼───────────────┤
cp_op.div │ <div,single> │ <div,short_b> │ <div,long_b> │
└───────────────┴────────────────┴───────────────┘
12 cross bins. Each cell must be hit ≥ at_least times to close.
One sample fills exactly ONE cell: the (current op bin, current len bin) pair.Auto-generated cross bins are named from their source bins: the report shows entries like <add,single> or, in some tools, x_op_len.add_x_single. Each sample increments exactly one cell — the cell selected by which bin each source coverpoint landed in for that sample.
Crossing more than two coverpoints
A cross can take any number of coverpoints, and the product grows multiplicatively with each one. Three points with 4, 3, and 2 bins yield 4 × 3 × 2 = 24 cross bins. This is legal and sometimes correct — but every added dimension must be justified by the verification plan, because closure effort grows with the product.
covergroup bus_cg @(posedge clk iff txn_done);
cp_dir : coverpoint is_write { bins rd = {0}; bins wr = {1}; }
cp_size : coverpoint xfer_size { bins b1 = {1}; bins b2 = {2}; bins b4 = {4}; }
cp_resp : coverpoint resp { bins okay = {0}; bins slverr = {1}; }
// 2 x 3 x 2 = 12 cross bins — every dir/size/resp combination
x_all : cross cp_dir, cp_size, cp_resp;
endgroupCoverpoints vs raw variables in a cross
cross cp_a, cp_b — crosses the named coverpoints; you control the bins, so you control the product size.
cross addr, len — legal shorthand: the tool creates implicit coverpoints with auto-bins, so a 32-bit addr explodes the cross. Almost always wrong.
A coverpoint used only inside a cross can still be weighted to 0 (option.weight = 0) so it does not double-count in the parent percentage.
Cross bins respect the source coverpoints' ignore_bins — a value ignored at the coverpoint never appears in any cross cell.
When a cross encodes real verification intent
A cross is worth its closure cost only when the combination changes DUT behavior. Ask: does the design have logic that looks at both fields together? If the answer is no, the cross measures noise.
Good crosses vs noise crosses
GOOD: write direction × burst length — write datapath has burst-specific buffering; reads do not.
GOOD: opcode × operand-is-zero — divide-by-zero is a real corner; add-by-zero is not special but div-by-zero is.
NOISE: packet ID × payload byte 3 — no logic correlates them; 256×256 bins of nothing.
NOISE: two fields already constrained to be equal — the off-diagonal cells are unreachable by construction.
Interview angle: "Why do we need cross coverage when both coverpoints are at 100%?" is a standard screen. The answer: 100% on each point proves each value occurred in some sample, but not in the same sample. Every add may have been a single beat and every div a long burst — <div,single> can be empty while both points read 100%. Follow-up interviewers like: "how big is the cross of 4 × 3 bins?" — say twelve, then immediately discuss whether all twelve matter.
Key takeaways
A cross multiplies source bins, not source values — bin design upstream controls cross size downstream.
Auto cross bins are named from the source bin pair, one cell per combination, one cell hit per sample.
Crossing N points multiplies all N bin counts — justify each dimension against the plan.
100% on individual coverpoints says nothing about combinations — that is the entire reason crosses exist.
Common pitfalls
Crossing raw wide variables (cross addr, data) — implicit auto-bin coverpoints explode the product.
Adding a cross for every pair of coverpoints 'to be safe' — closure effort explodes with zero added intent.
Assuming the cross samples independently — it samples when the covergroup samples; both fields come from the same event.
Forgetting that source-coverpoint ignore_bins already prune the cross — double-pruning in the cross hides report mismatches.