Skip to content
VLSI Mentor

Ethernet · Module 20

Error Injection

A runt is 0.08% of the coverage cross and three of fourteen design paths; and no sequence of frames can overflow a FIFO whose drain rate exceeds the line rate.

An error injector's first question is not which errors to inject. It is which errors a wire can inject at all, because two of the faults Module 19 spent chapters on cannot be produced from outside the design.

ErrorInjected fromCostsReaches, of Chapter 20.4's legal cross
a corrupted check valuethe wire32 XOR gates147 456 cells — 31.55%
an alignment errorthe wirea bit count on the interface147 456 — 31.55%
a giantthe wirea constraint bound24 576 — 5.26%
a runtthe wirea constraint bound384 — 0.08%
a mid-frame truncationNOT the wirea stalled consumera path, not a cell
a reorder-buffer overflowNOT the wireanother agent's parametera path, not a cell

Rows one and four are the chapter's central inversion. A corrupted check value is thirty-two XOR gates and reaches nearly a third of the cross; a runt is one constraint bound and reaches 384 cells of 467 328and the runt is the only injectable error that exercises Chapter 19.7 §7's two-bit addend, which is the counter path that is most wrong when the link is most broken.

Coverage value and design value are in opposite order, which is Chapter 20.4 §26's warning arriving with a number attached.


1. Scope, and Two Kinds of Error

Chapter 20.4 §8 priced this chapter's main lever before it was written: enabling illegal frame sizes is worth 1.06× of the coverage cross, the smallest of the three configuration levers. This chapter is what that 1.06× actually buys, and the answer is that the number and the value are unrelated.

The split that organises everything here:

Wire-injectableRequires a fault inside the design
what it isa property of the frame or the gapa property of the system's state
who produces itthe driveranother agent — memory, clock, bus
costgatesa second model
examplesbad FCS, runt, giant, dribble, short gapFIFO overflow, reorder overflow, drift

Row four's right-hand column is Chapter 20.1 §11's class C arriving in a different chapter, and the reason is the same: an error that is a property of the system rather than of the stimulus cannot be produced by changing the stimulus.

What this chapter owns: the injectable error set and its normative definitions; what each costs and what each reaches; the exclusivity Chapter 7.3 §3 imposes between two of them; the injection scheduler; and the proof that a correctly sized receive FIFO cannot be overflowed from the wire.

What it does not own: the error classifications themselves — Chapter 7.3 defines undersize, oversize, fragment, jabber and alignment — the counters that record them, which are Chapter 19.7's, and the coverage model that says which cells they fill, which is Chapter 20.4's.

What it does not build: the reusable agent is Chapter 20.6, and Module 21's debugging chapters are what somebody does with the errors this chapter produces.

One boundary is worth setting before the machinery, because it decides what a property may assert.

An injector knows what it injected. The design knows what it classified. Those are two different factsChapter 7.3 §3 makes a frame that is both bad-FCS and non-octet-aligned an alignment error and not a CRC errorso an injector that asserts "I injected a CRC error, therefore the CRC counter increments" fires on a correct design. Section 20's rejected class is that assertion.


2. What the Wire Can Inject

An error injector has five single actions it can take on a frame or on the space between two frames. It can exclusive-or the check value after the design has appended it, which costs thirty two gates. It can emit a length below sixty four octets or above the maximum transmission unit, each of which costs one constraint bound. It can end the frame part way through an octet, which costs three bits of bit count on every beat of the receive datapath. And it can suppress idle octets so that the interframe gap falls below the nine octet floor, which costs about ten gates. Those five actions produce nine error classes, because three of the classes are compositions: a fragment is a short frame with a corrupted check value, a jabber is an oversized frame with one, and an alignment error is a dribbled frame whose check value consequently fails. Which class a given frame lands in is decided by the frame validity chapter's normative definitions and not by the injector's intent, and the two differ whenever two actions are armed on one frame. That gap between an action and a class is where the chapter's rejected property lives.XOR the checkvalue32 gatesA length below 64one boundA length abovethe MTUone boundEnd mid-octet3 interface bitsSuppress idle10 gates7.3's definitionsdecidenot the injectorCRC errorwhole octets, FCSfailsAlignment errornot whole octetsFragment / jabbercompositionsCounted exactlyoncethe classes areexclusive12
Figure 1 — five actions, nine classes, and the classification belongs to the standard rather than to the injector.

Nine errors, and the list is shorter than it looks because three of them are compositions of two others.

#ErrorChapter 7.3's definitionHow the wire produces it
1corrupted check valuefails the FCS, whole octetsXOR the last four octets
2undersize (runt)below 64 octets, FCS passesa length
3oversize (giant)above the MTU, FCS passesa length
4fragmentbelow 64, FCS failsrows 1 and 2 together
5jabberabove the MTU, FCS failsrows 1 and 3 together
6alignment errorfails the FCS, NOT whole octetsend the frame mid-octet
7short interframe gapbelow Chapter 5.9's floor of 9a gap counter
8a filtered addressChapter 7.4a destination field
9a non-member VLANChapter 13.3a tag field

Rows four and five are compositions and rows eight and nine are not errors at all — they are frames the design correctly discards, and they matter here because Chapter 20.3 §13 needs all five legal drops exercised before a missing frame can be called a bug.

And row six is the one that is not what it appears to be.

Chapter 7.3 §3's normative definition: an alignment error is a frame that fails its check sequence and is not a whole number of octets. So it is not a separate kind of corruption; it is a bad-FCS frame with a dribble. The two conditions are not independent and Section 8 is what that does to an injector.

The five that are single injections cost very different amounts.

ErrorWhat the injector needsGates or lines
corrupted check valuea 32-bit XOR after the CRC appender32 gates
undersizea length below 64one constraint bound
oversizea length above the MTUone constraint bound
short gapa gap counter and a comparatorabout 10 gates
alignmenta BIT count on the interface, not a byte count3 bits, plus the datapath

Row five is the expensive one and the expense is structural rather than arithmetic. Chapter 19.1 §3's beat carries a byte-enable; a dribble frame ends part-way through an octet, so the interface has to carry how many bits of the final octet are valid. That is three extra bits on every beat of the receive datapath — a change to the design's interface, not to the testbench — and it is why alignment errors are the injectable error most often skipped.


3. RTL 1 — The Package and the Check-Value Stomper

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// errinj_pkg -- the injectable error set, and the boundary between what
// a wire can produce and what it cannot. Sections 2 through 12.
//
// Five single injections and two compositions come from the wire. Two
// more faults -- a receive FIFO overflow and a reorder-buffer overflow
// -- are properties of the SYSTEM's state and no frame produces them.
// Section 10 is that argument and Section 12 is its proof.
//
// The package deliberately does NOT carry a "what class is this"
// function. Chapter 7.3 Section 3 owns the classification and an
// injector that duplicates it is Section 20's rejected class.
// ---------------------------------------------------------------------
package errinj_pkg;

  localparam int DATA_B     = 64;
  localparam int MIN_FRAME  = 64;
  localparam int FCS_OCTETS = 4;
  localparam int MIN_GAP    = 9;       // Chapter 5.9's floor

  // What the injector was ASKED to do. This is an intent, not a
  // classification: Chapter 7.3 decides what the frame actually is, and
  // the two differ for alignment -- Section 8.
  typedef enum logic [3:0] {
    INJ_NONE       = 4'd0,
    INJ_FCS        = 4'd1,   // 32 XOR gates
    INJ_UNDERSIZE  = 4'd2,   // a length
    INJ_OVERSIZE   = 4'd3,   // a length
    INJ_DRIBBLE    = 4'd4,   // a bit count -- implies a failed FCS
    INJ_SHORT_GAP  = 4'd5,   // a gap counter
    INJ_BAD_ADDR   = 4'd6,   // Chapter 7.4 filters it
    INJ_BAD_VLAN   = 4'd7,   // Chapter 13.3 filters it
    INJ_STALL      = 4'd8,   // NOT a wire event -- Section 11
    INJ_REORDER    = 4'd9    // NOT a wire event -- Section 10
  } inject_e;

  // Where the injection has to happen. The column that decides whether
  // a testbench can do it at all.
  typedef enum logic [1:0] {
    AT_WIRE      = 2'd0,     // the driver, before the MAC sees it
    AT_PHY       = 2'd1,     // below the MAC -- symbol and dribble
    AT_CONSUMER  = 2'd2,     // the memory system, not the frame
    AT_BUS_MODEL = 2'd3      // the interconnect's own parameter
  } inject_site_e;

  function automatic inject_site_e site_of(input inject_e e);
    case (e)
      INJ_DRIBBLE: site_of = AT_PHY;
      INJ_STALL:   site_of = AT_CONSUMER;
      INJ_REORDER: site_of = AT_BUS_MODEL;
      default:     site_of = AT_WIRE;
    endcase
  endfunction

  typedef struct packed {
    inject_e      kind;
    logic [15:0]  frame_index;   // which frame in the stream
    logic [15:0]  parameter_a;   // a length, a gap, a bit count
    logic         armed;
  } inject_req_t;

endpackage
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// errinj_fcs_stomper -- corrupt the check value. Thirty-two XOR gates,
// and the best coverage value in this chapter. Sections 3 and 4.
//
// The stomp happens AFTER Chapter 19.4's appender, on the wire, so the
// design computes a correct check value and the injector breaks it.
// Corrupting the payload instead would work too and is worse: the
// design would then be verifying a frame the injector also has to model.
//
// Chapter 19.4 Section 10 classifies the result RES_BAD and Chapter 19.7
// counts it. Neither needs to know an injection happened.
// ---------------------------------------------------------------------
module errinj_fcs_stomper
  import errinj_pkg::*;
(
  input  logic              clk,
  input  logic              rst_n,

  input  logic              in_valid,
  input  logic [7:0]        in_octet,
  input  logic              in_last_four,     // this octet is in the FCS
  input  logic              arm,
  input  logic [31:0]       cfg_mask,         // which bits to flip

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

  // Observability. Sections 15 and 16.
  output logic [31:0]       c_stomped,
  output logic [1:0]        fcs_octet_index,
  output logic              stomp_outside_fcs
);

  logic        armed_q;
  logic [1:0]  idx_q;
  logic [7:0]  mask_byte;

  always_comb begin
    unique case (idx_q)
      2'd0: mask_byte = cfg_mask[31:24];
      2'd1: mask_byte = cfg_mask[23:16];
      2'd2: mask_byte = cfg_mask[15:8];
      2'd3: mask_byte = cfg_mask[7:0];
    endcase
  end

  // The whole injection. One XOR per bit, gated by the mask.
  assign out_octet = (armed_q && in_last_four) ? (in_octet ^ mask_byte)
                                               : in_octet;
  assign out_valid = in_valid;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      armed_q <= 1'b0; idx_q <= '0;
      c_stomped <= '0; stomp_outside_fcs <= 1'b0;
    end else begin
      if (arm) armed_q <= 1'b1;

      if (in_valid && in_last_four) begin
        idx_q <= idx_q + 1;
        if (armed_q && idx_q == 2'd3) begin
          c_stomped <= c_stomped + 1;
          armed_q   <= 1'b0;          // one frame per arm
        end
      end else if (in_valid) begin
        idx_q <= '0;
        // An arm that reaches a non-FCS octet means the framing signal
        // is wrong, and a stomp applied to the payload would be a
        // DIFFERENT error -- one the design might legitimately catch a
        // different way. Section 14's second prohibition.
        if (armed_q && cfg_mask != '0) stomp_outside_fcs <= 1'b1;
      end
    end
  end

  assign fcs_octet_index = idx_q;

endmodule

Classification: the cheapest injection in the chapter and the one with the best coverage return.

What it teaches: that stomping the check value is strictly better than corrupting the payload, and the reason is about what the injector has to model. Corrupt the payload and the design computes a check value over the corrupted data — which is correct, and the frame passes. To make a payload corruption visible the injector must corrupt the payload and leave the original check value, which means it must know what the original check value was — a CRC engine in the testbench, and Chapter 19.4 §14's trap right behind it. Stomping the FCS after the appender needs no model at all.

And it teaches that cfg_mask makes the injection a family rather than an event. A single-bit mask exercises the check value's single-bit detection; a 32-bit mask of all ones inverts it entirely, which Chapter 17.3 §6 says produces the mCRC residue — so an all-ones stomp on a preemption-capable port produces a valid fragment rather than an error, and that is a directed test rather than a corruption.

Deliberately simplified: in_last_four arrives as an input, so the stomper trusts the framing to tell it where the check value is — a real injector counts from eof backwards, which needs one beat of lookahead at a 512-bit datapath. The arm is one frame deep, so back-to-back injections need a queue. And idx_q counts FCS octets and resets on any non-FCS valid octet, which is correct only if the four FCS octets are contiguous on the wire — Chapter 19.3 §4 says they span a beat boundary on 4.74% of frame sizes, which this listing does not handle.

Production implication: stomp_outside_fcs is the bit that catches the injector being wired to the wrong signal, and it is the only self-check in the block. An injector armed on a frame whose in_last_four never asserts stomps nothing and reports success, which is Section 14's first prohibition; an injector whose in_last_four asserts early stomps the payload, which produces a different error the design may classify differently. One comparator separates the two, and neither is visible from the design's counters.


4. The Cheapest Injection and the Most Valuable

Measured against the coverage chapter's legal cross of four hundred and sixty seven thousand three hundred and twenty eight cells, the five error classes are very unequally sized. The classes that are legal in every size bucket — no error, a corrupted check value, and an alignment error — each occupy one hundred and forty seven thousand four hundred and fifty six cells, which is thirty one point five five per cent. Oversize is legal only in the largest bucket and occupies twenty four thousand five hundred and seventy six cells, five point two six per cent. Undersize is legal only in bucket zero, and bucket zero is a single frame size of sixty four octets and therefore has exactly one residue, so undersize occupies three hundred and eighty four cells, which is eight hundredths of one per cent. Now count design paths instead. A corrupted check value is the only stimulus for the residue classifier: one path. A runt of forty seven octets or fewer is the only stimulus for the undersize class, for the statistics counters' two bit addend, and for the check engine's two ends in a beat flag: three paths. The two rankings are inverted, and a verification plan that ranks by coverage puts the runt last.467 328 legalcells20.4's crossCheck value147 456 — 31.55%Alignment147 456 — 31.55%Oversize24 576 — 5.26%Undersize384 — 0.08%1 design paththe residue classifier3 design pathsclass, addend,two-endsThe rankingsinvertrank by paths12
Figure 2 — coverage share and design exposure rank the injections in opposite orders.

Section 3 built the cheapest one. This section is what each injectable error buys, measured against Chapter 20.4's legal cross of 467 328 cells.

The error dimension has five values and they are not equally reachable.

Error classLegal inCellsShare of the legal cross
noneevery bucket147 45631.55%
corrupted check valueevery bucket147 45631.55%
alignmentevery bucket147 45631.55%
oversizebucket 6 only24 5765.26%
undersizebucket 0 only3840.08%

The four rows sum to the whole legal cross, which is the check that this table and Chapter 20.4 §4's arithmetic agree.

Row five is the finding. Undersize is legal only in bucket 0, and Chapter 19.7 §2's bucket 0 is a single frame size — so it has one residue — and the undersize error class therefore occupies 1 × 64 × 3 × 2 = 384 cells. Eight hundredths of one per cent of the model.

And it is the only injectable error that reaches the counter path Module 19 spent a section deriving.

What a runt reaches
Chapter 19.7 §7's two-bit addendonly runts produce more than one frame ending per beat
Chapter 7.3's undersize and fragment classesnothing else does
Chapter 19.4 §7's two_ends_in_a_beatthe flag exists for exactly this
Chapter 20.4's cross384 cells — 0.08%

Three design paths and 0.08% of a coverage model. A plan that ranks injections by coverage return puts the runt last, and the runt is the only stimulus that exercises the arithmetic Chapter 19.7 §7 called the block's most-missed requirement.

The ranking by cost tells the opposite story again.

ErrorCostCoverageDesign paths reached
check value32 gates31.55%1 — the residue classifier
alignment3 interface bits31.55%2 — dribble handling and the exclusivity
oversizea bound5.26%1 — the MTU comparator
undersizea bound0.08%3 — Section 4's table

Column three and column four disagree on every row, and the column that should drive a verification plan is the fourth. Coverage is a measure of the stimulus's breadth and not of the design's exposure — which is Chapter 20.4 §11's boundary stated with an example rather than as a principle.


5. RTL 2 — The Length Violator

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// length_violator -- produce a frame outside the legal range. One
// constraint bound, two error classes, and very different value at each
// end. Sections 4, 5 and 6.
//
// Undersize reaches 384 cells of Chapter 20.4's cross and three design
// paths; oversize reaches 24 576 cells and one. The block is the same
// comparator either way, which is why the decision is about WHICH
// lengths to emit rather than about how.
// ---------------------------------------------------------------------
module length_violator
  import errinj_pkg::*;
#(
  parameter int CFG_MTU = 1518
) (
  input  logic              clk,
  input  logic              rst_n,

  input  logic              req_valid,
  input  inject_e           req_kind,
  input  logic [15:0]       req_length,
  output logic              req_ready,

  output logic              out_valid,
  output logic [15:0]       out_length,
  output logic              out_is_runt,
  output logic              out_is_giant,

  // Observability. Sections 15 and 16.
  output logic [31:0]       c_runts,
  output logic [31:0]       c_giants,
  output logic [63:0]       runt_lengths_seen,   // 5..63, one bit each
  output logic [5:0]        runt_lengths_count,
  output logic              multi_end_reachable
);

  int unsigned popcnt;

  assign req_ready    = 1'b1;
  assign out_valid    = req_valid;
  assign out_length   = req_length;
  assign out_is_runt  = req_valid && (req_length < 16'(MIN_FRAME));
  assign out_is_giant = req_valid && (req_length > 16'(CFG_MTU));

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      c_runts <= '0; c_giants <= '0;
      runt_lengths_seen <= '0; multi_end_reachable <= 1'b0;
    end else if (req_valid) begin
      if (out_is_runt) begin
        c_runts <= c_runts + 1;
        if (req_length >= 16'd5 && req_length < 16'd64)
          runt_lengths_seen[req_length - 16'd5] <= 1'b1;

        // Chapter 19.7 Section 7: a frame of 47 octets or fewer gives a
        // wire period of 64 or less, so a 64-octet beat can hold two
        // frame endings -- and below 16 octets it can hold three. That
        // is the ONLY stimulus that exercises the counters' two-bit
        // addend, and it is 0.08% of Chapter 20.4's cross.
        if (req_length <= 16'd47) multi_end_reachable <= 1'b1;
      end
      if (out_is_giant) c_giants <= c_giants + 1;
    end
  end

  always_comb begin
    popcnt = 0;
    for (int i = 0; i < 59; i++) if (runt_lengths_seen[i]) popcnt++;
    runt_lengths_count = 6'(popcnt);
  end

endmodule

Classification: a comparator, and the block whose value is entirely in which numbers it is asked to emit.

What it teaches: that multi_end_reachable is the bit that says whether the runt injection is doing the job it was added for. Chapter 19.7 §7 derived the thresholds: a frame of 48 octets or more gives one ending per beat; 16 to 47 gives two; 5 to 15 gives three. A run that injects runts of 50 and 55 octets produces undersize counts, fills coverage bins, and never exercises the two-bit addend — which is the whole reason runts were on the list.

And it teaches that the runt length range is 59 values and the useful part of it is 43. Lengths 5 to 63 are runts; lengths 48 to 63 are runts that behave like conformant frames for every purpose except the counter's class, and lengths 5 to 47 are the ones that pack two or three endings into a beat. A generator weighting uniformly over the runt range spends 27% of its runts on the uninteresting half.

Deliberately simplified: runt_lengths_seen is a 64-bit vector for a 59-value range, so five bits are permanently zero and the count is against 59 rather than 64. The block emits a length and does not build a frame, so the datapath that produces a 5-octet unit — which has no room for an EtherType — is elsewhere. And CFG_MTU is a parameter where a real design reads it from a register, so a run that changes the MTU at run time will see the giant threshold move under it.

Production implication: runt_lengths_count against 43 is the number to gate a regression on, not c_runts. Ten thousand runts all of 50 octets is one length, one behaviour and no multi-end beats; forty-three distinct lengths below 48 exercises every threshold Chapter 19.7 §7 derived. The counter is six bits and it distinguishes a runt injection that works from one that reports well.


6. The Runt Is 0.08% of the Cross and All of the Addend

Section 4 gave the ranking. This section is the arithmetic behind the runt's three design paths, because two of them are not obvious and the third is the one that gets skipped.

Chapter 19.7 §7's derivation, re-read:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
wire period = L + 8 (preamble and SFD) + 9 (minimum gap) = L + 17
endings in one 64-octet beat ≤ floor(64 / (L + 17)) + 1
Frame lengthWire periodEndings per beat
48 and above65 and above1
16 to 4733 to 642
5 to 1522 to 323

So the three design paths a runt reaches are:

PathRequiresWhere it is
the undersize and fragment classesany length below 64Chapter 7.3 §3
the counters' two-bit addenda length of 47 or belowChapter 19.7 §7
two_ends_in_a_beatthe sameChapter 19.4 §8

Row three is the one worth dwelling on because it is an error flag rather than a counter. Chapter 19.4's CRC engine may assume one frame ends per beat — a runt's residue is discarded anyway — so it raises two_ends_in_a_beat and stops trusting its own results. The statistics block may not. A runt injection therefore exercises two blocks that disagree about whether the condition is legal, which is the sharpest single test in Module 19 and it costs one constraint bound.

And the 0.08% has a specific cause worth naming.

Cells
bucket 0's bucket-residue pairs1 — bucket 0 is one frame size
× 64 offsets × 3 tag counts × 2 dual values384
of Chapter 20.4's legal 467 3280.08%

The runt's coverage share is small because Chapter 19.7 §2's bucket 0 is a single size, so every runt lands in the same bucket with the same residue. The dimension that makes runts look negligible is the one that makes them a single point, and a coverage model that weighted by design exposure rather than by cell count would rank them first.

Which gives this chapter's first diagnostic question.

Before ranking an injection by its coverage share, ask how many design paths it is the only stimulus for.


7. RTL 3 — The Dribble Injector

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// dribble_injector -- end a frame part-way through an octet. Sections 7
// and 8.
//
// This is the expensive injection and the expense is structural. Chapter
// 19.1 Section 3's beat carries a BYTE enable; a dribble frame ends after
// a partial octet, so the interface needs a BIT count as well -- three
// bits on every beat of the receive datapath, which is a change to the
// DESIGN and not to the testbench.
//
// And it does not produce an alignment error by itself. Chapter 7.3
// Section 3: an alignment error is a frame that fails its check sequence
// AND is not whole octets. The dribble supplies the second condition;
// the first follows because the check value no longer covers the frame.
// Section 8 is why that matters for a property.
// ---------------------------------------------------------------------
module dribble_injector
  import errinj_pkg::*;
(
  input  logic              clk,
  input  logic              rst_n,

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

  input  logic              arm,
  input  logic [2:0]        cfg_dribble_bits,   // 1..7 valid bits in the last octet

  output logic              out_valid,
  output logic [7:0]        out_octet,
  output logic              out_eof,
  output logic [2:0]        out_valid_bits,     // 0 means a whole octet

  // Observability. Sections 15 and 16.
  output logic [31:0]       c_dribbles,
  output logic [7:0]        dribble_widths_seen,
  output logic              interface_lacks_bit_count,
  output logic              dribble_without_fcs_fail
);

  logic       armed_q;
  logic [2:0] bits_q;

  assign out_valid      = in_valid;
  assign out_octet      = in_octet;
  assign out_eof        = in_eof;
  assign out_valid_bits = (armed_q && in_eof) ? bits_q : 3'd0;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      armed_q <= 1'b0; bits_q <= '0;
      c_dribbles <= '0; dribble_widths_seen <= '0;
      interface_lacks_bit_count <= 1'b0;
      dribble_without_fcs_fail <= 1'b0;
    end else begin
      if (arm) begin
        armed_q <= 1'b1;
        bits_q  <= (cfg_dribble_bits == 3'd0) ? 3'd1 : cfg_dribble_bits;
      end

      if (in_valid && in_eof && armed_q) begin
        c_dribbles <= c_dribbles + 1;
        dribble_widths_seen[bits_q] <= 1'b1;
        armed_q <= 1'b0;
      end

      // A receive path whose interface has no bit count cannot carry a
      // dribble at all: the frame arrives as a whole number of octets
      // and the injection is silently lost. Section 14's first
      // prohibition, in its most expensive form.
      if (arm && !INTERFACE_HAS_BIT_COUNT) interface_lacks_bit_count <= 1'b1;
    end
  end

  // Chapter 7.3 Section 3: alignment and CRC are MUTUALLY EXCLUSIVE, and
  // a dribble that somehow left the check value passing would be
  // neither -- which the standard does not define. If this fires, the
  // frame's check value was recomputed after the dribble, which means
  // the injection happened on the wrong side of Chapter 19.4's appender.
  always_comb dribble_without_fcs_fail = (c_dribbles != '0) && fcs_passed_on_dribble;

endmodule

Classification: the only injection in this chapter that requires a change to the design's interface.

What it teaches: that a dribble is three bits on every beat of the receive datapath and there is no way around it. Chapter 19.1 §3's beat carries a byte count; a frame that ends after five bits of its final octet cannot be described by one. So supporting alignment-error injection means the MAC's receive interface carries a bit count whether or not anybody ever injects one — and that is why interface_lacks_bit_count exists and why alignment errors are the injectable error most often quietly dropped from a plan.

And it teaches that the injection point decides whether the error is what it claims to be. A dribble applied before Chapter 19.4's appender produces a frame whose check value the design computes over the dribbled data — which passesand is then neither an alignment error nor a CRC error, a state Chapter 7.3 §3 does not define. The dribble must go after the appender, and dribble_without_fcs_fail is the check that it did.

Deliberately simplified: INTERFACE_HAS_BIT_COUNT and fcs_passed_on_dribble are referenced and not declared — they stand for an elaboration-time parameter and a downstream observation that a real environment supplies. out_valid_bits uses 0 to mean eight valid bits, which is the common encoding and is a trap for a reader who expects 8. And the block does not model the PHY's own dribble handling, which on some interfaces pads to an octet boundary before the MAC ever sees it — in which case the injection is absorbed below the design and reaches nothing.

Production implication: dribble_widths_seen should reach all seven values, and the reason is Chapter 7.3 §7's detector. That chapter's block decides whether a frame is whole octets by testing a bit count against zero; a detector that tests only one bit of it passes six of the seven widths and fails one, and no single-width injection finds that. Seven bits of a register, and it is the difference between testing a comparator and testing a comparison against zero.


8. Alignment and the Check Value Are Mutually Exclusive

Chapter 7.3 §3's normative definitions make two of this chapter's injections dependent, and an injector that treats them as independent asserts something false about a correct design.

The definitions, exactly:

FrameWhole octets?Check valueClass
ayespassesvalid
byesfailsCRC error
cnofailsalignment error
dnopassesnot defined

Row c is the alignment error and row b is the CRC error, and a frame is counted as exactly one. Chapter 7.3 §3: a frame with multiple error conditions is counted once, and the standard resolves the ambiguity by making the classes exclusive by construction.

So an injector that arms both a dribble and a check-value stomp on the same frame has produced row c, and the design counts one alignment error and zero CRC errors. An injector that recorded two intents and a property that asserts both counters move fires on a correct design.

And row d is the one that says where the dribble must be applied.

Dribble appliedThe design's check valueClass
before the appendercomputed over the dribbled frame — passesrow d, undefined
after the appenderno longer covers the frame — failsrow c, alignment error

Row one is a frame the standard has no name for, and a design that meets it will do something defensible and unpredictable. The injector's job is not to explore that; it is to produce row c, and the only way is to dribble after the check value has been appended.

Which gives the dependency table an injector has to carry.

IntentProducesDepends on
stomp the check valuea CRC errorthe frame being whole octets
dribblean alignment errorthe check value failing, which it does
both togetheran alignment error onlyChapter 7.3 §3's exclusivity
stomp on a runta fragmentChapter 7.3's composition
stomp on a gianta jabberthe same

Rows three to five are compositions where the injector's two intents produce one class, and every one of them is a place where "I injected X, therefore counter X increments" is wrong. Section 20's rejected class is that property, and this table is its evidence.


9. RTL 4 — The Gap Violator

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// gap_violator -- emit an interframe gap below Chapter 5.9's floor.
// Sections 2 and 9.
//
// Ten gates, and it is the injection that tests a chapter nobody
// associates with error injection. Chapter 19.5 Section 3's elastic
// buffer is one octet deep and its correctness rests on the FAR END
// leaving at least nine octets of gap; Chapter 4.4 Section 2 showed that
// without the discharge the accumulation is unbounded.
//
// So a short gap is the only injectable error that attacks a buffer
// rather than a frame.
// ---------------------------------------------------------------------
module gap_violator
  import errinj_pkg::*;
(
  input  logic              clk,
  input  logic              rst_n,

  input  logic              in_gap,           // idle octet on the wire
  input  logic              in_sof,

  input  logic              arm,
  input  logic [3:0]        cfg_gap_octets,   // 0..8 are violations

  output logic              out_gap,
  output logic              out_sof,
  output logic              suppress,

  // Observability. Sections 15 and 16.
  output logic [31:0]       c_short_gaps,
  output logic [15:0]       gaps_seen,        // one bit per width 0..15
  output logic [4:0]        min_gap_emitted,
  output logic              gap_at_or_above_floor
);

  logic [4:0] gap_count;
  logic       armed_q;

  // The whole injection: swallow idle octets until the configured count
  // is reached, then let the frame start. Emitting FEWER idle octets is
  // the only way to violate the floor; emitting more is always legal.
  assign suppress = armed_q && in_gap && (gap_count >= 5'(cfg_gap_octets));
  assign out_gap  = in_gap && !suppress;
  assign out_sof  = in_sof;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      gap_count <= '0; armed_q <= 1'b0;
      c_short_gaps <= '0; gaps_seen <= '0;
      min_gap_emitted <= 5'd31; gap_at_or_above_floor <= 1'b0;
    end else begin
      if (arm) armed_q <= 1'b1;

      if (in_gap) gap_count <= gap_count + 1;

      if (in_sof) begin
        if (gap_count < 5'd16) gaps_seen[gap_count[3:0]] <= 1'b1;
        if (gap_count < min_gap_emitted) min_gap_emitted <= gap_count;

        // Chapter 5.9's floor is nine octets. A gap of nine or more is
        // conformant however unusual, so an injection that lands there
        // has produced no error at all -- Section 14's first
        // prohibition in its quietest form.
        if (armed_q && gap_count >= 5'(MIN_GAP)) gap_at_or_above_floor <= 1'b1;
        if (armed_q && gap_count <  5'(MIN_GAP)) c_short_gaps <= c_short_gaps + 1;

        gap_count <= '0;
        armed_q   <= 1'b0;
      end
    end
  end

endmodule

Classification: ten gates, and the only injection that targets a buffer rather than a frame.

What it teaches: that a short gap is an attack on Chapter 19.5 §3's elastic buffer and on nothing else. That buffer is one octet deep — 1 518 octets at 200 ppm accumulates 0.3036 octets — and its correctness rests entirely on the partner leaving room to discharge. Chapter 4.4 §2 showed that without the discharge the accumulation is unbounded, so a stream of short gaps overruns a four-octet buffer while every frame on the wire is individually perfect.

And it teaches how fast. At 200 ppm the drift is one octet per 5 000 octets; a four-octet elastic buffer therefore overruns after 20 000 octets of undischarged traffic1.6 µs at 100 Gb/s. The injection is ten gates and the failure appears in microseconds, which makes it the highest-value-per-gate injection in the chapter after the check-value stomp.

Deliberately simplified: the violator suppresses idle octets rather than shortening a gap the transmitter is generating, so it assumes the upstream source emits at least cfg_gap_octets of idle — a source already emitting nine cannot be pushed below nine by suppression alone if the count is set to ten. gaps_seen is 16 bits for a gap that can exceed 15 octets, so long gaps all land in no bucket. And min_gap_emitted initialises to 31 rather than to a width-correct maximum.

Production implication: gap_at_or_above_floor is the bit that says the injection did nothing, and it is set by the most natural misconfiguration there is. Chapter 5.9's floor is nine, and a cfg_gap_octets of 9 or 10 produces a gap that is unusual, legal and harmless — the design handles it, no counter moves, and the injection reports success. The useful range is 0 to 8, which is nine values, so seven of the sixteen settings of a four-bit configuration field produce no error at all, and a naive sweep spends 43.8% of its injections on legal gaps.


10. What the Wire Cannot Inject

Five errors Module 19 spent sections on, and no frame produces any of them.

ErrorChapterWhat produces it
a receive FIFO overflowChapter 19.5 §14a stalled consumer
a reorder-buffer overflowChapter 19.6 §9the bus model's reorder probability
a 200 ppm clock driftChapter 19.5 §4two clock sources
a descriptor ownership violationChapter 18.2the driver model
a bus error responseChapter 18.5the interconnect model

Every row's third column is a different agent, which is Chapter 20.1 §11's class C — and the reason it recurs here is that an error injector is a stimulus component and these are not stimulus.

Row one is the important one and Section 12 proves it cannot be done from the wire. The others are worth a sentence each because the reasons differ.

Row two is a probability rather than an event. Chapter 19.6 §21: with eight outstanding transactions, filling the reorder buffer needs four of seven responses to arrive early at once — at an independent per-response probability of 0.05 that is once in 5 166 responses, and at zero it is never. No frame changes that probability.

Row three is a clock-generator configuration. Chapter 19.5 §21's scenario 47: 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.

Rows four and five are software and bus models, and they are here because Chapter 20.3 §13 needs all five of the MAC's legal drops exercised before a missing frame can be called a bug — and two of the five are not frame properties.

So the injector's responsibility for these is not to produce them.

What an error injector can do
produce the errorno
report that its site is not the wireyes — site_of(), one case statement
refuse to arm an injection it cannot deliveryes, and it should

And there is a third category between them, which is the reason Section 17's matrix has a column for it. Two of the most productive injections in this chapter — a destination address the filter will reject and a VLAN identifier the port is not a member of — are emitted by the wire and are not errors at all. Every octet is legal, the check value is correct, and Chapter 7.3 classifies the frame as good. The design discards it on a rule rather than on a fault, so no error counter moves and the coverage cross records a perfectly ordinary frame.

Error classCounter that movesDesign path reached
a corrupted check valueCRC errorc_crc_errorsthe residue classifier
a wrong destination addressnone — a good framea filter counter, if there is onethe address filter
a non-member VLANnone — a good framea membership counter, if there is onethe port membership rules

"If there is one" is the whole problem with this category. A discard on a rule is not a failure and an RMON counter set has no bin for it — so the evidence that the injection worked is a frame that did not arrive, which is Chapter 20.3 §13's legal-drop accounting and not an error counter at all. These two injections cost one field each to produce and are invisible to every measurement in Chapter 20.4.

Row three is the position this chapter takes and it differs from Chapter 20.1 §11's, which only reported. An injector that accepts INJ_STALL and does nothing has told a verification plan that the case is covered; one that refuses to arm it has told the plan to go and configure a memory model. The difference is a $fatal at elaboration.


11. RTL 5 — The Consumer Staller

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// consumer_staller -- the only way to produce Chapter 19.5 Section 14's
// mid-frame truncation. Sections 10, 11 and 12.
//
// This block is NOT on the wire. It sits on the receive FIFO's read
// side and stops it, which is the fault a memory system produces and no
// frame can. Section 12 proves that a correctly sized FIFO cannot be
// overflowed from the wire at all.
//
// The stall length is the whole parameter: Chapter 19.5 Section 11 sized
// the FIFO for a 2.000 microsecond stall and the FIFO holds 2.621
// microseconds of wire time, so the injection has to exceed 2.621 to
// reach the truncation path.
// ---------------------------------------------------------------------
module consumer_staller
  import errinj_pkg::*;
#(
  parameter int FIFO_BEATS     = 512,
  parameter int BEAT_NS_X100   = 512,    // 5.12 ns at 195.3125 MHz
  parameter int DESIGN_STALL_NS = 2000   // what Chapter 19.5 sized for
) (
  input  logic              clk,
  input  logic              rst_n,

  input  logic              arm,
  input  logic [31:0]       cfg_stall_ns,

  output logic              hold_read,

  // Observability. Sections 15 and 16.
  output logic [31:0]       c_stalls,
  output logic [31:0]       longest_stall_ns,
  output logic [31:0]       beats_of_headroom,
  output logic              stall_below_design_limit,
  output logic              stall_reaches_truncation
);

  // How long the FIFO can absorb before it overflows, in nanoseconds.
  // 512 beats x 5.12 ns is 2 621 ns -- Section 12.
  localparam int FIFO_NS = (FIFO_BEATS * BEAT_NS_X100) / 100;

  logic [31:0] elapsed;
  logic        active;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      elapsed <= '0; active <= 1'b0;
      c_stalls <= '0; longest_stall_ns <= '0;
      stall_below_design_limit <= 1'b0;
      stall_reaches_truncation <= 1'b0;
    end else begin
      if (arm && !active) begin
        active   <= 1'b1;
        elapsed  <= '0;
        c_stalls <= c_stalls + 1;

        // A stall shorter than what the FIFO was sized for reaches
        // nothing: Chapter 19.5's whole design is that the depth covers
        // the worst stall the memory system may produce.
        if (cfg_stall_ns <= 32'(DESIGN_STALL_NS))
          stall_below_design_limit <= 1'b1;

        // And a stall longer than the FIFO's own capacity is the only
        // one that reaches Chapter 19.5 Section 14's truncation path.
        if (cfg_stall_ns > 32'(FIFO_NS))
          stall_reaches_truncation <= 1'b1;
      end else if (active) begin
        elapsed <= elapsed + 32'((BEAT_NS_X100) / 100);
        if (elapsed >= cfg_stall_ns) begin
          active <= 1'b0;
          if (elapsed > longest_stall_ns) longest_stall_ns <= elapsed;
        end
      end
    end
  end

  assign hold_read = active;

  // How much of the FIFO is left. A run whose headroom never approaches
  // zero has not exercised the overflow guard however many stalls it
  // injected.
  assign beats_of_headroom = (elapsed * 32'd100) / 32'(BEAT_NS_X100) >= 32'(FIFO_BEATS)
                           ? 32'd0
                           : 32'(FIFO_BEATS) - ((elapsed * 32'd100) / 32'(BEAT_NS_X100));

endmodule

Classification: the chapter's only non-wire injector, and the block that proves an error injector is not one component.

What it teaches: that two thresholds matter and they are 621 nanoseconds apart. A stall of 2 000 ns is what Chapter 19.5 §11 sized the FIFO for — it reaches the almost-full level and nothing else. A stall of more than 2 621 ns overflows the FIFO and reaches Chapter 19.5 §14's truncation path. Between them is a 621 ns window where the design is under maximum stress and behaving correctly, which is a useful place to sit and is not where the interesting bug is.

And it teaches that stall_below_design_limit catches the most likely misconfiguration. A stall configured at the memory system's worst case — 2 000 ns, the number everybody hasexercises the almost-full path, the PAUSE emission and the headroom, and never the drop. An injection plan built from the design's own stall figure tests everything except the thing it was added for.

Deliberately simplified: elapsed accumulates in integer nanoseconds from a beat period expressed as hundredths, which loses 0.12 ns per beat and drifts by 61 ns over a 512-beat stall — a real staller counts beats and converts once. The block stalls the read side absolutely, where a memory system's stall is a distribution. And it does not model Chapter 19.6's reorder path at all, which is the other non-wire fault and belongs to the bus model.

Production implication: stall_reaches_truncation is the bit that gates whether Chapter 19.5 §14's c_frames_truncated can ever be non-zero, and therefore whether Chapter 19.6 §22's complaint-1 diagnosis has ever been exercised. That diagnosis — truncations against CRC errors, separating a buffer fault from a cable fault — is the most expensive field failure in Module 19, and the only stimulus that produces it is a stall longer than 2.621 µs. One parameter, and it is set below the threshold by default because the default is the design's own stall figure.


12. Why the Wire Cannot Overflow a Correctly Sized FIFO

The receive buffer chapter sized the first in first out buffer from three terms: three hundred and ninety one beats for a two microsecond memory stall, one hundred and twenty three beats for the pause round trip headroom, and four beats for the synchroniser reserve, rounded down to five hundred and twelve beats, which is two point six two one microseconds of wire time at one hundred gigabits per second. Now suppose the consumer never stops. The arrival rate is bounded by the line rate, because that is what a line rate is: five hundred and twelve bits at one hundred and ninety five point three one two five megahertz is exactly one hundred gigabits per second and the wire cannot deliver faster. The drain rate is the system side's, which the same chapter put at one hundred and twenty eight gigabits per second, a twenty eight per cent margin. So the buffer empties, whatever the frames look like: minimum size frames back to back, jumbo frames back to back, or any mixture, because the arrival rate is the same in every case. No sequence of frames can overflow a receive buffer whose drain rate exceeds the line rate. The consumer must stop, and only a stall longer than two point six two one microseconds reaches the truncation path, which means the diagnosis that separates a buffer fault from a cable fault is downstream of a fault no frame produces.Arrival: 100 Gb/sthe line rate, alwaysDrain: 128 Gb/sa 28% margin512 beats2.621 us of wire timeIt emptieswhatever the framesareThe consumerstopsnot a wire eventAbove 2.621 usthe only overflow19.6 Section 22'sdiagnosisdownstream of it12
Figure 3 — the arrival rate is the line rate, so the consumer must stop; and stopping it is not a wire event.

Section 10 asserted it and this section proves it, because the proof is short and it changes what an injection plan can promise.

Chapter 19.5 §11 sized the receive FIFO from three terms, and the whole point of the sizing is the claim this section needs:

TermBeatsCovers
the memory stall391a 2 µs stop
Chapter 14.2's headroom123the PAUSE round trip
the synchroniser reserve418.24 ns
rounded down to a power of two5122.621 µs of wire time

Now suppose the consumer never stops and the wire does its worst.

The arrival rate is bounded by the line rate. That is not an assumption; it is what a line rate isChapter 19.1 §4's 512 bits at 195.3125 MHz is exactly 100 Gb/s and the wire cannot deliver faster. The drain rate is the system side's, which Chapter 19.5 §4 put at 128 Gb/s — a 28% margin.

Rate
arrival, worst case100 Gb/s
drain, with the consumer running128 Gb/s
netthe FIFO empties

So with the consumer running the occupancy is bounded by a beat or two of jitter, whatever the wire sends. Minimum-size frames back to back, jumbo frames back to back, a mix — the arrival rate is the same 100 Gb/s in every case, because that is the definition of the line rate.

Which gives the result.

No sequence of frames can overflow a receive FIFO whose drain rate exceeds the line rate. The consumer must stop, and stopping it is not a wire event.

And the corollary is the part that matters for a plan. Chapter 19.5 §14's c_frames_truncated, Chapter 19.6 §22's complaint-1 diagnosis and Chapter 19.7 §15's likely_buffer_not_cable are all downstream of a fault no frame produces — so a regression whose error injection is entirely wire-side has zero coverage of the most expensive field failure in Module 19 and no indication that anything is missing.

The one exception is worth naming because it looks like a counterexample and is not.

A design whose drain rate is below the line rate can be overflowed from the wire trivially — it overflows on its own, without any injection. That is not an error injection; it is a design that does not meet its own rate, and Chapter 19.5 §17's row seven lists it as an assumption for exactly this reason. The FIFO is overflowable from the wire only when the design is already broken.


13. RTL 6 — The Injection Scheduler

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// errinj_scheduler -- decide which frame gets which injection, refuse
// the ones this site cannot deliver, and record what was attempted.
// Sections 10, 13 and 14.
//
// Two jobs. The first is ordinary: a rate, a frame index, an arm.
// The second is the chapter's: refuse to arm an injection whose site is
// not the wire, so a plan that lists INJ_STALL as covered is corrected
// at elaboration rather than believed for a quarter.
// ---------------------------------------------------------------------
module errinj_scheduler
  import errinj_pkg::*;
#(
  parameter int  RATE_PER_MILLION = 1000,
  parameter bit  HAVE_CONSUMER_STALLER = 1'b0,
  parameter bit  HAVE_BUS_MODEL        = 1'b0,
  parameter bit  HAVE_BIT_COUNT        = 1'b0
) (
  input  logic              clk,
  input  logic              rst_n,

  input  logic              frame_start,
  input  logic [15:0]       frame_index,

  input  logic              req_valid,
  input  inject_req_t       req,

  output logic              arm_fcs,
  output logic              arm_length,
  output logic              arm_dribble,
  output logic              arm_gap,
  output logic              arm_stall,

  // Observability. Sections 15 and 16.
  output logic [31:0]       c_armed [10],
  output logic [31:0]       c_refused,
  output logic [9:0]        kinds_armed,
  output logic              site_unavailable
);

  // Elaboration-time refusal. Section 10: an injector that accepts an
  // injection it cannot deliver has told a verification plan the case
  // is covered.
  initial begin
    if (!HAVE_CONSUMER_STALLER)
      $display("ERRINJ: INJ_STALL unavailable -- no consumer staller bound");
    if (!HAVE_BUS_MODEL)
      $display("ERRINJ: INJ_REORDER unavailable -- no bus model bound");
    if (!HAVE_BIT_COUNT)
      $display("ERRINJ: INJ_DRIBBLE unavailable -- interface has no bit count");
  end

  function automatic bit deliverable(input inject_e e);
    case (site_of(e))
      AT_CONSUMER:  deliverable = HAVE_CONSUMER_STALLER;
      AT_BUS_MODEL: deliverable = HAVE_BUS_MODEL;
      AT_PHY:       deliverable = HAVE_BIT_COUNT;
      default:      deliverable = 1'b1;
    endcase
  endfunction

  logic fire;
  assign fire = frame_start && req_valid && req.armed &&
                (req.frame_index == frame_index);

  always_comb begin
    arm_fcs     = 1'b0; arm_length = 1'b0; arm_dribble = 1'b0;
    arm_gap     = 1'b0; arm_stall  = 1'b0;
    if (fire && deliverable(req.kind)) begin
      unique case (req.kind)
        INJ_FCS:                        arm_fcs     = 1'b1;
        INJ_UNDERSIZE, INJ_OVERSIZE:    arm_length  = 1'b1;
        INJ_DRIBBLE:                    arm_dribble = 1'b1;
        INJ_SHORT_GAP:                  arm_gap     = 1'b1;
        INJ_STALL:                      arm_stall   = 1'b1;
        default: ;
      endcase
    end
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      for (int i = 0; i < 10; i++) c_armed[i] <= '0;
      c_refused <= '0; kinds_armed <= '0; site_unavailable <= 1'b0;
    end else if (fire) begin
      if (deliverable(req.kind)) begin
        c_armed[req.kind] <= c_armed[req.kind] + 1;
        kinds_armed[req.kind] <= 1'b1;
      end else begin
        c_refused        <= c_refused + 1;
        site_unavailable <= 1'b1;
      end
    end
  end

endmodule

Classification: a dispatcher whose interesting behaviour is refusal.

What it teaches: that deliverable() is three HAVE_ parameters and it is the difference between a plan that is wrong and a plan that is corrected at elaboration. An injector that accepts INJ_STALL with no consumer staller bound arms nothing, counts nothing and reports success — so the verification plan records the truncation path as exercised, and Chapter 19.6 §22's diagnosis is never tested. The refusal costs a case statement.

And it teaches that kinds_armed is the right shape for a summary line and c_armed is not. Ten counters say how many of each; ten bits say which kinds happened at all — and the second is the question a plan asks. A run with a million check-value stomps and zero dribbles reads well in the first and badly in the second, which is the correct reading.

Deliberately simplified: the scheduler fires on an exact frame-index match, so a request for a frame the run never reaches is silently never armed — a real scheduler falls back to a rate and reports the miss. RATE_PER_MILLION is declared and unused, which is where the rate-driven mode would go. And the $display messages are unconditional at elaboration, which is noisy and is deliberate: the alternative is a silence that reads as availability.

Production implication: the three HAVE_ parameters should come from the environment's own structure rather than from a configuration file, because a configuration file can say HAVE_CONSUMER_STALLER = 1 when no staller is bound. Deriving them from whether the corresponding module elaborated — Chapter 20.2 §11's bind census, applied to injectors — makes the refusal a fact rather than a claim, and it is the same argument that chapter made about a bind that matches nothing.


14. What an Error Injector Must Never Do

Six prohibitions, and three of them are ways an injection reports success and delivers nothing.

#Must neverBecauseSymptom
1arm an injection it cannot deliverSection 13a plan that records a covered case
2stomp anywhere but the check valueSection 3a different error, classified differently
3emit a "short" gap of nine or moreChapter 5.9's floorlegal, harmless, and counted as injected
4dribble before the check value is appendedSection 8's row da frame the standard does not define
5assert on its own intentSection 20's class 92a property that fires on a correct design
6inject only at lengths above 47Chapter 19.7 §7runts that never pack a beat

Rows one, three and six are the same failure wearing three costumes: the injection happened and reached nothing. Row one arms a site that does not exist; row three produces a legal gap; row six produces a runt that behaves exactly like a conformant frame for every purpose except its class. All three increment a counter called c_injected and none of them tests anything.

Row two deserves its own note because it is the one that produces a different error rather than none. A stomp applied to the payload is caught by the design's own check value — the design computes the FCS over the corrupted data if the stomp is upstream of the appender, in which case nothing is wrong at all, or the frame fails its check value if the stomp is downstream, in which case the injector has produced a CRC error by a longer route. Neither is what was asked for and one of them is silent.

Row five is the rejected class previewed, and the prohibition is broader than the assertion. Any check whose subject is the injector's intent rather than the frame's properties is checking the injector. Chapter 7.3 §3 owns what a frame is; the injector owns what it did to the frame, and the two are different facts that agree only when the injector's model of the classification matches the standard's.

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

Why it is a prohibition
row threea gap of nine is conformant; a report that calls it injected is false
row sixa runt above 47 reaches one design path instead of three

And there is a seventh rule that belongs to the injector's report rather than to its stimulus: never report an injection the design was never given a chance to see. Three of the prohibitions above share that shape — an unbound site, a conformant gap, a runt in the inert sixteen octetsand in all three the injector's own counter is accurate. c_stomped really did reach 1 000; the frames really were emitted; the number is true and useless. The telemetry of Section 15 exists to separate those two properties, and the fields that do it are paths_reached and injections_without_effect — one counting the design's exposure, the other naming the gap — neither of which a coverage model can compute, because both are facts about the injector rather than about the design.

NumberSaysCan be high while the design is untouched
c_injected_totalhow many injections firedyes
cross_share_pctwhat share of the cross they fillyes
paths_reachedhow many design paths they reachno — this is the one to gate on
injections_without_effectthe gap between the first two and the thirdit is the gap

Both produce a coverage number that rises and a design exposure that does not, which is the failure mode Chapter 20.4 §20's class 91 is about, arriving here as a stimulus decision rather than as a metric.


15. RTL 7 — Injection Telemetry

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// errinj_telemetry -- what was injected, what it reached, and the gap
// between the two. Section 15.
//
// The pair that matters is "injections armed" against "design paths
// reached", and they are not the same number. Section 4: a check-value
// stomp reaches one path and 31.55% of the coverage cross; a runt below
// 48 octets reaches three paths and 0.08%.
// ---------------------------------------------------------------------
module errinj_telemetry
  import errinj_pkg::*;
(
  input  logic              clk,
  input  logic              rst_n,

  input  logic [31:0]       c_stomped,
  input  logic [31:0]       c_runts,
  input  logic [31:0]       c_giants,
  input  logic [31:0]       c_dribbles,
  input  logic [31:0]       c_short_gaps,
  input  logic [31:0]       c_stalls,
  input  logic [5:0]        runt_lengths_count,
  input  logic [7:0]        dribble_widths_seen,
  input  logic [9:0]        kinds_armed,
  input  logic              multi_end_reachable,
  input  logic              stall_reaches_truncation,
  input  logic              gap_at_or_above_floor,
  input  logic [31:0]       c_frames,

  // What was injected.
  output logic [31:0]       c_injected_total,
  output logic [15:0]       inject_rate_ppm,

  // What it reached. Section 4's fourth column, counted.
  output logic [3:0]        paths_reached,
  output logic [15:0]       cross_share_pct,

  // The gap between the two.
  output logic              wire_only_plan,
  output logic              runts_too_long,
  output logic              injections_without_effect
);

  int unsigned n_dribble_widths;

  assign c_injected_total = c_stomped + c_runts + c_giants +
                            c_dribbles + c_short_gaps + c_stalls;

  assign inject_rate_ppm = (c_frames == 0) ? 16'd0
    : 16'((c_injected_total * 32'd1_000_000) / c_frames);

  // Section 4: the design paths each injection is the ONLY stimulus for.
  // The check value reaches the residue classifier; a runt below 48
  // reaches the undersize class, the two-bit addend and Chapter 19.4
  // Section 8's two-ends flag; a dribble reaches the whole-octet
  // detector and the exclusivity; a stall reaches the truncation path.
  always_comb begin
    paths_reached = '0;
    if (c_stomped != '0)              paths_reached += 4'd1;
    if (c_runts != '0)                paths_reached += 4'd1;
    if (multi_end_reachable)          paths_reached += 4'd2;
    if (c_dribbles != '0)             paths_reached += 4'd2;
    if (stall_reaches_truncation)     paths_reached += 4'd1;
  end

  // Section 4: each error class's share of Chapter 20.4's legal cross.
  always_comb begin
    cross_share_pct = 16'd0;
    if (c_stomped  != '0) cross_share_pct += 16'd32;   // 31.55, rounded
    if (c_dribbles != '0) cross_share_pct += 16'd32;
    if (c_giants   != '0) cross_share_pct += 16'd5;    // 5.26
    if (c_runts    != '0) cross_share_pct += 16'd0;    // 0.08 -- rounds away
  end

  // Section 12: a plan with no consumer staller has zero coverage of the
  // most expensive field failure in Module 19 and no sign of it.
  assign wire_only_plan = (c_stalls == '0) && (c_frames > 32'd100000);

  // Section 6: a runt above 47 octets reaches one design path, not three.
  assign runts_too_long = (c_runts > 32'd1000) && !multi_end_reachable;

  // Section 14's rows one, three and six, together.
  assign injections_without_effect = gap_at_or_above_floor ||
                                     runts_too_long ||
                                     (kinds_armed == 10'd0 && c_frames > 32'd10000);

  always_comb begin
    n_dribble_widths = 0;
    for (int i = 1; i < 8; i++) if (dribble_widths_seen[i]) n_dribble_widths++;
  end

endmodule

Classification: an observability block whose two outputs disagree with each other on purpose.

What it teaches: that cross_share_pct rounds the runt away and paths_reached does not. A run that injects check-value stomps and runts below 48 octets reports 32% of the cross and five design paths; a run that injects only stomps reports 32% and one. The coverage number is identical and the second run has tested a third as much — which is Section 4's inversion made into two registers that a reader can compare.

And it teaches that wire_only_plan is the single most valuable bit in this chapter. Chapter 19.5 §14's truncation path, Chapter 19.6 §22's diagnosis and Chapter 19.7 §15's likely_buffer_not_cable are all downstream of a stall no frame producesso a wire-only injection plan has zero coverage of the module's most expensive field failure, and nothing else in any report says so.

Deliberately simplified: cross_share_pct adds integer percentages and double-counts nothing, so a run injecting every class reports 69% where the true figure is 68.44 — close enough for a summary line and wrong for arithmetic. paths_reached weights the multi-end case as two paths, which is a judgement rather than a measurement. And n_dribble_widths is computed and never output, which is where Section 7's seven-width check should have gone.

Production implication: print paths_reached and cross_share_pct side by side and in that order. The first is what the injection plan bought and the second is what it will be reported as, and the two diverge most exactly where the design's exposure is highest. A team that reads only the second will delete the runt injection to save simulation time, because it costs the same as a check-value stomp and appears to buy nothing.


16. RTL 8 — The Injection Conformance Monitor

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// errinj_conformance_monitor -- verdicts about the injection plan.
// Section 16, and the eighteenth in Modules 18 to 20.
//
// Six verdicts. Two are about the injector's own correctness, three
// about whether the plan reaches anything, and one is the site
// availability Section 13 refuses on.
// ---------------------------------------------------------------------
module errinj_conformance_monitor
  import errinj_pkg::*;
#(
  parameter int MIN_RUNT_LENGTHS   = 43,
  parameter int MIN_DRIBBLE_WIDTHS = 7
) (
  input  logic              clk,
  input  logic              rst_n,

  input  logic              stomp_outside_fcs,
  input  logic              dribble_without_fcs_fail,
  input  logic              gap_at_or_above_floor,
  input  logic              site_unavailable,
  input  logic              wire_only_plan,
  input  logic              runts_too_long,
  input  logic [5:0]        runt_lengths_count,
  input  logic [7:0]        dribble_widths_seen,
  input  logic [9:0]        kinds_armed,
  input  logic [31:0]       c_frames,

  output logic              injector_misplaced,
  output logic              injection_ineffective,
  output logic              site_missing,
  output logic              runt_space_thin,
  output logic              dribble_space_thin,
  output logic              non_wire_faults_absent,
  output logic              none_of_the_above
);

  int unsigned widths;

  always_comb begin
    widths = 0;
    for (int i = 1; i < 8; i++) if (dribble_widths_seen[i]) widths++;

    // The injector is wired to the wrong signal. Sections 3 and 7.
    injector_misplaced = stomp_outside_fcs || dribble_without_fcs_fail;

    // An injection that reached nothing. Section 14's rows three and six.
    injection_ineffective = gap_at_or_above_floor || runts_too_long;

    // Section 13: an injection kind whose site is not bound.
    site_missing = site_unavailable;

    // Section 6: 43 runt lengths below 48 octets, not 43 runts.
    runt_space_thin = (c_frames > 32'd100000) &&
                      (runt_lengths_count < 6'(MIN_RUNT_LENGTHS));

    // Section 7: seven dribble widths, because Chapter 7.3 Section 7's
    // detector tests a bit count against zero and one width passes six
    // wrong implementations.
    dribble_space_thin = (c_frames > 32'd100000) &&
                         (widths < unsigned'(MIN_DRIBBLE_WIDTHS));

    // Section 12: the truncation path, the reorder path and the drift
    // are downstream of faults no frame produces.
    non_wire_faults_absent = wire_only_plan;

    none_of_the_above = !injector_misplaced && !injection_ineffective &&
                        !site_missing && !runt_space_thin &&
                        !dribble_space_thin && !non_wire_faults_absent;
  end

endmodule

Classification: a verdict generator, the eighteenth in three modules, and the first whose verdicts are mostly about what was not injected.

What it teaches: that four of the six verdicts fire on an injector that is working perfectly. runt_space_thin, dribble_space_thin and non_wire_faults_absent are all about breadth rather than correctness — the injector did exactly what it was asked, and what it was asked for reaches one design path instead of five. Only injector_misplaced is a bug in this chapter's own RTL.

And it teaches that non_wire_faults_absent is the verdict a team will want removed and should not remove. It fires on every wire-only regression, which is most of them, and the correct response is the one Chapter 19.6 §18 gave for reorder_untested: disable it with a written justification naming what is not being testedChapter 19.5 §14's truncation path and everything downstream of it — rather than lowering a threshold.

Deliberately simplified: MIN_RUNT_LENGTHS defaults to 43, which is every runt length below 48 and is achievable only with a directed sweep — a random runt distribution over 5 to 63 reaches it in a few hundred frames and a weighted one may never. widths counts 1 to 7 and ignores bit 0, which encodes a whole octet. And there is no verdict about the injection rate at all, though a rate of one per million frames and a rate of one per ten reach very different parts of the design's error handling.

Production implication: none_of_the_above for the eighteenth time, and the first where a clean verdict means the environment has a consumer staller, a bus model and a bit-count interface — three things that are absent from a default testbench. A regression that clears it has an error-injection plan that reaches all five design-path groups, which is a real claim; one that clears it because four verdicts were disabled has an injector and a list of waivers, and the waivers are the record of what is not tested.


17. The Injection Matrix

Counting the design paths each injection is the only stimulus for gives fourteen in total. A corrupted check value reaches the residue classifier. A dribble reaches the whole octet detector and the mutual exclusivity between alignment and check value errors. Any runt reaches the undersize class; a runt of forty seven octets or fewer additionally reaches the statistics counters' two bit addend and the check engine's two ends in a beat flag. A giant reaches the maximum transmission unit comparator. A short gap reaches the elastic buffer, and a sustained stream of short gaps reaches the unbounded accumulation case. A wrong destination address reaches the address filter and a non member virtual local area network identifier reaches the port membership rules. And a consumer stall longer than the buffer's two point six two one microseconds of capacity reaches the overflow guard, the buffer versus cable diagnosis and the likely buffer not cable telemetry bit — three paths, all downstream of a fault no frame produces. A wire only injection plan reaches eleven of the fourteen. The three it misses are the ones the memory interface chapter called the most expensive field failure in the module.Check value1 path, 31.55%Dribble2 paths, 31.55%Runt below 483 paths, 0.08%Sustained shortgap2 paths, 0%Wire-only total11 of 14 pathsConsumer stall3 paths, 0%The 3 it misses19.6 Section 22'sfailure14 design pathsthe column to rank by12
Figure 4 — fourteen design paths, five injections, and the three that matter most are the three a coverage plan omits.

Nine injectable errors, five sites, and the matrix is the chapter compressed into one table.

ErrorSiteCostCross shareDesign paths
corrupted check valuewire32 XOR gates31.55%1
alignment (dribble)PHY3 interface bits31.55%2
giantwirea bound5.26%1
runt, 48 to 63 octetswirea bound0.08%1
runt, 5 to 47 octetswirea bound0.08%3
short interframe gapwire10 gates02
filtered addresswirea field01
non-member VLANwirea field01
FIFO overflowconsumera second agent03

Rows six to nine have a cross share of zero and between them reach seven design paths.

That is not a defect in Chapter 20.4's model; it is what the model is. A short gap is not a frame property, so no cell of a frame-indexed cross can record it; an address filter drop produces no frame at the far side, so there is nothing to sample. Four of the nine most useful injections are invisible to a coverage cross built on frame properties, and a plan driven by coverage closure will not contain them.

The design paths, enumerated, because the fourth column is the one that should drive the plan:

PathReached only by
Chapter 19.4 §10's residue classifiera corrupted check value
Chapter 7.3 §7's whole-octet detectora dribble
Chapter 7.3 §3's exclusivitya dribble
Chapter 7.3's undersize classany runt
Chapter 19.7 §7's two-bit addenda runt of 47 or below
Chapter 19.4 §8's two_ends_in_a_beatthe same
the MTU comparatora giant
Chapter 19.5 §3's elastic buffera short gap
Chapter 4.4 §2's unbounded casea sustained short gap
Chapter 7.4's filtera wrong address
Chapter 13.3's membershipa non-member VLAN
Chapter 19.5 §14's overflow guarda stalled consumer
Chapter 19.6 §22's diagnosisthe same
Chapter 19.7 §15's likely_buffer_not_cablethe same

Fourteen paths, and the last three all depend on one non-wire fault. A regression whose error injection is wire-only reaches eleven of fourteen, and the three it misses are the ones Chapter 19.6 §22 called the most expensive field failure in Module 19.

Which gives the plan's shape, and it is short.

PriorityInjectionBecause
1a consumer stall above 2.621 µsthree paths, and no other stimulus reaches them
2runts of 5 to 47 octets, 43 lengthsthree paths, and the same
3a corrupted check valueone path, and it is cheap
4a dribble, all seven widthstwo paths, and it needs an interface change
5a sustained short gaptwo paths, and one of them is unbounded

Priority one is not a frame and priority two is 0.08% of the coverage cross, which is the ranking the fourth column of the first table produces and is exactly inverted from the ranking the third column produces.


18. What the Injector Assumes

Nine assumptions, and four of them are about where the injector sits rather than about what it does.

#AssumptionOwnerIf wrong
1the stomp is downstream of Chapter 19.4's appenderthe wiringthe design recomputes a valid check value
2the dribble is downstream toothe wiringSection 8's row d, undefined
3the interface carries a bit countChapter 19.1 §3the dribble is absorbed and reaches nothing
4a consumer staller is boundthe environmentSection 12's three paths are unreachable
5Chapter 7.3 §3 owns the classificationthe standardSection 20's class 92
6the drain rate exceeds the line rateChapter 19.5 §17the FIFO overflows without any injection
7the PHY does not pad a dribblethe PHY modelthe injection is absorbed below the design
8the MTU is what the injector was toldconfigurationthe giant threshold moves
9a runt of 5 octets is framed as a framethe receive framingChapter 19.7 §7's three-ending case is unreachable

Row seven is the assumption most likely to be silently false and it is not a design property. Some PHY interfaces pad a dribble to an octet boundary before the MAC sees it — which is a reasonable thing for a PHY to do and makes every alignment injection reach nothing. The evidence is Chapter 7.3's alignment counter staying at zero while c_dribbles climbs, and the two counters live in different blocks.

Row nine is the bound on Section 6's third path. Chapter 19.7 §7's three-endings-per-beat case needs frames of 15 octets or fewer, and a receive framing that requires a start delimiter, a destination address and a length may not call a 5-octet unit a frame at all. The injection is legal to emit and may be discarded below the counters, in which case the case is unreachable and the reason is the framing rather than the injector.

And one assumption that is not in the table because it is about time rather than about a signal. Every injection in Sections 2 through 9 is armed before the frame it damages is on the wire, because the stomper needs in_last_four, the length violator needs the size before the first beat, and the dribble injector needs the ending decided. So the injector cannot react to a frame it is currently transmitting — it decides, then the frame happens. That matters for exactly one case in the chapter: the consumer staller of Section 11 is the only injection whose trigger can be a frame already in flight, because it acts on the system side rather than on the wire, and the state it corrupts is the FIFO's rather than a frame's.

TimingArmed before the frameCan react mid-frame
the check-value stomperyesno — the mask is chosen at arm time
the length violatoryesno — the size is a constraint
the dribble injectoryesno — the ending is decided at arm time
the gap violatoryesno — it acts between frames
the consumer stallernoyes — it acts on Chapter 19.5's drain side

Which is the same split as Section 10's, arrived at from the other direction. The four wire injections are decisions about a frame and must precede it; the one system-side injection is a decision about the design's state and can be taken at any moment, including the worst one.

And two deliberately not assumed:

Not assumedWhy not
that an injected error is detectedthat is the design's job and the property's subject — Section 20
that the design's class matches the injector's intentChapter 7.3 §3's exclusivity says it sometimes does not

Row two is this chapter's central discipline. The injector records what it did; the design records what it saw; and Section 8's table lists five cases where those legitimately differ. An injector that assumes they agree has built Section 20's rejected property into its own telemetry.


19. The Cost, Accounted

Eight blocks, and the whole chapter is smaller than one of Module 19's.

BlockFlopsNotes
errinj_fcs_stomper~5032 XOR gates and a 2-bit index
length_violator~140the 59-bit seen-vector
dribble_injector~40plus 3 bits on every receive beat
gap_violator~70a 5-bit counter and a 16-bit vector
consumer_staller~110a 32-bit elapsed counter
errinj_scheduler~360ten counters
errinj_telemetry~200derived registers
errinj_conformance_monitor~20verdicts
total~990 flops

And the interface cost is the one that is not in the table.

Cost
the injector's own logic~990 flops, testbench only
3 bits of bit-count on the receive beat3 flops per pipeline stage
Chapter 19.1 §6's 16-stage receive pipeline48 flops, shipped

Forty-eight flops of the design exist so that an alignment error can be injected, which is a small number and is the only place in this batch where a verification requirement adds logic to a product. Chapter 7.3 §7's detector needs the bit count anyway — the standard defines the class and a conformant MAC must count it — so the 48 flops are the standard's and the injector merely uses them.

Module 20's running total, with five chapters built:

ChapterFlopsNature
Chapter 20.1 — the generator~1 050stimulus
Chapter 20.2 — the assertion library~730checks
Chapter 20.3 — the scoreboards~1 760checks
Chapter 20.4 — the coverage model~650measurement
this chapter — error injection~990stimulus
subtotal~5 180
Module 19's datapath, for comparison~14 166 flopsall of it ships

Five chapters of verification environment at 36.6% of the datapath's logic, and the two stimulus chapters — Chapter 20.1 and this one — are 2 040 flops between them against 3 140 of checking and measurement. Producing the cases is the smaller half; deciding whether they were right, and whether they happened at all, is the larger one.


20. Properties Worth Asserting, and One Worth Refusing

Thirty-three properties, and the refused one is the first assertion anybody writes about an injector.

Group 1 — the check-value stomper, where the properties are about where it acts.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The stomp lands on the check value and nowhere else. Section 3.
a_stomp_in_fcs: assert property (@(posedge clk) disable iff (!rst_n)
  (out_octet != in_octet) |-> in_last_four);

// Exactly one frame per arm.
a_one_frame_per_arm: assert property (@(posedge clk) disable iff (!rst_n)
  (arm && !armed_q) |-> ##[1:$] $rose(c_stomped));

// A zero mask changes nothing, which is a legal configuration and must
// not be counted as an injection.
a_zero_mask_no_change: assert property (@(posedge clk) disable iff (!rst_n)
  (cfg_mask == '0) |-> (out_octet == in_octet));

// The stomper never modifies an octet outside the check value, and
// says so when the framing signal claims otherwise.
a_outside_reported: assert property (@(posedge clk) disable iff (!rst_n)
  (armed_q && in_valid && !in_last_four && cfg_mask != '0)
    |=> stomp_outside_fcs);

// An all-ones mask on a preemption-capable port produces Chapter 17.3's
// mCRC residue, which is a VALID fragment and not an error.
a_all_ones_is_mcrc: assert property (@(posedge clk)
  (cfg_mask == 32'hFFFF_FFFF) |-> !expect_crc_error_on_preemptable_port);

Group 2 — lengths, and the thresholds Chapter 19.7 §7 derived.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// A runt is below the minimum frame and a giant is above the MTU.
a_runt_below_min: assert property (@(posedge clk) disable iff (!rst_n)
  out_is_runt |-> (out_length < 16'(MIN_FRAME)));

a_giant_above_mtu: assert property (@(posedge clk) disable iff (!rst_n)
  out_is_giant |-> (out_length > 16'(CFG_MTU)));

// Section 6: only a runt of 47 octets or fewer packs two frame endings
// into a beat, and that is the threshold, not 64.
a_multi_end_threshold: assert property (@(posedge clk) disable iff (!rst_n)
  multi_end_reachable |-> ##[0:$] (out_length <= 16'd47));

// A runt between 48 and 63 is a runt and reaches one path.
a_tall_runt_one_path: assert property (@(posedge clk) disable iff (!rst_n)
  (out_is_runt && out_length >= 16'd48) |-> !$rose(multi_end_reachable));

// The seen-vector only records lengths in range.
a_runt_vector_bounded: assert property (@(posedge clk) disable iff (!rst_n)
  $changed(runt_lengths_seen) |-> (out_length >= 16'd5 && out_length < 16'd64));

// A frame is never both a runt and a giant.
a_not_both: assert property (@(posedge clk) disable iff (!rst_n)
  !(out_is_runt && out_is_giant));

Group 3 — the dribble, and Chapter 7.3 §3's exclusivity.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// A dribble reports one to seven valid bits, never zero and never eight.
a_dribble_range: assert property (@(posedge clk) disable iff (!rst_n)
  (out_eof && armed_q) |-> (out_valid_bits inside {[1:7]}));

// Without an arm the final octet is whole.
a_no_arm_whole_octet: assert property (@(posedge clk) disable iff (!rst_n)
  (out_eof && !armed_q) |-> (out_valid_bits == 3'd0));

// Section 8's row d: a dribbled frame whose check value still passes is
// a state the standard does not define, and it means the injection is
// on the wrong side of Chapter 19.4's appender.
a_dribble_fails_fcs: assert property (@(posedge clk) disable iff (!rst_n)
  (c_dribbles != '0) |-> !fcs_passed_on_dribble);

// Chapter 7.3 Section 3: alignment and CRC error are mutually exclusive,
// so the design counts exactly one.
a_classes_exclusive: assert property (@(posedge clk) disable iff (!rst_n)
  frame_classified |-> $onehot0({is_crc_error, is_alignment_error}));

// An interface without a bit count cannot carry a dribble, and the
// injector says so rather than reporting success.
a_bit_count_required: assert property (@(posedge clk) disable iff (!rst_n)
  (arm && !INTERFACE_HAS_BIT_COUNT) |=> interface_lacks_bit_count);

Group 4 — the gap, where the floor is nine.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Chapter 5.9's floor. A gap of nine or more is conformant.
a_short_means_short: assert property (@(posedge clk) disable iff (!rst_n)
  $rose(c_short_gaps) |-> ($past(gap_count) < 5'(MIN_GAP)));

// And an injection that lands at or above it is reported as ineffective.
a_legal_gap_reported: assert property (@(posedge clk) disable iff (!rst_n)
  (in_sof && armed_q && gap_count >= 5'(MIN_GAP)) |=> gap_at_or_above_floor);

// The violator only suppresses idle; it never adds octets.
a_suppress_only: assert property (@(posedge clk) disable iff (!rst_n)
  out_gap |-> in_gap);

// Section 9: a four-octet elastic buffer at 200 ppm overruns after
// 20 000 undischarged octets, which is 1.6 us at 100 Gb/s.
a_elastic_overrun_bounded: assert property (@(posedge clk) disable iff (!rst_n)
  (c_short_gaps > 32'd300) |-> ##[1:$] elastic_overrun);

// The gap counter resets at every frame start.
a_gap_resets: assert property (@(posedge clk) disable iff (!rst_n)
  in_sof |=> (gap_count == '0));

// Section 14's row three: an injection that produced a legal gap is not
// an injection, and the counter must not move for it.
a_legal_gap_not_counted: assert property (@(posedge clk) disable iff (!rst_n)
  (in_sof && gap_count >= 5'(MIN_GAP)) |=> $stable(c_short_gaps));

// Chapter 5.9 permits gaps down to nine as an INSTANCE, so a gap of
// exactly nine is conformant and a gap of eight is not.
a_nine_is_legal: assert property (@(posedge clk) disable iff (!rst_n)
  (in_sof && gap_count == 5'd9) |-> !gap_violation);

Group 5 — the non-wire site, which is Section 12's proof as properties.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// A stall shorter than the FIFO's capacity does not overflow it.
a_short_stall_no_drop: assert property (@(posedge clk) disable iff (!rst_n)
  (c_stalls != '0 && cfg_stall_ns <= 32'd2621) |-> !fifo_overflowed);

// Only a longer one reaches Chapter 19.5 Section 14's truncation path.
a_long_stall_reaches: assert property (@(posedge clk) disable iff (!rst_n)
  (stall_reaches_truncation && c_stalls != '0) |-> ##[1:$] $rose(c_frames_truncated));

// Section 12: with the consumer running, no frame sequence overflows it.
a_wire_cannot_overflow: assert property (@(posedge clk) disable iff (!rst_n)
  !hold_read |-> (fifo_occupancy < 16'd512));

// An injection whose site is not bound is refused, not silently dropped.
a_refuse_unbound: assert property (@(posedge clk) disable iff (!rst_n)
  (fire && !deliverable(req.kind)) |=> ($rose(c_refused) && site_unavailable));

// And it never arms anything.
a_refused_arms_nothing: assert property (@(posedge clk) disable iff (!rst_n)
  (fire && !deliverable(req.kind)) |->
    !(arm_fcs || arm_length || arm_dribble || arm_gap || arm_stall));

// A wire-only plan is reported.
a_wire_only_reported: assert property (@(posedge clk) disable iff (!rst_n)
  (c_stalls == '0 && c_frames > 32'd100000) |-> wire_only_plan);

Group 6 — coverage.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
c_all_kinds:        cover property (@(posedge clk) kinds_armed[5:1] == 5'b11111);
c_runt_space:       cover property (@(posedge clk) runt_lengths_count >= 6'd43);
c_all_dribbles:     cover property (@(posedge clk) dribble_widths_seen[7:1] == 7'h7F);
c_gap_zero:         cover property (@(posedge clk) in_sof && gap_count == 5'd0);
c_stall_over_fifo:  cover property (@(posedge clk) stall_reaches_truncation);
c_composition:      cover property (@(posedge clk) out_is_runt && armed_q);
c_three_endings:    cover property (@(posedge clk) out_is_runt && out_length <= 16'd15);

21. Verification Scenarios

Fifty-eight scenarios for a component whose job is to break things, plus a five-run directed test whose content is an injection site.

The check-value stomper — 11 scenarios.

#ScenarioExpected
1a single-bit maskone bit flipped; RES_BAD
2a 32-bit all-ones maskthe residue's complement
3the same on a preemption-capable portan mCRC — a valid fragment, not an error
4a zero masknothing changed; not an injection
5the stomp upstream of the appenderthe design recomputes; the frame is valid
6the stomp on a payload octetstomp_outside_fcs
7an arm with in_last_four never assertingnothing stomped, success reported
8back-to-back armsthe second is lost — one frame deep
9a frame whose FCS spans a beat boundary4.74% of sizes — this listing mishandles it
101 000 stompsc_crc_errors = 1 000
11the same with a dribble also armedc_crc_errors = 0, alignment = 1 000

Rows ten and eleven are the rejected class as a test, and they differ by one arm: the same stomps, and the CRC counter goes from a thousand to zero because Chapter 7.3 §3 makes the classes exclusive.

Lengths — 12 scenarios.

#ScenarioExpected
12a 63-octet frameundersize; one ending per beat
13a 48-octet frameundersize; still one ending
14a 47-octet frametwo endings — the threshold
15a 16-octet frametwo endings
16a 15-octet framethree endings
17a 5-octet framethree; and the framing may refuse it
18a 1 519-octet frame at MTU 1 518oversize
19the same at MTU 9 000not oversize
20a runt with a stomped check valuea fragment, not an undersize
21a giant with a stomped check valuea jabber
22runts uniformly over 5 to 6327.1% land above 47 and reach one path
2343 distinct lengths below 48runt_lengths_count = 43

Rows thirteen and fourteen are one octet apart and differ by a design path, and row twenty-two is the cost of not knowing that: more than a quarter of a uniform runt injection reaches the undersize class and nothing else.

The dribble — 10 scenarios.

#ScenarioExpected
24one valid bit in the final octetan alignment error
25seven valid bitsthe same class, a different width
26all seven widthsdribble_widths_seen = 7'h7F
27a detector testing one bit of the countpasses six widths, fails one
28the dribble upstream of the appenderSection 8's row d — undefined
29an interface without a bit countinterface_lacks_bit_count
30a PHY that pads to an octetthe injection is absorbed; nothing reaches
31a dribble and a stomp togetherone alignment error
32a dribble on a runtan alignment error, not a fragment
33c_dribbles climbing, alignment counter flatrow 30's signature

Row thirty-three is the diagnostic for row thirty and the two counters live in different blocks, which is why nobody compares them: the injector's is in the testbench and the design's is in Chapter 19.7's bank.

The gap — 9 scenarios.

#ScenarioExpected
34a gap of 8 octetsbelow Chapter 5.9's floor — a violation
35a gap of 9conformant; gap_at_or_above_floor
36a gap of 0the extreme violation
37a sweep over a 4-bit field7 of 16 settings produce no error
38300 consecutive short gapsthe elastic buffer overruns
39the same at 100 Gb/s20 000 octets — 1.6 µs
40short gaps with conformant framesevery frame perfect, the buffer fails
41a single short gapabsorbed; the buffer discharges next frame
42the violator asked to lengthen a gapit cannot — it only suppresses

Rows forty and forty-one are the pair that shows what this injection is for. A single short gap is absorbed — the elastic buffer is four octets and the drift per frame is under two — and a sustained stream of them is Chapter 4.4 §2's unbounded case, which fails in 1.6 µs with no frame ever being wrong.

The non-wire site — 8 scenarios.

#ScenarioExpected
43a 1 000 ns consumer stallabsorbed; no counter moves
44a 2 000 ns stallstall_below_design_limit
45a 2 621 ns stallthe FIFO is exactly full
46a 3 000 ns stallc_frames_truncated
47minimum-size frames back to back, consumer runningno overflow — Section 12
48jumbo frames back to back, consumer runningthe same
49INJ_STALL with no staller boundrefused; site_unavailable
50the same accepted silentlythe plan records a covered case

Rows forty-seven and forty-eight are Section 12's proof as a test, and the point is that they are different stimulus with the same result: the arrival rate is the line rate in both cases.

Telemetry and the monitor — 8 scenarios.

#ScenarioExpected
51stomps onlypaths_reached = 1, cross_share_pct = 32
52stomps and runts below 48paths_reached = 5, the same 32
53a wire-only plannon_wire_faults_absent
54runts all above 47runts_too_long
55one dribble widthdribble_space_thin
56an injection that reached nothinginjections_without_effect
57everything bound and sweptnone_of_the_above
58the same with four verdicts waivedalso clean, and meaningless

Rows fifty-one and fifty-two are Section 15's inversion in two lines: the same coverage percentage, five times the design exposure.

The directed test — five runs a longer regression will not produce.

Every one of this chapter's important cases is a site or a length, and none responds to running longer.

CaseNeedsA longer run gives
the truncation patha consumer staller and a stall above 2.621 µsnothing
the two-bit addendrunts of 47 octets or fewernothing, if the range starts at 48
the exclusivitya dribble and a stomp on one framerarely, under independent arming
the elastic overrun300 consecutive short gapsnothing, if gaps are 9 or more
the seven dribble widthsa sweepall seven, eventually

Row five is the only one a longer run reaches, which is the ratio worth noticing: four of the five cases are configuration and one is time.

Construct it. Five runs.

RunConfigurationExercises
Astomps only, wire sitethe nominal path; 1 design path
Brunts swept 5 to 47, 43 lengthsthe two-bit addend and the two-ends flag
Ca dribble and a stomp on every framethe exclusivity; the CRC counter reads zero
D300 consecutive gaps of 4 octetsthe elastic overrun in 1.6 µs
Ea 3 000 ns consumer stallthe truncation path and everything downstream

Run C is one extra arm and it is the run that proves the rejected class. The same thousand stomps as run A, and c_crc_errors reads zero — because every frame is also a dribble and Chapter 7.3 §3 counts it as an alignment error. A property asserting "I stomped, therefore the CRC counter moved" fires a thousand times on a correct design.

Run E is the only run that is not a frame. It needs a consumer staller bound, a stall longer than the FIFO's 2.621 µs of capacity, and it is the only stimulus in this chapter that reaches Chapter 19.6 §22's complaint-1 diagnosis. Four of the five runs cannot produce it at any length.

The oracle, in four parts:

CheckABCDE
c_crc_errors1 0000000
paths_reached14321
multi_end_reachableclearSETclearclearclear
c_frames_truncated0000non-zero

Row one's run C entry is the finding and row four's is the gap. Run C stomps every frame and the CRC counter reads zero; run E is the only column where the truncation counter moves, and it is the only run whose injection is not a property of a frame.

Row two is the column to optimise and it is not correlated with row one. Run B reaches four design paths with no CRC errors at all, and run A reaches one with a thousand of them — which is the chapter's inversion appearing one last time, in the oracle rather than in the argument.


22. Debugging an Error Injector

Five complaints, and four of them are injections that reported success and reached nothing.

Complaint 1 — "we inject CRC errors and the CRC counter reads zero."

CheckIf yesMeaning
is a dribble also armed?Chapter 7.3 §3's exclusivityit is an alignment error
does the alignment counter read the same number?confirmsone class, not two
is the stomp upstream of the appender?the design recomputesthe frame is valid
stomp_outside_fcs set?the framing signal is wronga payload corruption instead

Row two is the diagnosis and it takes one read. The counts are equal and in the other column — the frames were corrupted, classified and counted, and the class is not the one the injector intended. Row three is the other outcome and it looks identical from the CRC counter: zero, because nothing was wrong at all.

Complaint 2 — "runts are injected and the two-bit addend never fires."

CheckIf yesMeaning
multi_end_reachable clear?every runt was 48 octets or longerSection 6's threshold
what is the runt length range?5 to 63 is the legal rangeonly 5 to 47 packs a beat
runt_lengths_count low?few distinct lengthsone length, one behaviour
does two_ends_in_a_beat ever fire?Chapter 19.4 §8's flagthe definitive check

Row two is the number to look at and it is the one nobody states. Chapter 7.3's undersize class begins below 64; Chapter 19.7 §7's multi-end case begins below 48sixteen octets apart, and a generator told "inject runts" produces the first and not the second 27.1% of the time on a uniform distribution.

Complaint 3 — "alignment errors are injected and nothing is counted."

CheckIf yesMeaning
c_dribbles climbing?the injector is armingso it is downstream
interface_lacks_bit_count set?the interface cannot carry itSection 7
does the PHY model pad to an octet?the injection is absorbedSection 18's row seven
observed_whole_octets always true?confirms row threedefinitively

Row three is the silent one and it has nothing to do with the design. A PHY model that pads a dribble to an octet boundary is doing a reasonable thing, and every alignment injection since that model was written has reached nothing. The only evidence is two counters in two different components disagreeing, and Section 20's replacement property 3 is the check that compares them.

Complaint 4 — "short gaps are injected and the elastic buffer is fine."

CheckIf yesMeaning
gap_at_or_above_floor set?the gaps were nine or moreconformant, and not an injection
were the gaps consecutive?a single short gap is absorbedthe buffer discharges next frame
how many in a row?300 at 200 ppmSection 9's 20 000 octets
is the drift zero?Chapter 19.5 §21's row 47one clock source, no accumulation

Row four is the one that makes the whole injection inert. The elastic buffer absorbs drift, and a testbench whose recovered and local clocks come from one source has no drift to accumulateso the gaps can be zero octets long and the buffer never moves. The injection is correct, the design is correct, and the case is unreachable for a reason in the clock generator.

Complaint 5 — "the truncation counter has never moved."

CheckIf yesMeaning
wire_only_plan set?no consumer staller is boundSection 12
stall_below_design_limit set?the stall is 2 000 nsthe FIFO absorbs it
is the stall above 2 621 ns?only then does it overflowSection 11's two thresholds
does a longer frame stream help?it cannotSection 12's proof

Row four is the answer and it is a proof rather than an observation. With the consumer running, the drain rate exceeds the line rate — Chapter 19.5 §4's 128 Gb/s against 100so no sequence of frames overflows the FIFO, and a team adding stimulus to reach the truncation path is adding stimulus to a case stimulus cannot reach.

Complaint 6 — "the runt injection fills its coverage bin but the two-bit addend never fires."

CheckIf yesMeaning
is the constraint's upper bound 63?yesthe bound is the bug, not the injector
runt_lengths_count against 43below itthe useful lengths are not all being hit
does c_runts still climb?yes, on all 59 lengthsthe counter cannot see the difference
runt_space_thin asserted?yesSection 16's monitor already said so

This is the one complaint in the list where every number on the dashboard is correct and the stimulus is still wrong. Chapter 7.3's undersize class begins below 64 octets; Chapter 19.7 §7's multi-end case begins below 48 — a frame of 48 to 63 octets is undersize, is counted, fills the bin, and ends in the same beat it started in, exactly like a conformant minimum-size frame. Sixteen of the 59 legal runt lengths produce a counter increment and no new behaviour — 27.1% of a uniform runt injectionand the only signal that distinguishes them is runt_lengths_count, which exists because Section 6 derived the 48 and nothing else in the environment knows it.

And the three symptoms this chapter is systematically blamed for:

SymptomBlamed onUsually is
an error counter that stays at zerothe designan injection in the wrong class, or absorbed
"the injector does not work"the injectora site that is not bound
a truncation path never exercisedthe stimulusa fault no stimulus produces

23. Misconceptions

Misconception 1 — "an error injector injects errors."

The wrong model: name the error, arm the injector, the design sees it.

What it costs: two of Module 19's most expensive faults, silently. A receive FIFO overflow and a reorder-buffer overflow are properties of the system's state, not of a frame — and the FIFO is sized so that no sequence of frames can overflow it while the consumer is running.

The corrected model: an injector has sites, and three of them are not the wire. A stall belongs to the memory model, a reordering to the bus model, a drift to the clock generator — and an injector that accepts those and does nothing has told a verification plan the cases are covered. Sections 10, 12, 13.

Misconception 2 — "a runt is a runt."

The wrong model: any frame below 64 octets exercises the undersize path.

What it costs: two of the three design paths a runt is the only stimulus for. Chapter 7.3's undersize class begins below 64; Chapter 19.7 §7's two-bit addend needs 47 or fewerand 27.1% of a uniform runt injection lands in the sixteen octets between them.

The corrected model: the useful runt range is 5 to 47 octets, 43 lengths, and the threshold that matters is 47 rather than 64. Below 16 a beat holds three endings, which is the bound the addend was sized for. runt_lengths_count against 43, not c_runts. Sections 5, 6.

Misconception 3 — "injecting an alignment error is injecting a corruption."

The wrong model: a dribble is a third kind of damage alongside a bad check value and a bad length.

What it costs: a property that fires on a correct design. Chapter 7.3 §3: an alignment error is a frame that fails its check sequence and is not whole octetsso it is a bad-FCS frame with a dribble, and the two classes are mutually exclusive by construction. A frame with both intents is counted once.

The corrected model: the dribble supplies one of two conditions and the failed check value follows. It must be applied downstream of Chapter 19.4's appender or the design recomputes a valid check value and produces a frame the standard does not define. Sections 7, 8.

Misconception 4 — "rank the injections by coverage."

The wrong model: a coverage model measures what the stimulus reached, so close the biggest gaps first.

What it costs: the runt. A corrupted check value is 31.55% of Chapter 20.4's legal cross and reaches one design path; a runt below 48 octets is 0.08% and reaches threeand four of the nine injectable errors have a cross share of exactly zero because a short gap and a filtered address are not frame properties at all.

The corrected model: rank by design paths the injection is the only stimulus for. Section 17's matrix has both columns and they disagree on every row. A plan driven by coverage closure will not contain a short-gap injection at all, and that injection is the only stimulus for Chapter 4.4 §2's unbounded case. Sections 4, 17.

Misconception 5 — "assert that the injected error was counted."

The wrong model: the injector knows what it injected, so check that the design agrees.

What it costs: a property that fires on a correct design — Chapter 7.3 §3's exclusivity — and then a repair that makes it worse: the injector acquires a copy of the classification rules, and the property compares the design's classifier against the testbench's copy of the same clause.

The corrected model: assert on the frame that arrived. observed_whole_octets and observed_fcs_ok are measured on the wire; Chapter 7.3 §3's table applied to them is a check the injector's intent plays no part in. And cover that the injection was observed at all, which is what catches a PHY absorbing a dribble. Section 20.

Misconception 6 — "a short gap is a minor error."

The wrong model: the frames are perfect; the gap is only spacing.

What it costs: an understanding of what Chapter 19.5 §3's four-octet elastic buffer depends on. At 200 ppm the drift is one octet per 5 000, so four octets is 20 000 octets of undischarged traffic — 1.6 µs at 100 Gb/s — and every frame on the wire is individually conformant while the buffer overruns.

The corrected model: a short gap is the only injectable error that attacks a buffer rather than a frame, and Chapter 4.4 §2 showed the failure is unbounded rather than absorbed. Ten gates, 1.6 µs to failure, and it does not appear in a frame-indexed coverage model at all. Sections 9, 17.


24. Interview Questions

Question 1 — "How would you inject a CRC error?"

What the answer should establish: XOR the check value after the design has appended it — thirty-two gates, and the design computes a correct FCS which the injector then breaks. A strong answer says why the obvious alternative is worse: corrupting the payload upstream of the appender produces a frame whose check value the design computes over the corrupted data, which is valid; corrupting it downstream needs the injector to know what the original check value was, which means a CRC engine in the testbench and Chapter 19.4 §14's trap behind it. Stomping the FCS needs no model at all.

Question 2 — "You inject a thousand CRC errors and the CRC counter reads zero. The design is correct. What happened?"

What the answer should establish: a dribble was armed on the same frames. Chapter 7.3 §3 makes alignment errors and CRC errors mutually exclusive by construction — a frame that fails its check sequence and is not whole octets is counted once, as an alignment error. A strong answer gives the diagnostic: the alignment counter reads a thousand, and the two counts are equal and in the other column, which distinguishes it from the other zero-CRC-counter cause: a stomp applied upstream of the appender, where nothing is wrong at all.

Question 3 — "Which injectable error gives the best coverage return, and which would you actually prioritise?"

What the answer should establish: they are different errors. A corrupted check value is 32 gates and 31.55% of Chapter 20.4's legal cross; a runt below 48 octets is one constraint bound and 0.08%384 cells, because Chapter 19.7 §2's bucket 0 is a single frame size and therefore a single residue. A strong answer prioritises the runt anyway, because it is the only stimulus for three design paths including the counters' two-bit addend, and notes that four of the nine injectable errors have a cross share of exactly zero — a short gap is not a frame property.

Question 4 — "Can you overflow a receive FIFO from the wire?"

What the answer should establish: no, and it is a proof rather than an observation. The arrival rate is bounded by the line rate — that is what a line rate is — and Chapter 19.5 §4's drain rate is 128 Gb/s against 100. With the consumer running the FIFO empties whatever the frames look like. A strong answer states the corollary: Chapter 19.5 §14's truncation path, Chapter 19.6 §22's diagnosis and Chapter 19.7 §15's likely_buffer_not_cable are all downstream of a fault no frame produces, so a wire-only injection plan has zero coverage of the module's most expensive field failure.

Question 5 — "What does it take to inject an alignment error?"

What the answer should establish: three bits on every beat of the receive datapath. Chapter 19.1 §3's beat carries a byte count; a frame ending after five bits of its final octet cannot be described by one — so the interface carries a bit count, which is a change to the design rather than to the testbench. A strong answer notes that it is the standard's cost, not the injector'sChapter 7.3 §7's detector needs the bit count to classify the frame at all — and that the injection must go downstream of Chapter 19.4's appender or it produces a frame the standard does not define.

Question 6 — "Your injector asserts that every injected error is counted. Is that a good property?"

What the answer should establish: no, and it fails before it passes. It fires on a correct design whenever two intents compose into one class — a dribble and a stomp produce one alignment errorand the natural repair is worse: giving the injector a copy of Chapter 7.3 §3's classification, so the property compares the design's classifier against the testbench's copy of the same rules. A strong answer gives the replacement: assert Chapter 7.3 §3's table against what was observed on the wire — whole octets and check-value pass, both measured — so the injector's intent appears nowhere in the property.


25. Questions and Answers


26. What's Next

This chapter produced the errors. Module 21 is what somebody does when one of them appears on a link nobody injected it into.

But one chapter of Module 20 comes first, and this chapter has just given it a specific problem.

Chapter 20.6 assembles the reusable agent, and Chapter 20.4 §26 named its job: four components produce four self-weakening numbers with no shared identifier. This chapter adds a fifth — Section 15's paths_reached — and it has the same problem and one that is worse: an injection's effect is observed in Chapter 19.7's counters, which belong to the design, so the join crosses the design boundary rather than a testbench one.

And the agent inherits this chapter's site taxonomy directly. An agent that "works across the xMII family" changes the beat width, which moves Chapter 19.7 §7's multi-end thresholds — a 47-octet runt packs two endings into a 64-octet beat and one into a 128-octet beat — so the runt injection's useful range is a function of the interface the agent is currently driving.

Then Module 21 begins, and its first chapter is the one this batch's five debugging sections have been rehearsing without naming. Every flagship chapter in Modules 18 to 20 ends with three or four complaints and a table of checks; Chapter 21.1 is the method those tables are instances of, and Chapter 21.2 is the catalogue this chapter's nine injectable errors are a subset of — with the faults no wire produces sitting alongside them, which is where a debugging chapter has to start rather than where an injection chapter can end.


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.