Skip to content
VLSI Mentor

Ethernet · Module 20

Packet Generation

A weight is a per-frame marginal, so it reaches a frame's own properties and nothing else — and 48 of the parser's 64 alignment offsets are unreachable from the transmit side at any weight.

Module 19 built a MAC and, in doing so, left five lists of cases that its own chapters proved random stimulus does not reach. This chapter builds the generator, and the first thing it has to establish is that the five lists are not one problem.

The caseChapterReached by a weight?
64 partial-word residue classesChapter 19.4 §4yes — 303.6 frames uniform
23 sizes that spill AND fill the deficitChapter 19.3 §20yes — and one weight buys 31.6×
runts under 16 octetsChapter 19.7 §7yes, once illegal sizes are enabled
a specific dual-frame beat offsetChapter 19.4 §7PARTLY — 16 of 64, and only 16
four simultaneous read reorderingsChapter 19.6 §21NO — at any weight

Rows four and five are the chapter. A constrained-random weight is a marginal distribution over one frame's own properties, and two of the five cases are not properties of a frame at all: one is a relationship between a frame and everything that came before it, and one belongs to a different agent entirely.

No amount of weighting reaches them, and the useful question is not how to weight harder but what kind of thing each case is.


1. Scope, and Five Lists Module 19 Left Behind

Chapter 19.1 through Chapter 19.7 each ended by naming a case its own verification would not produce. This chapter's job is to produce all of them, and its finding is that they need three different mechanisms.

What this chapter owns: the frame item and its constraints; the size distribution and the weights that reach the marginal cases; the phase tracker that makes the joint cases addressable; the injection point that decides which alignment offsets exist at all; the interconnect stressor for the cases that are not about frames; and the directed sequences for what none of the above reaches.

What it does not own: the checking — Chapter 20.3's scoreboards — the assertions — Chapter 20.2 — or the coverage model that says whether any of this worked, which is Chapter 20.4. This chapter produces stimulus and measures only whether it produced what it intended.

And it does not own the design. Every number here is quoted from Module 19 and re-derived; the generator is correct if it reaches the cases those chapters identified, and nothing in it depends on the MAC being right.

One boundary is worth stating early, because it changes the architecture. A generator can be attached at two places:

Injection pointDrivesControls
the transmit descriptor ringChapter 18.4's DMAframe contents; NOT the wire alignment
the receive wireChapter 19.2's parser directlyeverything, including alignment

Row one cannot produce 48 of the parser's 64 alignment offsets and Section 9 is why. Chapter 19.3 §6's gap mechanism lane-aligns every transmitted frame, so a generator that drives transmit and loops the wire back sees only start offsets that are multiples of four. That is a property of the design under test, not of the generator, and it is the clearest example in this chapter of a case that a weight cannot reach because the stimulus path removes it.


2. The Three Classes of Reachability

A constrained random generator draws one item at a time from a distribution over that item's own fields, which puts every target case into one of three classes. A marginal case is a property of one frame by itself: its length, its length modulo sixty four, its tag count, whether its check sequence is corrupted. A weight targets these directly, and for Chapter 19.3's twenty three spill sizes a fifty per cent weight reduces coupon collection from five thousand four hundred and thirty three frames to one hundred and seventy two, a factor of thirty one point six, after which the ceiling is eighty six frames and no further tuning helps. A joint case is a property of a frame together with everything that preceded it: the beat phase is a running sum of every previous length and gap modulo sixty four, so a weight shifts its distribution and cannot select a value. A foreign case is a property of a different agent entirely: whether four of eight outstanding read responses arrive out of order is a parameter of the bus functional model, and no field of a frame touches it. The three classes close in an hour, a day and a week respectively, so identifying the class is worth more than any amount of tuning.One item at atimea distribution overits fieldsClass A —marginallength, tags, errorsClass B — jointthe beat phaseClass C — foreignthe bus model's qA weight works31.6x, then a ceilingA sequence or atopologynot a weightAnother agent'sknobnothing elseAn hourtune and rerunA daychange the testbenchA weekanother team's modelhole_class, 3bitscomputed, notremembered12
Figure 1 — three kinds of unreachable case, three different people who can fix them.

A constrained-random generator produces one item at a time from a distribution over that item's fields. That sentence contains the whole taxonomy.

ClassThe case is a property ofA weight canExample
A — marginalone frame, by itselftarget it directlysize mod 64
B — jointa frame AND its predecessorsshift a distribution, not hit a targetthe beat phase
C — foreigna different agentnothing at allinterconnect reordering

Class A is what constrained random was built for and it is most of the work. A frame's length, its address type, its VLAN tags, its EtherType, whether its FCS is corrupted — all are fields of the item, all are constrainable, and a weight moves the distribution wherever it is wanted. Section 4.

Class B is where generators quietly fail. The beat phase — which octet of a 64-octet beat a frame starts on — is a function of every preceding frame's length and gap, accumulated modulo 64. A weight on this frame's length shifts the phase distribution and cannot select a phase, because the phase was decided before this item was randomised. Section 7.

Class C is where generators fail loudly and then get blamed. Chapter 19.6 §21's four-simultaneous reordering is a property of the interconnect model: it happens when four of the eight outstanding read responses arrive before the one the design is waiting for. Nothing about the frames decides that. Section 11.

Put numbers on the three and the difference stops being philosophical.

Class AClass BClass C
example targetall 23 spill sizesa start offset of 374 responses buffered
uniform random5 433 framesnever — see Section 91 in 5 166, at q = 0.05
with the best weight172 framesunchangedunchanged
what does reach itthe weighta different injection pointa knob on another agent

Row three is the finding. A weight buys 31.6× on the class A case and nothing on the other two — and a verification plan that responds to poor coverage by tuning weights will converge on the class A numbers and stall on the others, which is the shape of a coverage closure that plateaus at 90% and stays there.

And row four is the chapter's structure. Three classes, three mechanisms, and Sections 5, 10 and 12 build one each.


3. RTL 1 — The Frame Item

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// pktgen_pkg -- the frame item and the constraints that reach Module
// 19's class A cases. Sections 3 through 6.
//
// Every field here is a property of ONE frame, which is exactly the set
// a weight can target. The fields that are NOT here -- the beat phase,
// the interconnect's reordering -- are Sections 7 and 11, and they are
// absent from the item because they are not properties of it.
// ---------------------------------------------------------------------
package pktgen_pkg;

  localparam int DATA_B     = 64;
  localparam int MIN_FRAME  = 64;
  localparam int MAX_FRAME  = 1518;
  localparam int JUMBO_MAX  = 9000;
  localparam int FCS_OCTETS = 4;

  typedef enum logic [1:0] {
    ADDR_UNICAST = 2'd0, ADDR_MULTICAST = 2'd1, ADDR_BROADCAST = 2'd2
  } addr_kind_e;

  typedef enum logic [1:0] {
    ERR_NONE = 2'd0, ERR_FCS = 2'd1, ERR_ALIGN = 2'd2, ERR_TRUNCATE = 2'd3
  } err_kind_e;

  class eth_frame;

    rand int unsigned  length;        // octets on the wire, excluding preamble
    rand int unsigned  tags;          // 0, 1 or 2 -- Chapter 13.2
    rand addr_kind_e   addr_kind;
    rand err_kind_e    err_kind;
    rand bit           allow_illegal; // runts and giants -- Chapter 19.7

    // Knobs. A weight is a per-frame marginal and these are all of them.
    int unsigned w_min      = 10;   // exactly MIN_FRAME
    int unsigned w_max      = 10;   // exactly MAX_FRAME
    int unsigned w_spill    = 50;   // Chapter 19.3 Section 20's 23 sizes
    int unsigned w_uniform  = 25;   // anything legal
    int unsigned w_runt     =  5;   // Chapter 19.7 Section 7
    int unsigned cfg_mtu    = MAX_FRAME;

    // Chapter 19.3 Section 20: a size whose pre-FCS length is 63 modulo 64
    // spills the check value across a beat AND whose wire length is 3
    // modulo 4 fills the deficit. Both conditions reduce to
    // length == 3 (mod 64), and there are 23 such sizes between 64 and
    // 1518 -- 1.6% of the range. Section 6 prices the weight.
    constraint c_spill_set {
      solve err_kind before length;
      (w_spill > 0) -> soft (length % 64 == 3);
    }

    constraint c_legal {
      allow_illegal == 0 -> length inside {[MIN_FRAME:cfg_mtu]};
      allow_illegal == 1 -> length inside {[5:JUMBO_MAX]};
    }

    // Chapter 13.2's tags move every field after them, which is what
    // Chapter 19.2 Section 4's offsets depend on. Tagging is a marginal
    // property and therefore weightable.
    constraint c_tags { tags inside {[0:2]}; }

    constraint c_room_for_tags {
      length >= MIN_FRAME + 4 * tags || allow_illegal == 1;
    }

    // The distribution. This is the whole of what a weight can express:
    // a marginal over this frame's own length.
    constraint c_distribution {
      length dist {
        MIN_FRAME                  := w_min,
        cfg_mtu                    := w_max,
        [MIN_FRAME+1 : cfg_mtu-1]  :/ w_uniform,
        [5 : MIN_FRAME-1]          :/ w_runt
      };
    }

    function string describe();
      return $sformatf("len=%0d tags=%0d addr=%s err=%s resid=%0d",
                       length, tags, addr_kind.name(), err_kind.name(),
                       (length - FCS_OCTETS) % 64);
    endfunction

  endclass

endpackage

Classification: the stimulus item, and the boundary of what constrained random can address.

What it teaches: that the item's field list is the reachability taxonomy. Every class A case from Section 2 is a field here; no class B or class C case is, and that is not an omission — the beat phase is not a property of a frame and cannot be constrained on one. A generator whose item carries a start_offset field has lied about where the offset comes from, and the constraint solver will happily solve for a value the injection path then discards.

And it teaches that c_spill_set collapses two conditions into one. Chapter 19.3 §20 needed a size whose pre-FCS length is 63 mod 64 and whose wire length is 3 mod 4. Both reduce to length ≡ 3 (mod 64), which is a single constraint over 23 values — and writing it as two constraints produces the same solutions more slowly and reads as if there were two independent conditions.

Deliberately simplified: c_spill_set uses soft, so it yields to c_distribution whenever the distribution's weights are incompatible — which is what makes the two coexist and is also how a mis-set w_spill silently stops reaching the 23 sizes. allow_illegal gates runts through the length range rather than through a separate constraint block, so a run with it clear never generates a frame under 64 and the Chapter 19.7 §7 cases are absent without any warning. And the payload contents are not modelled at all, which matters for Chapter 19.2's parser and is Chapter 20.3's problem.

Production implication: describe() prints the residue, and that is the field a triage engineer actually needs. A failure log that says len=1475 requires somebody to compute (1475 − 4) mod 64 = 63 before the failure means anything; a log that says resid=63 names Chapter 19.4 §4's spill path directly. The cheapest thing a generator can do for debugging is print the derived quantity the design branches on, not the quantity the generator randomised.


4. Marginal Cases: What a Weight Can Do

Class A is the easy class and it is worth measuring rather than assuming, because the measurements are the argument for the other two sections.

Chapter 19.4 §4's sixty-four residue classes, under a uniform length distribution:

Value
classes64
legal pre-FCS lengths1 455
each class's shareabout 1/64
coupon collection, expected64 × H(64) = 303.6 frames

Three hundred frames, which is nothing — and it is why Chapter 19.4 §21's scenario 52 says a 1 000-frame random run "usually" covers all 64. The word doing the work there is "usually", and it is the difference between a distribution and a target.

Chapter 19.3 §20's twenty-three sizes are harder by a factor of eighteen.

UniformWith w_spill at 50%
target sizes2323
probability per frame23 / 1 455 = 1.58%50%
coupon collection5 433 frames172 frames
speedup31.6×

Row four is what a weight buys, and 31.6× is the whole of it. It is a real saving and it is bounded: the weight cannot do better than putting all the probability on the target set, at which point the cost is coupon collection over 23 items and nothing further is available.

Chapter 19.7 §7's runts are a knob rather than a weight.

allow_illegal = 0allow_illegal = 1, w_runt = 5%
sizes below 64none, ever43 of them
frames to cover all 43infinite3 741
what changeda boolean, not a weight

Row three is the distinction that matters for a verification plan. The runt cases are class A — a runt is a property of one framebut they are behind a gate that a weight cannot open, because the legal-length constraint excludes them entirely. A coverage report showing zero runt coverage is not a weighting problem and no amount of weight tuning moves it, which is the same symptom as a class B or C failure and a completely different cause.

Which gives the first of this chapter's three diagnostic questions.

If coverage of a case is zero rather than low, ask whether a constraint excludes it before asking about weights.


5. RTL 2 — The Size Distribution

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// size_distribution -- the weights, made observable. Sections 4, 5 and 6.
//
// A weight that is set and never measured is a hope. This block counts
// what the generator actually produced against what the weights asked
// for, because the most common generator failure is a soft constraint
// that lost to a hard one and a distribution that silently collapsed.
// ---------------------------------------------------------------------
module size_distribution
  import pktgen_pkg::*;
#(
  parameter int TARGET_SPILL_PCT = 50,
  parameter int TOLERANCE_PCT    = 5
) (
  input  logic              clk,
  input  logic              rst_n,

  input  logic              frame_valid,
  input  logic [15:0]       frame_length,
  input  logic              allow_illegal,

  output logic [31:0]       c_frames,
  output logic [31:0]       c_spill_sizes,
  output logic [31:0]       c_min,
  output logic [31:0]       c_max,
  output logic [31:0]       c_runts,
  output logic [63:0]       residues_seen,
  output logic [6:0]        residues_count,

  output logic [15:0]       spill_pct_x10,
  output logic              distribution_collapsed,
  output logic              residues_incomplete
);

  logic [5:0] resid;
  int unsigned popcnt;

  // Chapter 19.4 Section 4's class: the pre-FCS length modulo 64.
  assign resid = 6'((frame_length - 16'(FCS_OCTETS)) % 16'd64);

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      c_frames <= '0; c_spill_sizes <= '0; c_min <= '0; c_max <= '0;
      c_runts <= '0; residues_seen <= '0;
    end else if (frame_valid) begin
      c_frames <= c_frames + 1;
      residues_seen[resid] <= 1'b1;

      // Chapter 19.3 Section 20's set: length congruent to 3 modulo 64.
      // 23 sizes between 64 and 1518.
      if ((frame_length % 16'd64) == 16'd3) c_spill_sizes <= c_spill_sizes + 1;

      if (frame_length == 16'(MIN_FRAME)) c_min <= c_min + 1;
      if (frame_length == 16'(MAX_FRAME)) c_max <= c_max + 1;
      if (frame_length < 16'(MIN_FRAME))  c_runts <= c_runts + 1;
    end
  end

  assign spill_pct_x10 = (c_frames == 0) ? 16'd0
    : 16'((c_spill_sizes * 32'd1000) / c_frames);

  // THE check this block exists for. A soft constraint that lost leaves
  // the weight set and the distribution uniform, and the only evidence
  // is that the realised share is 1.6% instead of 50%.
  assign distribution_collapsed =
    (c_frames > 32'd1000) &&
    (spill_pct_x10 < 16'((TARGET_SPILL_PCT - TOLERANCE_PCT) * 10));

  always_comb begin
    popcnt = 0;
    for (int i = 0; i < 64; i++) if (residues_seen[i]) popcnt++;
    residues_count = 7'(popcnt);
  end

  assign residues_incomplete = (c_frames > 32'd2000) && (residues_count < 7'd64);

endmodule

Classification: a stimulus monitor, and the only block in this chapter that checks the generator rather than the design.

What it teaches: that distribution_collapsed catches the most common generator bug and nothing else does. A soft constraint that conflicts with a hard one is silently dropped by the solver — no error, no warning, no $displayand the generator continues producing a uniform distribution while w_spill sits at 50 in a configuration file. The realised share is 1.6% instead of 50%, and the only way to know is to count.

And it teaches that measuring the realised distribution is cheap and almost never done. Five counters and a division. A verification environment that tunes weights without measuring them is tuning an input to a system whose output it is not observing, which is a description nobody would accept about the design under test and is routine about the testbench.

Deliberately simplified: resid computes a modulo by 64 as a bit-slice, which is correct because 64 is a power of two and would not generalise to a datapath of 48 octets. distribution_collapsed tests only the spill weight, where a complete version checks every weight against its target with a per-weight tolerance. And the block is a module observing a stream of lengths, so it works equally on generated stimulus and on a capture from a real link — which is what makes it useful for Chapter 19.7 §15's traffic profiling as well.

Production implication: residues_count is the number to put on the regression's summary line, not in a coverage database. Chapter 19.4 §16's residue_classes_untested is the design-side verdict; this is the stimulus-side one, and having both distinguishes "the generator did not produce it" from "the design did not exercise it." Those are different bugs with the same coverage hole, and separating them is worth two counters in two different blocks.


6. The 31.6× a Single Weight Buys, and Its Ceiling

Section 4 measured the speedup. This section is what it costs elsewhere, because a weight is a redistribution and the probability it moves comes from somewhere.

Setting w_spill to 50% puts half the frames on 23 sizes. The other half covers the remaining 1 432.

Uniformw_spill = 50%
the 23 spill sizes, each0.069%2.17%
the other 1 432, each0.069%0.035%
coupon collection over the 235 433 frames172 frames
coupon collection over the 1 43211 233 frames22 466 frames

Row four is the cost and it is exactly a factor of two. The frames that were reaching the general population are now reaching the target set, so general size coverage takes twice as long. That is usually the right trade — the 1 432 ordinary sizes are not individually interestingbut it is a trade, and a plan that sets six weights to 50% has halved the general population six times.

And the ceiling is worth stating because it bounds what weight tuning can ever achieve.

w_spillShare on the 23Frames to cover them
0%1.58%5 433
50%50%172
90%90%95
100%100%86

From 50% to 100% the saving is 86 frameshalf a per cent of the uniform figureand the cost is that no other size is generated at all. So the useful range of the weight is essentially its first step, and a team that spends a week tuning weights from 50 to 90 has bought 77 frames of simulation.

Which gives this chapter's second diagnostic question.

Before tuning a weight, compute the coupon-collection bound at 100%. If the current number is close to it, the weight is finished.

And the reason the question matters is that class B and class C cases look exactly like an untuned weight. A coverage hole that will not close has three possible causes — an excluding constraint, a weight that is not at its ceiling, and a case that is not a marginal at alland only the second responds to tuning. Sections 7 and 11 are the other two.


7. Joint Cases: The Beat Phase and the Deficit

Class B is where a generator's model of the world stops matching the design's, and the beat phase is the clearest example because it is an accumulator.

Chapter 19.2 §3 established that a frame's header can begin at any of 64 offsets within a beat, and that the offset decides how many beats the parse spans. The offset is not random and it is not a property of the frame:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
offset(n+1) = (offset(n) + preamble + length(n) + gap(n)) mod 64

It is a running sum modulo 64, and every term in it belongs to a previous frame.

Decided byWhen
length(n)the generatorwhen frame n was randomised
gap(n)Chapter 19.3 §6's deficitafter frame n's length is known
offset(n+1)the accumulationbefore frame n+1 is randomised

Row three is the problem in one line: the offset of the frame being generated was decided before the generator was asked for it. A constraint on offset in the item class has nothing to solve for — the value is already determined by the stream — and a solver given such a constraint either ignores it or produces an item the injection path cannot honour.

What a weight can do is shift the distribution, and it is worth seeing how weakly.

Length distributionOffset distribution
all frames 64 octets, gap 12period 84; offsets cycle through 16 values
all frames 65 octets, gap 11period 84 again — the deficit compensates
uniform lengthsapproximately uniform over the reachable offsets
any distributionnever selects a single offset

Row two is the part that surprises. Chapter 19.3 §6's deficit chooses the gap to land the next frame on a four-octet lane boundary, so changing the length by one changes the gap by one in the opposite direction and the period is unchanged. The mechanism that makes the interframe gap conformant also makes the offset insensitive to the generator's length choices, which is the least convenient possible interaction.

And the deficit itself is the same shape. Chapter 19.3 §8's accumulator holds 0 to 3 and its value after frame n is a function of every preceding length. A test that needs the deficit at 3 needs a run of frames whose lengths are 3 modulo 4Chapter 19.3 §20's run D — which is a sequence, not a distribution.

TargetClassReached by
a size ≡ 3 mod 64Aa weight — Section 4
the deficit at 3Bfour consecutive such sizes — a sequence
a start offset of 37B, and unreachableSection 9

Row two is the general answer for class B: a sequence, not a weight. A constrained-random generator can emit a scripted run as easily as a random one, and the machinery for it already exists — a sequence that sets length deterministically for four frames and then returns control to the random stream. What it cannot do is arrive at the state by weighting.

Which gives the third diagnostic question.

If a case depends on a running quantity, a weight shifts its distribution and a sequence sets its value. Ask which one the coverage bin needs.


8. RTL 3 — The Phase Tracker

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// phase_tracker -- compute the beat phase the generator cannot control,
// so a sequence can steer toward it and coverage can record it.
// Sections 7, 8 and 9.
//
// The phase is a running sum modulo 64 over every preceding frame's
// length and gap. The generator cannot constrain it; what it CAN do is
// observe it and choose the next frame's length to move it -- which
// turns an unreachable target into a two-step search.
//
// This block is the observation half. Section 10's injector is the
// steering half.
// ---------------------------------------------------------------------
module phase_tracker
  import pktgen_pkg::*;
(
  input  logic              clk,
  input  logic              rst_n,

  input  logic              frame_start,
  input  logic [15:0]       frame_length,
  input  logic [4:0]        gap_octets,

  output logic [5:0]        phase,            // 0..63, this frame's start
  output logic [5:0]        next_phase,       // where the next one lands
  output logic [63:0]       phases_seen,
  output logic [6:0]        phases_count,

  // Observability. Sections 15 and 16.
  output logic [31:0]       c_frames,
  output logic              phase_is_lane_aligned,
  output logic              unaligned_phase_seen
);

  logic [5:0] phase_q;

  // preamble and SFD are 8 octets and are part of the wire period, so
  // they enter the accumulation. Chapter 5.2.
  localparam int PREAMBLE_OCTETS = 8;

  assign next_phase = 6'((32'(phase_q) + 32'(PREAMBLE_OCTETS) +
                          32'(frame_length) + 32'(gap_octets)) % 32'd64);

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      phase_q <= '0; phases_seen <= '0; c_frames <= '0;
      unaligned_phase_seen <= 1'b0;
    end else if (frame_start) begin
      c_frames           <= c_frames + 1;
      phases_seen[phase_q] <= 1'b1;
      phase_q            <= next_phase;

      // Section 9: a transmit path that obeys Chapter 19.3 Section 6's
      // lane alignment produces only phases that are multiples of four.
      // Seeing one that is not means the stimulus is being injected on
      // the RECEIVE side, which is the only place the other 48 exist.
      if (phase_q[1:0] != 2'b00) unaligned_phase_seen <= 1'b1;
    end
  end

  assign phase = phase_q;
  assign phase_is_lane_aligned = (phase_q[1:0] == 2'b00);

  always_comb begin
    phases_count = '0;
    for (int i = 0; i < 64; i++) if (phases_seen[i]) phases_count = phases_count + 7'd1;
  end

endmodule

Classification: an observer of a quantity the generator produces and cannot name.

What it teaches: that observing an accumulator converts it from unreachable to searchable. The generator cannot constrain the phase; it can read next_phase before randomising the next frame and choose a length that lands where it wants. That is a one-step lookahead — the phase is a linear function of the length modulo 64, so the required length is a subtraction — and it turns a class B target into a class A one at the cost of making the generator stateful.

And it teaches that unaligned_phase_seen is a statement about the test bench's topology rather than about the design. A run that never sees an odd phase is injecting stimulus through the transmit path, and 48 of the 64 offsets are structurally absent — Section 9. The bit costs nothing and answers a question that otherwise takes an afternoon of waveform reading.

Deliberately simplified: next_phase uses a modulo by 64 written as %, which synthesises to a bit-slice and is written this way to match the equation in Section 7. gap_octets arrives as an input rather than being derived, so the tracker trusts whoever is measuring the gap — Chapter 19.3 §7's enforcer on transmit, or a wire monitor on receive. And phases_count is a 64-bit population count computed combinationally, needed on read.

Production implication: phases_count against 64 is the coverage number that distinguishes the two injection topologies in one read. Sixteen means transmit-side injection and complete coverage of what that topology can reach; sixty-four means receive-side injection. Anything between means the run was too short — and the three cases have completely different responses: accept it, keep going, or change the testbench. Chapter 19.4 §16's residue_classes_untested could not distinguish them; this can, because it counts a quantity whose ceiling depends on the topology.


9. Sixteen of Sixty-Four

Chapter 19.2's receive parser handles a frame header beginning at any of sixty four offsets within a sixty four octet beat, and its barrel shifter is built in two stages: the first shifts by zero, eight, sixteen and so on up to fifty six octets, and the second shifts by zero to seven. Chapter 19.3's transmit assembler chooses each interframe gap so that the next frame begins on a four octet lane boundary, which is what keeps the mean gap at twelve while every frame lands where the physical layer expects it. The consequence for a testbench is that every frame this media access controller transmits starts at an offset that is a multiple of four, so a loopback topology produces sixteen of the sixty four offsets and never the other forty eight. Worse, a lane aligned offset is a multiple of four, so the barrel shifter's second stage only ever sees shift values of zero and four: six of its eight cases are untested. The fix is not a weight or a longer run. It is an element between the transmit output and the receive input that adds zero to three idle octets, which shifts the phase arbitrarily, keeps the gap above the nine octet floor, and costs a two bit counter.Parser inputspace64 offsets19.3 lane-alignsgap chosen for a4-octet laneLoopback reaches16multiples of 448 never occur75% of the spaceBarrel stage 2shifts 0 to 7Sees only 0 and 46 of 8 untestedAdd 0 to 3 idlea two-bit counterAll 64 restoredand the gap stayslegal12
Figure 2 — the design's own lane alignment removes three quarters of the parser's input space from a loopback.

Chapter 19.2 §3 built a barrel shifter for 64 possible start offsets. A generator driving the transmit path can produce sixteen of them, and the reason is a mechanism the design was required to have.

Chapter 19.3 §6's gap is chosen so that the next frame begins on a four-octet lane boundary. That is not an optimisation: a frame placed off-lane is misaligned by the PHY or has its gap silently stretched, and the deficit accumulator exists to make the lane alignment hold while the mean gap stays at twelve.

So every frame this MAC transmits starts at an offset that is a multiple of four.

Offsets
the parser's input space0 … 63 — 64 values
reachable from the transmit path0, 4, 8, … 60 — 16 values
unreachable from the transmit path48 values — 75%

Three quarters of the parser's alignment space does not exist on the transmit side, at any weight, in any sequence, for any frame length. The mechanism that removes it is in the design under test.

Which means a loopback testbench — transmit into receive, the most convenient topology there is — covers a quarter of Chapter 19.2's barrel shifter.

TopologyOffsets reachableWhat it tests
transmit, looped back16the barrel shift's lane-aligned cases
a wire-level receive driver64all of it
transmit plus a skewing element64if the skew is octet-granular

Row three is the compromise most environments reach without deciding to. Inserting anything between the transmit output and the receive input that changes the octet count — a PHY model, a media converter, a repeaterbreaks the lane alignment and restores the other 48 offsets, and whether it does depends on a component chosen for a different reason.

And the cost of the missing 48 is Chapter 19.2 §3's own arithmetic.

Value
the barrel shifter1 024 byte-muxes, two stages
stage 1 shifts by0, 8, 16 … 56 octets
stage 2 shifts by0 … 7 octets
lane-aligned offsets exercisestage 2 values 0 and 4 only

Row four is the finding. A lane-aligned offset is a multiple of four, so the second stage — which shifts by 0 to 7 — only ever sees 0 and 4. Six of its eight cases are untested in a loopback environment, and they are exactly the sub-lane shifts that a hand-written barrel shifter is most likely to get wrong.

So the joint coverage space is smaller than it looks and its shape is decided by the topology.

CellsCoupon collection
64 residues × 64 offsets4 09636 434 frames
64 residues × 16 offsets1 0247 689 frames
what a loopback run can close1 024 of 4 096 — 25%and then stop

Row three is a coverage report that reaches 25% and stays there through every weight change anybody makes, because the missing 75% is not a weighting problem. The response is a testbench change, and the evidence that it is needed is Section 8's phases_count reading exactly 16.


10. RTL 4 — The Alignment Injector

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// alignment_injector -- put the other 48 offsets back. Sections 9 and 10.
//
// The transmit path lane-aligns every frame -- Chapter 19.3 Section 6 --
// so a loopback topology reaches 16 of the parser's 64 offsets. This
// block sits between the transmit output and the receive input and
// inserts 0 to 3 extra idle octets, which shifts the phase by an
// arbitrary amount and restores the full space.
//
// It is a TESTBENCH component and it deliberately produces gaps that
// Chapter 5.9 permits but this MAC's transmitter would not choose --
// which is the point: the receive path must handle a partner that does
// not lane-align, and no partner is obliged to.
// ---------------------------------------------------------------------
module alignment_injector
  import pktgen_pkg::*;
(
  input  logic              clk,
  input  logic              rst_n,

  input  logic              in_valid,
  input  logic [7:0]        in_octet,
  input  logic              in_gap,

  input  logic [1:0]        cfg_skew_mode,    // 0 none, 1 fixed, 2 random, 3 sweep
  input  logic [1:0]        cfg_fixed_skew,
  input  logic [5:0]        target_phase,

  output logic              out_valid,
  output logic [7:0]        out_octet,
  output logic [1:0]        skew_applied,

  // Observability. Sections 15 and 16.
  output logic [31:0]       c_frames_skewed,
  output logic [3:0]        skews_used,
  output logic              gap_below_min
);

  logic [1:0]  skew_q;
  logic [4:0]  gap_count;
  logic [1:0]  sweep_q;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      skew_q <= '0; gap_count <= '0; sweep_q <= '0;
      c_frames_skewed <= '0; skews_used <= '0; gap_below_min <= 1'b0;
    end else begin
      if (in_gap) begin
        gap_count <= gap_count + 1;
      end else if (gap_count != '0) begin
        // A frame has started. Choose this frame's skew and record it.
        unique case (cfg_skew_mode)
          2'd0: skew_q <= 2'd0;
          2'd1: skew_q <= cfg_fixed_skew;
          2'd2: skew_q <= 2'($urandom_range(3, 0));
          2'd3: begin skew_q <= sweep_q; sweep_q <= sweep_q + 1; end
        endcase
        skews_used[skew_q] <= 1'b1;
        if (cfg_skew_mode != 2'd0) c_frames_skewed <= c_frames_skewed + 1;

        // Chapter 5.9's floor is 9 octets. Adding idle never violates it;
        // REMOVING idle would, and this block only adds -- which is why
        // it can shift the phase without producing an illegal gap.
        if (gap_count < 5'd9) gap_below_min <= 1'b1;
        gap_count <= '0;
      end
    end
  end

  assign out_valid    = in_valid;
  assign out_octet    = in_octet;
  assign skew_applied = skew_q;

endmodule

Classification: a testbench element that exists to remove a property of the design under test from the stimulus path.

What it teaches: that the fix for a class B unreachability is usually topological. No weight and no sequence restores the missing 48 offsets, because the transmit path removes them after the generator has finished. Inserting 0 to 3 idle octets between the transmitter and the receiver shifts the phase by an arbitrary amount and costs a two-bit counter — and the whole difficulty was recognising that the problem was where the stimulus was injected rather than what it contained.

And it teaches that the injector only ever ADDS idle. Chapter 5.9's floor is nine octets; removing idle could violate it and adding never can. So the block shifts the phase without producing a gap the receiver is entitled to reject, which keeps the stimulus legal — and a skewing element that deletes octets instead is testing error handling rather than alignment.

Deliberately simplified: the skew is applied by counting gap octets rather than by actually inserting them, so the listing shows the decision and not the datapath — a real injector holds a small elastic buffer, which is Chapter 4.4's structure in a testbench. $urandom_range in an always_ff is simulation-only, which is correct for this block and would be flagged by any lint. And cfg_skew_mode = 3 sweeps 0, 1, 2, 3 in order, which correlates the skew with the frame index and therefore with everything else the generator is sweeping.

Production implication: skews_used reaching 4'b1111 is the bit that says the other 48 offsets are being produced, and it should be a gate on the regression rather than a line in a coverage report. A run with cfg_skew_mode at zero — the default, and the mode a loopback environment starts in — reports 25% of the alignment cross and no error. The four-bit register costs nothing and distinguishes "we have not covered it yet" from "this testbench cannot cover it", which is the distinction Section 2's three classes exist to make.


11. Foreign Cases: What No Frame Weight Reaches

Class C cases are not properties of the stimulus at all, and the reason they get treated as generator problems is that they show up in the same coverage report.

Chapter 19.6 §21's case is the clearest. The reorder sink holds responses that arrived before the one the design is waiting for; filling a 7 KiB buffer needs four of the seven non-expected responses to arrive early at once. Whether they do is decided by the interconnect model.

Model it. Let q be the probability that a given outstanding response arrives before the expected one — a parameter of the bus functional model, not of the frames.

qP(4 or more buffered)Frames per occurrence
0.050.000191 in 5 166
0.100.002731 in 367
0.200.033341 in 30
0.300.126041 in 8
0.500.500001 in 2

Every row is a different bus functional model and none of them is a different frame stream. A generator can produce a hundred million frames at q = 0 and reach the case never; it can produce a hundred at q = 0.5 and reach it fifty times.

Which is uncomfortable because q is usually not a knob. A clean BFM reorders nothing — Chapter 19.6 §9's reorder_never_seen — and reordering is added, if at all, as an afterthought with a default of zero. So the case is not merely hard to reach; it is absent from the environment, and the coverage hole is in a component nobody was tuning.

The other class C cases in Module 19 have the same shape and different owners.

CaseOwnerKnob
four simultaneous reorderingsthe bus functional modelq, usually absent
Chapter 19.5 §21's 200 ppm driftthe clock generatortwo clock periods that differ
Chapter 19.5 §18's reset orderthe reset sequencerrelease order
Chapter 19.6 §8's 2 µs memory stallthe memory modela stall distribution
Chapter 14.2's PAUSE arrivingthe link partner modela PAUSE generator

Every row is a parameter of an agent the frame generator does not talk to, and every one produces a coverage hole that looks identical to an unweighted marginal. Chapter 19.5 §21's row 47 is the sharpest instance: a testbench whose two clocks come from one source has a drift of zero, so the case is not unreachable by chance but by construction.

So the generator's responsibility for class C is not to produce the case. It is to make the absence visible.

What the generator can do
produce the caseno
report that the environment cannotyes — one bit per agent
fail the run when a required agent is inertyes, and it should

Row three is the position this chapter takes. A regression whose interconnect model has q = 0 should not report a coverage percentage at all for the reordering bins; it should report that the agent producing them is disabled. A percentage invites tuning; a disabled-agent report invites a configuration change, and the two responses differ by weeks.


12. RTL 5 — The Interconnect Stressor

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// interconnect_stressor -- the class C agent for Chapter 19.6's reorder
// cases, and the reporting that says whether it is doing anything.
// Sections 11 and 12.
//
// No property of a frame reaches these cases. What reaches them is a
// probability parameter on a bus functional model, and the whole value
// of this block is that the parameter is VISIBLE -- a run with q at
// zero reports "agent inert" rather than "coverage 0%".
// ---------------------------------------------------------------------
module interconnect_stressor
  import pktgen_pkg::*;
#(
  parameter int MAX_OUTSTANDING = 8,
  parameter int REQUIRED_DEPTH  = 4      // Chapter 19.6 Section 8's fourth
) (
  input  logic              clk,
  input  logic              rst_n,

  input  logic [7:0]        cfg_reorder_q_pct,   // 0 = inert
  input  logic              response_valid,
  input  logic [3:0]        response_id,
  input  logic [3:0]        expected_id,

  output logic              hold_response,
  output logic [3:0]        held_count,

  // Observability. Sections 15 and 16.
  output logic [31:0]       c_responses,
  output logic [31:0]       c_reordered,
  output logic [3:0]        peak_held,
  output logic              depth_reached,
  output logic              agent_inert
);

  logic [3:0] held_q;

  // THE reporting this block exists for. An inert agent is a
  // configuration fact and must not be reported as a coverage
  // percentage -- Section 11.
  assign agent_inert = (cfg_reorder_q_pct == 8'd0);

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      held_q <= '0; c_responses <= '0; c_reordered <= '0;
      peak_held <= '0; depth_reached <= 1'b0;
    end else if (response_valid) begin
      c_responses <= c_responses + 1;

      if ((response_id != expected_id) &&
          (8'($urandom_range(99, 0)) < cfg_reorder_q_pct)) begin
        held_q      <= held_q + 1;
        c_reordered <= c_reordered + 1;
      end else if (response_id == expected_id && held_q != '0) begin
        held_q <= '0;                     // the wait resolves; the slots drain
      end

      if (held_q > peak_held) peak_held <= held_q;

      // Chapter 19.6 Section 8: a 7 KiB buffer holds 3.5 of this
      // datapath's 2 KiB bursts, so the FOURTH simultaneous reordering
      // is the one that overflows an undersized buffer. Reaching four
      // is the coverage goal and reaching seven is the bound.
      if (held_q >= 4'(REQUIRED_DEPTH)) depth_reached <= 1'b1;
    end
  end

  assign hold_response = (held_q != '0);
  assign held_count    = held_q;

endmodule

Classification: a stimulus agent for a dimension the frame generator does not have, and a reporter for its own inertness.

What it teaches: that agent_inert is the most valuable output in the block and it is one comparison against zero. A coverage report showing the reorder bins empty has two causes with opposite responses: the run was too short, or the agent was disabled. A percentage cannot distinguish them and a boolean can, and the cost is one bit. Chapter 19.6 §16's reorder_untested is the design-side statement of the same fact; this is the stimulus side, and having both says which end to fix.

And it teaches that q has a meaningful floor as well as a ceiling. At q = 0.05 the four-deep case appears once in 5 166 responses, which a long regression reaches; at q = 0.5 it appears once in two, which is not a model of any real interconnect. The parameter has to be chosen to reach the case rather than to be realistic, and saying so in the configuration is better than choosing a realistic value and never covering anything.

Deliberately simplified: the reordering decision is independent per response, so the model has no memory and cannot reproduce the correlated reordering a DRAM refresh actually produces — which is the mechanism Chapter 19.6 §8 cited. held_q clears completely when the expected response arrives, where a real sink drains slot by slot. And $urandom_range inside always_ff makes the block simulation-only, which is correct for a stressor.

Production implication: peak_held against REQUIRED_DEPTH is the number a verification lead should read before trusting any reorder coverage. Reaching four proves the undersized-buffer case was exercised; reaching seven proves the bound was. A run reporting peak_held = 1 with c_reordered in the thousands has an agent that reorders often and never deeply, which is the independent-per-response model showing its limitation — and it is the case a correlated model would reach and this one will not, however long it runs.


13. What the Generator Must Never Do

Six prohibitions, and three of them are about honesty rather than correctness.

#Must neverBecauseSymptom
1constrain a quantity it does not controlSection 7 — the phase is decided upstreama solved value the path discards
2report a coverage percentage for an inert agentSection 11weeks of weight tuning
3drop a soft constraint silentlythe solver does it for youSection 5's collapsed distribution
4generate only legal frames by defaultChapter 19.7 §7's runtsa counter path never exercised
5assert on its own generated valuesSection 20's class 88a property that cannot fail
6correlate a sweep with the frame indexSection 10's sweep modetwo dimensions that move together

Row one is the chapter's structural prohibition and it is easy to violate by accident. A rand bit [5:0] start_offset in the item class looks like every other field, solves fine, and is then overwritten by the accumulated phase. The generator reports that it produced offset 37; the design saw offset 12; and the coverage database records whichever one the sampling code happened to read. The two disagree and nothing says so — which is why Section 8's tracker samples the phase from the wire rather than from the item.

Row five is the rejected property and it is worth previewing here because the prohibition is broader than the assertion. Any check whose subject is a value the generator chose — an assertion, a coverage bin's guard, a scoreboard's expectation — is checking the solver. The solver works. Section 20 is the assertion form; the coverage form is a bin that can never be empty, and the scoreboard form is Chapter 20.3's.

Row six is a small, common and under-diagnosed error. Section 10's cfg_skew_mode = 3 sweeps the skew 0, 1, 2, 3 in frame order, so frame n's skew is n mod 4. If anything else in the generator also cycles with a period that shares a factor with four — a size sweep, a tag pattern, an address rotation — the two dimensions are locked and their cross is never covered. The fix is to randomise rather than sweep, or to use a sweep whose period is coprime with everything else's, and the diagnosis is a coverage cross that is exactly 25% full in a diagonal pattern.

And the two prohibitions that look like advice and are not:

Why it is a prohibition
row twoa percentage invites tuning; the fix is a configuration change
row foura default that excludes a case makes its coverage permanently zero

Both produce a coverage hole that responds to nothing, which is the single most expensive failure mode in a verification plan — not because the hole is large but because the effort spent on it is unbounded.


14. RTL 6 and 7 — The Directed Sequence and the Driver

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// directed_sequence -- the class B mechanism: set a running quantity by
// emitting a scripted run, then return to the random stream.
// Sections 7, 13 and 14.
//
// Chapter 19.3 Section 20's run D needs the deficit at 3, which needs four
// consecutive frames whose lengths are 3 modulo 4. That is a sequence
// and not a distribution -- no weight arrives at an accumulator's value.
//
// The block also implements Section 8's one-step lookahead: given the
// tracked phase and a target, it computes the length that lands there,
// which converts a class B target into a class A one at the cost of
// making the generator stateful.
// ---------------------------------------------------------------------
module directed_sequence
  import pktgen_pkg::*;
(
  input  logic              clk,
  input  logic              rst_n,

  input  logic              req,
  input  logic [2:0]        seq_id,
  input  logic [5:0]        current_phase,
  input  logic [5:0]        target_phase,
  input  logic [4:0]        expected_gap,

  output logic              seq_active,
  output logic              length_valid,
  output logic [15:0]       length_out,
  output logic              seq_done,

  // Observability. Sections 15 and 16.
  output logic [31:0]       c_sequences [8],
  output logic [31:0]       c_phase_hits,
  output logic [31:0]       c_phase_misses,
  output logic              target_unreachable
);

  localparam int PREAMBLE_OCTETS = 8;

  logic [2:0]  step_q;
  logic [2:0]  active_id;
  logic [15:0] needed_len;
  logic signed [7:0] delta;

  // Section 8's lookahead. The next phase is
  //   (phase + 8 + length + gap) mod 64
  // so the length that lands on target_phase is a subtraction. The
  // result must then be legal AND reachable, which is why the block
  // reports target_unreachable rather than emitting an illegal length.
  assign delta      = 8'signed'({2'b0, target_phase} -
                                {2'b0, current_phase});
  assign needed_len = 16'(((32'signed'(delta) - 32'(PREAMBLE_OCTETS) -
                            32'(expected_gap)) % 32'sd64 + 32'sd64) % 32'sd64);

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      step_q <= '0; active_id <= '0; seq_active <= 1'b0; seq_done <= 1'b0;
      for (int i = 0; i < 8; i++) c_sequences[i] <= '0;
      c_phase_hits <= '0; c_phase_misses <= '0; target_unreachable <= 1'b0;
    end else begin
      seq_done <= 1'b0;

      if (req && !seq_active) begin
        seq_active <= 1'b1;
        active_id  <= seq_id;
        step_q     <= '0;
        c_sequences[seq_id] <= c_sequences[seq_id] + 1;
      end else if (seq_active) begin
        step_q <= step_q + 1;

        // Sequence 0: Chapter 19.3 Section 20's run D. Four frames of a
        // size that is 3 modulo 4, which drives the deficit to its bound
        // and forces the long gap on the fourth.
        // Sequence 1: the phase lookahead, one frame.
        if ((active_id == 3'd0 && step_q == 3'd3) ||
            (active_id == 3'd1)) begin
          seq_active <= 1'b0;
          seq_done   <= 1'b1;
        end
      end

      // A target phase whose required length is outside the legal range
      // cannot be reached in one step. It is reachable in two -- any
      // phase is, because the step is a full residue system modulo 64 --
      // and this block does not implement the two-step search.
      if (req && (needed_len < 16'(MIN_FRAME)) && (seq_id == 3'd1))
        target_unreachable <= 1'b1;
    end
  end

  always_comb begin
    length_valid = seq_active;
    unique case (active_id)
      3'd0:    length_out = 16'd1519;          // 1519 mod 4 == 3
      3'd1:    length_out = needed_len;
      default: length_out = 16'(MIN_FRAME);
    endcase
  end

endmodule

Classification: the class B mechanism, and the block that makes a running quantity addressable.

What it teaches: that an accumulator is addressable in one step whenever its update is invertible, and the phase's is. phase(n+1) = (phase(n) + 8 + length + gap) mod 64 is linear in length, so the length that reaches a target phase is a subtraction modulo 64. The generator does not have to search; it has to compute — and the only thing standing between it and any of the 64 phases is whether the required length is legal.

And it teaches that target_unreachable is a one-step statement rather than a permanent one. Adding a constant modulo 64 is a full residue system, so every phase is reachable in at most two frames whatever the length constraints — the block reports that it did not implement the search, not that the phase cannot be reached. Saying which is the difference between a testbench limitation and a design property, and Section 9's 48 missing offsets are the latter.

Deliberately simplified: sequence 0 emits a fixed 1 519 rather than randomising within the length ≡ 3 (mod 4) set, so run D exercises the deficit and one size; a complete version randomises within the residue class. The two-step phase search is not implemented, which is the honest reading of target_unreachable. And c_sequences is an unpacked array of counters, needing flattening for a register interface.

Production implication: c_phase_hits against c_phase_misses is the measurement that says whether the lookahead is working, and a miss is not always a bug. The phase is computed from expected_gap, and Chapter 19.3 §6's deficit sometimes chooses a different gap than the one predicted — so a lookahead that assumes 12 and gets 9 lands three octets off. A miss rate near 25% means the gap prediction is ignoring the deficit, which is a testbench that has modelled the design's average rather than its mechanism, and it is the same error Chapter 19.3 §22's complaint 1 describes from the other side.


And the sequence layer is only half of the class B mechanism. The other half is the block that puts an item on the wire, because that is where the generator's intent and the design's behaviour first disagree.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// pktgen_driver -- put an item on the wire and record what the wire
// actually carried. Sections 13, 14 and 18.
//
// The driver is where Section 13's first prohibition is enforced. The
// item says what was asked for; this block reports what was delivered,
// and the two differ legitimately -- Chapter 19.3 Section 2 pads a short
// frame, Chapter 19.4 appends a check value, Chapter 13.2's tags may be
// inserted by the design. A scoreboard that compares them octet for
// octet fails on all three, which is Chapter 20.3's problem.
//
// What this block owns is the OBSERVED side: every property in Section
// 20's group 1 samples these outputs and not the item's fields.
// ---------------------------------------------------------------------
module pktgen_driver
  import pktgen_pkg::*;
(
  input  logic              clk,
  input  logic              rst_n,

  // From the sequence layer.
  input  logic              item_valid,
  input  logic [15:0]       item_length,
  input  logic [1:0]        item_tags,
  input  addr_kind_e        item_addr_kind,
  input  err_kind_e         item_err_kind,
  output logic              item_ready,

  // To the wire.
  output logic              tx_valid,
  output logic [7:0]        tx_octet,
  output logic              tx_sof,
  output logic              tx_eof,

  // What the wire actually carried, for Section 20's group 1.
  output logic              frame_end,
  output logic [15:0]       observed_length,
  output logic [5:0]        observed_resid,
  output logic [31:0]       observed_seq,

  // Observability. Sections 15 and 16.
  output logic [31:0]       c_items_accepted,
  output logic [31:0]       c_frames_driven,
  output logic [31:0]       c_length_changed,
  output logic              padding_observed,
  output logic              item_dropped
);

  logic [15:0] octets_sent;
  logic [15:0] requested_q;
  logic        driving;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      octets_sent <= '0; requested_q <= '0; driving <= 1'b0;
      observed_length <= '0; observed_seq <= '0;
      c_items_accepted <= '0; c_frames_driven <= '0; c_length_changed <= '0;
      padding_observed <= 1'b0; item_dropped <= 1'b0;
      frame_end <= 1'b0;
    end else begin
      frame_end <= 1'b0;

      if (item_valid && item_ready) begin
        requested_q      <= item_length;
        octets_sent      <= '0;
        driving          <= 1'b1;
        c_items_accepted <= c_items_accepted + 1;
      end else if (driving && tx_valid) begin
        octets_sent <= octets_sent + 1;

        if (tx_eof) begin
          driving         <= 1'b0;
          frame_end       <= 1'b1;
          observed_length <= octets_sent + 1;
          observed_seq    <= observed_seq + 1;
          c_frames_driven <= c_frames_driven + 1;

          // THE measurement this block exists for. Chapter 19.3 Section 2
          // pads a frame below the 60-octet floor, so a 20-octet request
          // arrives as 64 octets and the difference is correct. A
          // testbench that asserts on the item never sees it.
          if ((octets_sent + 1) != requested_q) begin
            c_length_changed <= c_length_changed + 1;
            if ((octets_sent + 1) > requested_q) padding_observed <= 1'b1;
          end
        end
      end

      // An item accepted and never driven is a dropped frame, and it is
      // the one failure that makes every coverage number in Section 15
      // optimistic without making anything fail.
      if (item_valid && item_ready && driving) item_dropped <= 1'b1;
    end
  end

  assign item_ready     = !driving;
  assign observed_resid = 6'((observed_length - 16'(FCS_OCTETS)) % 16'd64);

endmodule

Classification: the boundary between what the generator asked for and what the design saw, and the source of every honest assertion in Section 20.

What it teaches: that c_length_changed is a counter of legitimate differences and it should not be zero. Chapter 19.3 §2 pads every frame below the 60-octet floor, so a run with allow_illegal set must show a non-zero count — and a run that shows zero has either generated no short frames or is reading the item rather than the wire. The counter distinguishes those without any assertion firing.

And it teaches why Section 20's group 1 samples this block's outputs. observed_length is produced by counting octets on the wire; item_length is produced by a constraint solver. An assertion on the second checks the solver — the rejected class — and an assertion on the first can fail, which is the whole difference. The driver is not an interesting block and it is where the testbench stops being self-referential.

Deliberately simplified: the octet-level datapath is elided — tx_octet is declared and never driven — because the frame's contents are Chapter 20.3's subject and this block's purpose is the length accounting. item_dropped cannot assert as written, since item_ready is !driving; it is there as a documented impossibility for a version with a deeper pipeline. And observed_resid divides by 64 as a modulo on a 16-bit value, correct because 64 is a power of two.

Production implication: c_frames_driven against c_items_accepted is the pair that catches the most embarrassing testbench failure there is. A generator that produces a million items and a driver that emits nine hundred thousand frames has lost 10% of the stimulus, and every coverage number computed from the item stream is optimistic by that much. Neither counter is interesting alone; their difference is the only evidence that the two ends of the testbench agree, and it is the stimulus-side twin of Chapter 19.7 §13's requested-against-completed capture pair.


15. RTL 8 — Generator Telemetry

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// pktgen_telemetry -- what the generator produced, against what it was
// asked to produce. Section 15.
//
// Three groups, and the split is by WHO acts on the number.
// Plan view: coverage against the five lists Module 19 left.
// Mechanism view: which of Section 2's three classes each hole is in.
// Derived: the ceiling, so a weight that is finished can be left alone.
// ---------------------------------------------------------------------
module pktgen_telemetry
  import pktgen_pkg::*;
(
  input  logic              clk,
  input  logic              rst_n,

  input  logic [31:0]       c_frames,
  input  logic [6:0]        residues_count,     // of 64
  input  logic [6:0]        phases_count,       // of 64
  input  logic [3:0]        skews_used,
  input  logic [31:0]       c_spill_sizes,
  input  logic [31:0]       c_runts,
  input  logic [3:0]        peak_held,
  input  logic              agent_inert,
  input  logic              unaligned_phase_seen,

  // Plan view.
  output logic [15:0]       list_coverage_pct,
  output logic [4:0]        lists_complete,

  // Mechanism view.
  output logic [2:0]        hole_class,          // 0 none, 1 A, 2 B, 3 C
  output logic              topology_limited,

  // Derived.
  output logic [31:0]       frames_to_ceiling,
  output logic              weight_is_finished
);

  logic l_residues, l_spill, l_runts, l_phases, l_reorder;

  // The five lists. Chapter 19.4 Section 4, Chapter 19.3 Section 20,
  // Chapter 19.7 Section 7, Chapter 19.4 Section 7 and Chapter 19.6
  // Section 21 -- one bit each.
  assign l_residues = (residues_count == 7'd64);
  assign l_spill    = (c_spill_sizes  > 32'd23);
  assign l_runts    = (c_runts        > 32'd43);
  assign l_phases   = (phases_count   == 7'd64);
  assign l_reorder  = (peak_held      >= 4'd4);

  assign lists_complete = {l_reorder, l_phases, l_runts, l_spill, l_residues};

  always_comb begin
    list_coverage_pct = 16'd0;
    for (int i = 0; i < 5; i++) if (lists_complete[i]) list_coverage_pct += 16'd20;
  end

  // Section 9: sixteen phases means the stimulus is injected through the
  // transmit path, which lane-aligns. The remaining 48 are not a
  // coverage hole -- they are absent from this topology.
  assign topology_limited = (phases_count == 7'd16) && !unaligned_phase_seen;

  // Which class the FIRST incomplete list is in, so the response is the
  // right one. Section 2's whole taxonomy in three bits.
  always_comb begin
    if      (!l_residues || !l_spill || !l_runts) hole_class = 3'd1;  // A
    else if (!l_phases)                           hole_class = 3'd2;  // B
    else if (!l_reorder)                          hole_class = 3'd3;  // C
    else                                          hole_class = 3'd0;
  end

  // Section 6: the coupon-collection bound at a 100% weight. A run
  // already near it has a finished weight, and further tuning buys the
  // difference between 172 and 86 frames.
  localparam int CEILING_FRAMES = 86;
  assign frames_to_ceiling = (c_frames > 32'(CEILING_FRAMES))
                           ? 32'd0 : (32'(CEILING_FRAMES) - c_frames);
  assign weight_is_finished = (c_spill_sizes > 32'd23) &&
                              (c_frames < 32'd400);

endmodule

Classification: a plan-level reporter, and the only block in this chapter whose output is an instruction rather than a measurement.

What it teaches: that hole_class turns a coverage hole into a routed action. A percentage says how much is missing; three bits say whether to tune a weight, change a sequence, or configure another agent — and those responses take an hour, a day and a week respectively, so choosing the wrong one is the expensive mistake. Section 2's taxonomy exists to be computed, and this is where it is computed.

And it teaches that topology_limited is the flag that stops a team tuning forever. Sixteen phases with no unaligned phase ever seen is a complete result for a loopback topology, not a 25% one. Reporting it as 25% coverage of a 64-bin cross invites exactly the work that cannot succeed — Section 13's row two, arriving as a signal.

Deliberately simplified: list_coverage_pct weights the five lists equally, which is a presentation choice and not a measurement — the 23 spill sizes and the 64 residue classes are not equally important and the block cannot know which is. weight_is_finished uses a hard-coded ceiling of 86 frames from Section 6, valid only for the 23-size list. And l_spill tests a count rather than a set, so 24 frames all of the same spill size satisfies it — the complete check needs a 23-bit seen-vector, which Section 5's block has for residues and this does not have for sizes.

Production implication: lists_complete as a five-bit value is the right thing to put on a regression's summary line, and its bits should be read right to left in the order the responses get harder. Bits 0 to 2 are class A and close with weights; bit 3 is class B and closes with a topology change; bit 4 is class C and closes with another agent's parameter. A run reporting 5'b00111 is not 60% done — it is done with everything that tuning reaches, and the remaining two bits are two different conversations with two different people.


16. RTL 9 — The Generator Conformance Monitor

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// pktgen_conformance_monitor -- verdicts about the STIMULUS. Section 16.
//
// The fourteenth verdict generator in Modules 18 to 20 and the first
// whose subject is the testbench. Every previous one asked whether the
// design was correct; this one asks whether the run meant anything.
// ---------------------------------------------------------------------
module pktgen_conformance_monitor
  import pktgen_pkg::*;
#(
  parameter int MIN_FRAMES = 10000
) (
  input  logic              clk,
  input  logic              rst_n,

  input  logic [31:0]       c_frames,
  input  logic              distribution_collapsed,
  input  logic              residues_incomplete,
  input  logic              agent_inert,
  input  logic              topology_limited,
  input  logic              target_unreachable,
  input  logic [31:0]       c_phase_misses,
  input  logic [31:0]       c_phase_hits,
  input  logic              allow_illegal_cfg,

  output logic              weights_not_realised,
  output logic              stimulus_incomplete,
  output logic              agent_disabled,
  output logic              topology_insufficient,
  output logic              lookahead_broken,
  output logic              illegal_sizes_disabled,
  output logic              none_of_the_above
);

  always_comb begin
    // The generator did not produce what its configuration asked for.
    // Section 5: a dropped soft constraint is silent.
    weights_not_realised = distribution_collapsed;

    // The run was too short, or the weights are wrong. Class A.
    stimulus_incomplete = (c_frames > 32'(MIN_FRAMES)) && residues_incomplete;

    // Class C: another agent is off. Section 11 -- this must NOT be
    // reported as a coverage percentage.
    agent_disabled = agent_inert;

    // Class B: the topology removes 48 of 64 offsets. Section 9.
    topology_insufficient = topology_limited;

    // Section 14: a lookahead that ignores the deficit lands off target
    // about a quarter of the time.
    lookahead_broken = (c_phase_hits + c_phase_misses > 32'd1000) &&
                       (c_phase_misses * 32'd4 > (c_phase_hits + c_phase_misses));

    // Section 4: runts are behind a boolean, not a weight.
    illegal_sizes_disabled = !allow_illegal_cfg;

    none_of_the_above = !weights_not_realised && !stimulus_incomplete &&
                        !agent_disabled && !topology_insufficient &&
                        !lookahead_broken && !illegal_sizes_disabled;
  end

endmodule

Classification: a verdict generator whose subject is the environment, and the first of its kind in the track.

What it teaches: that every verdict here has a different owner and none of them is the design team. weights_not_realised belongs to whoever wrote the constraints; agent_disabled to whoever configured the bus model; topology_insufficient to whoever built the testbench; illegal_sizes_disabled to whoever set the run's options. Thirteen previous monitors in this track reported on a design; this one reports on four different people's configuration files.

And it teaches that none_of_the_above here means something weaker than in every previous chapter. A design-side verdict clearing means the design behaved; this one clearing means the run was capable of finding a bug, which is a precondition rather than a result. The two must be read together — a clean design-side verdict from a run whose stimulus monitor was asserting is worth nothing — and that pairing is the honest use of both.

Deliberately simplified: illegal_sizes_disabled fires on every run with legal-only stimulus, which is most runs, and it should be a per-regression waiver rather than a verdict — it is here to make the point that a default excludes a case. lookahead_broken's threshold of 25% is Section 14's predicted miss rate and is a literal. And there is no verdict about the frame contents at all, because this chapter does not model payload — Chapter 20.3 does.

Production implication: none_of_the_above for the fourteenth time, and the first time a team should expect it to be false on a run that is working as intended. illegal_sizes_disabled is set on every legal-traffic regression and agent_disabled on every run with a clean bus model. The verdict's value is that those are choices somebody made, recorded where the coverage report is read — and a regression suite whose stimulus monitor asserts three verdicts on every run has three configuration decisions nobody has revisited since the environment was written.


17. Composing the Five Lists

A fixed size stream has a fixed wire period, and because Chapter 19.3's deficit chooses the gap as twelve minus the length modulo four, that period is always a multiple of four. The period is the length plus twenty minus the length modulo four, and the number of distinct beat phases the stream visits is sixty four divided by the greatest common divisor of the period and sixty four. At a length of sixty four octets the gap is twelve, the period is eighty four, the greatest common divisor is four, and the stream visits sixteen phases, which is the maximum a lane aligning transmitter can produce. At a length of one thousand five hundred and eighteen octets the length modulo four is two, so the gap is ten and the period is one thousand five hundred and thirty six, which is exactly twenty four beats: the greatest common divisor is sixty four and the stream visits one phase, forever. Across the one thousand four hundred and fifty five legal lengths, half visit all sixteen phases, a quarter visit eight, twelve point four per cent visit four, and six point three per cent visit one. The degenerate lengths include one thousand five hundred and sixteen through one thousand five hundred and nineteen, which is a cluster at exactly the size a maximum size stress test aims at, and the same test at one thousand five hundred and twenty visits all sixteen.A fixed size La stress testGap = 12 - (L mod4)19.3's deficitPeriod a multipleof 4L + 20 - (L mod 4)L = 64period 84, gcd 4L = 1518period 1536, gcd 6416 phasesthe maximum1 phaseforever6.3% of sizesvisit exactly one12
Figure 3 — a fixed-size stress test's phase coverage is a greatest common divisor nobody computes.

Each list is reachable. The question this section answers is whether they are reachable together, and two of the five are in tension.

The five, with the mechanism each needs:

ListClassMechanismFrames
64 residue classesAuniform lengths304
23 spill sizesAw_spill at 50%172
43 runt sizesAallow_illegal, w_runt 5%3 741
64 beat phasesBSection 10's injector7 689 with 16 offsets
4 simultaneous reorderingsCq at 0.055 166 responses

The tension is between rows one and two. w_spill at 50% puts half the frames on 23 sizes, all of which have the same residue: (length − 4) mod 64 where length ≡ 3 (mod 64) gives residue 63. So the spill weight concentrates half the stimulus on one of the 64 residue classes.

Uniformw_spill = 50%
residue 63's share1.6%50.8%
the other 63 classes, each1.56%0.78%
coupon collection over 64304 frames608 frames

Row three is the cost and it is a factor of two, which is the same factor Section 6 found for the general population and for the same reason: the weight's probability comes from everywhere else. Two hundred extra frames is nothing, and the shape matters more than the sizetwo class A goals can conflict, and the conflict is invisible unless somebody notices that the target sets overlap in a derived quantity.

Rows three and four are in a different kind of tension and it is worse, because it is arithmetic nobody computes.

A fixed-size stream has a fixed wire period, and Chapter 19.3 §6's deficit makes that period a multiple of four.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
gap    = 12 − (L mod 4), or 12 when L is a multiple of 4
period = L + 8 + gap = L + 20 − (L mod 4)

So the period is always divisible by four, which is the lane alignment showing up as a number, and the phases a fixed-size stream visits are 64 / gcd(period, 64) — at most sixteen, which is Section 9's ceiling arriving from the other direction.

Frame sizeGapPeriodgcd with 64Phases visited
47 — a runt964641
481268416
641284416
1 51791 536641
1 518101 536641
9 000 — jumbo129 020416

Rows four and five are the finding and they are the two sizes every stress test uses. A 1 518-octet fixed-size stream has a wire period of 1 536 octets — exactly twenty-four beatsso every frame starts at the same offset in a beat, forever. The most common maximum-size stress test in Ethernet verification visits one of the parser's sixteen reachable phases and fifteen of them never.

And it is not a rare property.

Phases visitedHow many of the 1 455 legal sizesShare
1672850.0%
836425.0%
418012.4%
2926.3%
1916.3%

Half of all sizes are fine and 6.3% are degenerate, and the degenerate ones include 1 517, 1 518 and 1 519 — a block of sizes right at the maximum, which is exactly where a fixed-size test is aimed. A stress test at 1 518 covers one phase; the same test at 1 520 covers sixteen, and nothing in any coverage report explains the difference.

So the composition rule is short and is not obvious from any single list.

A fixed-size stream visits 64 / gcd(L + 20 − (L mod 4), 64) phases. Check it before choosing a stress size, and prefer a size whose period is 4 mod 8.

Which leaves the five lists composable, with two caveats that are both about derived quantities.

CaveatCostFix
w_spill concentrates residue 63coupon over 64 classes doubles — 607 framesaccept it; 300 frames is nothing
fixed-size streams collapse the phase1 phase instead of 16check the gcd, or randomise the size

Neither is a conflict between the lists as stated. Both are conflicts between a list and a quantity derived from the same field, which is the shape a composition problem takes when every goal is expressed in terms of the frame's length.


18. What the Generator Assumes

Eight assumptions, and the first two are about the design rather than about the stimulus.

#AssumptionOwnerIf wrong
1the transmit path lane-alignsChapter 19.3 §6Section 9's 16 becomes 64 and the injector is unnecessary
2the gap is 9 to 12 with a bounded deficitChapter 19.3 §6Section 14's lookahead mispredicts
3the datapath is 64 octets per beatChapter 19.1 §4every modulo in this chapter changes
4the solver honours dist over softthe simulatorSection 5's collapsed distribution
5another agent produces reorderingthe bus modelSection 11 — class C is empty
6the clocks differ by up to 200 ppmthe clock generatorChapter 19.5 §21's row 47
7illegal sizes are enabledthe run's optionsChapter 19.7 §7's cases absent
8the phase is sampled from the wirethis chapter — Section 8Section 13's row 1: two disagreeing values

Row one is unusual: the generator's architecture depends on a property of the design under test. Section 10's injector exists only because Chapter 19.3 §6 lane-aligns. A different MAC that did not would need no injector and would reach all 64 offsets from a loopback, so this chapter's most distinctive block is a consequence of a design decision three chapters earlier. That dependency should be written down, because it is the kind that survives into an environment reused on a different design.

Row four is the assumption most likely to be silently false. soft constraint resolution is implementation-defined in its details, and a solver that drops c_spill_set under a different dist produces a legal, uniform, useless distribution. Section 5's block exists because this assumption cannot be checked any other way.

And two deliberately not assumed:

Not assumedWhy not
that the design is correctthe generator's job is to produce stimulus, not to judge it
that coverage bins exist for any of thisChapter 20.4's problem; this chapter counts its own reach

Row two is the boundary with Chapter 20.4 and it is worth being firm about. This chapter's telemetry counts what the stimulus produced; a coverage model counts what the design experienced. They disagree whenever the injection path changes something — Section 9's alignment is the standing exampleand a chapter that conflates them cannot report that disagreement, which is the most informative thing either measurement produces.


19. The Cost, Accounted

Module 19 left five lists of cases that its own chapters proved random stimulus does not reach. Four of them are cheap in wire time. All sixty four partial word residue classes take about three hundred and four frames under a uniform length distribution, which is two microseconds of wire time. The twenty three sizes that both spill the check value and fill the interframe gap deficit take one hundred and seventy two frames with a fifty per cent weight, one point two microseconds. The forty three runt sizes take three thousand seven hundred and forty one frames, twenty five microseconds. The joint cross of sixty four residues against sixteen reachable phases takes seven thousand six hundred and eighty nine frames, fifty two microseconds. Together that is roughly twelve thousand frames and eighty point six microseconds of wire time, which at any register transfer level simulation rate is minutes. The fifth case is different in kind. Chapter 19.5's two hundred parts per million clock drift needs two million five hundred and sixty thousand beats to traverse a five hundred and twelve beat buffer, which is thirteen point one zero seven milliseconds, one hundred and sixty three times the entire rest of the plan, and no stimulus shortens it because it is not about frames at all. That chapter's answer is to build hardware that measures the rate in sixty five thousand five hundred and thirty six beats instead of waiting for the accumulation.64 residues304 frames — 2.0 us23 spill sizes172 frames — 1.2 us43 runt sizes3 741 frames — 25 us64 x 16 cross7 689 frames — 52 usAll five lists12 000 frames — 80.6us200 ppm tooverflow13.107 ms163x the wholeplanand not about framesMeasure the rate65 536 beats — 19.5Section 512
Figure 4 — the stimulus is eighty microseconds; one environment parameter is thirteen milliseconds.

This chapter's cost is measured in simulation time rather than in flops, and the accounting is unfamiliar enough to be worth laying out the same way.

BlockFlopsSimulation cost
the item classone solver call per frame
size_distribution~180negligible
phase_tracker~120negligible
alignment_injector~40negligible
interconnect_stressor~70one random call per response
directed_sequence~330negligible
pktgen_driver~200the wire-level drive
pktgen_telemetry~90negligible
pktgen_conformance_monitor~20negligible
total~1 050 flopsdominated by the solver

The flop count is irrelevant and is here for the comparison. This chapter's blocks are 1 050 flops against Module 19's 14 1667.4%and none of them ships. What costs something is the solver call per frame, and the frame counts in Section 17's table are the real budget.

GoalFramesAt 148.81 M/s, wire time
64 residue classes3042.0 µs
23 spill sizes, weighted1721.2 µs
43 runt sizes3 74125.1 µs
64 × 16 joint7 68951.7 µs
all five listsabout 12 00080.6 µs

Eighty microseconds of wire time is the whole of Module 19's stimulus requirement, which at any RTL simulation rate is minutes. That is the encouraging half.

The discouraging half is Chapter 19.5 §21's run B, which is not in the table.

FramesWire time
all five lists12 00080.6 µs
Chapter 19.5's 200 ppm drift to overflow13.107 ms
ratio163×

One class C case costs 163 times the entire rest of the plan, and it cannot be shortened by stimulus because it is not about frames at all. Chapter 19.5 §5's rate tracker is the answer — measure the drift in 65 536 beats rather than wait 2.56 million — and it is the only place in this batch where the correct response to a simulation cost is to build hardware.

Which is the accounting's real result. The stimulus for six chapters of MAC datapath is eighty microseconds; the environment around it — a clock generator with two frequencies, a bus model with a reorder probability, a reset sequencer with an order, an injector with four skews — is where both the cost and the coverage actually live.


20. Properties Worth Asserting, and One Worth Refusing

Thirty-two properties in six groups, and the refused one is the first assertion anybody writes in a testbench.

Group 1 — the item, where the properties must be about what the design SAW.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Every property in this group samples the interface, not the item.
// Section 20's rejected class is why.

// The length on the wire matches the length the generator asked for.
// This CAN fail -- the driver, the DMA path and Chapter 19.3's padding
// all sit between the two.
a_length_delivered: assert property (@(posedge clk) disable iff (!rst_n)
  frame_end |-> (observed_length == expected_length));

// Chapter 19.3 Section 2: a frame below the 60-octet floor is PADDED, so
// a 20-octet request arrives as 64 octets. The generator's value and the
// wire's differ legitimately, and the property has to know that.
a_short_frame_padded: assert property (@(posedge clk) disable iff (!rst_n)
  (frame_end && expected_length < 16'd64) |-> (observed_length == 16'd64));

// The observed residue is what Chapter 19.4 Section 4 branches on.
a_residue_observed: assert property (@(posedge clk) disable iff (!rst_n)
  frame_end |-> (observed_resid == ((observed_length - 4) % 64)));

// Illegal sizes reach the wire only when the knob is set.
a_illegal_gated: assert property (@(posedge clk) disable iff (!rst_n)
  (frame_end && observed_length < 16'd64) |-> allow_illegal_cfg);

// Chapter 13.2's tags move every field after them, so the observed
// EtherType offset must match the tag count the generator asked for.
a_tags_delivered: assert property (@(posedge clk) disable iff (!rst_n)
  frame_end |-> (observed_ethertype_offset == 16'd12 + 16'd4 * expected_tags));

// A frame the generator marked for FCS corruption arrives with a bad
// check value, and one it did not does not.
a_fcs_error_delivered: assert property (@(posedge clk) disable iff (!rst_n)
  frame_end |-> (observed_crc_bad == (expected_err == ERR_FCS)));

// The item and the wire agree on the address class, which the DMA path
// does not change.
a_addr_kind_delivered: assert property (@(posedge clk) disable iff (!rst_n)
  frame_end |-> (observed_addr_kind == expected_addr_kind));

// And a frame is never delivered twice, which a retrying driver does.
a_no_duplicate: assert property (@(posedge clk) disable iff (!rst_n)
  frame_end |-> (observed_seq != $past(observed_seq)));

Group 2 — the distribution, which is a property of a run rather than of a frame.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The realised share of the 23 spill sizes is within tolerance of the
// weight. Section 5: a dropped soft constraint is otherwise silent.
a_weight_realised: assert property (@(posedge clk) disable iff (!rst_n)
  (c_frames > 32'd1000) |-> !distribution_collapsed);

// Every residue class appears, eventually, under a uniform weight.
a_residues_reached: assert property (@(posedge clk) disable iff (!rst_n)
  (c_frames > 32'd2000) |-> (residues_count == 7'd64));

// The generator never produces a length outside its configured range.
a_range_respected: assert property (@(posedge clk) disable iff (!rst_n)
  frame_valid |-> (frame_length inside {[5:JUMBO_MAX]}));

// And the run is long enough for any of the above to mean anything.
a_run_long_enough: assert property (@(posedge clk) disable iff (!rst_n)
  $fell(run_active) |-> (c_frames > 32'd10000));

Group 3 — the phase, which the generator observes and does not control.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The tracked phase matches the wire. If these disagree the tracker's
// model of the gap is wrong -- Section 14's lookahead failure.
a_phase_matches_wire: assert property (@(posedge clk) disable iff (!rst_n)
  frame_start |-> (phase == wire_octet_index[5:0]));

// The phase advances by the wire period, modulo 64.
a_phase_advances: assert property (@(posedge clk) disable iff (!rst_n)
  frame_start |=> (phase == 6'((32'($past(phase)) + 32'd8 +
                                32'($past(frame_length)) +
                                32'($past(gap_octets))) % 32'd64)));

// Section 9: a transmit-injected run only ever sees lane-aligned phases.
a_tx_phases_aligned: assert property (@(posedge clk) disable iff (!rst_n)
  (frame_start && !injector_enabled) |-> (phase[1:0] == 2'b00));

// And with the injector on, it does not.
a_injector_unaligns: assert property (@(posedge clk) disable iff (!rst_n)
  (frame_start && injector_enabled && skew_applied != 2'd0)
    |-> ##[0:4] unaligned_phase_seen);

// The injector only ADDS idle, so the gap never goes below Chapter 5.9's
// floor. Section 10.
a_injector_adds_only: assert property (@(posedge clk) disable iff (!rst_n)
  injector_enabled |-> !gap_below_min);

// A lookahead target that is reported reachable is reached.
a_lookahead_lands: assert property (@(posedge clk) disable iff (!rst_n)
  (seq_done && active_id == 3'd1 && !target_unreachable)
    |-> ##1 (phase == target_phase));

Group 4 — the sequences, where the properties are about state the generator drove.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Run D drives the deficit to its bound. Chapter 19.3 Section 20.
a_seq0_fills_deficit: assert property (@(posedge clk) disable iff (!rst_n)
  (seq_done && active_id == 3'd0) |-> (observed_deficit == 2'd3));

// A sequence emits exactly the number of frames it claims.
a_seq_length: assert property (@(posedge clk) disable iff (!rst_n)
  (seq_active && active_id == 3'd0) |-> (step_q <= 3'd3));

// A sequence never overlaps another.
a_seq_exclusive: assert property (@(posedge clk) disable iff (!rst_n)
  (req && seq_active) |-> !$rose(seq_active));

// Control returns to the random stream.
a_seq_returns: assert property (@(posedge clk) disable iff (!rst_n)
  seq_done |=> !seq_active);

// Section 13 row 6: a sweep must not share a period with anything else.
a_sweep_coprime: assert property (@(posedge clk)
  (cfg_skew_mode != 2'd3) || (SIZE_SWEEP_PERIOD % 4 != 0));

Group 5 — the foreign agents, where every property is about whether the agent is doing anything.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// A run that requires reorder coverage must have a non-zero q.
a_stressor_configured: assert property (@(posedge clk)
  !require_reorder_coverage || (cfg_reorder_q_pct != 8'd0));

// The stressor reaches the depth Chapter 19.6 Section 8 needs.
a_depth_reached: assert property (@(posedge clk) disable iff (!rst_n)
  (c_responses > 32'd100000 && !agent_inert) |-> depth_reached);

// An inert agent is REPORTED, not silently zero.
a_inert_reported: assert property (@(posedge clk) disable iff (!rst_n)
  agent_inert |-> agent_disabled);

// The clocks actually differ. Chapter 19.5 Section 21's row 47: a
// testbench with one clock source has a drift of zero.
a_clocks_differ: assert property (@(posedge clk) disable iff (!rst_n)
  (c_windows > 32'd16) |-> (drift_ppm != '0));

// The reset order is exercised in both directions.
a_reset_order_swept: assert property (@(posedge clk)
  reset_order_seen == 2'b11);

Group 6 — coverage.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
c_all_residues:   cover property (@(posedge clk) residues_count == 7'd64);
c_all_phases:     cover property (@(posedge clk) phases_count == 7'd64);
c_sixteen_phases: cover property (@(posedge clk) phases_count == 7'd16);
c_all_skews:      cover property (@(posedge clk) skews_used == 4'b1111);
c_spill_and_runt: cover property (@(posedge clk)
                    (c_spill_sizes > 32'd23) && (c_runts > 32'd43));
c_deficit_full:   cover property (@(posedge clk) observed_deficit == 2'd3);
c_reorder_four:   cover property (@(posedge clk) peak_held >= 4'd4);

21. Verification Scenarios

Fifty-seven scenarios for a block whose subject is verification, plus a five-run directed test whose content is a topology rather than a stimulus.

The item and its constraints — 12 scenarios.

#ScenarioExpected
1w_spill = 0, 10 000 framesspill share 1.58%
2w_spill = 50, 10 000 framesshare 50% ± 5
3w_spill = 50 with a conflicting hard constraintdistribution_collapsed
4allow_illegal = 0no frame below 64, ever
5allow_illegal = 1, w_runt = 543 runt sizes in 3 741 frames
6cfg_mtu = 9 000lengths up to 9 000
7cfg_mtu = 1 518 with a 9 000 requestthe constraint wins
8tags = 2 on a 64-octet framec_room_for_tags forces a longer frame
9100 000 frames, uniformall 64 residues in ~304
10the 23 spill sizes, individuallyall reachable
11a length of 5 octetslegal only with allow_illegal
12describe() on a 1 475-octet frameresid=63

Row three is the scenario that justifies Section 5's existence, and it is the one most environments never run: a soft constraint that loses is silent, so the only way to test the weight machinery is to break it deliberately and check that something notices.

The phase — 13 scenarios.

#ScenarioExpected
13a 64-octet fixed-size stream16 phases, all lane-aligned
14a 1 518-octet fixed-size stream1 phase — period 1 536
15a 1 520-octet fixed-size stream16 phases
16a 47-octet runt stream1 phase — period 64
17uniform lengths, injector off16 phases
18uniform lengths, injector on64 phases
19the tracker against the wirea_phase_matches_wire
20a lookahead to phase 37, injector offtarget_unreachable or a miss
21a lookahead to phase 36, injector offhits
22a lookahead assuming gap 12 on a size ≡ 3 mod 4misses by 3
23the same, deficit-awarehits
241 000 lookaheads, deficit ignoredmiss rate near 25%
25a phase sweep over all 16phases_count = 16 and stops

Rows fourteen and fifteen are two octets apart and differ by a factor of sixteen in phase coverage, and row twenty-two is Section 14's predicted failure: a lookahead that models the gap as a constant lands three octets off on the 75% of sizes whose length is not a multiple of four.

The injector and topology — 10 scenarios.

#ScenarioExpected
26cfg_skew_mode = 0skews_used = 4'b0001
27cfg_skew_mode = 2, 1 000 framesall four skews
28cfg_skew_mode = 3 with a size sweep of period 4skew and size locked
29the same with a size sweep of period 5the cross fills
30the injector inserting idlegap_below_min clear
31an injector that DELETES idlegap_below_min — an illegal gap
32injector on, unaligned_phase_seenset within four frames
33injector off, 1 000 000 framesunaligned_phase_seen never
34topology_limited with 16 phasesset
35topology_limited with 17 phasesclear — the injector is working

Row twenty-eight is Section 13's row 6 as a test and its signature is a coverage cross exactly 25% full in a diagonal pattern, which is the most recognisable coverage-hole shape there is and is almost always a shared sweep period.

The foreign agents — 12 scenarios.

#ScenarioExpected
36cfg_reorder_q_pct = 0agent_inert; no coverage percentage
37q = 5, 100 000 responsesdepth_reached
38q = 5, 1 000 responsesusually not — 1 in 5 166
39q = 501 in 2 — unrealistic and useful
40q = 5, peak_held over a long runreaches 4, rarely 7
41an independent-per-response modeldeep reordering is rare by construction
42a correlated modelreaches 7 routinely
43two clocks from one sourcedrift 0 — Chapter 19.5 §21's row 47
44two clocks 200 ppm apartdrift measurable in 65 536 beats
45a single global resetone release order only
46staggered reset, both ordersreset_order_seen = 2'b11
47a memory model with no stallChapter 19.6 §8's cases absent

Rows forty-one and forty-two are the stressor's limitation stated as a pair. An independent model reaches depth four at q = 0.05 once in 5 166 and reaches depth seven essentially never; a correlated model — one DRAM refresh delaying several responses together — reaches seven routinely. The second is what the hardware sees and the first is what testbenches contain.

The generator's own reporting — 10 scenarios.

#ScenarioExpected
48a complete runlists_complete = 5'b11111
49a loopback run5'b01111 — bit 3 clear
50a run with a clean bus modelbit 4 clear
51a legal-only runbit 2 clear
52hole_class with residues missing1 — class A
53hole_class with only phases missing2 — class B
54hole_class with only reorder missing3 — class C
55weight_is_finished after 300 framesset — Section 6's ceiling
56none_of_the_above on a default regressionFALSE, correctly
57an assertion on a generated valuepasses on a broken generator

Row fifty-six is the scenario a verification lead should be shown first, because the monitor asserting on a working regression is the intended behaviour and looks like a bug. Row fifty-seven is Section 20's rejected class as a test: break the generator, run the assertion, watch it pass.

The directed test — five runs a stimulus change cannot produce.

This chapter's own hard cases are topologies rather than frames, which is the argument of Sections 9 and 11 arriving in a test plan.

CaseNeedsA default environment provides
48 unaligned phasesan injector between TX and RXa direct loopback
deep reorderinga correlated bus modelan in-order BFM
the drift overflowtwo clock sourcesone
the reset-order filla staggered releaseone global reset
the collapsed distributiona deliberately conflicting constrainta working one

Row five is the only one that is a stimulus change, and it is a deliberate break rather than a case — which is the shape of every test of a testbench.

Construct it. Five runs.

RunTopologyStimulusExercises
Adirect loopbackuniform lengths16 phases; the class A lists
Binjector, sweep modeuniform lengthsall 64 phases
Cdirect loopbackfixed 1 518ONE phase — Section 17
Dinjector, plus q = 5uniform, illegal enabledall five lists
Edirect loopbackw_spill with a conflicting hard constraintdistribution_collapsed

Run C is one line of configuration and it is the run that proves Section 17's arithmetic. A 1 518-octet fixed-size stream has a wire period of 1 536 octets, exactly twenty-four beats, so every frame starts at the same phase. phases_count reads 1 after a million frames, and the same run at 1 520 reads 16.

Run D is the only run that completes the plan, and it needs three configuration changes from the default: the injector on, illegal sizes enabled, and a non-zero reorder probability. None of them is a weight.

The oracle, in four parts:

CheckABCDE
phases_count166416416
lists_complete5'b000115'b010115'b000115'b111115'b00010
hole_class11101
none_of_the_abovefalsefalsefalseTRUEfalse

Row one is the topology's fingerprint and row four is the only place in this chapter where a clean verdict appears: run D is the only configuration that can find every bug Module 19 identified, and four of the five runs cannot — not because they are short but because of how they are wired.

Row three's constant 1 across A, B, C and E is the most useful entry in the table. Four different failures — a short run, a missing weight, a degenerate size, a collapsed distribution — all report class A, because hole_class reports the first incomplete list and the class A lists come first. That is a deliberate ordering and it has a cost: the class B and C holes are invisible until the class A ones close, which is the right priority and should be said out loud.


22. Debugging a Generator

Four complaints. Three of them present as coverage holes and have three different owners.

Complaint 1 — "coverage of the alignment cross is stuck at 25%."

CheckIf yesMeaning
phases_count exactly 16?only lane-aligned phasesSection 9 — the topology
unaligned_phase_seen ever?the injector is offconfirms
topology_limited set?the monitor already said soread it before tuning
does a weight change move it?it will notclass B

Row four is the diagnostic and the answer is always the same: no. The missing 48 offsets are removed by Chapter 19.3 §6's lane alignment after the generator has finished, so nothing in the item, the constraints or the weights reaches them. topology_limited exists so that this conversation takes one read instead of one sprint.

Complaint 2 — "the spill sizes are at 1.6% and the weight says 50."

CheckIf yesMeaning
distribution_collapsed set?the realised share is far below targetSection 5
is c_spill_set a soft constraint?it lost to a hard onesilently
does the solver report anything?it will notdropped softs are not diagnostics
does making it hard cause a solver failure?then the constraints genuinely conflictand the conflict is the bug

Row four is the test that separates the two cases and it is worth running before rewriting anything. A soft that loses and a soft that conflicts look identical from the outside; promoting it to hard turns the silent loss into a solver error naming the constraint it fights with, which is the diagnosis.

Complaint 3 — "reorder coverage is zero after a week of regressions."

CheckIf yesMeaning
agent_inert set?q is zeroclass C — nothing to tune
c_reordered at zero?confirmsthe bus model is in-order
peak_held at 1 with c_reordered high?the model reorders shallowlyindependent-per-response
does a longer run help?not at q = 0; not much at depththe model's structure, not its rate

Row three is the subtler failure and it survives a fix to row one. An independent-per-response model at q = 0.05 reaches depth four once in 5 166 and depth seven essentially never; the hardware's reordering is correlated — one DRAM refresh delays several responses together — so the model reproduces the rate and not the shape. peak_held is the number that shows it.

Complaint 4 — "a fixed-size stress test finds nothing."

CheckIf yesMeaning
what is the size?compute L + 20 − (L mod 4)Section 17
is the period a multiple of 64?one phase, forever1 516 to 1 519 all are
phases_count = 1?confirms15 of 16 phases unvisited
does the same test at size + 2 differ?16 phasesdefinitively

Row four is a two-octet change that multiplies the phase coverage by sixteen, and row two is the reason the classic maximum-size stress test is one of the worst: 1 518 gives a period of 1 536, which is exactly twenty-four beats. Ninety-one of the 1 455 legal sizes are degenerate this way, and four of them are clustered at the maximum where everybody aims.

Complaint 5 — "the item stream and the coverage numbers disagree."

CheckIf yesMeaning
c_items_accepted above c_frames_driven?items accepted and never drivenSection 14's item_dropped
c_length_changed at zero with runts enabled?nothing is reading the wirethe coverage samples the item
padding_observed clear on a run with short frames?Chapter 19.3 §2's pad is invisibleconfirms
does the coverage sample the driver's outputs?it shouldand usually does not

Row two is the most common form of a testbench that measures itself. A coverage model sampling item_length records what the generator asked for; the design saw what the driver delivered, and Chapter 19.3 §2's padding makes those differ on every short frame. c_length_changed reading zero on a run that generated runts is proof that the two ends have never been compared — and it costs one counter to know.

And the three symptoms this chapter is systematically blamed for:

SymptomBlamed onUsually is
a coverage hole that will not closeweightsa topology or another agent
"the random seeds are bad"the seeda constraint that excludes the case
a stress test that finds nothingthe designa period that divides the beat
coverage that disagrees with the logthe samplerthe item and the wire, compared to each other
"the weight is set and nothing changed"the solvera soft constraint that lost, silently

23. Misconceptions

Misconception 1 — "a weight can reach any case."

The wrong model: constrained random plus enough tuning reaches everything.

What it costs: unbounded effort on cases that do not respond. A weight is a marginal distribution over one frame's own fields, and two of Module 19's five lists are not that: the beat phase is a running sum over every preceding frame, and the interconnect's reordering belongs to a different agent.

The corrected model: three classes. Class A responds to weights — 31.6× on the spill sizes and then a ceiling. Class B responds to a sequence or a topology change. Class C responds to another agent's parameter and to nothing else. The first question about a coverage hole is which class it is in, and the answer routes an hour, a day or a week of work. Sections 2, 6, 9, 11.

Misconception 2 — "a loopback testbench exercises the receive path."

The wrong model: transmit into receive covers both directions.

What it costs: 75% of Chapter 19.2's barrel shifter. Chapter 19.3 §6's deficit lane-aligns every transmitted frame, so only 16 of the parser's 64 start offsets ever occur — and the second barrel stage, which shifts by 0 to 7 octets, sees only 0 and 4. Six of its eight cases are untested.

The corrected model: the loopback's coverage ceiling is a property of the design under test, not of the run's length. An injector that adds 0 to 3 idle octets restores the other 48, costs a two-bit counter, and is the only thing that does. phases_count reading exactly 16 is the evidence. Sections 9, 10.

Misconception 3 — "an empty coverage bin means run longer."

The wrong model: coverage is a function of simulation time.

What it costs: weeks. A bin can be empty because the run was short, because a constraint excludes it, because the topology removes it, or because the agent that produces it is disabled — and only the first responds to running longer. Chapter 19.7 §7's runts are behind a boolean; Chapter 19.6 §21's reorderings are behind another model's parameter.

The corrected model: distinguish low from zero. A bin at 2% is a weighting question; a bin at exactly zero after a long run is almost always a gate, and Section 4's first diagnostic question is to look for the excluding constraint before touching a weight. Sections 4, 11, 13.

Misconception 4 — "assert that the generated frames are legal."

The wrong model: the testbench should check its own stimulus.

What it costs: an assertion that cannot fail. The constraint solver produced the value to satisfy the same expression the property tests, so the property checks the solver — which works — and is blind to the constraint being wrong, the configuration defaulting, the driver changing the frame, and the generator producing nothing at all.

The corrected model: move the subject somewhere the solver did not construct. Assert on what the design saw — the wire, where Chapter 19.3 §2's padding is visible — assert against a bound transcribed independently from the standard, cover that the antecedent fires, and check the realised distribution. Section 20.

Misconception 5 — "a fixed-size stress test is the harshest case."

The wrong model: minimum size for rate, maximum size for throughput, and those are the corners.

What it costs: phase coverage, silently. A fixed-size stream has a fixed wire period, and Chapter 19.3 §6 makes that period a multiple of four, so the phases visited are 64 / gcd(period, 64). At 1 518 octets the period is 1 536 — exactly twenty-four beats — and the test visits one phase.

The corrected model: compute the gcd before choosing a stress size. Half of all legal sizes visit all sixteen reachable phases and 6.3% visit one, and the degenerate block includes 1 516 through 1 519. The same test at 1 520 covers sixteen, which is a two-octet change and a factor of sixteen. Section 17.

Misconception 6 — "the environment is done when coverage is at 100%."

The wrong model: the coverage model defines completeness.

What it costs: the cases the coverage model does not contain. A loopback environment reaching 100% of a 16-bin phase model has covered 25% of the design's alignment space, and the model was written to match the environment because that is where the bins came from. Chapter 19.6 §16's reorder_untested and this chapter's agent_inert are the same warning from the two ends.

The corrected model: the generator reports what it reached; the coverage model reports what the design experienced; and the two disagree whenever the injection path changes something. That disagreement is the most informative measurement either produces, and an environment that conflates them cannot report it. Section 18.


24. Interview Questions

Question 1 — "Your coverage on one bin is stuck at zero. What do you check first?"

What the answer should establish: whether it is zero or low, because the two have different causes. Low is a weighting question; exactly zero after a long run is almost always a gate — a constraint that excludes the case, a boolean default, or an agent that is disabled. A strong answer gives the taxonomy: a case is a property of one item, of an item and its predecessors, or of a different agent, and only the first responds to a weight. It also names the ceiling: the best a weight can do is put all the probability on the target set, so computing the coupon bound at 100% says whether tuning is finished.

Question 2 — "You loop the transmit path back into the receive path. What does that fail to test?"

What the answer should establish: 48 of the 64 start offsets the receive parser handles. Chapter 19.3 §6's deficit lane-aligns every transmitted frame, so the loopback produces only offsets that are multiples of four. A strong answer goes one level deeper: Chapter 19.2 §3's barrel shifter is two stages, and the second shifts by 0 to 7 octets — a lane-aligned offset exercises only 0 and 4, so six of eight sub-lane cases are untested. The fix is an element between the two paths that adds 0 to 3 idle octets, and it costs a two-bit counter.

Question 3 — "How many beat phases does a stream of 1 518-octet frames visit?"

What the answer should establish: one. The gap is chosen so the next frame lands on a lane boundary — 1 518 mod 4 is 2, so the gap is 10 — and the wire period is 1 518 + 8 + 10 = 1 536 octets, exactly twenty-four 64-octet beats. Every frame therefore starts at the same offset. A strong answer gives the general form: a fixed-size stream visits 64 / gcd(L + 20 − (L mod 4), 64) phases, half of all legal sizes visit all sixteen, and 1 516 through 1 519 all visit one — which is a cluster at exactly the size a maximum-size stress test uses.

Question 4 — "Can a constrained-random generator produce four simultaneous read reorderings?"

What the answer should establish: no, at any weight. Whether four of eight outstanding responses arrive before the expected one is a parameter of the bus functional model, and no field of a frame item touches it. A strong answer quantifies the parameter: at an independent per-response probability of 0.05 the case appears once in 5 166 responses and at 0.5 once in two, and a clean model has it at zero — so the coverage hole is in a component nobody was tuning. It also names the model's limitation: independent reordering reaches depth four rarely and depth seven essentially never, while the hardware's reordering is correlated.

Question 5 — "You assert that every generated frame has a legal length. What have you checked?"

What the answer should establish: the constraint solver, which works. The item's constraint and the property are the same expression written twice, so the property cannot fail — and it is blind to the constraint's bound being wrong, to a configuration default, to the driver changing the frame, and to no frames being generated at all. A strong answer gives the replacements: assert on what the design saw, where Chapter 19.3 §2's padding is visible; assert against a bound transcribed independently from the standard; cover that the antecedent fires; and check the realised distribution, which is the only thing that catches a dropped soft constraint.

Question 6 — "How much simulation does Module 19's whole stimulus plan need?"

What the answer should establish: about twelve thousand frames — eighty microseconds of wire time — for all five lists, which at any RTL rate is minutes. A strong answer immediately names the exception: Chapter 19.5's 200 ppm drift takes 13.107 milliseconds to overflow a 512-beat FIFO, 163 times the entire rest of the plan, and it cannot be shortened by stimulus because it is not about frames. The answer there is hardware — that chapter's rate tracker measures the drift in 65 536 beats — which is the only case in this batch where a simulation cost is solved by adding logic.


25. Questions and Answers


26. What's Next

This chapter produces stimulus and measures only whether it produced what it intended. The next three chapters are the three things that look at the result.

Chapter 20.2 turns twenty chapters of assertions into a library. Every flagship chapter in this track has ended with thirty-odd properties and exactly one rejected class, and the series is now eighty-eight classes long — this chapter's is the latest. What has never been asked is what taxonomy those classes fall into, which of them a parameterised package can express once rather than eighty-eight times, and where the line between a simulation assertion and a formal one falls on a block like Chapter 19.4's 8 512-term network.

Chapter 20.3 builds the scoreboards, and inherits this chapter's rejected class at frame granularity. Chapter 19.4 §14 established that an equivalence reference must be independently authored; a scoreboard is that problem for a whole frame, and it has an extra difficulty this chapter does not: the design is allowed to change the frame. Chapter 19.3 §2's padding, Chapter 19.4's appended check value and Chapter 13.2's tag insertion are all legitimate modifications, and a scoreboard that compares octet for octet fails on every one of them.

And Chapter 20.4 builds the coverage model, which is where this chapter's three classes have to become bins. Chapter 19.4 §21 showed that a model tracking residue classes alone cannot distinguish a run covering one class and six barrel stages from one covering one and oneso the cross is the model — and Section 9's arithmetic says the cross's reachable size depends on the topology. A coverage goal of 100% against a 1 024-cell cross is a different statement from 100% against a 4 096-cell one, and only one of them is a statement about the design.


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.