A cross names two or more coverpoints and generates one cross bin for every combination of the source bins. It crosses bins, not raw values: a length field bucketed into 3 named bins contributes 3 things, not 256.
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.
// not-run: Verilator 5.048 ignores covergroups (COVERIGN) and Icarus 13 has none; the bins model counts it
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;
endgroupcp_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 needing at_least hits to close. One sample fills exactly one cell: the pair of its current op bin and its current length bin.
Auto bins are named from their source bin pair, and reports show entries like <add,single>. Some tools use a hierarchical form such as x_op_len.add_x_single instead; the exact auto-bin name is tool-dependent, not defined by the standard.
Each sample increments exactly one cell of the auto product. That one-cell rule holds for auto bins only: named selections can overlap so that one sample hits several bins, and a sample ignored everywhere counts toward no goal bin.
A cross takes any number of coverpoints, and the product grows multiplicatively. Three points with 4, 3, and 2 bins yield 4 × 3 × 2 = 24 cross bins.
The listing below is not run in this course's checks, for the same reason.
// not-run: Verilator 5.048 ignores covergroups (COVERIGN) and Icarus 13 has none; the bins model counts it
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;
endgroupCross form | Product control | Verdict |
|---|---|---|
cross cp_a, cp_b | Named coverpoints with shaped bins; the product size is known | Preferred |
cross addr, len | Implicit coverpoints with auto bins over raw widths | Almost always wrong |
Coverpoint with option.weight = 0 | Still samples for the cross but skips the parent percentage | Use for cross-only helpers |
Cross bins respect the source coverpoints' ignore_bins. A value ignored at the coverpoint never appears in any cross cell.
A cross earns its closure cost only when the combination changes DUT behavior. Ask whether the design has logic that looks at both fields together; if not, the cross measures noise.
Combination | Shared logic? | Verdict |
|---|---|---|
Write direction × burst length | Burst buffering exists on the write path only | Good cross |
Opcode × operand-is-zero | Divide-by-zero is a real corner | Good cross |
Packet ID × payload byte 3 | No logic correlates the two fields | Noise |
Two fields constrained to be equal | Off-diagonal cells are unreachable by construction | Noise |
The values never shared a sample. Per-point hits say nothing about combinations, so the combination cell stayed empty while both points read 100%.
The tool builds implicit coverpoints with auto bins over raw values. The product counts unshaped values instead of a handful of shaped bins.
The bench builds the lesson's 4 × 3 cross with the bins model and feeds it a regression in which every div is a long burst and every single beat is an add.
Both coverpoints read 100%: every op and every length bin was seen. The cross reads 7 of 12, 58.33%. <div,single> and four other cells are holes, because those values never shared a sample.
// uses: covmodel
// The 4 x 3 op-by-length cross: both coverpoints close while the cross does not.
module tb_cross_basics;
cm_point cp_op = new("cp_op"), cp_len = new("cp_len");
cm_cross x_op_len;
initial begin
cp_op.add("add", 0, 0); cp_op.add("sub", 1, 1); cp_op.add("mul", 2, 2); cp_op.add("div", 3, 3);
cp_len.add("single", 1, 1); cp_len.add("short_b", 2, 4); cp_len.add("long_b", 5, 16);
x_op_len = new("x_op_len", cp_op, cp_len); // cross cp_op, cp_len;
$display(" cross bins: %0d", x_op_len.n_bins());
// A regression where every div is a long burst and every single beat is an add.
begin
int ops[8] = '{0, 0, 1, 1, 2, 2, 3, 3};
int lens[8] = '{1, 3, 3, 6, 2, 7, 6, 9};
foreach (ops[i]) begin cp_op.sample(ops[i]); cp_len.sample(lens[i]); x_op_len.sample(); end
end
cp_op.report(); cp_len.report(); x_op_len.report();
if (x_op_len.n_bins() != 12 || cp_op.get_coverage() != 100.0 || cp_len.get_coverage() != 100.0 || x_op_len.n_covered() != 7)
$fatal(1, "FAIL: cross math");
$display("PASS: both coverpoints read 100%% and the cross reads 7 of 12; <div,single> was never sampled together");
$finish;
end
endmodule cross bins: 12
coverpoint cp_op: 100.00% (4 of 4 bins)
add 2
sub 2
mul 2
div 2
coverpoint cp_len: 100.00% (3 of 3 bins)
single 1
short_b 3
long_b 4
cross x_op_len: 58.33% (7 of 12 bins)
hole: <add, long_b>
hole: <sub, single>
hole: <mul, single>
hole: <div, single>
hole: <div, short_b>
PASS: both coverpoints read 100% and the cross reads 7 of 12; <div,single> was never sampled togetherCounted 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.
Both coverpoints read 100% while the cross read 7 of 12, with <div,single> among the holes.