Ethernet · Module 20
Coverage
A six-dimension cross over this MAC declares 860 160 cells; 54.3% of them are reachable and a loopback topology reaches 13.6% — so 100% means three different things.
Chapter 19.4 §21 found that a coverage model tracking one dimension cannot distinguish a thorough run from a narrow one: a run covering one residue class and six barrel stages and a run covering one and one look identical. So the cross is the model. This chapter is what the cross costs.
| Cells | |
|---|---|
| six dimensions, declared | 860 160 |
| reachable at all | 467 328 — 54.3% |
| reachable from a loopback topology | 116 832 — 13.6% |
Three numbers and one coverage report. A run that closes "100% of the cross" has closed 13.6% of what was declared if it was a loopback, 25.0% of what is reachable, and 100% of what that topology can produce. All three statements are true and only the third is useful, and no coverage tool distinguishes them.
1. Scope, and What a Cross Costs
A coverage model has three parts and only the first is usually written down.
| Part | What it is | Usually |
|---|---|---|
| the bins | the dimensions and their values | written |
| the illegal set | combinations that cannot occur | discovered, one by one, from unclosable bins |
| the reachable set | combinations this testbench can produce | never computed |
Rows two and three are different and both are missing. An illegal bin is one no conformant design can produce — Chapter 7.3's undersize error on a 1 518-octet frame. An unreachable bin is legal and this environment cannot make it — Chapter 20.1 §9's forty-eight alignment offsets, removed by the design's own lane alignment before the stimulus reaches the parser.
Reporting them the same way is why coverage closure stalls. A bin that is illegal should be excluded and never mentioned again; a bin that is unreachable should be a testbench change, and a bin that is merely uncovered should be more simulation. Three causes, three responses, one number.
What this chapter owns: the MAC's actual coverage dimensions and where each comes from; the arithmetic of the cross before and after exclusion; how to detect a dimension that is a function of another; and the closure report that says which of the three causes an open bin has.
What it does not own: the stimulus that fills the bins — Chapter 20.1 — the checks that make filling them meaningful — Chapter 20.2 and Chapter 20.3 — and the question of whether a covered case was also checked, which is the boundary Section 17 draws.
What it does not build: error injection is Chapter 20.5 and the reusable agent is Chapter 20.6.
2. The Dimensions Module 19 Left
Six dimensions, and every one of them is a number some chapter derived rather than a field somebody chose.
| Dimension | Values | From |
|---|---|---|
| size bucket | 7 | Chapter 19.7 §2's RMON histogram |
| partial-word residue | 64 | Chapter 19.4 §4 |
| beat start offset | 64 | Chapter 19.2 §3 |
| VLAN tag count | 3 | Chapter 13.2 |
| error class | 5 | Chapter 7.3 |
| dual-frame beat | 2 | Chapter 19.4 §7 |
The product is 860 160 cells, which is a large number for six dimensions and is the first thing to notice: a cross grows multiplicatively and a verification plan grows linearly.
And the choice of these six is itself an argument. Each was named by a chapter as a case its own verification would miss:
| Dimension | The chapter's finding |
|---|---|
| residue | the classic seven frame sizes reach 3 of 64 |
| start offset | a loopback reaches 16 of 64 |
| dual-frame beat | 52.38% of beats at minimum size, 2.73% at maximum |
| size bucket | a fixed-size stress test reaches 1 of 7 |
| tag count | Chapter 19.2 §4's offsets depend on it |
| error class | Chapter 19.7 §7's runts need two of them |
Six dimensions, six chapters, and a 860 160-cell cross — which is what "the cross is the model" costs when the model is built from what the design actually branches on.
And the six are not equally independent of each other, which Section 6's detector is for and Section 4's arithmetic already shows for one pair.
| Pair | Product | Reachable | Relationship |
|---|---|---|---|
| bucket × residue | 448 | 384 | partly dependent — 85.7% |
| bucket × error | 35 | 23 | partly dependent — 65.7% |
| residue × offset | 4 096 | 4 096 | independent |
| offset × dual | 128 | 128 | independent |
| tags × residue | 192 | 192 | independent |
| bucket × tags | 21 | 21 | independent |
Rows one and two are the only pairs that are not free, and they are 45.7% of the model's exclusions between them. The other thirteen pairs of the fifteen are independent, which is why the cross is worth building at all — a model whose dimensions were mostly dependent would be a list.
Two dimensions were considered and rejected, and the rejections are Section 5's subject.
| Rejected | Why |
|---|---|
| barrel stage selected | a function of the residue — Section 5 |
| interframe gap value | a function of the frame length — Chapter 19.3 §6 |
Both would have multiplied the declared space and added nothing reachable, which is the failure Section 20's rejected class is about and is the most expensive mistake available in a coverage model.
3. RTL 1 — The Coverage Package and the Bin-Space Calculator
// ---------------------------------------------------------------------
// coverage_pkg -- the six dimensions and the three sets. Sections 2
// through 7.
//
// The declared space is the product of the dimensions: 860 160 cells.
// The legal space excludes combinations no conformant design produces.
// The reachable space excludes combinations THIS testbench cannot make.
// A coverage report that shows one number has merged three questions
// with three different answers.
// ---------------------------------------------------------------------
package coverage_pkg;
localparam int N_BUCKET = 7; // Chapter 19.7 Section 2
localparam int N_RESIDUE = 64; // Chapter 19.4 Section 4
localparam int N_OFFSET = 64; // Chapter 19.2 Section 3
localparam int N_TAGS = 3; // Chapter 13.2
localparam int N_ERROR = 5; // Chapter 7.3
localparam int N_DUAL = 2; // Chapter 19.4 Section 7
localparam int DECLARED = N_BUCKET * N_RESIDUE * N_OFFSET *
N_TAGS * N_ERROR * N_DUAL; // 860 160
typedef enum logic [2:0] {
ERR_NONE = 3'd0, ERR_CRC = 3'd1, ERR_ALIGN = 3'd2,
ERR_UNDER = 3'd3, ERR_OVER = 3'd4
} err_class_e;
typedef struct packed {
logic [2:0] bucket;
logic [5:0] residue;
logic [5:0] offset;
logic [1:0] tags;
err_class_e err;
logic dual;
} cov_point_t;
// Why a bin is open. Three causes, three responses, and merging them
// is Section 1's complaint.
typedef enum logic [1:0] {
OPEN_UNCOVERED = 2'd0, // run longer, or reweight
OPEN_UNREACHABLE = 2'd1, // change the testbench
OPEN_ILLEGAL = 2'd2 // exclude it and stop counting it
} open_reason_e;
endpackage// ---------------------------------------------------------------------
// bin_space_calc -- compute the three set sizes at elaboration, so a
// coverage percentage has a denominator somebody chose. Sections 3, 4
// and 7.
//
// Declared: 860 160
// Legal: 467 328 -- 54.3%
// Reachable: 116 832 in a loopback -- 13.6% of declared, 25.0% of legal
//
// The three numbers are the chapter. A report of "100%" means one of
// them and a tool does not say which.
// ---------------------------------------------------------------------
module bin_space_calc
import coverage_pkg::*;
#(
parameter int OFFSETS_REACHABLE = 16 // 16 loopback, 64 with an injector
) (
output logic [31:0] declared,
output logic [31:0] legal,
output logic [31:0] reachable,
output logic [15:0] legal_pct,
output logic [15:0] reachable_pct_of_declared,
output logic [15:0] reachable_pct_of_legal
);
// Chapter 19.7 Section 2's buckets against Chapter 19.4 Section 4's
// residues. A frame of wire length L has bucket(L) and residue
// (L-4) mod 64, so the pair is not free: bucket 0 is exactly 64
// octets and therefore exactly ONE residue -- 60.
function automatic int bucket_of(input int L);
if (L <= 64) bucket_of = 0;
else if (L <= 127) bucket_of = 1;
else if (L <= 255) bucket_of = 2;
else if (L <= 511) bucket_of = 3;
else if (L <= 1023) bucket_of = 4;
else if (L <= 1518) bucket_of = 5;
else bucket_of = 6;
endfunction
// Chapter 7.3: undersize occurs only in bucket 0 and oversize only in
// bucket 6, so twelve of the thirty-five bucket-error pairs cannot
// occur at all.
function automatic bit err_legal(input int b, input int e);
if (e == 3) err_legal = (b == 0);
else if (e == 4) err_legal = (b == 6);
else err_legal = 1'b1;
endfunction
int unsigned pairs, legal_cells, reach_cells, n_err;
bit seen [N_BUCKET][N_RESIDUE];
initial begin
for (int b = 0; b < N_BUCKET; b++)
for (int r = 0; r < N_RESIDUE; r++) seen[b][r] = 1'b0;
for (int L = 64; L <= 9000; L++)
seen[bucket_of(L)][(L - 4) % 64] = 1'b1;
pairs = 0; legal_cells = 0; reach_cells = 0;
for (int b = 0; b < N_BUCKET; b++)
for (int r = 0; r < N_RESIDUE; r++)
if (seen[b][r]) begin
pairs++;
n_err = 0;
for (int e = 0; e < N_ERROR; e++) if (err_legal(b, e)) n_err++;
legal_cells += N_OFFSET * N_TAGS * n_err * N_DUAL;
reach_cells += OFFSETS_REACHABLE * N_TAGS * n_err * N_DUAL;
end
end
assign declared = 32'(DECLARED);
assign legal = 32'(legal_cells);
assign reachable = 32'(reach_cells);
assign legal_pct = 16'((legal * 32'd100) / declared);
assign reachable_pct_of_declared = 16'((reachable * 32'd100) / declared);
assign reachable_pct_of_legal = 16'((reachable * 32'd100) / legal);
endmoduleClassification: an elaboration-time calculator, and the only block in this chapter whose output is three denominators.
What it teaches: that the bucket and the residue are not independent, and the degenerate case is the one everybody uses. Bucket 0 is "exactly 64 octets" — Chapter 19.7 §2's first RMON bucket is a single size — so it crosses with exactly one of the 64 residue classes, residue 60. Sixty-three of the sixty-four cells in that row are illegal, and a coverage report that lists them as open has 63 bins nobody can ever close.
And it teaches that the error dimension is bucket-dependent. Chapter 7.3's undersize is a frame below 64 octets — bucket 0 only — and oversize is above the MTU — bucket 6 only. So twelve of the thirty-five bucket-error pairs are illegal, which is 34.3% of that pair's space and is entirely mechanical from the definitions.
Deliberately simplified: the reachability calculation applies OFFSETS_REACHABLE uniformly, where Chapter 20.1 §17's arithmetic says a fixed-size stream reaches 64 / gcd(period, 64) offsets — so the real reachable set depends on the stimulus's size distribution and not only on the topology. The bucket-residue enumeration runs to 9 000 octets, assuming jumbo support; at a 1 518 MTU bucket 6 disappears entirely. And the initial block computes at elaboration and drives combinational outputs, which works in simulation and is not synthesisable.
And the calculator's three outputs have three different lifetimes, which is why they belong in three different places.
| Changes when | Belongs in | |
|---|---|---|
| declared | a dimension is added | the model's source |
| legal | the protocol or the MTU changes | a checked-in constant, diffed |
| reachable | a configuration boolean changes | the run's log |
Row two is the one to check in. It is derived from definitions, it should be regenerated on every build, and a diff against the previous value is the alarm that a bucket boundary or an error definition moved — which Section 10's filter depends on and which nothing else would notice.
Production implication: the three percentages should appear at the top of every coverage report, and the one that matters depends on the question. legal_pct is a property of the protocol and never changes; reachable_pct_of_declared is a property of this testbench and changes when the topology does; reachable_pct_of_legal is what a closure target should be measured against. A team reporting 100% without saying which has answered a question nobody asked, and the arithmetic to produce all three is forty lines that run once.
4. The Cross, Before and After
Section 3's calculator produces three numbers. This section is where each exclusion comes from, because the reductions are unequal and two of them are much larger than they look.
Start with the product.
| Cells | Factor | |
|---|---|---|
| 7 × 64 × 64 × 3 × 5 × 2 | 860 160 | — |
First exclusion: the bucket and the residue are dependent.
A frame's wire length decides both. Enumerate every legal length from 64 to 9 000 and record which (bucket, residue) pairs occur:
| Pairs | |
|---|---|
| product | 448 |
| reachable | 384 |
| share | 85.7% |
And the 64 missing pairs are not spread evenly.
| Bucket | Residues reachable |
|---|---|
| 0 — exactly 64 octets | 1 |
| 1 — 65 to 127 | 63 |
| 2 to 6 | 64 each |
Row one is the whole reduction. Bucket 0 is a single frame size, so it has one residue; sixty-three of its sixty-four cells are illegal, and bucket 1 is one short because it spans 63 sizes. The dimension that looks coarsest — seven buckets — is the one that constrains the finest.
Second exclusion: the error class is bucket-dependent.
| Pairs | |
|---|---|
| product | 35 |
| legal | 23 |
| share | 65.7% |
Undersize exists only in bucket 0 and oversize only in bucket 6 — Chapter 7.3's definitions — so twelve pairs are illegal. That is a 34.3% reduction on a dimension pair nobody thinks of as constrained.
Together:
| Cells | Of declared | |
|---|---|---|
| declared | 860 160 | 100% |
| legal | 467 328 | 54.3% |
Forty-six per cent of the declared space cannot occur, and both exclusions came from definitions rather than from experiment. Neither needed a single simulation.
Third reduction: reachability, which is the testbench's and not the protocol's.
Chapter 20.1 §9 established that a loopback topology reaches 16 of the 64 beat offsets, because Chapter 19.3 §6 lane-aligns every transmitted frame.
| Topology | Offsets | Reachable cells | Of declared | Of legal |
|---|---|---|---|---|
| loopback | 16 | 116 832 | 13.6% | 25.0% |
| with an injector | 64 | 467 328 | 54.3% | 100% |
Row one is the number a coverage report in a loopback environment is implicitly measured against, and it is 13.6% of what the model declares. A run reporting "100% coverage" has closed 116 832 cells of 860 160 — and the tool's percentage is against whichever denominator the model's exclusions happened to produce.
Which is the arithmetic behind Section 1's three statements, and the reason they need separating.
| Claim | True? |
|---|---|
| "we closed the cross" | yes, for this topology |
| "we covered the design's behaviour" | 25.0% of it |
| "we covered what we declared" | 13.6% |
All three from the same run, and only the first is what anybody said.
5. A Dimension That Is a Function of Another
Section 2 rejected two candidate dimensions. This section is why, and the arithmetic is the sharpest in the chapter.
Chapter 19.4 §5's correction barrel has six stages, selected by the bits of the shortfall — 64 − j, where j is the residue. So "which stages ran" is a natural coverage dimension: six stages, sixty-four combinations.
And it is a function of the residue.
| Cells | |
|---|---|
| declared: residue × stage-set | 64 × 64 = 4 096 |
| reachable | 64 |
| share | 1.6% |
Adding the dimension multiplies the declared space by 64 and the reachable space by 1. Every residue selects exactly one stage-set; there is no pair to cover.
And the effect on the whole cross is the same factor.
| Cells | |
|---|---|
| six dimensions, declared | 860 160 |
| seven dimensions, declared | 55 050 240 |
| seven dimensions, legal | 467 328 — unchanged |
| apparent coverage of the seven-dimension model | 0.85% |
Row four is what happens next. The model now reports 0.85% where it reported 54.3%, the number never moves, and a team spends a quarter trying to close bins that do not exist. The dimension added nothing and divided the reported percentage by 64.
Which is exactly Chapter 19.4 §21's original complaint turned inside out. That chapter observed that a model tracking residues alone cannot distinguish a run covering one residue and six stages from one covering one and one — and the fix is not to add the stage as a dimension, because the stage-set is determined. The fix is that the residue dimension already contains the information, and the two runs differ in which residue they covered: residue 1 selects all six stages and residue 60 selects one.
| Residue | Shortfall | Stages selected |
|---|---|---|
| 1 | 63 | all six |
| 60 | 4 | one |
| 32 | 32 | one |
| 63 | 1 | one |
So Chapter 19.4 §21's two runs are "covered residue 1" and "covered residue 60", and a residue-indexed model distinguishes them perfectly. The information was never missing; the report was reading it wrong.
And the same argument applies to a third candidate that looks more reasonable than either: the shortfall itself.
| Candidate | Relationship to the residue | **Declared × ** |
|---|---|---|
| stage-set | a function of it | 64 |
shortfall 64 − j | a bijection with it | 64 |
| number of stages selected | a function of it | 7 |
Row two is the sharpest case because a bijection feels like new information and is exactly none. The shortfall and the residue are the same quantity written two ways; crossing them gives 64 reachable cells of 4 096, and a model that carries both has doubled its dimension count for a change of variable. Row three at least has a smaller factor — seven values, from zero to six stages — and it is still a function, so it is still 64 reachable cells of 448.
And the general test is mechanical.
Before adding a dimension, ask whether it is a function of a dimension you already have. If it is, the cross grows and the coverage does not.
The second rejected dimension is Chapter 19.3 §6's interframe gap, which takes one of four values determined by the frame length modulo four — so gap × length is 4 × 1 455 declared and 1 455 reachable, and the same argument applies.
6. RTL 2 — The Dependency Detector
// ---------------------------------------------------------------------
// dependency_detector -- find a dimension that is a function of another
// before it is added to the model. Sections 5 and 6.
//
// The test is mechanical: sample both dimensions together and check
// whether each value of A ever appears with more than one value of B.
// If it does not, B is a function of A and adding it multiplies the
// declared space by |B| and the reachable space by one.
//
// Section 5: residue and barrel stage-set fail this test -- 64 of 4 096
// pairs -- and so do frame length and interframe gap.
// ---------------------------------------------------------------------
module dependency_detector
import coverage_pkg::*;
#(
parameter int N_A = 64,
parameter int N_B = 64
) (
input logic clk,
input logic rst_n,
input logic sample,
input logic [7:0] a_value,
input logic [7:0] b_value,
output logic [15:0] distinct_pairs,
output logic [15:0] distinct_a,
output logic b_is_function_of_a,
output logic [15:0] pair_pct_x10,
output logic [31:0] c_samples
);
bit seen [N_A][N_B];
bit seen_a [N_A];
int unsigned pairs, a_count, max_b_per_a, b_for_this_a;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
c_samples <= '0;
for (int i = 0; i < N_A; i++) begin
seen_a[i] <= 1'b0;
for (int j = 0; j < N_B; j++) seen[i][j] <= 1'b0;
end
end else if (sample) begin
c_samples <= c_samples + 1;
seen[a_value][b_value] <= 1'b1;
seen_a[a_value] <= 1'b1;
end
end
always_comb begin
pairs = 0; a_count = 0; max_b_per_a = 0;
for (int i = 0; i < N_A; i++) begin
if (seen_a[i]) a_count++;
b_for_this_a = 0;
for (int j = 0; j < N_B; j++) if (seen[i][j]) begin pairs++; b_for_this_a++; end
if (b_for_this_a > max_b_per_a) max_b_per_a = b_for_this_a;
end
distinct_pairs = 16'(pairs);
distinct_a = 16'(a_count);
// THE test. If no value of A has ever been seen with two values of
// B, B is a function of A -- as far as this run can tell. It is a
// necessary condition, not a sufficient one, and Section 7 says why
// that is the honest framing.
b_is_function_of_a = (max_b_per_a <= 1) && (c_samples > 32'd10000);
pair_pct_x10 = 16'((32'(pairs) * 32'd1000) / (32'(N_A) * 32'(N_B)));
end
endmoduleClassification: a measurement of a coverage model rather than of a design, and the only block in this chapter that could have prevented Section 5's mistake.
What it teaches: that the dependency is observable long before it is proved. Sampling residue and stage-set together for ten thousand frames and never once seeing a residue with two different stage-sets is strong evidence that the second is determined, and it costs a two-dimensional bit array. Nobody runs the test because the dimensions are added by different people at different times, and the array is cheaper than the quarter Section 5 describes.
And it teaches that pair_pct_x10 is the number to look at rather than the boolean. A pair count of 64 out of 4 096 — 1.6% — says the dimensions are nearly dependent even if a rare case breaks strict functionality. A pair count of 60% says they are genuinely independent and the cross is worth having, and the two readings are a hundred times apart.
Deliberately simplified: the seen array is N_A × N_B bits and at 64 × 64 that is 4 096 bits, which is fine and does not scale to a dimension with thousands of values. b_is_function_of_a requires ten thousand samples with no argument for the threshold. And the test is one-directional — it checks whether B is a function of A and not the reverse — so a genuine bijection reports as a dependency in both directions and is one, which is the right answer for a coverage model.
Production implication: run this on every pair of dimensions before the model is written, which for six dimensions is fifteen pairs and one afternoon. The dependencies that turn up are not always the obvious ones — residue and stage-set is obvious in hindsight, and bucket and residue is not, and Section 4 found that one reduces the space by 14.3%. A model built without the fifteen measurements has an unknown number of cells that cannot close and no way to tell which.
7. Illegal, Unreachable, Uncovered
Section 1 named three causes for an open bin. This section is how to tell them apart, because the tool reports all three identically and the responses differ by weeks.
| Cause | Test | Response |
|---|---|---|
| illegal | can any conformant design produce it? | exclude and never count it again |
| unreachable | can THIS testbench produce it? | change the testbench |
| uncovered | neither of the above | run longer or reweight |
The first test is a protocol question and it is answerable at elaboration. Chapter 7.3 says an undersize frame is below 64 octets, so (bucket 6, undersize) is illegal — no simulation, no waiting, no evidence needed. Section 3's err_legal function is the whole test, and it excluded twelve of thirty-five pairs.
The second test is a testbench question and it is the one that is never asked. Chapter 20.1 §9's forty-eight missing offsets are legal — a partner that does not lane-align produces them every day — and this environment cannot. The bin is not illegal and it will never close, and the only way to know is to model the topology.
And the third is what everybody assumes all of them are.
Put numbers on the three for this MAC's cross.
| Cells | Share of declared | |
|---|---|---|
| illegal | 392 832 | 45.7% |
| unreachable in a loopback | 350 496 | 40.7% |
| possible to cover | 116 832 | 13.6% |
Row two is larger than row three, which is the finding: more of this model's declared space is blocked by the testbench's topology than is available to close. A team working through an open-bin list in order spends more time on unreachable bins than on uncovered ones, and nothing in the list says which is which.
And there is a fourth diagnostic that costs nothing and identifies the cause without any modelling: how many bins share a dimension value.
| Observation | Likely cause |
|---|---|
| all open bins share one bucket | that frame size is not being generated |
| all open bins share one error class | a knob is off — Chapter 20.1 §4's allow_illegal |
| all open bins have an odd offset | the topology — Section 8 |
| open bins are scattered | genuinely uncovered; run longer |
Row three is the pattern a loopback produces and it is unmistakable once looked for: every open bin has an offset that is not a multiple of four. Nothing in a coverage tool groups bins that way, and a two-line query over the open list does — which is the cheapest thing in this chapter and the one most likely to be skipped.
The fix is to compute the three sets rather than to discover them.
| Cost | |
|---|---|
| the illegal set | two functions, at elaboration — Section 3 |
| the reachable set | a topology model — Section 8 |
| the difference | the honest denominator |
Both are arithmetic and neither needs a simulation. What they buy is that every open bin arrives with its reason attached, which turns a closure list into three lists with three owners — and only one of them is the verification engineer's.
8. RTL 3 — The Reachability Model
// ---------------------------------------------------------------------
// reachability_model -- compute what THIS testbench can produce, so an
// unreachable bin is identified before somebody tries to close it.
// Sections 7 and 8.
//
// Three topology facts decide it:
// the injection point -- loopback reaches 16 offsets, a wire driver 64
// the illegal-size knob -- Chapter 20.1's allow_illegal gates 43 sizes
// the queue count -- one queue removes the reorder dimension
//
// None is a property of the design and all three are configuration.
// ---------------------------------------------------------------------
module reachability_model
import coverage_pkg::*;
(
input logic clk,
input logic rst_n,
// Topology configuration.
input logic injector_enabled,
input logic allow_illegal,
input logic [3:0] num_queues,
input logic jumbo_enabled,
output logic [5:0] offsets_reachable,
output logic [2:0] buckets_reachable,
output logic [2:0] errors_reachable,
output logic [31:0] reachable_cells,
output logic [15:0] reachable_pct,
output logic [2:0] limiting_factor
);
// Chapter 20.1 Section 9: the transmit path lane-aligns, so a loopback
// produces only offsets that are multiples of four.
assign offsets_reachable = injector_enabled ? 6'd63 : 6'd15; // count-1
// Chapter 19.7 Section 2's seven buckets. Without jumbo the seventh
// cannot occur at all.
assign buckets_reachable = jumbo_enabled ? 3'd7 : 3'd6;
// Chapter 7.3's five classes. Undersize and its fragment form need
// Chapter 20.1's allow_illegal, which is a boolean no weight opens.
assign errors_reachable = allow_illegal ? 3'd5 : 3'd3;
assign reachable_cells =
32'(buckets_reachable) * 32'd64 *
(32'(offsets_reachable) + 32'd1) * 32'(N_TAGS) *
32'(errors_reachable) * 32'(N_DUAL);
assign reachable_pct = 16'((reachable_cells * 32'd100) / 32'(DECLARED));
// Which configuration is costing the most. A closure plan should
// address this one first and it is usually not the one being tuned.
always_comb begin
if (!injector_enabled) limiting_factor = 3'd1; // 4.00x
else if (!jumbo_enabled) limiting_factor = 3'd2; // 1.27x
else if (!allow_illegal) limiting_factor = 3'd3; // 1.06x
else if (num_queues == 4'd1) limiting_factor = 3'd4; // reorder dimension
else limiting_factor = 3'd0;
end
endmoduleClassification: a model of the testbench rather than of the design, and the block that turns an unclosable bin into a configuration line.
What it teaches: that limiting_factor ranks the configuration decisions by what they cost in coverage, and the ranking is not intuitive. The injector is worth 4.00× — sixteen offsets against sixty-four. Jumbo support is worth 1.27×, because it adds bucket 6 and sixty-four bucket-residue pairs. allow_illegal is worth 1.06× — it adds two error classes, each legal in exactly one bucket. A team that enables illegal sizes because it feels thorough has bought 6% where one line of injector configuration buys 300%.
And it teaches that every one of the three is a boolean in a configuration file. None is a weight, none responds to more simulation, and all three are set once when the environment is built and never revisited — Chapter 20.1 §16's verdict about exactly this. The reachability model's value is that it prices them, so the conversation is about a 4× rather than about a coverage percentage.
Deliberately simplified: offsets_reachable is 16 or 64 with nothing between, where Chapter 20.1 §17 showed that a fixed-size stream reaches 64 / gcd(period, 64) — as few as one. The queue count affects limiting_factor and not reachable_cells, because the six-dimension cross of Section 2 has no queue dimension; adding one is a seventh dimension and Section 5's test should be run on it first. And the bucket-residue dependency of Section 4 is not applied here, so reachable_cells overstates by the same 14.3%.
And the model has a use beyond reporting: it prices a testbench change before anybody makes it.
| Change | Cost to make | Reachable cells gained |
|---|---|---|
| enable the injector | a two-bit counter | +350 496 |
| enable jumbo | a configuration register | +98 304 |
| enable illegal sizes | a boolean | +24 960 |
| add a second queue | a design configuration | 0 in this cross |
Row four is the one worth noticing. Multi-queue changes Chapter 20.3's matching and Chapter 19.6's reordering and adds nothing to this six-dimension cross, because the cross has no queue dimension. A change that matters enormously for the scoreboard is invisible to the coverage model, which is the clearest single example of why four components need four measurements.
Production implication: the output to act on is limiting_factor, and it should be printed whether or not anybody asked. A coverage report that says "83% closed" and a reachability model that says "the injector is disabled, worth 4×" are two halves of one sentence, and only one of them is usually produced. The second is four comparisons and a case statement, and it converts a plateau into a to-do.
9. RTL 4 — The Cross Sampler
// ---------------------------------------------------------------------
// cross_sampler -- record the six-dimension point for each frame, and
// nothing else. Sections 2, 9 and 17.
//
// The sampler's only interesting decision is WHERE it samples. Chapter
// 20.1 Section 13's first prohibition: a coverage model that samples the
// generator's item records what was asked for; the design saw what the
// driver delivered, and Chapter 19.3 Section 2's padding makes those
// differ on every short frame.
//
// So every field here comes from the wire or from the design's own
// boundary, and none from the stimulus object.
// ---------------------------------------------------------------------
module cross_sampler
import coverage_pkg::*;
(
input logic clk,
input logic rst_n,
input logic frame_end,
input logic [15:0] wire_length, // observed, not requested
input logic [5:0] start_offset, // from Chapter 20.1 Section 8
input logic [1:0] tags_observed,
input err_class_e err_observed,
input logic dual_beat,
output cov_point_t point,
output logic point_valid,
output logic [31:0] c_points,
output logic [31:0] c_distinct,
output logic sampled_from_stimulus
);
bit seen [N_BUCKET][N_RESIDUE];
function automatic logic [2:0] bucket_of(input logic [15:0] L);
if (L <= 16'd64) bucket_of = 3'd0;
else if (L <= 16'd127) bucket_of = 3'd1;
else if (L <= 16'd255) bucket_of = 3'd2;
else if (L <= 16'd511) bucket_of = 3'd3;
else if (L <= 16'd1023) bucket_of = 3'd4;
else if (L <= 16'd1518) bucket_of = 3'd5;
else bucket_of = 3'd6;
endfunction
assign point.bucket = bucket_of(wire_length);
assign point.residue = 6'((wire_length - 16'd4) % 16'd64);
assign point.offset = start_offset;
assign point.tags = tags_observed;
assign point.err = err_observed;
assign point.dual = dual_beat;
assign point_valid = frame_end;
// A grep target. Chapter 20.1 Section 13: a model that samples the
// item records the request; this one records the delivery, and the
// two differ by Chapter 19.3 Section 2's pad on every short frame.
assign sampled_from_stimulus = 1'b0;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
c_points <= '0; c_distinct <= '0;
for (int b = 0; b < N_BUCKET; b++)
for (int r = 0; r < N_RESIDUE; r++) seen[b][r] <= 1'b0;
end else if (frame_end) begin
c_points <= c_points + 1;
if (!seen[point.bucket][point.residue]) begin
seen[point.bucket][point.residue] <= 1'b1;
c_distinct <= c_distinct + 1;
end
end
end
endmoduleClassification: a sampler, and the block whose correctness is entirely a question of which wire it is attached to.
What it teaches: that sampling the stimulus object and sampling the wire give different coverage on every short frame. A generator asked for a 20-octet frame; Chapter 19.3 §2 padded it to 64. A model sampling the item records bucket 0 with a residue of 16; the design saw bucket 0 with a residue of 60. One of those is what happened and it is not the one that is easier to sample.
And it teaches that c_distinct over the bucket-residue pair is the cheapest sanity check on the whole model. Section 4 derived 384 reachable pairs; a run reporting more has a bug in bucket_of or in the residue arithmetic, and a run reporting far fewer has a size distribution that is not what anybody thinks. One 448-bit array and a counter.
Deliberately simplified: sampled_from_stimulus is tied low and is a grep target rather than a check — the same device Chapter 19.4 §14 used for reference_is_independent, and for the same reason: no signal can carry the fact. The residue arithmetic assumes the check value is present on the wire, which it is on transmit and is not in host memory. And start_offset arrives as an input from Chapter 20.1 §8's tracker, so the sampler trusts somebody else's accumulator.
Production implication: c_distinct against 384 belongs in the regression summary beside the coverage percentage, because the two answer different questions. The percentage says how much of the model was hit; c_distinct says whether the model's own arithmetic agrees with Section 4's. A run at 83% coverage with c_distinct reading 200 has a stimulus distribution that never produces five of the seven buckets, and the coverage percentage alone cannot say that.
10. RTL 5 — The Illegal-Bin Filter
// ---------------------------------------------------------------------
// illegal_bin_filter -- exclude what no conformant design can produce,
// at elaboration, from definitions. Sections 3, 7 and 10.
//
// Two exclusions, both mechanical:
// bucket x residue -- a frame's length decides both; 64 of 448 pairs
// cannot occur, and 63 of them are in bucket 0
// bucket x error -- undersize is bucket 0 only, oversize bucket 6
// only; 12 of 35 pairs cannot occur
//
// Together they remove 45.7% of the declared space, and neither needed
// a simulation to find.
// ---------------------------------------------------------------------
module illegal_bin_filter
import coverage_pkg::*;
(
input cov_point_t point,
input logic point_valid,
output logic is_legal,
output logic illegal_bucket_residue,
output logic illegal_bucket_error,
output logic [31:0] c_illegal_seen
);
// The lengths each bucket spans. Bucket 0 is a SINGLE size -- Chapter
// 19.7 Section 2's first RMON bucket is "64 octets" -- which is why it
// has exactly one residue.
function automatic int bucket_lo(input int b);
case (b)
0: bucket_lo = 64; 1: bucket_lo = 65; 2: bucket_lo = 128;
3: bucket_lo = 256; 4: bucket_lo = 512; 5: bucket_lo = 1024;
default: bucket_lo = 1519;
endcase
endfunction
function automatic int bucket_hi(input int b);
case (b)
0: bucket_hi = 64; 1: bucket_hi = 127; 2: bucket_hi = 255;
3: bucket_hi = 511; 4: bucket_hi = 1023; 5: bucket_hi = 1518;
default: bucket_hi = 9000;
endcase
endfunction
// Is there any length in this bucket with this residue?
function automatic bit pair_possible(input int b, input int r);
bit found;
begin
found = 1'b0;
for (int L = bucket_lo(b); L <= bucket_hi(b); L++)
if (((L - 4) % 64) == r) found = 1'b1;
pair_possible = found;
end
endfunction
assign illegal_bucket_residue = point_valid &&
!pair_possible(int'(point.bucket),
int'(point.residue));
// Chapter 7.3's definitions, as a two-line function.
assign illegal_bucket_error = point_valid &&
(((point.err == ERR_UNDER) && (point.bucket != 3'd0)) ||
((point.err == ERR_OVER) && (point.bucket != 3'd6)));
assign is_legal = point_valid && !illegal_bucket_residue &&
!illegal_bucket_error;
// An illegal point OBSERVED is not a coverage event -- it is a design
// or a sampler failure, and counting it as covered would close a bin
// that should never close.
always_comb c_illegal_seen = 32'(illegal_bucket_residue) +
32'(illegal_bucket_error);
endmoduleClassification: a filter whose entire content is two definitions, and the block that removes 45.7% of a coverage model.
What it teaches: that an illegal bin observed is a bug, not a coverage event. If the sampler reports bucket 3 with residue 17 — which no length between 256 and 511 produces — something is wrong in the sampler, the design or the wire, and recording it as covered would close a bin that can never legitimately close. The filter's output is therefore two things: an exclusion for the model and an alarm for the run.
And it teaches that pair_possible is a loop over lengths and that is the honest implementation. The closed form exists — a bucket spanning 64 or more consecutive lengths covers every residue — and writing the loop makes the dependency on Chapter 19.7 §2's bucket boundaries explicit. Change a boundary and the exclusion set changes; a hand-derived table would not notice.
Deliberately simplified: the loop runs at elaboration for every (bucket, residue) query, which is up to 7 482 iterations per call and should be a precomputed 448-bit constant. c_illegal_seen is combinational and does not accumulate, so it reports the current point rather than a count. And the filter handles two dimension pairs of the fifteen — Section 6's detector should be run on the other thirteen, and this chapter has not.
Production implication: the exclusion set is 448 bits and it should be generated, checked in, and diffed. A change to Chapter 19.7 §2's bucket boundaries or to Chapter 7.3's error definitions changes it, and a coverage model whose exclusions were derived once by hand silently stops matching the design. The generation is forty lines and the diff is the alarm.
11. What a Coverage Model Cannot Say
Four things, and the fourth is the one that makes the other three matter.
| Why not | |
|---|---|
| whether the covered case was checked | a bin fills whether or not an assertion or a scoreboard looked |
| whether an open bin is worth closing | that is an engineering judgement about risk |
| whether the dimensions are the right ones | Section 2's six came from six chapters' findings |
| whether anything was found | coverage is about the stimulus, not the outcome |
Row one is the gap between this chapter and the two before it. A cross bin fills when a frame with those properties occurs; Chapter 20.2 §15's fired_pct_x10 says whether a property was evaluated, and Chapter 20.3 §14's octet_coverage_pct says how much of the frame was compared. Three measurements of three different things, and a coverage report shows one.
Put them together and the gap is visible.
| What it measures | |
|---|---|
| cross coverage | the case occurred |
fired_pct_x10 | a property evaluated |
octet_coverage_pct | the frame was compared |
A run can score 100% on the first and 40% on the second, which means 60% of the environment's properties never evaluated on the cases that occurred. That combination is not visible from any one report and is the honest description of most regressions.
Which suggests a fourth measurement that nobody builds: a cross between the coverage point and whether it was checked.
| Cells | |
|---|---|
| the six-dimension cross | 860 160 |
| crossed with "an assertion fired" | 1 720 320 |
| crossed with "the scoreboard compared it" | 3 440 640 |
Those numbers are why nobody builds it, and the useful version is not a cross at all: a single per-bin flag saying whether any check was active when the bin filled. That is one bit per bin — 860 160 bits, 105 KiB — and it converts "we covered it" into "we covered it and looked."
And there is a cheaper approximation of the join that is worth having even without the per-bin flag.
| Approximation | Cost | What it says |
|---|---|---|
| per-bin checked flag | 105 KiB | exactly which bins were unchecked |
| per-bucket checked share | 7 counters | which frame sizes were unchecked |
| a single checked share | 1 counter | how much of the run was checked at all |
Row three is one counter and it answers the question that matters most often. A run at 100% coverage with 60% of its filled bins unchecked is a run that produced every case and looked at three fifths of them — and a single ratio says so. Row two localises it to a frame size, which is usually enough to find the unbound assertion. Row one is exact and nobody needs exact.
Row four of the first table is the one to end on. A coverage model measures what the stimulus produced and says nothing about whether any of it was wrong. Chapter 20.1 generates, Chapter 20.2 and Chapter 20.3 check, and this chapter counts — and a plan that reports only the third has measured its own thoroughness and not the design's correctness.
12. RTL 6 — The Closure Reporter
// ---------------------------------------------------------------------
// closure_reporter -- every open bin arrives with its reason attached.
// Sections 7, 11 and 12.
//
// Three causes, three owners:
// illegal -> nobody; exclude it
// unreachable -> whoever configures the testbench
// uncovered -> whoever tunes the stimulus
//
// A single open-bin list merges them, and Section 7's arithmetic says
// the unreachable set is larger than the coverable one -- so a team
// working the list in order spends most of its time on bins that
// cannot close.
// ---------------------------------------------------------------------
module closure_reporter
import coverage_pkg::*;
(
input logic clk,
input logic rst_n,
input logic point_valid,
input cov_point_t point,
input logic is_legal,
input logic is_reachable,
input logic [31:0] declared,
input logic [31:0] legal,
input logic [31:0] reachable,
output logic [31:0] covered,
output logic [15:0] pct_of_declared,
output logic [15:0] pct_of_legal,
output logic [15:0] pct_of_reachable,
output logic [31:0] open_illegal,
output logic [31:0] open_unreachable,
output logic [31:0] open_uncovered,
output logic [2:0] dominant_open_reason
);
// A per-bin covered flag. At 860 160 cells this is 105 KiB of
// testbench memory -- Section 11 -- which is nothing in a simulator
// and is the reason a coverage database is a database.
bit hit [DECLARED];
int unsigned idx;
always_comb idx = ((((int'(point.bucket) * N_RESIDUE +
int'(point.residue)) * N_OFFSET +
int'(point.offset)) * N_TAGS +
int'(point.tags)) * N_ERROR +
int'(point.err)) * N_DUAL + int'(point.dual);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
covered <= '0;
for (int i = 0; i < DECLARED; i++) hit[i] <= 1'b0;
end else if (point_valid && is_legal && !hit[idx]) begin
hit[idx] <= 1'b1;
covered <= covered + 1;
end
end
assign pct_of_declared = 16'((covered * 32'd100) / declared);
assign pct_of_legal = 16'((covered * 32'd100) / legal);
assign pct_of_reachable = 16'((covered * 32'd100) / reachable);
assign open_illegal = declared - legal;
assign open_unreachable = legal - reachable;
assign open_uncovered = reachable - covered;
// Which list is longest. Section 7: in a loopback the unreachable set
// is larger than the coverable one, so this reports 2 on a healthy
// run and a team that expected 3 is about to spend a quarter wrongly.
always_comb begin
if (open_illegal >= open_unreachable && open_illegal >= open_uncovered)
dominant_open_reason = 3'd1;
else if (open_unreachable >= open_uncovered)
dominant_open_reason = 3'd2;
else
dominant_open_reason = 3'd3;
end
endmoduleClassification: a reporter, and the block whose output is three numbers where a tool gives one.
What it teaches: that the three percentages are all correct and they differ by a factor of four. A run that has covered every reachable cell in a loopback reports 100% of reachable, 25.0% of legal and 13.6% of declared, and every one of those is a defensible thing to put in a status report. The dishonesty is not in any of them; it is in showing one without the others, because a reader assumes the denominator they expect.
And it teaches that dominant_open_reason usually reads 1 or 2 and almost never 3. The illegal set is 392 832 cells — 45.7% — and the unreachable set is 350 496 — 40.7%; the coverable remainder is 116 832. So on any run before closure, the longest list is one nobody can act on and the second-longest belongs to whoever configured the testbench. A closure meeting that opens with the open-bin list is looking at the wrong two thirds.
Deliberately simplified: hit is a bit array of DECLARED entries iterated in a reset loop, which is 860 160 iterations and is a testbench construct; a real environment uses the simulator's own coverage database. The index arithmetic assumes the dimension sizes are compile-time constants, which they are. And is_reachable arrives as an input from Section 8's model rather than being recomputed here.
Production implication: print all three percentages and dominant_open_reason, in that order, on every regression. The percentages tell a reader what was measured; the reason tells them who to talk to. A report that says "83% of reachable, 21% of legal, 11% of declared, dominant reason: unreachable" is four numbers and a complete instruction, and the alternative — "83%" — is the same run described in a way that sends the next week to the wrong place.
13. RTL 7 — Coverage Telemetry
// ---------------------------------------------------------------------
// coverage_telemetry -- the model's own health, as distinct from the
// design's. Section 13.
//
// Three things a coverage percentage cannot say: whether the model's
// arithmetic agrees with Section 4's, whether the dimensions are
// independent, and whether the bins that filled were also checked.
// ---------------------------------------------------------------------
module coverage_telemetry
import coverage_pkg::*;
(
input logic clk,
input logic rst_n,
input logic [31:0] c_points,
input logic [31:0] covered,
input logic [15:0] distinct_pairs, // bucket x residue
input logic [31:0] c_illegal_observed,
input logic [15:0] pct_of_reachable,
input logic [15:0] checked_bins, // bins that also had a check
input logic b_is_function_of_a,
output logic [15:0] pairs_vs_expected_pct,
output logic [15:0] checked_share_pct,
output logic [31:0] points_per_bin,
output logic model_arithmetic_wrong,
output logic dependent_dimension,
output logic covered_but_unchecked
);
// Section 4 derived 384 reachable bucket-residue pairs. A run that
// reports more has a sampler bug; one that reports far fewer has a
// size distribution nobody expected.
localparam int EXPECTED_PAIRS = 384;
assign pairs_vs_expected_pct =
16'((32'(distinct_pairs) * 32'd100) / 32'(EXPECTED_PAIRS));
assign model_arithmetic_wrong = (distinct_pairs > 16'(EXPECTED_PAIRS)) ||
(c_illegal_observed != '0);
// Section 6's test, promoted to a run-time report.
assign dependent_dimension = b_is_function_of_a;
// Section 11's gap. A bin that filled while no assertion was bound
// and no scoreboard compared it has recorded that the case occurred
// and nothing else.
assign checked_share_pct = (covered == 0) ? 16'd0
: 16'((32'(checked_bins) * 32'd100) / covered);
assign covered_but_unchecked = (covered > 32'd1000) &&
(checked_share_pct < 16'd80);
// How hard each bin was hit. A model at 90% with one point per bin
// is a model that got lucky; one with a thousand points per bin has
// been exercised.
assign points_per_bin = (covered == 0) ? '0 : (c_points / covered);
endmoduleClassification: an observability block whose subject is the coverage model, and the third such block in three chapters.
What it teaches: that points_per_bin distinguishes a model that closed from a model that got lucky. A cross at 90% with one point per bin means almost every bin was hit exactly once — a distribution with no margin, where one seed change reopens a hundred bins. A thousand points per bin means the closure is stable. The percentage is identical in both cases and the two runs are not comparable.
And it teaches that covered_but_unchecked is the measurement Section 11 argued for and nobody makes. A bin fills when a case occurs; whether any assertion was bound or any scoreboard compared it is a separate fact, and crossing the two is one bit per bin. A run at 100% coverage with a checked share of 60% has produced every case and looked at three fifths of them — which is a sentence no coverage tool has ever printed.
Deliberately simplified: checked_bins arrives as an input and computing it requires the coverage sampler and the check infrastructure to share a bin index, which is an integration this chapter describes and does not build. EXPECTED_PAIRS is a literal from Section 4 rather than being taken from Section 3's calculator. And points_per_bin is a mean, which hides the distribution that actually matters — a model with a thousand points per bin on average and one on the tail is the unstable case.
And there is one more comparison worth making, between this chapter's three denominators and the other three chapters' self-weakening numbers.
| Chapter | The number that weakens its own pass | Typical value |
|---|---|---|
| Chapter 20.1 §15 | lists_complete, five bits | 5'b00011 on a default run |
| Chapter 20.2 §15 | fired_pct_x10 | below 70% on a directed test |
| Chapter 20.3 §14 | octet_coverage_pct | 23% on control traffic |
| this chapter | pct_of_legal | 19.7% on a default environment |
Every one of the four is a number a component produces about itself, and every one makes its own result look worse. That is the pattern Module 20 has converged on without any of the four chapters planning it — and the reason is the same in all four cases: a pass without its denominator is a claim the component did not earn.
Production implication: pairs_vs_expected_pct is the model's self-check and it should gate the report. A run whose bucket-residue pair count exceeds 384 has a sampler that is computing a residue or a bucket wrongly, and every percentage downstream of it is meaningless. The check is one comparison against a number Section 4 derived at elaboration, and it is the only thing in this chapter that verifies the coverage model itself rather than the design.
14. What a Coverage Model Must Never Do
Six prohibitions, and the first three are about the denominator.
| # | Must never | Because | Symptom |
|---|---|---|---|
| 1 | report one percentage | Section 4 — three denominators, a factor of four apart | a reader assumes the wrong one |
| 2 | merge illegal with unreachable | Section 7 — different owners | weeks on bins nobody can close |
| 3 | count an illegal point as covered | Section 10 | a bin that should never close, closes |
| 4 | add a dependent dimension | Section 5 — 64× declared, 1× reachable | a percentage that never moves |
| 5 | sample the stimulus object | Chapter 20.1 §13 | the model records the request, not the delivery |
| 6 | treat a filled bin as a checked case | Section 11 | "covered" read as "verified" |
Row four is the one that costs a quarter and it is committed with good intentions. Somebody notices Chapter 19.4 §21's complaint — a residue-only model cannot distinguish six barrel stages from one — and adds the stage as a dimension. The declared space goes from 860 160 to 55 050 240, the reachable space does not move, and the reported percentage falls from 54.3% to 0.85% and stays there. The information was already in the residue dimension; the report was reading it wrong.
Row six is the prohibition that this whole module has been circling. A bin fills when a case occurs; Chapter 20.2 §15 says whether a property evaluated and Chapter 20.3 §14 says how much of the frame was compared. Three measurements of three things, and "100% coverage" is routinely read as all three.
And the two that look like process advice and are not:
| Why it is a prohibition | |
|---|---|
| row one | a single percentage is not wrong; it is ambiguous by a factor of four |
| row five | the model and the design disagree on every short frame |
Row five's arithmetic is worth repeating because it is small and specific. A 20-octet request becomes a 64-octet frame — Chapter 19.3 §2 — so a model sampling the item records residue 16 and the design saw residue 60. Both are in bucket 0; only one of them happened.
15. RTL 8 — The Coverage Conformance Monitor
// ---------------------------------------------------------------------
// coverage_conformance_monitor -- verdicts about the coverage model.
// Section 15, and the seventeenth in Modules 18 to 20.
//
// Six verdicts. Two are about the model's arithmetic, two about the
// environment, one about the closure, and one is the admission that a
// filled bin is not a checked case.
// ---------------------------------------------------------------------
module coverage_conformance_monitor
import coverage_pkg::*;
#(
parameter int MIN_REACHABLE_PCT = 90,
parameter int MIN_POINTS_PER_BIN = 10,
parameter int MIN_CHECKED_PCT = 80
) (
input logic clk,
input logic rst_n,
input logic model_arithmetic_wrong,
input logic dependent_dimension,
input logic sampled_from_stimulus,
input logic [15:0] pct_of_reachable,
input logic [31:0] points_per_bin,
input logic [15:0] checked_share_pct,
input logic [2:0] limiting_factor,
input logic [2:0] dominant_open_reason,
input logic denominators_reported, // a human sets this
output logic model_wrong,
output logic model_inflated,
output logic sampling_wrong,
output logic topology_limiting,
output logic closure_fragile,
output logic covered_not_checked,
output logic denominator_ambiguous,
output logic none_of_the_above
);
always_comb begin
// About the model's arithmetic. Section 13: more than 384
// bucket-residue pairs, or an illegal point observed.
model_wrong = model_arithmetic_wrong;
// Section 5: a dimension that is a function of another multiplies
// the declared space and not the reachable one.
model_inflated = dependent_dimension;
// Chapter 20.1 Section 13's first prohibition, one level up.
sampling_wrong = sampled_from_stimulus;
// Section 8: the injector is worth 4x and is usually off.
topology_limiting = (limiting_factor != 3'd0);
// Section 13: 90% with one point per bin is one seed away from 80%.
closure_fragile = (pct_of_reachable >= 16'(MIN_REACHABLE_PCT)) &&
(points_per_bin < 32'(MIN_POINTS_PER_BIN));
// Section 11: the case occurred; nothing says anybody looked.
covered_not_checked = (checked_share_pct < 16'(MIN_CHECKED_PCT));
// Section 14's first prohibition, as a verdict a person clears.
denominator_ambiguous = !denominators_reported;
none_of_the_above = !model_wrong && !model_inflated && !sampling_wrong &&
!topology_limiting && !closure_fragile &&
!covered_not_checked && !denominator_ambiguous;
end
endmoduleClassification: a verdict generator, the seventeenth in three modules, and the last in this track's Ethernet datapath sequence.
What it teaches: that closure_fragile catches the run that is about to regress. Ninety per cent of reachable with one point per bin means most bins were hit exactly once; a seed change, a weight change or a new frame size reopens a hundred of them, and the coverage graph that looked like convergence was a coincidence. The percentage is identical to a stable run's and points_per_bin is the only thing that separates them.
And it teaches that topology_limiting fires on almost every environment. Section 8's limiting_factor is non-zero whenever the injector is off, illegal sizes are disabled, jumbo is off or there is one queue — which is the default state of every environment when it is first built. The verdict is not a fault; it is a price list, and the injector's 4× is the entry that should be argued about first.
Deliberately simplified: MIN_CHECKED_PCT presumes checked_share_pct is computed at all, which Section 13 said requires the coverage sampler and the check infrastructure to share a bin index — an integration this chapter describes and does not build. closure_fragile uses a mean rather than a distribution. And denominators_reported is a single bit for a report that has three numbers in it, which is coarse and is better than nothing.
Production implication: none_of_the_above for the seventeenth time, and the hardest of them all to clear. A model asserting it has correct arithmetic, no dependent dimension, wire-side sampling, a fully enabled topology, a stable closure, checks on 80% of its filled bins and a report with three denominators. topology_limiting and covered_not_checked will both be set on a first run, and the honest use of the verdict is as a list of seven things to argue about rather than as a gate — which is what a coverage model is for.
16. The Cost, Accounted
Eight blocks, and for the first time in this track the dominant cost is a bit array.
| Block | Flops | Storage |
|---|---|---|
bin_space_calc | 0 — elaboration only | 448 bits |
dependency_detector | ~60 | 4 096 bits per pair |
reachability_model | ~40 | — |
cross_sampler | ~120 | 448 bits |
illegal_bin_filter | ~30 | 448 bits, precomputed |
closure_reporter | ~200 | 860 160 bits — 105 KiB |
coverage_telemetry | ~180 | — |
coverage_conformance_monitor | ~20 | — |
| total | ~650 flops | ~106 KiB |
The closure reporter's bit array is 99% of the storage and it is one bit per declared cell. A hundred and five kilobytes is nothing in a simulator and it is the reason a coverage database is a database rather than a register — and it scales with the declared space, so Section 5's rejected dimension would have made it 6.6 MiB.
| Model | Declared cells | Bit array |
|---|---|---|
| six dimensions | 860 160 | 105 KiB |
| seven, with the stage-set | 55 050 240 | 6.6 MiB |
| reachable either way | 467 328 | — |
Row two is Section 5's mistake priced in memory as well as in percentage, and the 6.6 MiB buys exactly zero additional reachable cells.
And the run-time cost is a single array write per frame.
| Value | |
|---|---|
| sampler work per frame | one index computation, one bit write |
| frames per second at 100 Gb/s | 148.81 M |
| against Chapter 20.2's 1 848 property evaluations per cycle | negligible |
Coverage is the cheapest of the three verification chapters at run time and the most expensive in memory, which is the opposite of the assertion library and is worth knowing when a regression is being tuned: disabling coverage saves almost nothing.
And the declared space's sensitivity to each dimension is worth tabulating, because it decides which dimension to question first when the model is too large.
| Dimension | Values | Removing it divides the space by |
|---|---|---|
| residue | 64 | 64 |
| offset | 64 | 64 |
| bucket | 7 | 7 |
| error | 5 | 5 |
| tags | 3 | 3 |
| dual | 2 | 2 |
The two 64-value dimensions are 4 096× of the model between them, and they are also the two that Module 19 argued hardest for — Chapter 19.4 §4's residues and Chapter 19.2 §3's offsets. A model that is too large cannot be shrunk without giving up the cases it was built for, which is the honest position and is different from Section 5's, where a dimension could be removed for free.
Module 20's running total, with four chapters built:
| Chapter | Flops | Storage |
|---|---|---|
| Chapter 20.1 — the generator | ~1 050 | — |
| Chapter 20.2 — the assertion library | ~730 | — |
| Chapter 20.3 — the scoreboards | ~1 760 | 816 octets |
| this chapter — the coverage model | ~650 | ~106 KiB |
| subtotal | ~4 190 | ~106 KiB |
| Module 19's datapath, for comparison | ~14 166 flops | 55 KiB |
Four chapters of verification environment at 29.6% of the datapath's logic and twice its memory — and the memory is one bit per cell of a cross that is 45.7% illegal. Section 10's filter would shrink the array to the legal set; nobody does it, because 105 KiB is cheap and the arithmetic is the point rather than the saving.
17. What a Coverage Model Assumes
Nine assumptions, and the first four are about who supplies each number.
| # | Assumption | Owner | If wrong |
|---|---|---|---|
| 1 | the seven buckets are Chapter 19.7 §2's | RMON | Section 10's 448-bit exclusion set is wrong |
| 2 | the 64 residues are Chapter 19.4 §4's | the datapath width | every residue bin moves |
| 3 | 16 offsets in a loopback | Chapter 19.3 §6's lane alignment | Section 8's 4× is wrong |
| 4 | the sampler reads the wire | Section 9 | the model records the request |
| 5 | the dimensions are independent | Section 6's detector | Section 5's 64× inflation |
| 6 | the exclusion set is regenerated | a script | it silently stops matching the design |
| 7 | the MTU is 9 000 | configuration | bucket 6 does not exist |
| 8 | a filled bin means the case occurred | the sampler's placement | it means the case was requested |
| 9 | somebody reads three percentages | a human | Section 14's first prohibition |
Row seven is the assumption that quietly changes the model's shape. At a 1 518-octet MTU bucket 6 cannot occur at all, so the declared space should be six buckets and not seven — and a model that keeps the seventh has 122 880 permanently open cells which are neither illegal nor unreachable in the usual sense: they are excluded by a configuration register.
That is a fourth category and it is worth naming.
| Cause | Changes with |
|---|---|
| illegal | never — the protocol |
| unreachable | the testbench's topology |
| uncovered | more simulation |
| out of configuration | a register the design reads |
Row four is the one Section 7's three-way split misses, and it behaves like "illegal" for a given run and like "reachable" across a regression suite. The honest handling is to compute the exclusion set per configuration, which Section 3's calculator does if it is given the MTU.
And two deliberately not assumed:
| Not assumed | Why not |
|---|---|
| that the six dimensions are the right ones | they came from six chapters' findings, and a seventh chapter would add a seventh |
| that closing the cross means the design works | Section 11 — coverage measures the stimulus |
Row one is worth being explicit about because it is the model's largest unexamined choice. Six dimensions were chosen because six chapters named a case their own verification would miss. A seventh chapter would add a seventh dimension and multiply the space again, and there is no principle in this chapter that says when to stop — only Section 5's test for when a candidate adds nothing.
18. Closure, and What It Is Evidence Of
Section 12 reports three percentages. This section is what each one licenses somebody to say, because the licences are much narrower than the numbers suggest.
| Statement | Licensed by |
|---|---|
| "every case this testbench can produce has occurred" | 100% of reachable |
| "every case the protocol permits has occurred" | 100% of legal |
| "every case the model declares has occurred" | 100% of declared — impossible; 45.7% is illegal |
Row three is impossible by construction, which is the first thing a three-denominator report makes obvious and a one-number report hides: a tool showing 54.3% as its maximum is not broken.
And row one — the achievable one — licenses less than it sounds.
| It does say | It does not say |
|---|---|
| the case occurred | that anything checked it |
| the stimulus reached it | that the design handled it correctly |
| the bin filled at least once | that it filled repeatedly — Section 13's points_per_bin |
Row one of the right-hand column is Section 11's gap and it is the one that matters. A cross bin fills when a frame with those six properties occurs; Chapter 20.2 §15's fired_pct_x10 and Chapter 20.3 §14's octet_coverage_pct are separate measurements, and a regression can be at 100% on this chapter's number and 40% on the second.
So the honest closure statement has four parts and a coverage tool produces one.
| Part | From |
|---|---|
| what fraction of the reachable cross occurred | this chapter |
| what fraction of the bound properties evaluated | Chapter 20.2 §15 |
| what fraction of each frame was compared | Chapter 20.3 §14 |
| which of the five lists Module 19 left were reached | Chapter 20.1 §15 |
Four numbers from four chapters, and no tool assembles them, because each belongs to a different component and they have no shared identifier. Section 13's checked_share_pct is the one cross that would join two of them — a bin index shared between the sampler and the check infrastructure — and it is one bit per bin.
And the four numbers have a natural order, which is worth stating because a report that lists them in any other order misleads.
| Order | Number | If it is low, the ones after it are meaningless |
|---|---|---|
| 1 | Chapter 20.1 §15's five-bit list | the cases were never produced |
| 2 | this chapter's reachable percentage | the cross was not closed |
| 3 | Chapter 20.2 §15's fired percentage | the properties did not evaluate |
| 4 | Chapter 20.3 §14's compared share | the frames were barely compared |
Each one is a precondition for the next meaning anything, and a report that leads with the fourth — "the scoreboard compared 99% of every frame" — has said something true about a run that may never have produced a runt. The order is production, occurrence, evaluation, comparison, and it is the order the four chapters were written in.
Which is where this module's argument ends. Chapter 20.1 produces, Chapter 20.2 and Chapter 20.3 check, this chapter counts — and the four measurements that would make a closure claim honest exist, are cheap, and are not joined.
19. The Three Percentages, Worked
One run, three denominators, and the arithmetic laid out because the gap between the numbers is where every misunderstanding in this chapter lives.
Take a loopback regression that has closed everything it can.
| Cells | |
|---|---|
| declared | 860 160 |
| illegal — Section 4's two exclusions | 392 832 |
| legal | 467 328 |
| unreachable in this topology | 350 496 |
| reachable | 116 832 |
| covered | 116 832 |
Three reports of the same run:
| Denominator | Percentage | What it means |
|---|---|---|
| reachable | 100.0% | this testbench has done all it can |
| legal | 25.0% | a quarter of what the protocol permits |
| declared | 13.6% | an eighth of what the model lists |
And now enable Chapter 20.1 §10's injector and rerun.
| Cells | |
|---|---|
| reachable | 467 328 |
| covered, immediately after the change | 116 832 |
| reported against reachable | 25.0% |
The same run, the same coverage, and the percentage falls from 100% to 25% — because the denominator grew by 4× and nothing else changed. A team that reports "coverage regressed to 25%" has described a testbench improvement as a failure, and this is the most common form of the confusion.
The correct reading is that the first run's 100% was a 25% wearing a smaller denominator. Which is why the report needs all three: the legal percentage is the only one that does not move when the topology changes, and it went from 25.0% to 25.0%. It is the number a closure target should be set against.
| Before the injector | After | |
|---|---|---|
| of reachable | 100.0% | 25.0% |
| of legal | 25.0% | 25.0% |
| of declared | 13.6% | 13.6% |
Rows two and three are stable and row one is not, and a plan that tracks row one alone cannot distinguish progress from a denominator change.
20. Properties Worth Asserting, and One Worth Refusing
Thirty-three properties about a coverage model, and the refused one is the target every verification plan in the world is written against.
Group 1 — the three set sizes, checked at elaboration.
// The declared space is the product of the dimensions.
a_declared_product: assert property (@(posedge clk)
declared == 32'(N_BUCKET * N_RESIDUE * N_OFFSET * N_TAGS * N_ERROR * N_DUAL));
// The legal set is smaller and non-empty.
a_legal_ordered: assert property (@(posedge clk)
(legal > 32'd0) && (legal < declared));
// The reachable set is a subset of the legal one.
a_reachable_subset: assert property (@(posedge clk) reachable <= legal);
// Section 4's arithmetic, as a check on this chapter's own numbers.
a_legal_value: assert property (@(posedge clk) legal == 32'd467328);
// And the loopback reachable set is a quarter of it. Chapter 20.1
// Section 9: 16 offsets of 64.
a_loopback_quarter: assert property (@(posedge clk)
(OFFSETS_REACHABLE == 16) |-> (reachable * 32'd4 == legal));Group 2 — the exclusions, which come from definitions.
// Bucket 0 is exactly 64 octets, so it has exactly one residue.
a_bucket0_one_residue: assert property (@(posedge clk) disable iff (!rst_n)
(point_valid && point.bucket == 3'd0) |-> (point.residue == 6'd60));
// Undersize occurs only in bucket 0.
a_undersize_bucket0: assert property (@(posedge clk) disable iff (!rst_n)
(point_valid && point.err == ERR_UNDER) |-> (point.bucket == 3'd0));
// Oversize occurs only in bucket 6.
a_oversize_bucket6: assert property (@(posedge clk) disable iff (!rst_n)
(point_valid && point.err == ERR_OVER) |-> (point.bucket == 3'd6));
// An illegal point observed is a bug, not a coverage event.
a_illegal_not_counted: assert property (@(posedge clk) disable iff (!rst_n)
(point_valid && !is_legal) |-> !$rose(covered));
// The bucket-residue pair count never exceeds Section 4's derivation.
a_pairs_bounded: assert property (@(posedge clk) disable iff (!rst_n)
distinct_pairs <= 16'd384);
// And exceeding it is reported.
a_pairs_excess_reported: assert property (@(posedge clk) disable iff (!rst_n)
(distinct_pairs > 16'd384) |-> model_arithmetic_wrong);Group 3 — the dependency test, which is Section 5's whole argument.
// A dimension that is a function of another is reported before it is
// added, not after a quarter of closure work.
a_dependency_reported: assert property (@(posedge clk) disable iff (!rst_n)
b_is_function_of_a |-> dependent_dimension);
// The pair count is bounded by the product.
a_pairs_le_product: assert property (@(posedge clk) disable iff (!rst_n)
distinct_pairs <= 16'(N_A * N_B));
// A functional dependency means at most one B per A.
a_function_means_one: assert property (@(posedge clk) disable iff (!rst_n)
b_is_function_of_a |-> (distinct_pairs == distinct_a));
// The test needs samples before it means anything.
a_dependency_needs_samples: assert property (@(posedge clk) disable iff (!rst_n)
b_is_function_of_a |-> (c_samples > 32'd10000));
// Residue and stage-set fail the test. Section 5: 64 pairs of 4 096.
a_residue_determines_stages: assert property (@(posedge clk) disable iff (!rst_n)
(dim_a == DIM_RESIDUE && dim_b == DIM_STAGESET) |-> b_is_function_of_a);Group 4 — the sampler, where the properties are about which wire it reads.
// Chapter 20.1 Section 13: the model records the delivery, not the request.
a_sampled_from_wire: assert property (@(posedge clk) !sampled_from_stimulus);
// The bucket follows from the observed length.
a_bucket_from_length: assert property (@(posedge clk) disable iff (!rst_n)
point_valid |-> (point.bucket == bucket_of(wire_length)));
// So does the residue.
a_residue_from_length: assert property (@(posedge clk) disable iff (!rst_n)
point_valid |-> (point.residue == 6'((wire_length - 16'd4) % 16'd64)));
// A padded request and its delivered frame land in different residues.
// Chapter 19.3 Section 2: a 20-octet request becomes 64 octets.
a_pad_moves_residue: assert property (@(posedge clk) disable iff (!rst_n)
(point_valid && req_length == 16'd20) |-> (point.residue == 6'd60));
// Every valid frame produces exactly one point.
a_one_point_per_frame: assert property (@(posedge clk) disable iff (!rst_n)
frame_end |-> ##1 $rose(c_points) || $stable(c_points));
// The offset comes from the tracker and not from the item.
a_offset_from_tracker: assert property (@(posedge clk) disable iff (!rst_n)
point_valid |-> (point.offset == tracked_phase));Group 5 — closure and its denominators.
// Covered never exceeds reachable.
a_covered_bounded: assert property (@(posedge clk) disable iff (!rst_n)
covered <= reachable);
// The three percentages are consistent with the three sets.
a_pct_consistent: assert property (@(posedge clk) disable iff (!rst_n)
(pct_of_declared <= pct_of_legal) && (pct_of_legal <= pct_of_reachable));
// The open sets partition the declared space.
a_open_partition: assert property (@(posedge clk) disable iff (!rst_n)
(covered + open_illegal + open_unreachable + open_uncovered) == declared);
// A stable closure has been hit more than once per bin.
a_stable_closure: assert property (@(posedge clk) disable iff (!rst_n)
(pct_of_reachable >= 16'd90 && points_per_bin < 32'd10) |-> closure_fragile);
// Enabling the injector grows the denominator and not the coverage.
a_injector_grows_denominator: assert property (@(posedge clk) disable iff (!rst_n)
$rose(injector_enabled) |=> (reachable == $past(reachable) * 32'd4));
// And the legal percentage does not move when it does. Section 19.
a_legal_pct_stable: assert property (@(posedge clk) disable iff (!rst_n)
$rose(injector_enabled) |=> $stable(pct_of_legal));Group 6 — coverage of the coverage model.
c_all_buckets: cover property (@(posedge clk) distinct_pairs >= 16'd300);
c_bucket0: cover property (@(posedge clk) point_valid && point.bucket == 3'd0);
c_bucket6: cover property (@(posedge clk) point_valid && point.bucket == 3'd6);
c_all_error_classes: cover property (@(posedge clk) point.err == ERR_UNDER);
c_dual_beat_point: cover property (@(posedge clk) point_valid && point.dual);
c_injector_on: cover property (@(posedge clk) injector_enabled);
c_dependency_found: cover property (@(posedge clk) dependent_dimension);21. Verification Scenarios
Fifty-seven scenarios for a model whose subject is measurement, plus a five-run directed test whose content is a configuration change.
The three set sizes — 11 scenarios.
| # | Scenario | Expected |
|---|---|---|
| 1 | the six dimensions as declared | 860 160 cells |
| 2 | the bucket-residue exclusion | 384 of 448 pairs |
| 3 | the bucket-error exclusion | 23 of 35 pairs |
| 4 | both applied | legal 467 328 — 54.3% |
| 5 | loopback, 16 offsets | reachable 116 832 — 13.6% |
| 6 | with the injector, 64 offsets | reachable 467 328 — 54.3% |
| 7 | allow_illegal clear | 3 error classes; reachable falls 1.06× |
| 8 | jumbo disabled | 6 buckets, 320 pairs; reachable falls 1.27× |
| 9 | all three limits at once | reachable 92 160 — 10.7% |
| 10 | MTU 1 518 | bucket 6 impossible — a fourth category |
| 11 | a seventh dimension added | declared 55 050 240; legal unchanged |
Row nine is the number worth having on hand. A default environment — loopback, legal sizes only, no jumbo — reaches 92 160 cells of 860 160, which is 10.7% of declared and 19.7% of legal, and reports "100% coverage" when it closes them.
The dependency test — 10 scenarios.
| # | Scenario | Expected |
|---|---|---|
| 12 | residue against stage-set | 64 pairs of 4 096 — dependent |
| 13 | frame length against gap | 1 455 of 5 820 — dependent |
| 14 | residue against offset | many pairs — independent |
| 15 | bucket against residue | 384 of 448 — nearly dependent |
| 16 | fewer than 10 000 samples | no verdict |
| 17 | one A value with two B values | not a function |
| 18 | a bijection | dependent in both directions |
| 19 | pair_pct_x10 at 16 | 1.6% — strongly dependent |
| 20 | pair_pct_x10 at 600 | 60% — independent |
| 21 | the test run before the model is written | fifteen pairs, one afternoon |
Row fifteen is the interesting middle case. Bucket and residue are 85.7% independent — not a function, and not free either — and the 14.3% they share is Section 4's first exclusion. The test distinguishes "add it" from "do not add it" and also from "add it and exclude the impossible pairs."
The sampler — 12 scenarios.
| # | Scenario | Expected |
|---|---|---|
| 22 | a 64-octet frame | bucket 0, residue 60 |
| 23 | a 20-octet request, padded | bucket 0, residue 60 |
| 24 | the same sampled from the item | bucket 0, residue 16 — wrong |
| 25 | a 1 518-octet frame | bucket 5, residue 42 |
| 26 | a 1 519-octet frame | bucket 6, residue 43 |
| 27 | a 9 000-octet frame | bucket 6 |
| 28 | an untagged frame | tags 0 |
| 29 | a double-tagged frame | tags 2 |
| 30 | a dual-frame beat | dual set |
| 31 | a runt with allow_illegal | bucket 0, ERR_UNDER |
| 32 | a runt without it | never occurs |
| 33 | 1 000 frames, c_distinct | at most 384 |
And the bucket-residue relationship is worth checking directly, because it is the model's only non-trivial exclusion.
| Bucket | Lengths it spans | Residues it reaches |
|---|---|---|
| 0 | 64 only | 1 — residue 60 |
| 1 | 65 to 127 — 63 sizes | 63 |
| 2 | 128 to 255 — 128 sizes | 64 |
| 3 to 5 | 256 or more | 64 each |
| 6 | 1 519 to 9 000 | 64 |
A bucket spanning 64 or more consecutive lengths reaches every residue, which is the closed form Section 10's loop computes — and buckets 0 and 1 are the only two that do not. Sixty-four cells of 448 missing, and sixty-three of them in one row.
Rows twenty-three and twenty-four are the sampling prohibition as a test, and they differ by one wire: the item says residue 16 and the wire says residue 60, and only one of them is what the design's Chapter 19.4 §5 masker saw.
Exclusions and the reporter — 12 scenarios.
| # | Scenario | Expected |
|---|---|---|
| 34 | bucket 0, residue 17 | illegal — pair_possible false |
| 35 | bucket 3, residue 17 | legal |
| 36 | bucket 6, ERR_UNDER | illegal |
| 37 | bucket 0, ERR_UNDER | legal |
| 38 | an illegal point observed | model_arithmetic_wrong |
| 39 | an illegal point counted as covered | a bin closes that never should |
| 40 | the exclusion set regenerated after a bucket change | it differs — the diff is the alarm |
| 41 | covered = reachable | 100% of reachable, 25.0% of legal |
| 42 | the injector enabled after that | 25.0% of reachable — the same run |
| 43 | pct_of_legal across the change | 25.0% both times — stable |
| 44 | dominant_open_reason on a fresh run | 1 — illegal is the largest set |
| 45 | the same after closure | 2 — unreachable exceeds uncovered |
Rows forty-one to forty-three are Section 19's table as a test, and row forty-three is the one to build a target on: the legal percentage does not move when the topology does.
Telemetry and the monitor — 12 scenarios.
| # | Scenario | Expected |
|---|---|---|
| 46 | c_distinct at 200 | a narrow size distribution |
| 47 | c_distinct at 400 | model_arithmetic_wrong — above 384 |
| 48 | 90% of reachable, 1 point per bin | closure_fragile |
| 49 | 90% of reachable, 1 000 points per bin | clear |
| 50 | checked_share_pct at 60 | covered_not_checked |
| 51 | the injector off | topology_limiting, factor 1 — worth 4.00× |
| 52 | jumbo off | factor 2 — worth 1.27× |
| 53 | allow_illegal clear | factor 3 — worth 1.06× |
| 54 | everything enabled | factor 0 |
| 55 | a stage-set dimension added | model_inflated |
| 56 | sampling from the item | sampling_wrong |
| 57 | one percentage reported | denominator_ambiguous |
Row fifty-one is the verdict that fires on nearly every real environment, and Section 8's ranking says it is worth 4.00× — more than rows fifty-two and fifty-three multiplied together, which is 1.35×.
The directed test — five runs a longer regression will not produce.
Every one of this chapter's failures is a configuration or a modelling decision, so none of them responds to simulation time.
| Failure | Needs | A longer run gives |
|---|---|---|
| the 4× denominator change | enabling the injector | nothing |
| the inflated model | adding a dependent dimension | nothing |
| the wrong sampling point | wiring the sampler to the item | nothing |
| the fragile closure | a narrow distribution and a seed change | it hides the fragility |
| the unchecked bins | unbinding the assertions | nothing |
Row four is the only one where a longer run makes things worse rather than neutral, and it is worth dwelling on: more simulation raises points_per_bin and hides exactly the instability the metric exists to reveal.
Construct it. Five runs.
| Run | Configuration | Exercises |
|---|---|---|
| A | loopback, legal sizes, no jumbo | the default; 92 160 reachable, 10.7% |
| B | the same, plus the injector | reachable × 4.00; the percentage falls |
| C | everything enabled | reachable 467 328; limiting_factor = 0 |
| D | a stage-set dimension added | declared 55 050 240; the percentage divided by 64 |
| E | the sampler wired to the stimulus item | residue 16 where the design saw 60 |
Run B is one boolean and it is the run that teaches the denominator. The same stimulus, the same design, the same covered cells — and the reported percentage falls from 100% to 25% because the reachable set grew. A status report that shows only that number describes a testbench improvement as a regression.
Run D is one line in a coverage model and it costs a quarter. The declared space becomes 55 050 240, the legal space does not move, and the reported percentage against declared goes from 10.7% to 0.17% and stays there whatever anybody does to the stimulus.
The oracle, in four parts:
| Check | A | B | C | D | E |
|---|---|---|---|---|---|
reachable | 92 160 | 368 640 | 467 328 | 467 328 | 92 160 |
pct_of_reachable | 100.0 | 25.0 | 19.7 | 19.7 | 100.0 |
pct_of_legal | 19.7 | 19.7 | 19.7 | 0.17 | 19.7 |
point.residue on a padded frame | 60 | 60 | 60 | 60 | 16 |
Row three is the row to read and it is constant across A, B and C at 19.7%. The same covered set, three topologies, and the only percentage that does not move is the one measured against what the protocol permits. Row three's run D entry is the inflation and row four's run E entry is the sampling error — two different ways to make a number wrong that a single coverage percentage reports identically.
22. Debugging a Coverage Model
Four complaints, and none of them is about the design.
Complaint 1 — "coverage regressed from 100% to 25% and nothing changed."
| Check | If yes | Meaning |
|---|---|---|
did reachable change? | the denominator grew | Section 19 |
| was the injector enabled? | 4.00× — Chapter 20.1 §10 | a testbench improvement |
did covered change? | it did not | confirms — the same run |
what is pct_of_legal? | unchanged at 19.7% | definitively |
Row four is the check that settles it in one read. The legal percentage does not move when the topology does, so a regression in pct_of_reachable with pct_of_legal flat is a denominator change and not a loss — and the correct report is "the reachable space grew fourfold and we now cover a quarter of it."
Complaint 2 — "the percentage has not moved in six weeks."
| Check | If yes | Meaning |
|---|---|---|
model_inflated set? | a dependent dimension was added | Section 5 — 64× declared, 1× reachable |
dominant_open_reason = 2? | the open list is unreachable bins | a testbench change, not more simulation |
dominant_open_reason = 1? | illegal bins are being counted | Section 10's filter is missing |
limiting_factor non-zero? | a configuration is capping it | and Section 8 prices which |
Row one is the expensive answer and it has a date. Somebody added a dimension six weeks ago because Chapter 19.4 §21 said a single dimension was insufficient — and the stage-set is a function of the residue, so the declared space went up 64× and nothing else did. The dependency detector would have said so in an afternoon.
Complaint 3 — "coverage is at 95% and we keep finding bugs."
| Check | If yes | Meaning |
|---|---|---|
checked_share_pct low? | bins filled with no check bound | Section 11 |
points_per_bin near 1? | the closure is coincidental | closure_fragile |
is Chapter 20.2 §15's fired_pct_x10 low? | properties never evaluated | the cases occurred and nobody looked |
is Chapter 20.3 §14's octet_coverage_pct low? | the frames were barely compared | 23.4% on short traffic |
Rows three and four are the other two chapters' measurements and they are the answer. A cross bin fills when a case occurs; 95% coverage with 40% of properties never firing means the cases happened and the checks did not. Coverage measures the stimulus and the complaint is about the checking, which is Section 11's whole point arriving as a bug report.
Complaint 4 — "some bins have been open since the model was written."
| Check | If yes | Meaning |
|---|---|---|
| are they illegal? | Section 10's filter never ran | exclude them |
| bucket 0 with a residue other than 60? | 63 such bins | definitively illegal |
| a residue with an impossible bucket? | 64 pairs of 448 | the same cause |
| an offset that is not a multiple of four? | 48 of 64 | unreachable, not illegal |
Row two is the cluster to look for first. Bucket 0 is a single frame size and therefore a single residue; sixty-three of its sixty-four cells can never fill, and a model without Section 10's filter carries them on an open-bin list forever. One function, run at elaboration, removes them.
Complaint 5 — "the same regression reports different coverage on different days."
| Check | If yes | Meaning |
|---|---|---|
points_per_bin near 1? | most bins were hit exactly once | closure_fragile |
| did the seed change? | a hundred bins reopened | confirms |
| did the frame-size distribution change? | Chapter 20.1 §17's gcd | a fixed size can collapse the offsets |
| does a longer run stabilise it? | yes, and it hides the fragility | not a fix |
Row four is the uncomfortable one. More simulation raises points_per_bin and makes the instability invisible, so a team that responds to day-to-day variation by lengthening the regression has bought a stable number and learned nothing. The metric that would have told them is the one the longer run suppresses.
Complaint 6 — "the coverage database has bins we do not recognise."
| Check | If yes | Meaning |
|---|---|---|
c_distinct above 384? | the sampler is computing a bucket or a residue wrongly | Section 13 |
| an illegal point observed? | model_arithmetic_wrong | and a bin closed that never should |
| did a bucket boundary change? | the exclusion set is stale | Section 10's diff |
| is the MTU different from the model's? | bucket 6 exists or does not | Section 17's fourth category |
Row three is the maintenance failure and it is silent. The 448-bit exclusion set is derived from Chapter 19.7 §2's bucket boundaries; change a boundary and the set changes, and a model whose exclusions were computed once by hand stops matching the design with no error anywhere. Generating it and diffing it is forty lines.
And the three symptoms this chapter is systematically blamed for:
| Symptom | Blamed on | Usually is |
|---|---|---|
| a percentage that regressed | the stimulus | a denominator that grew |
| a percentage that will not move | the seeds | a dependent dimension, or unreachable bins |
| "high coverage, still buggy" | the coverage model | the checks, measured elsewhere |
| bins open since the model was written | the stimulus | 63 of them in bucket 0, illegal |
| coverage that varies day to day | the simulator | one point per bin, and a new seed |
23. Misconceptions
Misconception 1 — "coverage is a percentage."
The wrong model: one number, and higher is better.
What it costs: a factor of four in ambiguity. The same run reports 100.0%, 25.0% and 13.6% against reachable, legal and declared — and 45.7% of the declared space cannot be covered at all, so a tool showing 54.3% as its maximum is not broken.
The corrected model: three denominators, computed at elaboration from definitions. The reachable percentage says whether this testbench has done all it can; the legal percentage is the only one that does not move when the topology changes, and it is what a closure target should be set against. Sections 4, 12, 19.
Misconception 2 — "an open bin means more simulation."
The wrong model: coverage is a function of run time.
What it costs: weeks on bins that cannot close. Of this model's 860 160 declared cells, 392 832 are illegal and 350 496 are unreachable in a loopback — so the unreachable set is three times the coverable one, and a team working an open-bin list in order spends most of its time on bins with a different owner.
The corrected model: three causes, three responses. Illegal — exclude it and never count it again. Unreachable — change the testbench. Uncovered — run longer or reweight. Both exclusions are computable at elaboration from definitions, and neither needs a single simulation. Sections 7, 10, 12.
Misconception 3 — "if one dimension is not enough, add another."
The wrong model: Chapter 19.4 §21 said a residue-only model cannot distinguish six barrel stages from one, so track the stage too.
What it costs: the declared space goes from 860 160 to 55 050 240 and the reachable space does not move. The stage-set is a function of the residue — sixty-four pairs of 4 096 — so the percentage falls by 64× and stays there, and the bit array goes from 105 KiB to 6.6 MiB.
The corrected model: the information was already in the residue dimension. Residue 1 selects all six stages and residue 60 selects one, so Chapter 19.4 §21's two runs are "covered residue 1" and "covered residue 60" — distinguishable perfectly by a model that already existed. Test for dependency before adding: fifteen pairs, one afternoon. Sections 5, 6.
Misconception 4 — "sample the stimulus; it is easier to get at."
The wrong model: the generator knows what it sent.
What it costs: a model that records the request rather than the delivery. A 20-octet request becomes a 64-octet frame — Chapter 19.3 §2 — so the item says residue 16 and the design's masker saw residue 60. Both are in bucket 0 and only one of them happened.
The corrected model: sample the wire, or the design's own boundary. Every field of the coverage point comes from what was delivered, which is Chapter 20.1 §13's first prohibition one level up and is the same argument Chapter 20.3 §20 makes about a scoreboard's subject. Sections 9, 14.
Misconception 5 — "100% coverage is the goal."
The wrong model: a target on a ratio.
What it costs: an incentive to shrink the denominator. Cover everything a loopback reaches and the property passes; enable Chapter 20.1 §10's injector — a two-bit counter — and the same run reports 25% and the property fails. Nothing about the design changed. The cheapest route to 100% is to delete bins.
The corrected model: assert the numerator against a named list — Chapter 20.1 §15's five bits, one per case a Module 19 chapter argued for — and assert separately that the denominator is as large as the topology permits. Two properties, opposite incentives, and neither is a percentage. Section 20.
Misconception 6 — "a covered bin is a verified case."
The wrong model: coverage is evidence about the design.
What it costs: the gap between three measurements that are routinely read as one. A bin fills when the case occurs. Chapter 20.2 §15's fired_pct_x10 says whether a property evaluated. Chapter 20.3 §14's octet_coverage_pct says how much of the frame was compared. A run can be at 100% on the first and 40% on the second.
The corrected model: coverage measures the stimulus. The check that joins it to the checking is one bit per bin — 860 160 bits, 105 KiB — saying whether any check was active when the bin filled, and it turns "we covered it" into "we covered it and looked." Sections 11, 13, 18.
24. Interview Questions
Question 1 — "Your coverage report says 100%. What did you actually cover?"
What the answer should establish: one of three things, and the report does not say which. Against the reachable set it means this testbench has done all it can; against the legal set it is 25.0%; against the declared set 13.6% — and 45.7% of the declared set is illegal and can never be covered. A strong answer names where the three numbers come from: the illegal exclusions are computable from definitions at elaboration, the reachable set needs a model of the topology, and only the legal percentage is stable when the topology changes.
Question 2 — "Coverage dropped from 100% to 25% overnight. What happened?"
What the answer should establish: somebody improved the testbench. Enabling an element that adds 0 to 3 idle octets between transmit and receive restores the 48 beat offsets Chapter 19.3 §6's lane alignment removes, so the reachable set grows 4.00× and the same covered cells report a quarter. A strong answer gives the diagnostic: pct_of_legal is unchanged at 19.7%, so the numerator did not move and the denominator did — and the honest report is "the reachable space grew fourfold."
Question 3 — "A residue-only coverage model cannot distinguish six barrel stages from one. Do you add the stage as a dimension?"
What the answer should establish: no — the stage-set is a function of the residue. The barrel's stages are selected by the bits of 64 − residue, so each residue produces exactly one stage-set: 64 pairs out of 4 096. Adding it multiplies the declared space by 64 and the reachable space by 1. A strong answer says where the information already was: residue 1 selects all six stages and residue 60 selects one, so the two runs are "covered residue 1" and "covered residue 60" and the existing dimension separates them. The test is mechanical and takes an afternoon for fifteen pairs.
Question 4 — "How much of your coverage model can never be covered?"
What the answer should establish: 45.7%, and it is computable before any simulation. Two dependencies do it: a frame's length decides both its size bucket and its residue — and bucket 0 is a single frame size, so it has exactly one residue and 63 illegal cells — and Chapter 7.3's undersize occurs only in bucket 0 and oversize only in bucket 6, which removes 12 of 35 bucket-error pairs. A strong answer notes that an illegal point observed is a bug rather than a coverage event, because counting it closes a bin that should never close.
Question 5 — "Is '100% coverage' a good verification target?"
What the answer should establish: no — it is a target on a ratio whose denominator the environment controls. The cheapest route to 100% is to delete bins, and improving the testbench makes the target fail while the design and the stimulus are unchanged. A strong answer gives the replacement: assert the numerator against a named list — Chapter 20.1 §15's five bits, one per case a Module 19 chapter argued for — and assert separately that the denominator is as large as the topology allows. Two properties with opposite incentives, and neither is a percentage.
Question 6 — "Coverage is at 95% and you are still finding bugs. Where do you look?"
What the answer should establish: at the other two measurements, because coverage is about the stimulus. A bin fills when the case occurs; Chapter 20.2 §15's fired_pct_x10 says whether any property evaluated and Chapter 20.3 §14's octet_coverage_pct says how much of the frame was compared. A run at 95% coverage with 40% of properties never firing has produced the cases and not looked at them. A strong answer names the join that would show it: one bit per bin recording whether a check was active — 105 KiB — which nobody builds.
25. Questions and Answers
26. What's Next
Module 20 has two chapters left and this chapter has set the frame for both.
Chapter 20.5 injects errors, and it is the chapter that fills the dimension this one declared and could not reach. Chapter 7.3's five error classes are a coverage dimension here and a stimulus problem there — and Section 8's arithmetic says enabling them is worth 1.06×, which is the smallest of the three configuration levers and is the one that reaches the cases Chapter 19.7 §7's two-bit addend was built for. A small denominator change and a large behavioural one, which is exactly the kind of case a percentage misranks.
Chapter 20.6 assembles the reusable agent, and it inherits this module's four measurements and the fact that they do not join. Chapter 20.1 §15's five-bit list, Chapter 20.2 §15's fired percentage, Chapter 20.3 §14's compared share and this chapter's three denominators are produced by four components that share no identifier — and an agent is the place a shared identifier could exist.
And the series is now ninety-one classes long. Chapter 20.2 §8 sorted the first eighty-eight into six groups and found three of them outside any tool's reach; this chapter's class 91 belongs to a seventh group that did not exist before it — a property whose satisfaction the environment can arrange by changing a definition. Chapter 20.2's taxonomy will need rerunning, which is the ordinary fate of a taxonomy built from eighty-eight examples and tested against three more.
Continue learning
Related tutorials
- Related topic
Packet Generation
A weight is a per-frame marginal, so it reaches a frame's own properties and nothing else — and 48 of the parser's 64 alignment offsets are unreachable from the transmit side at any weight.
- Related topic
Scoreboards
A scoreboard that compares octet for octet fails on padding, on an appended check value and on a tag — and on a minimum-size frame only fifteen octets are invariant.
- Related topic
Error Injection
A runt is 0.08% of the coverage cross and three of fourteen design paths; and no sequence of frames can overflow a FIFO whose drain rate exceeds the line rate.
- Related topic
A Reusable UVM Ethernet Agent
Module 20's four components share no key, so the agent is where one lives; and every modulus in the coverage model is the beat width, so the cross moves 128x between 10 and 100 Gb/s.
Standards & specifications
- Governing standard
- IEEE Std 802.3 (Ethernet)(opens IEEE in a new tab)
Defines the Ethernet MAC, the media-independent interfaces and the physical-layer sublayers, including framing, access control, auto-negotiation and per-rate PHY specifications. VLAN tagging, priority and time-sensitive shaping are defined by IEEE 802.1, not by 802.3.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the Ethernet curriculum.
