Skip to content
VLSI Mentor

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, declared860 160
reachable at all467 328 — 54.3%
reachable from a loopback topology116 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.

PartWhat it isUsually
the binsthe dimensions and their valueswritten
the illegal setcombinations that cannot occurdiscovered, one by one, from unclosable bins
the reachable setcombinations this testbench can producenever 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 itChapter 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.3and 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.

DimensionValuesFrom
size bucket7Chapter 19.7 §2's RMON histogram
partial-word residue64Chapter 19.4 §4
beat start offset64Chapter 19.2 §3
VLAN tag count3Chapter 13.2
error class5Chapter 7.3
dual-frame beat2Chapter 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:

DimensionThe chapter's finding
residuethe classic seven frame sizes reach 3 of 64
start offseta loopback reaches 16 of 64
dual-frame beat52.38% of beats at minimum size, 2.73% at maximum
size bucketa fixed-size stress test reaches 1 of 7
tag countChapter 19.2 §4's offsets depend on it
error classChapter 19.7 §7's runts need two of them

Six dimensions, six chapters, and a 860 160-cell crosswhich 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.

PairProductReachableRelationship
bucket × residue448384partly dependent — 85.7%
bucket × error3523partly dependent — 65.7%
residue × offset4 0964 096independent
offset × dual128128independent
tags × residue192192independent
bucket × tags2121independent

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.

RejectedWhy
barrel stage selecteda function of the residue — Section 5
interframe gap valuea 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// 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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// 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);

endmodule

Classification: 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 whenBelongs in
declareda dimension is addedthe model's source
legalthe protocol or the MTU changesa checked-in constant, diffed
reachablea configuration boolean changesthe 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

Six coverage dimensions over this media access controller give eight hundred and sixty thousand one hundred and sixty declared cells: seven size buckets, sixty four partial word residues, sixty four beat start offsets, three virtual local area network tag counts, five error classes and two values for whether a beat carried two frames. Two exclusions follow from definitions alone. First, a frame's wire length decides both its bucket and its residue, so the pair is not free: enumerating every legal length gives three hundred and eighty four reachable pairs of four hundred and forty eight, and the missing sixty four are almost all in bucket zero, which is a single frame size of sixty four octets and therefore has exactly one residue. Second, the error class depends on the bucket: undersize occurs only in bucket zero and oversize only in bucket six, so twelve of the thirty five bucket error pairs cannot occur. Together those remove three hundred and ninety two thousand eight hundred and thirty two cells, leaving four hundred and sixty seven thousand three hundred and twenty eight legal ones, which is fifty four point three per cent. A third reduction is not the protocol's but the testbench's: a loopback topology reaches only sixteen of the sixty four beat offsets, because the transmit path lane aligns every frame, so the reachable set is one hundred and sixteen thousand eight hundred and thirty two cells, thirteen point six per cent of declared and twenty five per cent of legal.Declared860 160 cellsBucket x residue384 of 448 pairsBucket x error23 of 35 pairsBucket 0 is onesizeone residue, 63illegalLegal467 328 — 54.3%Loopback: 16offsets19.3's lane alignmentReachable116 832 — 13.6%25.0% of legalthe stable denominator12
Figure 1 — 860 160 declared, 467 328 legal, 116 832 reachable, and two exclusions that need no simulation.

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.

CellsFactor
7 × 64 × 64 × 3 × 5 × 2860 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
product448
reachable384
share85.7%

And the 64 missing pairs are not spread evenly.

BucketResidues reachable
0 — exactly 64 octets1
1 — 65 to 12763
2 to 664 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
product35
legal23
share65.7%

Undersize exists only in bucket 0 and oversize only in bucket 6Chapter 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:

CellsOf declared
declared860 160100%
legal467 32854.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.

TopologyOffsetsReachable cellsOf declaredOf legal
loopback16116 83213.6%25.0%
with an injector64467 32854.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 160and 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.

ClaimTrue?
"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

The cyclic redundancy check engine's correction barrel has six stages, and which of them run is decided by the bits of sixty four minus the residue. So the stage set is a function of the residue: residue one gives a shortfall of sixty three and selects all six stages, residue sixty gives a shortfall of four and selects one. Crossing residue against stage set therefore has four thousand and ninety six declared cells and exactly sixty four reachable ones, one point six per cent. Adding the stage set as a seventh coverage dimension takes the whole model's declared space from eight hundred and sixty thousand one hundred and sixty cells to fifty five million fifty thousand two hundred and forty, leaves the legal space at four hundred and sixty seven thousand three hundred and twenty eight, and drops the reported percentage from ten point seven to zero point one seven where it stays for ever. The bit array that records which cells have been hit grows from one hundred and five kilobytes to six point six megabytes. The information the extra dimension appeared to add was already present: the two runs that a residue only model supposedly could not distinguish are covered residue one and covered residue sixty, which the residue dimension separates perfectly. The test that prevents the mistake is to sample both candidate dimensions together and check whether any value of the first ever appears with two values of the second.Residue64 valuesShortfall = 64 -jits binarydecompositionStage setone per residueResidue 1all six stagesResidue 60one stage64 of 4 096 pairs1.6%Declared x 6455 050 240 cellsReachableunchanged467 328105 KiB to 6.6MiBfor nothing12
Figure 2 — adding the barrel stage multiplies the declared space by sixty-four and the reachable space by one.

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-set64 × 64 = 4 096
reachable64
share1.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, declared860 160
seven dimensions, declared55 050 240
seven dimensions, legal467 328 — unchanged
apparent coverage of the seven-dimension model0.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 oneand 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.

ResidueShortfallStages selected
163all six
604one
3232one
631one

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.

CandidateRelationship to the residue**Declared × **
stage-seta function of it64
shortfall 64 − ja bijection with it64
number of stages selecteda function of it7

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 fourso gap × length is 4 × 1 455 declared and 1 455 reachable, and the same argument applies.


6. RTL 2 — The Dependency Detector

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// 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

endmodule

Classification: 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

An open coverage bin has one of three causes and they need three different responses. A bin is illegal when no conformant design can produce it, such as an undersize error on a fifteen hundred and eighteen octet frame: the response is to exclude it and stop counting it, and the test is answerable at elaboration from the protocol's definitions. A bin is unreachable when it is legal and this testbench cannot produce it, such as a beat start offset that is not a multiple of four in a loopback topology: the response is a change to the testbench, and the test needs a model of the injection path. A bin is uncovered when it is neither of those: the response is more simulation or a reweighted distribution. For this model in a loopback the three sets are three hundred and ninety two thousand eight hundred and thirty two illegal, three hundred and fifty thousand four hundred and ninety six unreachable, and one hundred and sixteen thousand eight hundred and thirty two coverable. The unreachable set is three times the coverable one, so a team working an open bin list in order spends three quarters of its effort on bins that belong to whoever configures the environment, and nothing in the list says so.An open binone list, three causesIllegal392 832 — 45.7%Unreachable350 496 — 40.7%Uncovered116 832 — 13.6%Exclude itelaboration,definitionsChange thetestbenchthe injector, 4.00xRun or reweightthe only one that isyoursThree ownersand one reportUnreachable is 3xcoverablethe list is misleading12
Figure 3 — three causes for an open bin, three owners, and a tool that reports them identically.

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.

CauseTestResponse
illegalcan any conformant design produce it?exclude and never count it again
unreachablecan THIS testbench produce it?change the testbench
uncoveredneither of the aboverun 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.

CellsShare of declared
illegal392 83245.7%
unreachable in a loopback350 49640.7%
possible to cover116 83213.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.

ObservationLikely cause
all open bins share one bucketthat frame size is not being generated
all open bins share one error classa knob is off — Chapter 20.1 §4's allow_illegal
all open bins have an odd offsetthe topology — Section 8
open bins are scatteredgenuinely 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 doeswhich 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 settwo functions, at elaboration — Section 3
the reachable seta topology model — Section 8
the differencethe 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// 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

endmodule

Classification: 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.

ChangeCost to makeReachable cells gained
enable the injectora two-bit counter+350 496
enable jumboa configuration register+98 304
enable illegal sizesa boolean+24 960
add a second queuea design configuration0 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// 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

endmodule

Classification: 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// 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);

endmodule

Classification: 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 producessomething 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 residueand 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 checkeda bin fills whether or not an assertion or a scoreboard looked
whether an open bin is worth closingthat is an engineering judgement about risk
whether the dimensions are the right onesSection 2's six came from six chapters' findings
whether anything was foundcoverage 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 coveragethe case occurred
fired_pct_x10a property evaluated
octet_coverage_pctthe 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 cross860 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 KiBand 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.

ApproximationCostWhat it says
per-bin checked flag105 KiBexactly which bins were unchecked
per-bucket checked share7 counterswhich frame sizes were unchecked
a single checked share1 counterhow 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 countsand a plan that reports only the third has measured its own thoroughness and not the design's correctness.


12. RTL 6 — The Closure Reporter

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// 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

endmodule

Classification: 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// 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);

endmodule

Classification: 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 themwhich 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.

ChapterThe number that weakens its own passTypical value
Chapter 20.1 §15lists_complete, five bits5'b00011 on a default run
Chapter 20.2 §15fired_pct_x10below 70% on a directed test
Chapter 20.3 §14octet_coverage_pct23% on control traffic
this chapterpct_of_legal19.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 itand 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 neverBecauseSymptom
1report one percentageSection 4 — three denominators, a factor of four aparta reader assumes the wrong one
2merge illegal with unreachableSection 7 — different ownersweeks on bins nobody can close
3count an illegal point as coveredSection 10a bin that should never close, closes
4add a dependent dimensionSection 5 — 64× declared, 1× reachablea percentage that never moves
5sample the stimulus objectChapter 20.1 §13the model records the request, not the delivery
6treat a filled bin as a checked caseSection 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 oneand 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 onea single percentage is not wrong; it is ambiguous by a factor of four
row fivethe 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// 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

endmodule

Classification: 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.

BlockFlopsStorage
bin_space_calc0 — elaboration only448 bits
dependency_detector~604 096 bits per pair
reachability_model~40
cross_sampler~120448 bits
illegal_bin_filter~30448 bits, precomputed
closure_reporter~200860 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.

ModelDeclared cellsBit array
six dimensions860 160105 KiB
seven, with the stage-set55 050 2406.6 MiB
reachable either way467 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 frameone index computation, one bit write
frames per second at 100 Gb/s148.81 M
against Chapter 20.2's 1 848 property evaluations per cyclenegligible

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.

DimensionValuesRemoving it divides the space by
residue6464
offset6464
bucket77
error55
tags33
dual22

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:

ChapterFlopsStorage
Chapter 20.1 — the generator~1 050
Chapter 20.2 — the assertion library~730
Chapter 20.3 — the scoreboards~1 760816 octets
this chapter — the coverage model~650~106 KiB
subtotal~4 190~106 KiB
Module 19's datapath, for comparison~14 166 flops55 KiB

Four chapters of verification environment at 29.6% of the datapath's logic and twice its memoryand 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.

#AssumptionOwnerIf wrong
1the seven buckets are Chapter 19.7 §2'sRMONSection 10's 448-bit exclusion set is wrong
2the 64 residues are Chapter 19.4 §4'sthe datapath widthevery residue bin moves
316 offsets in a loopbackChapter 19.3 §6's lane alignmentSection 8's 4× is wrong
4the sampler reads the wireSection 9the model records the request
5the dimensions are independentSection 6's detectorSection 5's 64× inflation
6the exclusion set is regenerateda scriptit silently stops matching the design
7the MTU is 9 000configurationbucket 6 does not exist
8a filled bin means the case occurredthe sampler's placementit means the case was requested
9somebody reads three percentagesa humanSection 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.

CauseChanges with
illegalnever — the protocol
unreachablethe testbench's topology
uncoveredmore simulation
out of configurationa 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 assumedWhy not
that the six dimensions are the right onesthey came from six chapters' findings, and a seventh chapter would add a seventh
that closing the cross means the design worksSection 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.

StatementLicensed 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 sayIt does not say
the case occurredthat anything checked it
the stimulus reached itthat the design handled it correctly
the bin filled at least oncethat 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.

PartFrom
what fraction of the reachable cross occurredthis chapter
what fraction of the bound properties evaluatedChapter 20.2 §15
what fraction of each frame was comparedChapter 20.3 §14
which of the five lists Module 19 left were reachedChapter 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.

OrderNumberIf it is low, the ones after it are meaningless
1Chapter 20.1 §15's five-bit listthe cases were never produced
2this chapter's reachable percentagethe cross was not closed
3Chapter 20.2 §15's fired percentagethe properties did not evaluate
4Chapter 20.3 §14's compared sharethe 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 countsand the four measurements that would make a closure claim honest exist, are cheap, and are not joined.


19. The Three Percentages, Worked

Take a loopback regression that has covered everything it can: one hundred and sixteen thousand eight hundred and thirty two cells. Reported against the reachable set that is one hundred per cent, against the legal set twenty five per cent, and against the declared set thirteen point six per cent. All three are true and a reader assumes whichever denominator they expect. Now enable the alignment injector, which is a two bit counter that adds zero to three idle octets between the transmit output and the receive input, restoring the forty eight beat offsets the transmit path's lane alignment removes. The reachable set grows fourfold to four hundred and sixty seven thousand three hundred and twenty eight. The covered set does not change. The same run now reports twenty five per cent against reachable, twenty five per cent against legal and thirteen point six per cent against declared. So the reachable percentage fell from one hundred to twenty five because the testbench improved, the legal percentage did not move at all, and the declared percentage did not move either. A status report that shows only the first number describes a fourfold improvement as a regression, and the legal percentage is the one a closure target should be set against because it is invariant to the topology.One run116 832 coveredOf reachable100.0%Of legal25.0%Of declared13.6%Enable theinjectora two-bit counterOf reachable:25.0%FELL — denominator x4Of legal: 25.0%unchangedOf declared:13.6%unchangedSet targets onlegaltopology-invariant12
Figure 4 — the same covered set, three denominators, and only one of them survives a topology change.

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
declared860 160
illegal — Section 4's two exclusions392 832
legal467 328
unreachable in this topology350 496
reachable116 832
covered116 832

Three reports of the same run:

DenominatorPercentageWhat it means
reachable100.0%this testbench has done all it can
legal25.0%a quarter of what the protocol permits
declared13.6%an eighth of what the model lists

And now enable Chapter 20.1 §10's injector and rerun.

Cells
reachable467 328
covered, immediately after the change116 832
reported against reachable25.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 injectorAfter
of reachable100.0%25.0%
of legal25.0%25.0%
of declared13.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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.

#ScenarioExpected
1the six dimensions as declared860 160 cells
2the bucket-residue exclusion384 of 448 pairs
3the bucket-error exclusion23 of 35 pairs
4both appliedlegal 467 328 — 54.3%
5loopback, 16 offsetsreachable 116 832 — 13.6%
6with the injector, 64 offsetsreachable 467 328 — 54.3%
7allow_illegal clear3 error classes; reachable falls 1.06×
8jumbo disabled6 buckets, 320 pairs; reachable falls 1.27×
9all three limits at oncereachable 92 160 — 10.7%
10MTU 1 518bucket 6 impossible — a fourth category
11a seventh dimension addeddeclared 55 050 240; legal unchanged

Row nine is the number worth having on hand. A default environment — loopback, legal sizes only, no jumboreaches 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.

#ScenarioExpected
12residue against stage-set64 pairs of 4 096 — dependent
13frame length against gap1 455 of 5 820 — dependent
14residue against offsetmany pairs — independent
15bucket against residue384 of 448 — nearly dependent
16fewer than 10 000 samplesno verdict
17one A value with two B valuesnot a function
18a bijectiondependent in both directions
19pair_pct_x10 at 161.6% — strongly dependent
20pair_pct_x10 at 60060% — independent
21the test run before the model is writtenfifteen 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.

#ScenarioExpected
22a 64-octet framebucket 0, residue 60
23a 20-octet request, paddedbucket 0, residue 60
24the same sampled from the itembucket 0, residue 16 — wrong
25a 1 518-octet framebucket 5, residue 42
26a 1 519-octet framebucket 6, residue 43
27a 9 000-octet framebucket 6
28an untagged frametags 0
29a double-tagged frametags 2
30a dual-frame beatdual set
31a runt with allow_illegalbucket 0, ERR_UNDER
32a runt without itnever occurs
331 000 frames, c_distinctat most 384

And the bucket-residue relationship is worth checking directly, because it is the model's only non-trivial exclusion.

BucketLengths it spansResidues it reaches
064 only1 — residue 60
165 to 127 — 63 sizes63
2128 to 255 — 128 sizes64
3 to 5256 or more64 each
61 519 to 9 00064

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.

#ScenarioExpected
34bucket 0, residue 17illegal — pair_possible false
35bucket 3, residue 17legal
36bucket 6, ERR_UNDERillegal
37bucket 0, ERR_UNDERlegal
38an illegal point observedmodel_arithmetic_wrong
39an illegal point counted as covereda bin closes that never should
40the exclusion set regenerated after a bucket changeit differs — the diff is the alarm
41covered = reachable100% of reachable, 25.0% of legal
42the injector enabled after that25.0% of reachable — the same run
43pct_of_legal across the change25.0% both times — stable
44dominant_open_reason on a fresh run1 — illegal is the largest set
45the same after closure2 — 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.

#ScenarioExpected
46c_distinct at 200a narrow size distribution
47c_distinct at 400model_arithmetic_wrong — above 384
4890% of reachable, 1 point per binclosure_fragile
4990% of reachable, 1 000 points per binclear
50checked_share_pct at 60covered_not_checked
51the injector offtopology_limiting, factor 1 — worth 4.00×
52jumbo offfactor 2 — worth 1.27×
53allow_illegal clearfactor 3 — worth 1.06×
54everything enabledfactor 0
55a stage-set dimension addedmodel_inflated
56sampling from the itemsampling_wrong
57one percentage reporteddenominator_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.

FailureNeedsA longer run gives
the 4× denominator changeenabling the injectornothing
the inflated modeladding a dependent dimensionnothing
the wrong sampling pointwiring the sampler to the itemnothing
the fragile closurea narrow distribution and a seed changeit hides the fragility
the unchecked binsunbinding the assertionsnothing

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.

RunConfigurationExercises
Aloopback, legal sizes, no jumbothe default; 92 160 reachable, 10.7%
Bthe same, plus the injectorreachable × 4.00; the percentage falls
Ceverything enabledreachable 467 328; limiting_factor = 0
Da stage-set dimension addeddeclared 55 050 240; the percentage divided by 64
Ethe sampler wired to the stimulus itemresidue 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:

CheckABCDE
reachable92 160368 640467 328467 32892 160
pct_of_reachable100.025.019.719.7100.0
pct_of_legal19.719.719.70.1719.7
point.residue on a padded frame6060606016

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

CheckIf yesMeaning
did reachable change?the denominator grewSection 19
was the injector enabled?4.00× — Chapter 20.1 §10a testbench improvement
did covered change?it did notconfirms — 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."

CheckIf yesMeaning
model_inflated set?a dependent dimension was addedSection 5 — 64× declared, 1× reachable
dominant_open_reason = 2?the open list is unreachable binsa testbench change, not more simulation
dominant_open_reason = 1?illegal bins are being countedSection 10's filter is missing
limiting_factor non-zero?a configuration is capping itand 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."

CheckIf yesMeaning
checked_share_pct low?bins filled with no check boundSection 11
points_per_bin near 1?the closure is coincidentalclosure_fragile
is Chapter 20.2 §15's fired_pct_x10 low?properties never evaluatedthe cases occurred and nobody looked
is Chapter 20.3 §14's octet_coverage_pct low?the frames were barely compared23.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."

CheckIf yesMeaning
are they illegal?Section 10's filter never ranexclude them
bucket 0 with a residue other than 60?63 such binsdefinitively illegal
a residue with an impossible bucket?64 pairs of 448the same cause
an offset that is not a multiple of four?48 of 64unreachable, 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."

CheckIf yesMeaning
points_per_bin near 1?most bins were hit exactly onceclosure_fragile
did the seed change?a hundred bins reopenedconfirms
did the frame-size distribution change?Chapter 20.1 §17's gcda fixed size can collapse the offsets
does a longer run stabilise it?yes, and it hides the fragilitynot 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."

CheckIf yesMeaning
c_distinct above 384?the sampler is computing a bucket or a residue wronglySection 13
an illegal point observed?model_arithmetic_wrongand a bin closed that never should
did a bucket boundary change?the exclusion set is staleSection 10's diff
is the MTU different from the model's?bucket 6 exists or does notSection 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:

SymptomBlamed onUsually is
a percentage that regressedthe stimulusa denominator that grew
a percentage that will not movethe seedsa dependent dimension, or unreachable bins
"high coverage, still buggy"the coverage modelthe checks, measured elsewhere
bins open since the model was writtenthe stimulus63 of them in bucket 0, illegal
coverage that varies day to daythe simulatorone 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 loopbackso 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 frameChapter 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 listChapter 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 KiBsaying 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 ita 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

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.