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.

systemverilog
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;
endgroup

The product matrix

diagram
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.

systemverilog
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;
endgroup

Coverpoints 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.