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.
| Error | Injected from | Costs | Reaches, of Chapter 20.4's legal cross |
|---|---|---|---|
| a corrupted check value | the wire | 32 XOR gates | 147 456 cells — 31.55% |
| an alignment error | the wire | a bit count on the interface | 147 456 — 31.55% |
| a giant | the wire | a constraint bound | 24 576 — 5.26% |
| a runt | the wire | a constraint bound | 384 — 0.08% |
| a mid-frame truncation | NOT the wire | a stalled consumer | a path, not a cell |
| a reorder-buffer overflow | NOT the wire | another agent's parameter | a 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 328 — and 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-injectable | Requires a fault inside the design | |
|---|---|---|
| what it is | a property of the frame or the gap | a property of the system's state |
| who produces it | the driver | another agent — memory, clock, bus |
| cost | gates | a second model |
| examples | bad FCS, runt, giant, dribble, short gap | FIFO 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 facts — Chapter 7.3 §3 makes a frame that is both bad-FCS and non-octet-aligned an alignment error and not a CRC error — so 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
Nine errors, and the list is shorter than it looks because three of them are compositions of two others.
| # | Error | Chapter 7.3's definition | How the wire produces it |
|---|---|---|---|
| 1 | corrupted check value | fails the FCS, whole octets | XOR the last four octets |
| 2 | undersize (runt) | below 64 octets, FCS passes | a length |
| 3 | oversize (giant) | above the MTU, FCS passes | a length |
| 4 | fragment | below 64, FCS fails | rows 1 and 2 together |
| 5 | jabber | above the MTU, FCS fails | rows 1 and 3 together |
| 6 | alignment error | fails the FCS, NOT whole octets | end the frame mid-octet |
| 7 | short interframe gap | below Chapter 5.9's floor of 9 | a gap counter |
| 8 | a filtered address | Chapter 7.4 | a destination field |
| 9 | a non-member VLAN | Chapter 13.3 | a 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.
| Error | What the injector needs | Gates or lines |
|---|---|---|
| corrupted check value | a 32-bit XOR after the CRC appender | 32 gates |
| undersize | a length below 64 | one constraint bound |
| oversize | a length above the MTU | one constraint bound |
| short gap | a gap counter and a comparator | about 10 gates |
| alignment | a BIT count on the interface, not a byte count | 3 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
// ---------------------------------------------------------------------
// 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// ---------------------------------------------------------------------
// 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;
endmoduleClassification: 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
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 class | Legal in | Cells | Share of the legal cross |
|---|---|---|---|
| none | every bucket | 147 456 | 31.55% |
| corrupted check value | every bucket | 147 456 | 31.55% |
| alignment | every bucket | 147 456 | 31.55% |
| oversize | bucket 6 only | 24 576 | 5.26% |
| undersize | bucket 0 only | 384 | 0.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 addend | only runts produce more than one frame ending per beat |
| Chapter 7.3's undersize and fragment classes | nothing else does |
Chapter 19.4 §7's two_ends_in_a_beat | the flag exists for exactly this |
| Chapter 20.4's cross | 384 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.
| Error | Cost | Coverage | Design paths reached |
|---|---|---|---|
| check value | 32 gates | 31.55% | 1 — the residue classifier |
| alignment | 3 interface bits | 31.55% | 2 — dribble handling and the exclusivity |
| oversize | a bound | 5.26% | 1 — the MTU comparator |
| undersize | a bound | 0.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
// ---------------------------------------------------------------------
// 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
endmoduleClassification: 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:
wire period = L + 8 (preamble and SFD) + 9 (minimum gap) = L + 17
endings in one 64-octet beat ≤ floor(64 / (L + 17)) + 1| Frame length | Wire period | Endings per beat |
|---|---|---|
| 48 and above | 65 and above | 1 |
| 16 to 47 | 33 to 64 | 2 |
| 5 to 15 | 22 to 32 | 3 |
So the three design paths a runt reaches are:
| Path | Requires | Where it is |
|---|---|---|
| the undersize and fragment classes | any length below 64 | Chapter 7.3 §3 |
| the counters' two-bit addend | a length of 47 or below | Chapter 19.7 §7 |
two_ends_in_a_beat | the same | Chapter 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 pairs | 1 — bucket 0 is one frame size |
| × 64 offsets × 3 tag counts × 2 dual values | 384 |
| of Chapter 20.4's legal 467 328 | 0.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
// ---------------------------------------------------------------------
// 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;
endmoduleClassification: 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 passes — and 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:
| Frame | Whole octets? | Check value | Class |
|---|---|---|---|
| a | yes | passes | valid |
| b | yes | fails | CRC error |
| c | no | fails | alignment error |
| d | no | passes | not 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 applied | The design's check value | Class |
|---|---|---|
| before the appender | computed over the dribbled frame — passes | row d, undefined |
| after the appender | no longer covers the frame — fails | row 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.
| Intent | Produces | Depends on |
|---|---|---|
| stomp the check value | a CRC error | the frame being whole octets |
| dribble | an alignment error | the check value failing, which it does |
| both together | an alignment error only | Chapter 7.3 §3's exclusivity |
| stomp on a runt | a fragment | Chapter 7.3's composition |
| stomp on a giant | a jabber | the 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
// ---------------------------------------------------------------------
// 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
endmoduleClassification: 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 traffic — 1.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.
| Error | Chapter | What produces it |
|---|---|---|
| a receive FIFO overflow | Chapter 19.5 §14 | a stalled consumer |
| a reorder-buffer overflow | Chapter 19.6 §9 | the bus model's reorder probability |
| a 200 ppm clock drift | Chapter 19.5 §4 | two clock sources |
| a descriptor ownership violation | Chapter 18.2 | the driver model |
| a bus error response | Chapter 18.5 | the 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 error | no |
| report that its site is not the wire | yes — site_of(), one case statement |
| refuse to arm an injection it cannot deliver | yes, 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 class | Counter that moves | Design path reached | |
|---|---|---|---|
| a corrupted check value | CRC error | c_crc_errors | the residue classifier |
| a wrong destination address | none — a good frame | a filter counter, if there is one | the address filter |
| a non-member VLAN | none — a good frame | a membership counter, if there is one | the 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
// ---------------------------------------------------------------------
// 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));
endmoduleClassification: 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 has — exercises 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
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:
| Term | Beats | Covers |
|---|---|---|
| the memory stall | 391 | a 2 µs stop |
| Chapter 14.2's headroom | 123 | the PAUSE round trip |
| the synchroniser reserve | 4 | 18.24 ns |
| rounded down to a power of two | 512 | 2.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 is — Chapter 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 case | 100 Gb/s |
| drain, with the consumer running | 128 Gb/s |
| net | the 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
// ---------------------------------------------------------------------
// 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
endmoduleClassification: 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 never | Because | Symptom |
|---|---|---|---|
| 1 | arm an injection it cannot deliver | Section 13 | a plan that records a covered case |
| 2 | stomp anywhere but the check value | Section 3 | a different error, classified differently |
| 3 | emit a "short" gap of nine or more | Chapter 5.9's floor | legal, harmless, and counted as injected |
| 4 | dribble before the check value is appended | Section 8's row d | a frame the standard does not define |
| 5 | assert on its own intent | Section 20's class 92 | a property that fires on a correct design |
| 6 | inject only at lengths above 47 | Chapter 19.7 §7 | runts 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 three | a gap of nine is conformant; a report that calls it injected is false |
| row six | a 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 octets — and 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.
| Number | Says | Can be high while the design is untouched |
|---|---|---|
c_injected_total | how many injections fired | yes |
cross_share_pct | what share of the cross they fill | yes |
paths_reached | how many design paths they reach | no — this is the one to gate on |
injections_without_effect | the gap between the first two and the third | it 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
// ---------------------------------------------------------------------
// 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
endmoduleClassification: 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 produces — so 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
// ---------------------------------------------------------------------
// 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
endmoduleClassification: 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 tested — Chapter 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
Nine injectable errors, five sites, and the matrix is the chapter compressed into one table.
| Error | Site | Cost | Cross share | Design paths |
|---|---|---|---|---|
| corrupted check value | wire | 32 XOR gates | 31.55% | 1 |
| alignment (dribble) | PHY | 3 interface bits | 31.55% | 2 |
| giant | wire | a bound | 5.26% | 1 |
| runt, 48 to 63 octets | wire | a bound | 0.08% | 1 |
| runt, 5 to 47 octets | wire | a bound | 0.08% | 3 |
| short interframe gap | wire | 10 gates | 0 | 2 |
| filtered address | wire | a field | 0 | 1 |
| non-member VLAN | wire | a field | 0 | 1 |
| FIFO overflow | consumer | a second agent | 0 | 3 |
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:
| Path | Reached only by |
|---|---|
| Chapter 19.4 §10's residue classifier | a corrupted check value |
| Chapter 7.3 §7's whole-octet detector | a dribble |
| Chapter 7.3 §3's exclusivity | a dribble |
| Chapter 7.3's undersize class | any runt |
| Chapter 19.7 §7's two-bit addend | a runt of 47 or below |
Chapter 19.4 §8's two_ends_in_a_beat | the same |
| the MTU comparator | a giant |
| Chapter 19.5 §3's elastic buffer | a short gap |
| Chapter 4.4 §2's unbounded case | a sustained short gap |
| Chapter 7.4's filter | a wrong address |
| Chapter 13.3's membership | a non-member VLAN |
| Chapter 19.5 §14's overflow guard | a stalled consumer |
| Chapter 19.6 §22's diagnosis | the same |
Chapter 19.7 §15's likely_buffer_not_cable | the 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.
| Priority | Injection | Because |
|---|---|---|
| 1 | a consumer stall above 2.621 µs | three paths, and no other stimulus reaches them |
| 2 | runts of 5 to 47 octets, 43 lengths | three paths, and the same |
| 3 | a corrupted check value | one path, and it is cheap |
| 4 | a dribble, all seven widths | two paths, and it needs an interface change |
| 5 | a sustained short gap | two 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.
| # | Assumption | Owner | If wrong |
|---|---|---|---|
| 1 | the stomp is downstream of Chapter 19.4's appender | the wiring | the design recomputes a valid check value |
| 2 | the dribble is downstream too | the wiring | Section 8's row d, undefined |
| 3 | the interface carries a bit count | Chapter 19.1 §3 | the dribble is absorbed and reaches nothing |
| 4 | a consumer staller is bound | the environment | Section 12's three paths are unreachable |
| 5 | Chapter 7.3 §3 owns the classification | the standard | Section 20's class 92 |
| 6 | the drain rate exceeds the line rate | Chapter 19.5 §17 | the FIFO overflows without any injection |
| 7 | the PHY does not pad a dribble | the PHY model | the injection is absorbed below the design |
| 8 | the MTU is what the injector was told | configuration | the giant threshold moves |
| 9 | a runt of 5 octets is framed as a frame | the receive framing | Chapter 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.
| Timing | Armed before the frame | Can react mid-frame |
|---|---|---|
| the check-value stomper | yes | no — the mask is chosen at arm time |
| the length violator | yes | no — the size is a constraint |
| the dribble injector | yes | no — the ending is decided at arm time |
| the gap violator | yes | no — it acts between frames |
| the consumer staller | no | yes — 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 assumed | Why not |
|---|---|
| that an injected error is detected | that is the design's job and the property's subject — Section 20 |
| that the design's class matches the injector's intent | Chapter 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.
| Block | Flops | Notes |
|---|---|---|
errinj_fcs_stomper | ~50 | 32 XOR gates and a 2-bit index |
length_violator | ~140 | the 59-bit seen-vector |
dribble_injector | ~40 | plus 3 bits on every receive beat |
gap_violator | ~70 | a 5-bit counter and a 16-bit vector |
consumer_staller | ~110 | a 32-bit elapsed counter |
errinj_scheduler | ~360 | ten counters |
errinj_telemetry | ~200 | derived registers |
errinj_conformance_monitor | ~20 | verdicts |
| 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 beat | 3 flops per pipeline stage |
| Chapter 19.1 §6's 16-stage receive pipeline | 48 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:
| Chapter | Flops | Nature |
|---|---|---|
| Chapter 20.1 — the generator | ~1 050 | stimulus |
| Chapter 20.2 — the assertion library | ~730 | checks |
| Chapter 20.3 — the scoreboards | ~1 760 | checks |
| Chapter 20.4 — the coverage model | ~650 | measurement |
| this chapter — error injection | ~990 | stimulus |
| subtotal | ~5 180 | — |
| Module 19's datapath, for comparison | ~14 166 flops | all 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.
// 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.
// 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.
// 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.
// 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.
// 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.
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.
| # | Scenario | Expected |
|---|---|---|
| 1 | a single-bit mask | one bit flipped; RES_BAD |
| 2 | a 32-bit all-ones mask | the residue's complement |
| 3 | the same on a preemption-capable port | an mCRC — a valid fragment, not an error |
| 4 | a zero mask | nothing changed; not an injection |
| 5 | the stomp upstream of the appender | the design recomputes; the frame is valid |
| 6 | the stomp on a payload octet | stomp_outside_fcs |
| 7 | an arm with in_last_four never asserting | nothing stomped, success reported |
| 8 | back-to-back arms | the second is lost — one frame deep |
| 9 | a frame whose FCS spans a beat boundary | 4.74% of sizes — this listing mishandles it |
| 10 | 1 000 stomps | c_crc_errors = 1 000 |
| 11 | the same with a dribble also armed | c_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.
| # | Scenario | Expected |
|---|---|---|
| 12 | a 63-octet frame | undersize; one ending per beat |
| 13 | a 48-octet frame | undersize; still one ending |
| 14 | a 47-octet frame | two endings — the threshold |
| 15 | a 16-octet frame | two endings |
| 16 | a 15-octet frame | three endings |
| 17 | a 5-octet frame | three; and the framing may refuse it |
| 18 | a 1 519-octet frame at MTU 1 518 | oversize |
| 19 | the same at MTU 9 000 | not oversize |
| 20 | a runt with a stomped check value | a fragment, not an undersize |
| 21 | a giant with a stomped check value | a jabber |
| 22 | runts uniformly over 5 to 63 | 27.1% land above 47 and reach one path |
| 23 | 43 distinct lengths below 48 | runt_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.
| # | Scenario | Expected |
|---|---|---|
| 24 | one valid bit in the final octet | an alignment error |
| 25 | seven valid bits | the same class, a different width |
| 26 | all seven widths | dribble_widths_seen = 7'h7F |
| 27 | a detector testing one bit of the count | passes six widths, fails one |
| 28 | the dribble upstream of the appender | Section 8's row d — undefined |
| 29 | an interface without a bit count | interface_lacks_bit_count |
| 30 | a PHY that pads to an octet | the injection is absorbed; nothing reaches |
| 31 | a dribble and a stomp together | one alignment error |
| 32 | a dribble on a runt | an alignment error, not a fragment |
| 33 | c_dribbles climbing, alignment counter flat | row 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.
| # | Scenario | Expected |
|---|---|---|
| 34 | a gap of 8 octets | below Chapter 5.9's floor — a violation |
| 35 | a gap of 9 | conformant; gap_at_or_above_floor |
| 36 | a gap of 0 | the extreme violation |
| 37 | a sweep over a 4-bit field | 7 of 16 settings produce no error |
| 38 | 300 consecutive short gaps | the elastic buffer overruns |
| 39 | the same at 100 Gb/s | 20 000 octets — 1.6 µs |
| 40 | short gaps with conformant frames | every frame perfect, the buffer fails |
| 41 | a single short gap | absorbed; the buffer discharges next frame |
| 42 | the violator asked to lengthen a gap | it 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.
| # | Scenario | Expected |
|---|---|---|
| 43 | a 1 000 ns consumer stall | absorbed; no counter moves |
| 44 | a 2 000 ns stall | stall_below_design_limit |
| 45 | a 2 621 ns stall | the FIFO is exactly full |
| 46 | a 3 000 ns stall | c_frames_truncated |
| 47 | minimum-size frames back to back, consumer running | no overflow — Section 12 |
| 48 | jumbo frames back to back, consumer running | the same |
| 49 | INJ_STALL with no staller bound | refused; site_unavailable |
| 50 | the same accepted silently | the 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.
| # | Scenario | Expected |
|---|---|---|
| 51 | stomps only | paths_reached = 1, cross_share_pct = 32 |
| 52 | stomps and runts below 48 | paths_reached = 5, the same 32 |
| 53 | a wire-only plan | non_wire_faults_absent |
| 54 | runts all above 47 | runts_too_long |
| 55 | one dribble width | dribble_space_thin |
| 56 | an injection that reached nothing | injections_without_effect |
| 57 | everything bound and swept | none_of_the_above |
| 58 | the same with four verdicts waived | also 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.
| Case | Needs | A longer run gives |
|---|---|---|
| the truncation path | a consumer staller and a stall above 2.621 µs | nothing |
| the two-bit addend | runts of 47 octets or fewer | nothing, if the range starts at 48 |
| the exclusivity | a dribble and a stomp on one frame | rarely, under independent arming |
| the elastic overrun | 300 consecutive short gaps | nothing, if gaps are 9 or more |
| the seven dribble widths | a sweep | all 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.
| Run | Configuration | Exercises |
|---|---|---|
| A | stomps only, wire site | the nominal path; 1 design path |
| B | runts swept 5 to 47, 43 lengths | the two-bit addend and the two-ends flag |
| C | a dribble and a stomp on every frame | the exclusivity; the CRC counter reads zero |
| D | 300 consecutive gaps of 4 octets | the elastic overrun in 1.6 µs |
| E | a 3 000 ns consumer stall | the 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:
| Check | A | B | C | D | E |
|---|---|---|---|---|---|
c_crc_errors | 1 000 | 0 | 0 | 0 | 0 |
paths_reached | 1 | 4 | 3 | 2 | 1 |
multi_end_reachable | clear | SET | clear | clear | clear |
c_frames_truncated | 0 | 0 | 0 | 0 | non-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."
| Check | If yes | Meaning |
|---|---|---|
| is a dribble also armed? | Chapter 7.3 §3's exclusivity | it is an alignment error |
| does the alignment counter read the same number? | confirms | one class, not two |
| is the stomp upstream of the appender? | the design recomputes | the frame is valid |
stomp_outside_fcs set? | the framing signal is wrong | a 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."
| Check | If yes | Meaning |
|---|---|---|
multi_end_reachable clear? | every runt was 48 octets or longer | Section 6's threshold |
| what is the runt length range? | 5 to 63 is the legal range | only 5 to 47 packs a beat |
runt_lengths_count low? | few distinct lengths | one length, one behaviour |
does two_ends_in_a_beat ever fire? | Chapter 19.4 §8's flag | the 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 48 — sixteen 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."
| Check | If yes | Meaning |
|---|---|---|
c_dribbles climbing? | the injector is arming | so it is downstream |
interface_lacks_bit_count set? | the interface cannot carry it | Section 7 |
| does the PHY model pad to an octet? | the injection is absorbed | Section 18's row seven |
observed_whole_octets always true? | confirms row three | definitively |
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."
| Check | If yes | Meaning |
|---|---|---|
gap_at_or_above_floor set? | the gaps were nine or more | conformant, and not an injection |
| were the gaps consecutive? | a single short gap is absorbed | the buffer discharges next frame |
| how many in a row? | 300 at 200 ppm | Section 9's 20 000 octets |
| is the drift zero? | Chapter 19.5 §21's row 47 | one 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 accumulate — so 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."
| Check | If yes | Meaning |
|---|---|---|
wire_only_plan set? | no consumer staller is bound | Section 12 |
stall_below_design_limit set? | the stall is 2 000 ns | the FIFO absorbs it |
| is the stall above 2 621 ns? | only then does it overflow | Section 11's two thresholds |
| does a longer frame stream help? | it cannot | Section 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 100 — so 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."
| Check | If yes | Meaning |
|---|---|---|
| is the constraint's upper bound 63? | yes | the bound is the bug, not the injector |
runt_lengths_count against 43 | below it | the useful lengths are not all being hit |
does c_runts still climb? | yes, on all 59 lengths | the counter cannot see the difference |
runt_space_thin asserted? | yes | Section 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 injection — and 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:
| Symptom | Blamed on | Usually is |
|---|---|---|
| an error counter that stays at zero | the design | an injection in the wrong class, or absorbed |
| "the injector does not work" | the injector | a site that is not bound |
| a truncation path never exercised | the stimulus | a 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 fewer — and 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 octets — so 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 three — and 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's — Chapter 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 error — and 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
Related tutorials
- Related topic
A Parallel CRC-32 Engine in RTL
The wide next-state function is linear, so it is generated rather than derived — and the cost per bit falls as the datapath widens. What is designed is the final partial word, where eight sub-networks exist and real traffic reaches two of them.
- Related topic
Packet Generation
A weight is a per-frame marginal, so it reaches a frame's own properties and nothing else — and 48 of the parser's 64 alignment offsets are unreachable from the transmit side at any weight.
- Related topic
Scoreboards
A scoreboard that compares octet for octet fails on padding, on an appended check value and on a tag — and on a minimum-size frame only fifteen octets are invariant.
- Related topic
Coverage
A six-dimension cross over this MAC declares 860 160 cells; 54.3% of them are reachable and a loopback topology reaches 13.6% — so 100% means three different things.
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.
