Ethernet · Module 21
Packet Capture Analysis
A capture reaches 14 of Module 21's 57 faults, holds 84 milliseconds per gigabyte, and drops 12% of them itself — and it is still the only instrument that shows relationships between frames.
Every chapter in Module 21 put the capture last and none of them used it. This one says why, and what it is actually for.
| Value | |
|---|---|
| faults Module 21's six enumerating chapters named | 57 |
| a wire capture reaches outright | 14 — 24.6% |
| partially, needing a second observation | 9 — 15.8% |
| cannot reach at all | 34 — 59.6% |
| frames in a 1 GB capture, minimum size | 1.25 × 10⁷ |
| frames a counter has seen in a week | 9.00 × 10¹³ |
| the ratio | 7.2 million |
Row seven is Chapter 21.1 §12's "three hours for about a bit" with a number attached. A capture's sample is 7.2 million times smaller than the counters already running on the port, and it costs three hours and a maintenance window while they cost nothing.
And the apparatus drops frames of its own.
| At 100 Gb/s | |
|---|---|
| arrival rate | 12.50 GB/s |
| a sustained NVMe write | 3.00 GB/s — 24.0% of line |
| an 8 GB ring buffer | full after 842 ms |
| fraction of a 1-second capture the apparatus drops | 12.0% |
That is Chapter 19.5's buffer problem one level up, with one difference that matters more than the arithmetic: the capture's own drops are indistinguishable from the link's. A capture taken to diagnose Chapter 21.6's silent drops silently drops frames.
What it does show, uniquely, is two things.
| Which no counter has | |
|---|---|
| contents | every octet — and Chapter 21.7 §8's three ordering failures corrupt contents |
| relationships between frames | order, spacing, and pairing, all of which a counter aggregates away |
And both come with a condition. Contents diagnose Chapter 21.7's group only when compared against a second observation of the same frames at the destination — which is Chapter 20.6's join problem arriving in a data centre instead of a testbench.
1. Scope, and Eight Chapters That Deferred to This One
Scope: what a packet capture observes, which of Module 21's faults that reaches, what it costs, and what the apparatus does to the thing it is measuring.
Not in scope: the faults themselves. Every one has a chapter — Chapter 21.1 through Chapter 21.8 — and this chapter is about an instrument rather than about a failure. That makes it the only chapter in Module 21 whose subject is a tool, and the reason it comes last is that eight chapters had to establish what the tool is competing with.
Start with what those eight said about it.
| Chapter | Said |
|---|---|
| Chapter 21.1 §12 | step 6 — three hours for an observation worth about one bit |
| Chapter 21.3 §18 | a hundred failing frames report the same one bit a hundred times |
| Chapter 21.4 §1 | on a down link there are no frames to capture |
| Chapter 21.6 §1 | the frames in question did not arrive anywhere |
| Chapter 21.7 §8 | three failures deliver every frame and corrupt the contents |
| Chapter 21.8 §14 | there is no event to count and none to capture |
Five of those six are reasons a capture will not help and the sixth — Chapter 21.7 §8 — is the one place in the module where no counter can help either. That asymmetry is the chapter's whole content: the capture is the wrong instrument for almost everything Module 21 diagnoses and the only instrument for one specific group.
Chapter 21.3 §18's argument is worth restating exactly, because it is the sharpest.
A check sequence's verdict is one bit. A capture of a hundred failing frames tells you that one bit a hundred times, and the damage's shape — which is what separates a burst from random bit errors — is not in any of them.
So a capture's value cannot be "it shows you the frames." It has to be something the frames carry that a counter discards, and Sections 6 and 8 are the two candidates.
Three things this chapter is and is not.
| Choice | |
|---|---|
| it counts faults, not features | 57 of them, from six chapters that enumerated |
| it prices the apparatus | including the frames the apparatus itself loses |
| it treats the capture as part of the system | because a span port adds load and a tap adds loss |
Row three is the chapter's structural claim and Section 20's rejected property. A capture is not an outside observer: a mirroring port consumes fabric bandwidth, an inline tap inserts loss, and a capture host's storage is slower than the wire. The instrument is inside the system it measures, and a property that assumes otherwise records the instrument's limits as the subject's.
2. What a Capture Point Actually Observes
A capture records frames. Enumerating precisely what that gives is the first half of deciding what it is for.
| Observable | Available? | From |
|---|---|---|
| every octet of every captured frame | yes | the frame itself |
| the frame's length | yes | the capture |
| the arrival timestamp | yes, to the capture clock's resolution | the apparatus |
| the inter-frame spacing | derived from two timestamps | the apparatus |
| the order of frames | yes, at this point | the capture |
| the check sequence's verdict | recomputable from the octets | the frame |
| trailing bits — Chapter 7.3 §7's count | usually NOT | discarded by most capture paths |
| the interframe gap in octets | NOT — only a timestamp difference | the apparatus |
| symbol errors | NOT — a PCS event below the frame | the PHY |
Rows seven, eight and nine are the ones people assume and do not get. A capture is a record of frames, and three of the things Module 21's chapters depend on are not frames: Chapter 21.2 §9's symbol errors in the interframe gap, Chapter 7.3 §7's trailing-bit count, and Chapter 5.9's gap in octets — which Chapter 21.8 §13 needed and which a timestamp difference approximates badly at 100 Gb/s.
And the capture point matters as much as the capture.
| Point | Sees | Misses |
|---|---|---|
| a passive optical tap | the light on the fibre, before our PHY | anything our PHY does to it |
| a mirrored (span) port | frames after our PHY and after the switch's parser | whatever the PHY corrected or mangled |
| an inline device | everything, and adds latency and a failure point | — |
Row one against row two is the difference that decides Chapter 21.1's sites 2 and 3. A passive tap shows what arrived; a span port shows what our PHY made of it — so the two disagree exactly when the PHY or the xMII is at fault, which is the pair Chapter 21.4 §6 found fused under every MDIO reading.
Two capture points, one on each side of the PHY, separate a lane-alignment fault from a channel fault — and almost nobody has both.
Row two also has a property row one does not: it perturbs.
| A passive tap | A span port | |
|---|---|---|
| adds load to the fabric | no | yes — the mirrored copy is real traffic |
| can itself drop | no | yes, when the mirror destination is oversubscribed |
| inserts optical loss | yes, a few dB | no |
| is in the failure path | no | no |
| shows the frames the switch discarded | no — it is upstream | depends where the mirror is taken |
Row five is the one that matters for Chapter 21.6's class G. A mirror taken at ingress shows frames that were subsequently dropped; one taken at egress does not. So the same instrument answers or fails to answer the module's hardest question depending on a configuration line, and the line is rarely documented.
3. RTL 1 — The Capture Package and the Observable Set
// ---------------------------------------------------------------------
// capdiag_pkg -- what a capture point observes, what it costs, and the
// one structural fact: the apparatus is inside the system it measures.
//
// Chapter 19.5's receive FIFO had an arrival rate bounded by the line
// rate and a drain rate above it, so it emptied. A capture's sink is
// BELOW the line rate, so it does not -- and Section 10 is that
// arithmetic run the other way.
// ---------------------------------------------------------------------
package capdiag_pkg;
// Section 2's table, as a set. Three of the nine are not frames and
// are therefore not in a capture at all.
typedef enum logic [3:0] {
OBS_OCTETS = 4'd0,
OBS_LENGTH = 4'd1,
OBS_TIMESTAMP = 4'd2,
OBS_SPACING = 4'd3, // derived from two timestamps
OBS_ORDER = 4'd4,
OBS_FCS_VERDICT= 4'd5, // recomputable from the octets
OBS_TRAILING = 4'd6, // usually discarded
OBS_GAP_OCTETS = 4'd7, // NOT available -- a timestamp is not a gap
OBS_SYMBOL = 4'd8 // NOT available -- below the frame
} observable_e;
localparam logic [8:0] IN_A_CAPTURE = 9'b0_0011_1111;
localparam logic [8:0] NOT_IN_CAPTURE = 9'b1_1100_0000;
typedef enum logic [1:0] {
PT_TAP = 2'd0, // passive, upstream of our PHY
PT_SPAN = 2'd1, // mirrored, downstream of it, and it perturbs
PT_INLINE = 2'd2
} capture_point_e;
// Chapter 19.5 Section 3's arithmetic, inverted. At 100 Gb/s the
// wire delivers 12.5 GB/s and a sink does not.
localparam int LINE_MBPS = 100000;
localparam int WIRE_MBYTES_S = 12500;
localparam int SINK_MBYTES_S = 3000; // a sustained NVMe write
localparam int BUFFER_MBYTES = 8000;
// Section 10: the buffer lasts BUFFER / (arrival - drain).
function automatic int buffer_ms(int buf_mb, int arr_mbs, int sink_mbs);
if (sink_mbs >= arr_mbs) return 1000000; // never overflows
return (buf_mb * 1000) / (arr_mbs - sink_mbs);
endfunction
// Section 12: a filter on a fault of rate 1-in-N multiplies the
// window a fixed store covers by N.
function automatic int window_gain(int one_in_n);
return one_in_n;
endfunction
// Section 4's verdicts.
typedef enum logic [1:0] {
REACH_YES = 2'd0, // the evidence is a property of the frames
REACH_PARTIAL = 2'd1, // needs a second point, or a lost field
REACH_NO = 2'd2
} reach_e;
endpackageClassification: a package whose two constants are a rate the apparatus cannot keep up with and a mask of three things a capture does not contain.
What it teaches: that NOT_IN_CAPTURE has three members and all three are load-bearing elsewhere in Module 21. Chapter 21.2 §9 built a whole section on symbol errors in the interframe gap — the earliest channel warning available — and they are not frames. Chapter 7.3 §7's trailing-bit count decides the alignment class and most capture paths discard it. Chapter 21.8 §13's gap in octets is the only evidence about the offered load and a capture gives a timestamp difference instead.
And it teaches that SINK_MBYTES_S being below WIRE_MBYTES_S is the chapter's structural fact rather than a configuration detail. Chapter 19.5 §4 established that the MAC's receive FIFO empties because its drain rate exceeds the line rate; the capture's does not, so its buffer is a countdown rather than a cushion.
Deliberately simplified: SINK_MBYTES_S at 3 000 is one plausible storage path and the range in practice is an order of magnitude either way. window_gain is the identity function, which is correct and is written out because the result is surprising: filtering on a 1-in-10⁶ fault makes a 1 GB store cover 22 hours instead of 80 milliseconds. IN_A_CAPTURE is a literal rather than derived. And capture_point_e has no representation of where a span port is mirrored from, which Section 2 showed decides whether it sees dropped frames.
Production implication: the missing mirror-point representation is the configuration question to ask first and it is one line in a switch's configuration. A mirror taken at ingress records the frames the switch subsequently discarded; one taken at egress records only what survived. For Chapter 21.6's class G — five sites whose entire symptom is a frame that vanished — that single line decides whether the capture contains the evidence or provably cannot. It is worth more than the choice of capture tool.
// ---------------------------------------------------------------------
// observable_set -- given a capture point, say what is recorded and
// what is not, and refuse to answer questions about the rest.
//
// The block's purpose is to make an ABSENCE explicit. A capture that
// silently lacks the trailing-bit count produces an alignment
// diagnosis that cannot be right.
// ---------------------------------------------------------------------
module observable_set
import capdiag_pkg::*;
#(
parameter capture_point_e POINT = PT_TAP
)(
input logic clk,
input logic rst_n,
input logic frame_captured,
input logic trailing_bits_recorded,
input logic timestamp_ns_resolution,
output logic [8:0] available,
output logic sees_before_phy,
output logic perturbs_the_system,
output logic gap_is_a_timestamp_difference,
output logic [31:0] c_frames_captured,
output logic alignment_undiagnosable
);
always_comb begin
available = IN_A_CAPTURE;
// Section 2: most capture paths discard the trailing-bit count,
// and Chapter 7.3 Section 3's alignment class needs it.
if (!trailing_bits_recorded) available[OBS_TRAILING] = 1'b0;
sees_before_phy = (POINT == PT_TAP);
perturbs_the_system = (POINT == PT_SPAN) || (POINT == PT_INLINE);
// A timestamp difference is not a gap. At 100 Gb/s a 12-octet gap
// is 0.96 ns and a capture timestamp is rarely finer than 1 ns.
gap_is_a_timestamp_difference = 1'b1;
alignment_undiagnosable = !available[OBS_TRAILING];
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) c_frames_captured <= '0;
else if (frame_captured) c_frames_captured <= c_frames_captured + 32'd1;
end
endmoduleClassification: a capability descriptor, and its useful outputs are all negations.
What it teaches: that gap_is_a_timestamp_difference is unconditionally true and is the chapter's quietest limitation. Chapter 5.9's minimum gap is twelve octets, which at 100 Gb/s is 0.96 nanoseconds — below the resolution of almost every capture timestamp. So Chapter 21.8 §13's offered-load test, which counts frames arriving at the gap floor, cannot be run from a capture at 100 Gb/s at all, and can be run at 1 Gb/s where the same gap is 96 ns.
And it teaches that alignment_undiagnosable is a property of the capture path rather than of the link. Chapter 21.2 §3's alignment class is not whole octets and the check failed; a capture that records whole octets only can see the second condition and not the first — so it reports a check-sequence error where the design reported an alignment error, and the disagreement is the capture's.
Deliberately simplified: POINT is a parameter rather than a discovered property, so a tool that does not know how it was connected cannot fill it in. available is combinational over one input. And perturbs_the_system is a boolean where Section 2 showed a span port's perturbation is a quantity — the mirrored copy is real traffic on a real fabric, and how much depends on the mirrored port's load.
Production implication: the 0.96-nanosecond gap is the limitation that most changes what a capture can be used for, and it is rate-dependent in the direction nobody expects. At 1 Gb/s the minimum gap is 96 ns and an ordinary capture timestamp resolves it; at 100 Gb/s it is 0.96 ns and nothing in an ordinary capture path does. So every timing-based analysis a capture supports gets harder by a factor of a hundred across the rates this track covers, which is Chapter 20.6 §8's argument arriving in an instrument rather than in a coverage model.
4. Module 21's Faults, Against the Capture's Reach
Six chapters of Module 21 enumerated their fault spaces. This section runs a capture against all of them.
The rule is one line: a wire capture reaches a fault if and only if the fault's evidence is a property of the frames on the wire.
| Chapter | Faults | Reached | Partial | Not reached |
|---|---|---|---|---|
| Chapter 21.1 — receive-path sites | 12 | 3 | 1 | 8 |
| Chapter 21.2 — error classes | 13 | 7 | 3 | 3 |
| Chapter 21.4 — link sites | 14 | 0 | 0 | 14 |
| Chapter 21.6 — class-G mechanisms | 6 | 2 | 1 | 3 |
| Chapter 21.7 — descriptor failures | 8 | 0 | 3 | 5 |
| Chapter 21.8 — ceilings | 4 | 2 | 1 | 1 |
| total | 57 | 14 | 9 | 34 |
Twenty-four point six per cent reached outright, 15.8% partially, and 59.6% not at all.
The three rows worth reading individually.
Chapter 21.4 is fourteen of fourteen unreachable and the reason is definitional. A link that will not come up produces no frames, so there is nothing to capture. A capture on such a link records an empty file, and the empty file is consistent with all fourteen sites — which is Chapter 21.4 §1's whole argument, restated from the instrument's side.
Chapter 21.2 is the capture's best chapter — seven of thirteen outright — because six of its thirteen classes are predicates over the octets of a frame, which is exactly what a capture contains. The three partials are the three that need a field a capture path discards (trailing bits) or a distinction the wire cannot carry (our truncation against the far end's).
Chapter 21.7 is zero outright and three partial, and the three partials are the chapter's whole point. Its ordering failures corrupt the frame after it leaves the wire, so a wire capture records the frame as it was — correct — and that correct record is the reference a second observation is compared against. The capture is half an instrument and Section 6 is the other half.
And the partial column has a structure worth naming.
| Why a fault is only partial | Count |
|---|---|
| needs a second observation point | 4 |
| needs a field the capture path discarded | 3 |
| shows the symptom, not the cause | 2 |
Row one is four of the nine and it is Chapter 20.6's join problem arriving in a data centre. Two captures, at two points, of the same frames — and nothing identifies which frame is which except its contents, which is Chapter 20.3 §3's digest and Chapter 20.6 §4's collision analysis, in a setting where neither end can mint an identifier.
5. RTL 2 — The Two-Point Comparator
// ---------------------------------------------------------------------
// two_point_frame_join -- the same frames observed at two places, and
// the problem of saying which is which.
//
// Chapter 20.6 Section 4 met this in a testbench and could not mint an
// identifier either: the DUT does not carry one. The resolution there
// was a CONTENT DIGEST plus an ordering assumption, and the collision
// rate was the thing to bound. In the field the same resolution
// applies and the ordering assumption is weaker, because the two
// points are separated by queues.
// ---------------------------------------------------------------------
module two_point_frame_join
import capdiag_pkg::*;
#(
parameter int DIGEST_W = 32,
parameter int WINDOW = 64 // frames of reorder tolerance
)(
input logic clk,
input logic rst_n,
// point A -- upstream, e.g. a tap on the fibre
input logic a_valid,
input logic [DIGEST_W-1:0] a_digest,
input logic [63:0] a_time_ns,
// point B -- downstream, e.g. the host's receive queue
input logic b_valid,
input logic [DIGEST_W-1:0] b_digest,
input logic [63:0] b_time_ns,
output logic matched,
output logic [63:0] transit_ns,
output logic a_unmatched_aged_out, // lost between the points
output logic b_without_a, // injected, or missed at A
output logic content_differs, // same slot, other octets
output logic [31:0] c_matched,
output logic [31:0] c_lost,
output logic [31:0] c_ambiguous
);
logic [DIGEST_W-1:0] a_dig [WINDOW];
logic [63:0] a_t [WINDOW];
logic a_occ [WINDOW];
logic [$clog2(WINDOW)-1:0] wp;
// Two entries in the window with the same digest make the join
// ambiguous. Chapter 20.6 Section 4 bounded this; here it is counted,
// because in the field the rate is a measurement rather than a
// parameter.
logic hit;
int hit_idx;
int hit_count;
always_comb begin
hit = 1'b0; hit_idx = 0; hit_count = 0;
for (int i = 0; i < WINDOW; i++)
if (a_occ[i] && a_dig[i] == b_digest) begin
hit = 1'b1; hit_idx = i; hit_count++;
end
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
wp <= '0; c_matched <= '0; c_lost <= '0; c_ambiguous <= '0;
matched <= 1'b0; b_without_a <= 1'b0; transit_ns <= '0;
for (int i = 0; i < WINDOW; i++) a_occ[i] <= 1'b0;
end else begin
matched <= 1'b0; b_without_a <= 1'b0;
if (a_valid) begin
// Overwriting an occupied slot IS the aged-out case: the frame
// was seen at A and never at B.
if (a_occ[wp]) c_lost <= c_lost + 32'd1;
a_dig[wp] <= a_digest; a_t[wp] <= a_time_ns; a_occ[wp] <= 1'b1;
wp <= wp + 1'b1;
end
if (b_valid) begin
if (hit) begin
matched <= 1'b1;
transit_ns <= b_time_ns - a_t[hit_idx];
a_occ[hit_idx] <= 1'b0;
c_matched <= c_matched + 32'd1;
if (hit_count > 1) c_ambiguous <= c_ambiguous + 32'd1;
end else begin
b_without_a <= 1'b1;
end
end
end
end
assign a_unmatched_aged_out = a_valid && a_occ[wp];
assign content_differs = b_valid && !hit;
endmoduleClassification: a join across two observation points, with the ambiguity counted rather than assumed away.
What it teaches: that a capture at one point answers almost nothing about delivery and a capture at two points answers a great deal, and that the step between them is a join on a key neither point can mint. Chapter 20.6 §4 met this exactly, in a testbench, where the generator could have minted one and deliberately did not — because a real link does not. Here the same constraint is not a choice.
And it teaches what the three unmatched outputs mean, which are three different faults. a_unmatched_aged_out is a frame that entered and did not leave — Chapter 21.6's class G, localised to the span between the points. b_without_a is a frame that left and did not enter, which is either an injection or a miss at A — and at a span port, a miss at A is far more likely than an injection. content_differs is Chapter 21.7's corruption class and it is the only one of the three that no counter anywhere can report.
Deliberately simplified: the window is a flat array searched combinationally, which at WINDOW = 64 is a 64-way comparator and is fine in a testbench and not at rate. hit_count > 1 counts ambiguity and then proceeds anyway, picking the last match, which is a decision the block should expose rather than make. And content_differs is b_valid && !hit, which conflates corrupted with never seen at A — a real comparator needs a weaker key (a flow tuple plus a sequence field) to distinguish them, and Chapter 21.6 §7 showed why no such field is guaranteed.
Production implication: the ambiguity counter is what makes the join trustworthy rather than merely convenient. A digest collision produces a match that is wrong, and a wrong match reports a transit time that is meaningless and a delivery that did not happen — so a join with no collision measurement can report 100% delivery on a path that is losing frames. Publish c_ambiguous alongside c_matched always, and treat any run where the ratio exceeds a fraction of a per cent as uninterpretable rather than as evidence. This is Chapter 20.3 §3's digest argument with the stakes raised: in a testbench a collision costs a debug hour; here it costs a false clean bill.
6. Contents Need Two Points, and That Is the Join Again
Section 4 said a capture reaches 14 of 57 faults outright and 9 partially, and that 4 of the 9 partials need a second observation point. This section is why, and what the second point costs.
Start with the asymmetry. A capture at one point establishes what was on the wire there, at that time. That is a statement about a location, and every fault in Module 21 that is interesting is a statement about a difference between two locations.
| Question | One point | Two points |
|---|---|---|
| was this frame corrupt on the wire? | yes — recompute the check sequence | — |
| did this frame arrive at the host? | no | yes |
| was it corrupted between wire and host? | no | yes — Chapter 21.7's class |
| were frames reordered in the path? | no | yes |
| how long did the path take? | no | yes |
| is the far end sending at the rate it claims? | partly — from spacing | yes |
Rows two through five are four of Module 21's partials and every one becomes reachable with a second point. That is the chapter's most useful practical statement: the single biggest increase in a capture's diagnostic power is not a bigger buffer or a faster sink — it is a second capture point.
And the second point is expensive in a way the first is not.
| Cost | First point | Second point |
|---|---|---|
| hardware | a tap or a mirror configuration | the same again, elsewhere |
| storage | as Section 12 | twice |
| clock | any | the two must be comparable |
| the join | none needed | a digest, a window, and an ambiguity rate |
| analysis | read the frames | match them first |
Row three is the one that surprises people. Comparing timestamps across two points needs the two clocks related, and two free-running capture hosts drift by microseconds over a capture's length — which swamps the microsecond-scale transit times the comparison exists to measure. Chapter 16.2 built an entire protocol to solve this, and a capture setup that does not use it is measuring drift and calling it transit.
Two capture points without a common time reference measure their own clock difference. The traffic contributes a term smaller than the error.
Row four is Chapter 20.6's join arriving in the field, and the field version is harder for a reason worth stating. In a testbench the two observation points are separated by the DUT and the frames between them are in order, so a digest match plus a monotonic assumption is enough. Between two points in a data centre the frames are separated by queues, load balancers and possibly multiple paths — so the window has to absorb real reordering, and a wider window raises the digest collision probability quadratically. The join gets less reliable exactly as the network gets more interesting.
The arithmetic is worth one line. With a 32-bit digest and a window of W frames, the chance that a given lookup finds a spurious match is about W / 2³². At W = 64 that is 1.5 × 10⁻⁸ — one bad match per 67.1 million frames, which is 0.45 seconds of 100 Gb/s minimum-size traffic. At W = 4 096, to absorb a badly reordered path, it is 9.5 × 10⁻⁷ — one per 1.05 million frames, or every 7.0 milliseconds. The window is not free.
7. RTL 3 — The Relationship Extractor
// ---------------------------------------------------------------------
// frame_relationship_extractor -- the properties of a SEQUENCE of
// frames, which is the half of a capture's content no counter holds.
//
// Chapter 19.7's counter block spent 2672 flops on 27 counters, and
// every one of them is a SUM. A sum destroys order, destroys spacing,
// and destroys pairing. This block recovers the three, from the same
// frames, and the cost of doing so is the point of Section 19.
// ---------------------------------------------------------------------
module frame_relationship_extractor
import capdiag_pkg::*;
#(
parameter int BURST_NS = 100000, // 100 us -- Section 8's window
parameter int SEQ_W = 32
)(
input logic clk,
input logic rst_n,
input logic f_valid,
input logic [63:0] f_time_ns,
input logic [15:0] f_len,
input logic [31:0] f_flow,
input logic [SEQ_W-1:0] f_seq, // from an upper layer, if present
input logic f_seq_valid,
// relationship 1 -- ORDER
output logic out_of_order,
output logic [31:0] c_out_of_order,
// relationship 2 -- SPACING, and the burst it reveals
output logic [63:0] gap_ns,
output logic back_to_back,
output logic [31:0] burst_peak_bytes,
// relationship 3 -- PAIRING / repetition
output logic duplicate_seq,
output logic [31:0] c_duplicate,
output logic [31:0] c_frames
);
logic [63:0] last_time;
logic [SEQ_W-1:0] last_seq;
logic have_last;
// A rolling byte count over BURST_NS. A one-second counter average
// cannot represent this: Section 8 shows an 88.9 us burst that a
// one-second average records as 0.0089% utilisation.
logic [63:0] win_start;
logic [31:0] win_bytes;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
have_last <= 1'b0; c_frames <= '0; c_out_of_order <= '0;
c_duplicate <= '0; burst_peak_bytes <= '0; win_bytes <= '0;
win_start <= '0; out_of_order <= 1'b0; duplicate_seq <= 1'b0;
gap_ns <= '0; back_to_back <= 1'b0;
end else begin
out_of_order <= 1'b0;
duplicate_seq <= 1'b0;
back_to_back <= 1'b0;
if (f_valid) begin
c_frames <= c_frames + 32'd1;
// ORDER -- meaningful only with an upper-layer sequence. Chapter
// 21.6 Section 7 established that Ethernet itself carries none,
// which is why this output is gated on f_seq_valid.
if (have_last && f_seq_valid) begin
if (f_seq < last_seq) begin
out_of_order <= 1'b1;
c_out_of_order <= c_out_of_order + 32'd1;
end else if (f_seq == last_seq) begin
duplicate_seq <= 1'b1;
c_duplicate <= c_duplicate + 32'd1;
end
last_seq <= f_seq;
end else if (f_seq_valid) begin
last_seq <= f_seq;
end
// SPACING
if (have_last) begin
gap_ns <= f_time_ns - last_time;
// At 100 Gb/s a minimum frame plus its gap is 6.72 ns.
back_to_back <= ((f_time_ns - last_time) <= 64'd7);
end
last_time <= f_time_ns;
have_last <= 1'b1;
// BURSTINESS
if ((f_time_ns - win_start) > BURST_NS) begin
win_start <= f_time_ns;
win_bytes <= {16'b0, f_len};
end else begin
win_bytes <= win_bytes + {16'b0, f_len};
if ((win_bytes + f_len) > burst_peak_bytes)
burst_peak_bytes <= win_bytes + {16'b0, f_len};
end
end
end
end
endmoduleClassification: three relationship extractors sharing one frame stream, and each recovers something a sum destroyed.
What it teaches: that the three relationships are not equally available. Spacing and burstiness come from timestamps the apparatus adds and are always available at the resolution the apparatus offers. Order and duplication come from a sequence field the frame may not carry — which is Chapter 21.6 §7's finding, and the reason out_of_order is gated on f_seq_valid rather than computed unconditionally. A capture of frames with no upper-layer sequence cannot tell you they were reordered, only what order this point saw.
And it teaches why burst_peak_bytes is the output with the most practical value in the module. Chapter 19.7 §19's counter block is 27 sums; a sum over a one-second interval represents an 88.9-microsecond burst as 0.0089% utilisation. The burst is 1.11 MB delivered at line rate — enough to overrun a 1 MB egress buffer completely — and the counter that would have to see it is averaging over an interval a hundred million times longer than the event.
Deliberately simplified: f_seq < last_seq treats a sequence space as unwrapped, which is wrong at the wrap and is Chapter 18.2's ring-index wrap in miniature. back_to_back compares against 7 ns, a constant baked for 100 Gb/s, where the same test at 1 Gb/s needs 672. The burst window is a jumping window rather than a sliding one, so a burst straddling the boundary is reported as two half-bursts. And f_flow is an input and is never used, which is deliberate: a real extractor keeps per-flow state and the storage for that is the module's real cost.
Production implication: the unused f_flow port is the honest signal of what this costs. Per-flow relationship state is the difference between a block that fits in a corner of an FPGA and one that does not — one last_seq, one last_time, one window per flow, at roughly 130 flops each, means 10 000 flows is 1.3 million flops, which is 92 times Chapter 19.7 §19's entire 14 166-flop datapath. That ratio is why relationship analysis lives on a capture host with gigabytes of DRAM and not in the MAC, and it is the real reason Chapter 19.7's counters are sums — not because sums are informative, but because sums are the only thing that fits.
8. What Only a Capture Shows: Relationships Between Frames
Section 4 established that a capture reaches 24.6% of Module 21's faults. This section establishes that for one group of questions it is the only instrument there is.
The argument is structural rather than empirical. Chapter 19.7's statistics block is 27 counters and every counter is a sum over an interval. A sum is a function that is not injective: many sequences of frames map to the same total. So the information a sum destroys cannot be recovered from the sum, no matter how many sums are kept, and the only way to have it is to have kept the frames.
Three kinds of information are destroyed, and they are the three the extractor recovers.
| Relationship | What a counter shows | What a capture shows |
|---|---|---|
| order | nothing — a sum is commutative | the sequence, at this point |
| spacing | an average over the interval | every gap individually |
| pairing | two independent totals | which response answered which request |
Row one's phrasing is exact and worth pausing on. A sum is commutative: the total of a thousand frames is identical whichever order they arrived in. So no counter, of any kind, at any resolution, can report a reordering — this is not a limitation of Chapter 19.7's implementation but a property of the function. A reordering counter would have to be a different kind of object, and the block that computes it is the one in Section 7.
Row two has the numbers.
| Value | |
|---|---|
| a burst filling a 1 MB egress buffer (100 Gb/s in, 10 Gb/s out) | 88.9 µs |
| bytes delivered in it | 1.11 MB |
| frames, at 1 518 octets | 659 |
| what a one-second byte counter reports | 0.0089% utilisation |
| ratio of counter interval to event | 11 250 : 1 |
Row four is the whole case for a capture in one number. A port that overran its buffer and dropped frames reports a hundredth of one per cent utilisation, because the drop happened in eighty-nine microseconds and the counter is averaging over a second. Chapter 21.8 §14 named the symptom — a link with no errors that is slow — and the microburst is one of its two commonest causes. The capture is the instrument because the capture keeps the timestamps.
Row three — pairing — is the least discussed and is the one that matters for Chapter 21.7.
A request counter and a response counter that agree prove that the totals match. They do not prove that any particular request was answered, and a system that answers every request with the previous request's response satisfies both counters exactly.
That is not a hypothetical. Chapter 21.7 §8's three ordering failures — a descriptor written back before its data, a completion visible before its payload, and a ring index advanced past an incomplete write — all preserve totals and corrupt pairing. Every counter in Chapter 19.7's 27 is blind to them by construction, and so is every counter that could be added, because adding a counter adds a sum.
So the capture's unique contribution is precisely: the frames' relationships to each other. Contents matter too — Section 6 showed contents need a second point — but relationships need only one point and no counter can ever supply them. That is the honest answer to the question eight chapters deferred.
9. RTL 4 — The Capture Sink and Its Buffer
// ---------------------------------------------------------------------
// capture_sink_model -- the apparatus as a queue, with an arrival rate
// it cannot match.
//
// Chapter 19.5 Section 4's receive FIFO empties because its drain rate
// EXCEEDS the line rate; the FIFO exists to absorb a burst, not a
// sustained excess. This queue's drain rate is BELOW the line rate, so
// the buffer is a countdown. Same structure, opposite sign, and the
// consequence is that the apparatus drops.
// ---------------------------------------------------------------------
module capture_sink_model
import capdiag_pkg::*;
#(
parameter int ARRIVE_MBYTES_S = WIRE_MBYTES_S, // 12500 at 100 Gb/s
parameter int SINK_MBYTES_S_P = SINK_MBYTES_S, // 3000, an NVMe write
parameter int BUFFER_MB = BUFFER_MBYTES // 8000
)(
input logic clk, // 1 kHz tick -- one per millisecond
input logic rst_n,
input logic capturing,
input logic [15:0] offered_mbytes_this_ms, // may be below line rate
output logic [31:0] occupancy_mb,
output logic buffer_full,
output logic [31:0] dropped_mb,
output logic [15:0] self_drop_pct_x10, // tenths of a per cent
output logic [31:0] ms_until_full,
output logic sink_keeps_up
);
localparam int SINK_MB_PER_MS = SINK_MBYTES_S_P / 1000; // 3 MB/ms
logic [31:0] total_offered_mb;
assign sink_keeps_up = (SINK_MBYTES_S_P >= ARRIVE_MBYTES_S);
always_comb begin
// Section 10's headline: buffer / (arrival - drain).
if (sink_keeps_up) ms_until_full = 32'hFFFF_FFFF;
else ms_until_full =
(BUFFER_MB - occupancy_mb) /
((ARRIVE_MBYTES_S - SINK_MBYTES_S_P) / 1000);
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
occupancy_mb <= '0; dropped_mb <= '0; total_offered_mb <= '0;
buffer_full <= 1'b0; self_drop_pct_x10 <= '0;
end else if (capturing) begin
automatic int net;
automatic int drained;
total_offered_mb <= total_offered_mb + offered_mbytes_this_ms;
drained = (occupancy_mb > SINK_MB_PER_MS) ? SINK_MB_PER_MS
: occupancy_mb;
net = occupancy_mb - drained + offered_mbytes_this_ms;
if (net > BUFFER_MB) begin
// THIS is the line that makes the apparatus part of the system.
// The excess is discarded, and nothing in the capture file
// distinguishes it from a frame the LINK dropped.
dropped_mb <= dropped_mb + (net - BUFFER_MB);
occupancy_mb <= BUFFER_MB;
buffer_full <= 1'b1;
end else begin
occupancy_mb <= net;
buffer_full <= 1'b0;
end
if (total_offered_mb != 0)
self_drop_pct_x10 <= 16'((dropped_mb * 1000) / total_offered_mb);
end
end
endmoduleClassification: a leaky-bucket model of a storage path, with the leak too small.
What it teaches: that the capture apparatus is Chapter 19.5's FIFO with the inequality reversed, and that reversal changes everything. Chapter 19.5 §4's receive FIFO exists to absorb a burst: arrivals exceed the drain briefly and the FIFO empties afterwards, so its depth is sized against a burst length. A capture's sink never catches up, so its buffer is sized against the whole capture — and Section 10 shows that at 100 Gb/s no affordable buffer is large enough for a capture longer than a few seconds.
And it teaches which line makes this a chapter about an instrument rather than about a disk. dropped_mb <= dropped_mb + (net - BUFFER_MB) discards frames that were on the wire, and the capture file it produces is indistinguishable from a capture of a link that dropped them. A person diagnosing Chapter 21.6's class G with this apparatus sees exactly the evidence they came looking for, and the apparatus manufactured it.
Deliberately simplified: the model works in megabytes per millisecond, so a burst finer than a millisecond is invisible — which is ironic given Section 8's 88.9-microsecond burst and is the right simplification for a model whose subject is a storage path rather than a link. SINK_MBYTES_S_P is constant, where a real NVMe write rate falls by a factor of two or more once its SLC cache is exhausted, typically tens of seconds in. And self_drop_pct_x10 is computed from the previous cycle's dropped_mb, so it lags by a millisecond.
Production implication: the missing SLC-cache behaviour is the one that catches people, because it makes the apparatus's drop rate a function of how long the capture has been running. A ten-second capture on a fresh device drops nothing; the same capture at minute three drops several per cent; and the difference looks exactly like a fault that gets worse under sustained load — which is Chapter 21.8's entire symptom class. Always capture the apparatus's own drop counter alongside the frames, and treat a capture with no such counter as evidence of nothing at all.
10. The Capture's Own Drop Rate
The apparatus drops. This section prices it, and the price is high enough to change what a capture can be used for.
The arithmetic is two lines. At 100 Gb/s the wire delivers 12.50 gigabytes per second. A capture stores the frame plus a per-record header — sixteen octets in the usual formats — so stored bytes track wire bytes closely: 80 stored against 84 on the wire at minimum size (95.2%), 1 534 against 1 538 at maximum (99.7%). Within five per cent, storing a 100 Gb/s link costs 12.5 GB/s of write bandwidth.
And nothing ordinary provides it.
| Sink | GB/s | Fraction of line rate | 8 GB buffer lasts |
|---|---|---|---|
| a 1 Gb/s management link, exported live | 0.125 | 1.0% | 646 ms |
| a 10 Gb/s export link, 80% sustained | 1.00 | 8.0% | 696 ms |
| a sustained NVMe write | 3.00 | 24.0% | 842 ms |
| a striped NVMe array | 6.00 | 48.0% | 1 231 ms |
| host DRAM, no persistence | > 12.5 | > 100% | never — but the buffer is the capture |
Row five is the honest one and it is why ring-buffer captures into RAM are the normal practice. DRAM keeps up, so nothing is dropped while it is being written — and the capture's length is then simply the buffer divided by 12.5 GB/s, which Section 12 shows is 640 milliseconds for eight gigabytes.
Rows one to four are what happens when a capture is asked to run longer than that.
| Over a 1-second capture, 8 GB buffer | Stored | Dropped | Self-drop |
|---|---|---|---|
| 3.00 GB/s sink | 11.0 GB | 1.50 GB | 12.0% |
| 1.00 GB/s sink | 9.00 GB | 3.50 GB | 28.0% |
| 0.125 GB/s sink | 8.13 GB | 4.37 GB | 35.0% |
Twelve to thirty-five per cent, and none of it appears in the capture file as a loss.
A one-second capture taken to find out why frames are being dropped drops between one frame in eight and one in three, itself, and reports them as missing from the link.
That is the chapter's most important practical result, and it has a clean statement as a rule.
| Rule | Consequence |
|---|---|
| a capture is trustworthy only while its buffer is not full | so its trustworthy length is buffer ÷ 12.5 GB/s |
| beyond that length the loss rate is (12.5 − sink) ÷ 12.5 | and it is invisible in the frames |
| a filter moves the arrival rate, not the line rate | so Section 12's filter is the only way to run long |
Row three is the escape and it is the subject of Section 12. The 12.5 GB/s figure is the rate of frames the apparatus is asked to store; a filter applied before the store reduces it. A filter applied after — in the analysis tool, on the file — does not, and the distinction between a hardware or kernel-level filter and a display filter is therefore the difference between a capture that runs for hours and one that runs for six hundred milliseconds.
One more comparison closes the section, because it is Chapter 19.5's section read backwards.
| Chapter 19.5's receive FIFO | the capture sink | |
|---|---|---|
| arrival | ≤ line rate | = line rate |
| drain | > line rate | < line rate, by 4× to 100× |
| what the depth absorbs | a burst | a fraction of the whole capture |
| overflow behaviour | drop, and count it in a visible counter | drop, and usually count it nowhere |
| who sees the loss | Chapter 19.7's counters | nobody |
Row four is the design lesson. Chapter 19.5's FIFO drops and reports; that report is Chapter 21.1's site 9 and the whole reason a receive overrun is diagnosable at all. A capture apparatus that drops without reporting has committed the design error that chapter spent a section preventing — and it is the commonest configuration in the field.
11. RTL 5 — The Self-Drop Detector
// ---------------------------------------------------------------------
// capture_self_drop_detector -- make the apparatus's own loss visible
// in the apparatus's own output.
//
// Section 10's rule: a capture that drops without reporting has
// committed the error Chapter 19.5 spent a section preventing. This
// block is the minimum that prevents it, and its three signals are
// three different ways of knowing.
// ---------------------------------------------------------------------
module capture_self_drop_detector
import capdiag_pkg::*;
(
input logic clk,
input logic rst_n,
// 1. the apparatus knows, if it is asked
input logic sink_overflow,
// 2. an independent frame counter at the capture point
input logic point_frame_seen,
input logic record_written,
// 3. the link's own counters, read out of band
input logic [31:0] port_rx_frames,
input logic port_counter_valid,
output logic [31:0] c_seen,
output logic [31:0] c_written,
output logic [31:0] c_self_dropped,
output logic self_drop_active,
output logic apparatus_below_port, // Section 10's signature
output logic [15:0] coverage_pct,
output logic coverage_unknown // NO counter available
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
c_seen <= '0; c_written <= '0; c_self_dropped <= '0;
self_drop_active <= 1'b0;
end else begin
if (point_frame_seen) c_seen <= c_seen + 32'd1;
if (record_written) c_written <= c_written + 32'd1;
if (point_frame_seen && !record_written)
c_self_dropped <= c_self_dropped + 32'd1;
self_drop_active <= sink_overflow;
end
end
always_comb begin
// The port's own counter is the only INDEPENDENT reference. Without
// it the apparatus is the sole witness to its own completeness,
// which is Section 20's rejected property in one sentence.
apparatus_below_port = port_counter_valid &&
(c_written < port_rx_frames);
coverage_unknown = !port_counter_valid;
coverage_pct = (port_counter_valid && port_rx_frames != 0)
? 16'((c_written * 100) / port_rx_frames)
: 16'd0;
end
endmoduleClassification: a completeness monitor for an instrument, and its most important output is the one that admits it cannot tell.
What it teaches: that there are three independent ways to know a capture is incomplete, and they are not equally trustworthy. sink_overflow is the apparatus reporting on itself. point_frame_seen && !record_written is a counter inside the same apparatus, one stage earlier. port_rx_frames is the switch's own counter — Chapter 19.7's block, read over the management interface — and it is the only reference not made of the thing being checked.
And it teaches that coverage_unknown is the honest default. Most captures are taken with no port counter read at the same instants, so most captures have no evidence of their own completeness at all — and the analysis proceeds as though they do. The block makes that state explicit rather than reporting 100% by omission.
Deliberately simplified: apparatus_below_port compares a local frame count with a remote counter with no common time base, so the comparison is only valid when both are sampled at the same moment — which needs Chapter 16.2's machinery or a stop-the-world read. coverage_pct is integer per cent, which cannot express the 12.0% self-drop of Section 10 to useful precision at the low end. And point_frame_seen assumes a stage that sees every frame, which for a span port is exactly the assumption in doubt: the mirror may have dropped before this point.
Production implication: the missing common time base is why this comparison is usually done wrong. A capture count taken at the end of the run compared against a port counter read a minute later includes a minute of traffic the capture never saw, and reports a self-drop rate that is entirely the minute. Read the port counter immediately before starting and immediately after stopping, and compare the difference — the procedure is Chapter 19.7 §16's snapshot discipline applied to an instrument instead of to a design, and it converts a meaningless number into the only completeness evidence a capture ever has.
12. Frames per Fault, and the Filter That Changes Everything
Chapter 21.1 §12 priced a capture at three hours. This section prices it in frames, which is the unit that decides whether the fault is in the file.
Start with how much wire time a store covers.
| Store | Min-size frames | Max-size frames | Wire time |
|---|---|---|---|
| 1 GB | 1.25 × 10⁷ | 6.52 × 10⁵ | 80–84 ms |
| 8 GB | 1.00 × 10⁸ | 5.21 × 10⁶ | 642–672 ms |
| 64 GB | 8.00 × 10⁸ | 4.17 × 10⁷ | 5.1–5.4 s |
| 1 TB | 1.25 × 10¹⁰ | 6.52 × 10⁸ | 80–84 s |
A terabyte holds about eighty seconds of a 100 Gb/s link. That single line reframes the instrument: the largest capture most people can take is a minute and a half, and the fault has to happen inside it.
Which brings the second table, the one that decides everything.
| Fault rate | Frames needed to see one | Store | Wire time |
|---|---|---|---|
| 1 in 10³ | 10³ | 80 kB | 6.7 µs |
| 1 in 10⁶ | 10⁶ | 80 MB | 6.7 ms |
| 1 in 10⁹ | 10⁹ | 80 GB | 6.7 s |
| 1 in 10¹² | 10¹² | 80 TB | 1 h 52 m |
Row three is the boundary of the practical. A one-in-a-billion fault — which is the rate a 10⁻¹² bit error rate produces on minimum-size frames, and Chapter 21.3 §6's central case — needs eighty gigabytes to catch one instance. Row four, a rate an in-spec optical link comfortably achieves, needs eighty terabytes, which nobody captures.
Unfiltered, a capture is an instrument for faults above about one in ten million. Below that it is an expensive way to record correct traffic.
Now the filter, which changes the exponent rather than the constant.
A filter applied before the store — in the capture NIC, in the kernel's BPF, in a hardware tap's own matching engine — reduces the arrival rate at the sink by exactly the selectivity. A fault occurring at rate p whose frames a filter can name arrives at 12.5 GB/s × p instead of 12.5 GB/s, so a fixed store covers 1/p times more wire time.
| At a 1-in-10⁶ fault rate, a 1 GB store covers | Wire time |
|---|---|
| unfiltered | 84 ms |
| filtered on the fault | 84 000 s — 23.3 hours |
From eighty-four milliseconds to twenty-three hours, for the same gigabyte. That is the single largest lever in the entire chapter and it costs nothing but a filter expression.
And it has a precondition that is usually the hard part.
| The filter must | Which means |
|---|---|
| name the faulty frames | you already know something about them |
| run before the store | in hardware or in the kernel, not in the viewer |
| not itself drop | a filter that cannot keep up drops what it was meant to keep |
Row one is circular in a way worth stating plainly. A filter that isolates the fault requires a hypothesis about the fault, and Chapter 21.1's procedure exists precisely because you do not have one yet. So the capture is the last step of the procedure and not the first — which is exactly where all eight chapters put it, and this section is the arithmetic behind that placement rather than a new recommendation.
Row three is the trap. A software filter that drops under load drops the matching frames as readily as the non-matching ones, so a filtered capture that is 99.999% selective but overruns still loses the very frames the filter selected. Chapter 19.5 §9's lesson — a queue that overflows loses the head as well as the tail — applies to the filter's output queue exactly as it applies to a MAC's.
13. RTL 6 — The Trigger and the Pre-Trigger Buffer
// ---------------------------------------------------------------------
// capture_trigger_ring -- Section 12's arithmetic says a store holds
// under a second of wire time. The only way to spend it on the right
// second is to keep writing and stop on the fault.
//
// The pre-trigger fraction is the whole design decision: a fault's
// CAUSE is before it and its CONSEQUENCES are after it, and Module 21
// has examples of both mattering.
// ---------------------------------------------------------------------
module capture_trigger_ring
import capdiag_pkg::*;
#(
parameter int DEPTH_FRAMES = 1024,
parameter int PRE_PCT = 75 // 75% before the trigger
)(
input logic clk,
input logic rst_n,
input logic f_valid,
input logic [15:0] f_len,
// trigger sources, in the order Chapter 21.2's taxonomy ranks them
input logic trig_fcs_bad,
input logic trig_undersize,
input logic trig_oversize,
input logic trig_external, // a host-side symptom
input logic arm,
output logic armed,
output logic triggered,
output logic capture_complete,
output logic [31:0] pre_frames_held,
output logic [31:0] post_frames_taken,
output logic trigger_was_first_of_a_burst,
output logic [31:0] c_triggers_seen,
output logic trigger_lost_context // ring not yet full at trigger
);
localparam int PRE_TARGET = (DEPTH_FRAMES * PRE_PCT) / 100;
localparam int POST_TARGET = DEPTH_FRAMES - PRE_TARGET;
logic [31:0] ring_fill;
logic any_trig;
assign any_trig = trig_fcs_bad | trig_undersize | trig_oversize
| trig_external;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
armed <= 1'b0; triggered <= 1'b0; capture_complete <= 1'b0;
ring_fill <= '0; post_frames_taken <= '0; c_triggers_seen <= '0;
trigger_was_first_of_a_burst <= 1'b0; trigger_lost_context <= 1'b0;
end else begin
if (arm) begin
armed <= 1'b1; triggered <= 1'b0; capture_complete <= 1'b0;
ring_fill <= '0; post_frames_taken <= '0;
end
if (armed && !triggered && f_valid && ring_fill < DEPTH_FRAMES)
ring_fill <= ring_fill + 32'd1;
if (armed && !triggered && any_trig) begin
triggered <= 1'b1;
c_triggers_seen <= c_triggers_seen + 32'd1;
// Chapter 21.3 Section 18: a hundred failing frames report the
// same bit a hundred times. The FIRST is the one with context.
trigger_was_first_of_a_burst <= 1'b1;
// If the ring never filled, the pre-trigger window is short and
// the cause may simply not be in it.
trigger_lost_context <= (ring_fill < PRE_TARGET);
end
if (triggered && !capture_complete && f_valid) begin
post_frames_taken <= post_frames_taken + 32'd1;
if (post_frames_taken + 1 >= POST_TARGET) begin
capture_complete <= 1'b1;
armed <= 1'b0;
end
end
end
end
assign pre_frames_held = (ring_fill > PRE_TARGET) ? PRE_TARGET : ring_fill;
endmoduleClassification: a ring with a stop condition, which is the only structure that makes a sub-second store useful on a fault that happens once an hour.
What it teaches: that PRE_PCT is the design's real content and 75% is a claim about where the evidence is. Chapter 21.2's error classes are symptoms; their causes are frames or conditions that came before — a preceding oversized frame, a burst that filled a queue, a partner's transmission that collided. So most of the ring belongs to the past. The 25% after the trigger exists for the one case where the consequence matters: Chapter 21.7's corruption class, where the question is what the next frames looked like.
And it teaches why trigger_lost_context is the module's most useful output. A trigger that fires before the ring has filled produces a capture with a truncated pre-trigger window, and that capture is silently less informative than one taken later. The output says so, which is Chapter 21.6 §12's principle — an instrument should report the conditions under which its own answer is weak — applied to a trigger.
Deliberately simplified: the trigger sources are four booleans where a real trigger is an expression over frame contents, and the interesting triggers are usually combinations: a bad check sequence on a particular flow, or an undersized frame within a microsecond of an oversized one. POST_TARGET frames rather than post-trigger time, which for a bursty link makes the post-trigger window's duration vary by a factor of a thousand. And trigger_was_first_of_a_burst is set unconditionally on the first trigger rather than actually distinguishing an isolated fault from the head of a burst — which needs a quiet-period test the module does not have.
Production implication: the frames-not-time post-trigger window is a real and common bug in capture tooling. A post-trigger window of 256 frames on an idle link covers whatever interval those 256 frames take — possibly seconds; on a loaded 100 Gb/s link it covers 1.7 microseconds. The person who configured it was thinking about how much evidence, and what they got was a duration that varies with load — so the same configuration captures the aftermath on a quiet link and misses it entirely on the busy one where the fault actually occurs. Configure post-trigger in time, and derive the frame count from the worst-case rate.
14. What a Capture Must Never Do
Section 1 claimed the capture is inside the system it measures. This section is the evidence, and it is stronger than the claim.
Three capture arrangements, three perturbations.
| Arrangement | What it adds to the system |
|---|---|
| a passive optical tap | optical loss on the path it is observing |
| a mirrored (span) port | a full copy of the traffic, onto a real fabric |
| capture on the host under test | CPU, memory bandwidth and interrupts, taken from the subject |
Row one is the sharpest because the numbers are unforgiving.
| dB | |
|---|---|
| a 50/50 optical tap, through path | ≈ 3.0 plus excess |
| a 70/30 tap, through path | ≈ 1.5 plus excess |
| the channel insertion-loss allowance, 100GBASE-SR4 over 100 m OM4 | under 2 |
A fifty-fifty tap consumes more than the entire link budget. Inserting it into an in-spec link makes the link out of spec, so the receiver's margin collapses and errors appear — and they appear in the capture, which is what the capture was installed to look for. Chapter 21.2 §7's channel class is manufactured by the instrument, and the instrument's output is entirely consistent with the channel having been marginal all along.
A tap that is not in the link budget does not observe the errors. It creates them, records them, and cannot tell you which it did.
That is Section 20's rejected property, stated in optics rather than in SystemVerilog.
Row two's perturbation is quantitative in a different way.
| Mirror | Copy traffic | Destination | Mirror-side loss |
|---|---|---|---|
| one 100 Gb/s port | 100 Gb/s | a 10 Gb/s analyser port | 90% |
| one 100 Gb/s port | 100 Gb/s | a 100 Gb/s analyser port | 0% at average, > 0 on any burst |
| eight 25 Gb/s ports | 200 Gb/s | a 100 Gb/s analyser port | 50% |
| one direction of one port | ≤ 100 Gb/s | a 100 Gb/s port | 0% |
Row one is the commonest arrangement in the field and it loses nine frames in ten. The loss is not random: the mirror destination's queue drops tail, so it discards bursts preferentially — exactly the traffic pattern Section 8 showed is the reason to take a capture in the first place. The instrument is biased against the phenomenon.
Row two deserves its own sentence. A 100 Gb/s mirror of a 100 Gb/s port is exactly full, with no headroom, so any transient — including the microburst of Section 8 — overflows it. A mirror sized at line rate is not a mirror with margin; it is a mirror with zero margin, and Chapter 19.5 §4's whole argument is that a queue with no rate headroom does not drain.
Row three is worse and is what people configure when they want to see a whole rack.
Row four is the only arrangement that is safe, and it costs you the other direction.
And row three of the first table — capturing on the host under test — is the one that ruins Chapter 21.7 and Chapter 21.8 investigations. A capture process on the host consumes memory bandwidth, which is exactly the resource Chapter 21.7 §14's descriptor stalls contend for, and it takes interrupt-handling time from the driver whose completion latency Chapter 21.8 §11's Little's-law bound depends on. Capturing a throughput problem on the machine with the throughput problem changes the number you are measuring, in the direction that confirms your hypothesis.
So the rules are three.
| Never | Because |
|---|---|
| insert a tap without re-checking the link budget | the tap's loss is in it |
| mirror more traffic than the destination can carry | the loss is biased toward bursts |
| capture on the host whose performance is in question | the apparatus competes for the contended resource |
15. RTL 7 — Capture Telemetry
// ---------------------------------------------------------------------
// capdiag_telemetry -- everything the apparatus knows about itself,
// in one readable block, including the fields that say its answer is
// weak.
//
// Chapter 19.7 Section 16's snapshot discipline applies: a set of
// counters read one at a time across a bus is not a consistent set,
// and for a self-completeness check an inconsistent set is worse than
// none, because the inconsistency looks like loss.
// ---------------------------------------------------------------------
module capdiag_telemetry
import capdiag_pkg::*;
(
input logic clk,
input logic rst_n,
input logic snapshot,
input logic [31:0] i_frames_seen,
input logic [31:0] i_records_written,
input logic [31:0] i_self_dropped,
input logic [31:0] i_matched,
input logic [31:0] i_join_lost,
input logic [31:0] i_join_ambiguous,
input logic [31:0] i_out_of_order,
input logic [31:0] i_duplicate,
input logic [31:0] i_burst_peak_bytes,
input logic [31:0] i_sink_occupancy_mb,
input logic [15:0] i_self_drop_pct_x10,
input logic i_coverage_unknown,
input logic i_seq_field_absent,
input logic i_single_point_only,
input logic i_trailing_bits_absent,
input logic i_apparatus_perturbs,
output logic [31:0] o_frames_seen,
output logic [31:0] o_records_written,
output logic [31:0] o_self_dropped,
output logic [31:0] o_matched,
output logic [31:0] o_join_lost,
output logic [31:0] o_join_ambiguous,
output logic [31:0] o_out_of_order,
output logic [31:0] o_duplicate,
output logic [31:0] o_burst_peak_bytes,
output logic [31:0] o_sink_occupancy_mb,
output logic [15:0] o_self_drop_pct_x10,
output logic [4:0] o_caveats,
output logic o_result_is_interpretable
);
// The five caveats, in the order they invalidate a conclusion.
localparam int CAV_COVERAGE = 0; // no independent completeness check
localparam int CAV_SELFDROP = 1; // the apparatus dropped
localparam int CAV_AMBIGUOUS = 2; // the join guessed
localparam int CAV_NO_SEQ = 3; // ordering unanswerable
localparam int CAV_PERTURBS = 4; // the tap or mirror is in the system
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
o_frames_seen <= '0; o_records_written <= '0; o_self_dropped <= '0;
o_matched <= '0; o_join_lost <= '0; o_join_ambiguous <= '0;
o_out_of_order <= '0; o_duplicate <= '0; o_burst_peak_bytes <= '0;
o_sink_occupancy_mb <= '0; o_self_drop_pct_x10 <= '0;
o_caveats <= '0;
end else if (snapshot) begin
o_frames_seen <= i_frames_seen;
o_records_written <= i_records_written;
o_self_dropped <= i_self_dropped;
o_matched <= i_matched;
o_join_lost <= i_join_lost;
o_join_ambiguous <= i_join_ambiguous;
o_out_of_order <= i_out_of_order;
o_duplicate <= i_duplicate;
o_burst_peak_bytes <= i_burst_peak_bytes;
o_sink_occupancy_mb <= i_sink_occupancy_mb;
o_self_drop_pct_x10 <= i_self_drop_pct_x10;
o_caveats[CAV_COVERAGE] <= i_coverage_unknown;
o_caveats[CAV_SELFDROP] <= (i_self_dropped != 32'd0);
o_caveats[CAV_AMBIGUOUS] <= (i_join_ambiguous != 32'd0);
o_caveats[CAV_NO_SEQ] <= i_seq_field_absent;
o_caveats[CAV_PERTURBS] <= i_apparatus_perturbs;
end
end
// A capture is interpretable as EVIDENCE OF LOSS only if the
// apparatus did not lose. Everything else is a qualification; this
// one is disqualifying.
assign o_result_is_interpretable = !o_caveats[CAV_SELFDROP] &&
!o_caveats[CAV_COVERAGE];
endmoduleClassification: a snapshot register file whose last output is a verdict on whether the rest of it means anything.
What it teaches: that o_result_is_interpretable is deliberately asymmetric across the five caveats. Three of them — ambiguity, a missing sequence field, a perturbing apparatus — qualify a conclusion: they narrow what can be said. Two of them — the apparatus dropped, and there is no independent completeness check — destroy one specific conclusion entirely, namely the link lost frames. That is the only conclusion Module 21's hardest chapter needed, and it is the one the apparatus can fake.
And it teaches the snapshot discipline's second life. Chapter 19.7 §16 made a snapshot bank so that 27 counters read across a slow bus would be mutually consistent; here the same bank makes i_frames_seen and i_records_written consistent, and without it their difference — which is the self-drop count — contains the bus read latency as a term.
Deliberately simplified: no read-clear, so a long capture needs external differencing. o_caveats is five bits where the useful artefact is five bits plus the numbers that justify them — a report saying the apparatus dropped is much less useful than one saying the apparatus dropped 12.0%. And i_single_point_only and i_trailing_bits_absent are accepted and never used, which is the honest shape of a first implementation: they belong in the caveat set and there are only five bits.
Production implication: the two unused inputs are the two caveats most often missing from real capture reports, and both change conclusions. A single-point capture cannot support any statement about delivery — Section 6's table — yet single-point captures are routinely used to argue that a path is clean. A capture with no trailing-bit count cannot distinguish Chapter 21.2's alignment class from its check-sequence class, yet the analysis tool will confidently label every one of them a check-sequence error. Both caveats belong in the output, and widening o_caveats to seven bits is one line.
16. RTL 8 — The Capture Conformance Monitor
// ---------------------------------------------------------------------
// capdiag_conformance_monitor -- properties about the APPARATUS, which
// is the only subject in this chapter whose behaviour we control.
//
// Section 20 explains why the tempting property -- every frame on the
// wire appears in the capture -- is the one to refuse. These are what
// remains after refusing it, and every one of them is a statement
// about the instrument rather than about the link.
// ---------------------------------------------------------------------
module capdiag_conformance_monitor
import capdiag_pkg::*;
(
input logic clk,
input logic rst_n,
input logic frame_at_point,
input logic record_written,
input logic sink_overflow,
input logic buffer_full,
input logic [31:0] occupancy_mb,
input logic [31:0] c_seen,
input logic [31:0] c_written,
input logic [31:0] c_self_dropped,
input logic [31:0] c_matched,
input logic [31:0] c_ambiguous,
input logic coverage_unknown,
input logic result_is_interpretable,
input logic triggered,
input logic capture_complete,
input logic armed
);
// 1. Conservation INSIDE the apparatus. Seen equals written plus
// self-dropped -- a statement about the instrument's own
// bookkeeping, which it fully controls.
p_apparatus_conservation: assert property (@(posedge clk) disable iff (!rst_n)
c_seen == c_written + c_self_dropped);
// 2. A record is written only for a frame the point saw. The
// converse is NOT asserted: that is the refused property.
p_no_invented_records: assert property (@(posedge clk) disable iff (!rst_n)
record_written |-> $past(frame_at_point) || frame_at_point);
// 3. Overflow implies the buffer was full. The apparatus may not
// drop for any other reason without saying so.
p_drop_only_on_full: assert property (@(posedge clk) disable iff (!rst_n)
sink_overflow |-> buffer_full);
// 4. Occupancy never exceeds the buffer.
p_occupancy_bounded: assert property (@(posedge clk) disable iff (!rst_n)
occupancy_mb <= BUFFER_MBYTES);
// 5. The interpretability verdict is monotone: once the apparatus
// has dropped, no later cycle may claim the result is clean.
p_interpretable_is_sticky: assert property (@(posedge clk) disable iff (!rst_n)
(c_self_dropped != 0) |-> !result_is_interpretable);
// 6. Coverage unknown is also disqualifying.
p_unknown_coverage_disqualifies: assert property (@(posedge clk) disable iff (!rst_n)
coverage_unknown |-> !result_is_interpretable);
// 7. Matches never exceed frames written at the near point.
p_matches_bounded: assert property (@(posedge clk) disable iff (!rst_n)
c_matched <= c_written);
// 8. Ambiguous matches are a SUBSET of matches, not a separate class.
p_ambiguous_subset: assert property (@(posedge clk) disable iff (!rst_n)
c_ambiguous <= c_matched);
// 9. Completion follows a trigger, never precedes it.
p_complete_after_trigger: assert property (@(posedge clk) disable iff (!rst_n)
capture_complete |-> $past(triggered));
// 10. Arming clears the previous verdict.
p_arm_clears: assert property (@(posedge clk) disable iff (!rst_n)
armed && $rose(armed) |-> !capture_complete);
endmoduleClassification: ten properties whose subject is uniformly the instrument, because the instrument is the only thing here with a specification.
What it teaches: that property 2's missing converse is the chapter. record_written |-> frame_at_point says the apparatus invents nothing; the converse, frame_at_point |-> record_written, says it loses nothing — and Section 10 proved it loses between 12% and 35%. Asserting the converse does not make the apparatus complete; it makes the property fail, or worse, pass vacuously because frame_at_point is itself generated by the apparatus. Section 20 is that argument at full length.
And it teaches that properties 5 and 6 are the ones with teeth. They are monotonicity constraints on a verdict: once the apparatus has dropped, nothing may later report the capture as clean. In a system where an operator reads a summary rather than a counter, that monotonicity is the whole protection, and it is the same shape as Chapter 21.6 §16's sticky evidence-quality flag.
Deliberately simplified: property 1 is a conservation law across counters that in a real apparatus are committed at different stages, which is exactly Chapter 19.7 §19's rejected class 87 — it holds here only because the model commits all three in one cycle. Property 2's $past(frame_at_point) || frame_at_point is a two-cycle window chosen to fit the model's timing rather than derived from a pipeline depth. And property 9 uses $past(triggered) where the ring's completion is several frames later, so it is weaker than it looks.
Production implication: the ten properties here are worth having in a capture appliance's own verification, and every one of them is checkable without any reference to the link at all — which is what makes them the right properties. An appliance vendor can prove all ten in a testbench with a synthetic frame source, and a customer who asks does your appliance drop, and does it say so is asking about properties 3, 5 and 6. The question nobody can answer with a property is the one everybody asks — did the capture contain every frame — and Section 20 explains why that is not a shortcoming of the properties.
17. What a Capture Point Cannot See
Section 4 counted 34 of 57 faults out of reach. This section says why, and the reasons fall into four groups rather than thirty-four.
| Group | Faults | Reason |
|---|---|---|
| there are no frames | 14 | Chapter 21.4's entire chapter |
| the evidence is below the frame | 6 | symbols, codewords and PCS state are not frames |
| the evidence is in memory, not on the wire | 8 | Chapter 21.7's host side |
| there is no event at all | 6 | Chapter 21.8's ceilings |
Group one is Chapter 21.4's dark class and it is the largest. Fourteen sites, and on every one of them the link is down, negotiating, or flapping — so the capture file is empty or nearly so, and an empty file is consistent with all fourteen. Chapter 21.4 §5 showed that MDIO separates some of those sites and that the rest need a cooperating partner; a capture separates none of them. It is the one chapter of Module 21 where the instrument contributes exactly zero.
Group two is the most interesting and the least appreciated.
| Below the frame | Seen by | Seen by a capture? |
|---|---|---|
| symbol errors in the interframe gap | the PCS | no |
| FEC symbol corrections per codeword | the FEC decoder | no |
| block-lock and alignment-marker state | the PCS | no |
| the actual interframe gap in octets | the MAC | no — only a timestamp difference |
| lane skew | the PMA | no |
Row two is the one that changes how you should think about a clean capture. Chapter 3.7 §4's KR4 code corrects up to seven 10-bit symbol errors per 528-symbol codeword, and KP4 corrects fifteen. A link correcting six symbols per codeword delivers frames that are bit-for-bit perfect, so a capture of a million frames shows a million perfect frames — and the link is one symbol from the cliff.
FEC's job is to make the wire look perfect. A capture looks at the wire after FEC has done its job, and therefore cannot see how hard the job was.
Chapter 3.7 §15 named this exactly — read the slope, not the cliff — and the instrument that reads the slope is the FEC decoder's pre-correction counters, which Chapter 3.7 §10 built. A capture is the wrong instrument, and it is wrong in the most dangerous direction: it returns a clean result from a link about to fail.
Group three is Chapter 21.7's host side.
| In memory | Capture? |
|---|---|
| the descriptor's ownership bit | no |
| the order in which a descriptor and its payload became visible | no |
| the ring's producer and consumer indices | no |
| whether a completion was posted before its data landed | no |
Every one of those is a write to host DRAM and no wire carries it. Chapter 21.7 §8's three ordering failures are all in this table, which is why Section 4 scored that chapter zero-outright — and why the capture's role there is as the reference half of Section 5's two-point comparison rather than as the instrument itself.
Group four is Chapter 21.8's and it is the strangest of the four. A link running at 65.5 Gb/s against a 100 Gb/s line rate is producing entirely correct frames, correctly spaced, with no errors — so the capture is a perfect record of a system working as designed, and the fault is that the design's ceiling is lower than the line rate. There is nothing in the frames to find. Chapter 21.8 §14's link with no errors that is slow is a capture's null case: the instrument works, returns a complete and accurate answer, and the answer contains no information about the problem.
One row of group four is an exception, and it is worth having. The spacing relationship of Section 8 — visible only in a capture — does distinguish a source that is not offering load from a path that is limiting it. Chapter 21.8 §13's offered-load question is answerable from gaps, at rates where the gap exceeds the timestamp resolution, which Section 3 showed means 1 Gb/s and not 100 Gb/s. That is the one ceiling of the four a capture reaches, and it is the partial in Section 4's table.
18. What the Capture Assumes
Every instrument has premises. A capture's are unusually numerous and unusually unstated.
| Assumption | If it is false |
|---|---|
| the apparatus recorded every frame it saw | Section 10 — between 12% and 35% did not |
| the point saw every frame on the link | a mirror may drop before the point; a tap may be on one fibre |
| the timestamp is close to the arrival | queueing inside the apparatus displaces it |
| timestamps from two points are comparable | Section 6 — two free-running clocks drift past the signal |
| the frames are as the wire carried them | a span port shows them after the switch's parser |
| the analysis tool's classification is the design's | Chapter 21.2's classes are not the tool's |
| the capture is representative of the link's behaviour | it is 84 ms of a week |
Row seven is the assumption that does the most damage and it is almost never examined. Section 12's arithmetic: a one-gigabyte capture is 84 milliseconds of a 100 Gb/s link. A week of operation is 604 800 seconds. So the capture covers 1.4 × 10⁻⁷ of the interval the problem was reported over — one part in seven million — and it is then used to argue that the link is fine.
"I took a capture and it looked clean" is a statement about 84 milliseconds. The complaint was about a week.
Row six is the one that produces confident wrong answers. Chapter 21.2 §3 established that the error classes are defined by the receiving MAC's rules, including a tie-break; an analysis tool recomputes its own classification from the octets, with its own rules, and the two need not agree. A frame the design counted as an alignment error and the tool counts as a check-sequence error is the same frame, classified twice, by two authorities — and only one of them is the one whose counters the operator is trying to explain.
Row three deserves its number. A capture timestamp is applied where the timestamp is applied, which for a software capture is after the kernel has queued the frame, and that queue is tens of microseconds deep under load. Chapter 16.3 §4's whole argument — that a timestamp must be taken at the MAC/PHY boundary or it measures the software stack — applies to a capture apparatus identically, and a software capture's timestamps are therefore useful for ordering and useless for intervals below about a hundred microseconds. Which makes Section 8's 88.9-microsecond microburst invisible to exactly the tool people use to look for microbursts.
And one assumption is so basic it is usually not even articulated.
| The frames in the file | are the frames that were on the wire |
|---|---|
| truncated at a snap length? | commonly 64 or 128 octets — the payload is gone |
| stripped of the check sequence? | most capture paths discard it |
| stripped of the VLAN tag? | some NICs do, and the file then shows an untagged frame |
Row three is Chapter 13.2's tag going missing between the wire and the file, and it has produced more than one confident diagnosis of a misconfigured trunk where the trunk was correct and the capture NIC had stripped the tag. Row two removes the ability to recompute the check sequence at all — the single most valuable thing a capture's octets support — and it is the default on several common capture paths.
19. The Cost, Accounted
Every chapter in Modules 19 to 21 has priced its mechanism in flops against Chapter 19.7 §19's 14 166-flop datapath. This chapter's mechanism is different in kind and the accounting shows it.
| Block | Flops | Share of 14 166 |
|---|---|---|
observable_set | 32 | 0.2% |
two_point_frame_join | 6 378 | 45.0% |
frame_relationship_extractor | 388 | 2.7% |
capture_sink_model | 113 | 0.8% |
capture_self_drop_detector | 97 | 0.7% |
capture_trigger_ring | 101 | 0.7% |
capdiag_telemetry | 712 | 5.0% |
capdiag_conformance_monitor | 204 | 1.4% |
| total | 8 025 | 56.6% |
One block is 45% of the total and it is the join. Sixty-four entries of digest, timestamp and occupancy — 6 378 flops for a reorder tolerance of sixty-four frames, which at 100 Gb/s minimum size is 430 nanoseconds of tolerance. A path with real queueing needs thousands.
| Join window | Flops | Against the datapath | Tolerance at 100 Gb/s |
|---|---|---|---|
| 64 | 6 208 | 43.8% | 430 ns |
| 256 | 24 832 | 1.75× | 1.72 µs |
| 1 024 | 99 328 | 7.01× | 6.88 µs |
| 4 096 | 397 312 | 28.0× | 27.5 µs |
By a tolerance of four microseconds the join alone is seven times the entire receive datapath. That is the quantitative reason two-point analysis lives on a host and not in a MAC, and it is the same shape as Section 7's per-flow argument — both conclusions are that relationship analysis has a storage cost proportional to how much history it must hold, and a MAC holds none.
And the trigger ring's real cost is not in the table at all, because it is not flops.
| Storage | Bits | As flip-flops, against 14 166 |
|---|---|---|
| 1 024 frames at 1 518 octets | 1.24 × 10⁷ | 878× |
| 8 GB | 6.4 × 10¹⁰ | 4.5 million × |
Row two is the number that ends the argument. An eight-gigabyte capture buffer, expressed in the unit this track has priced everything else in, is four and a half million times the entire receive datapath — so no capture of any useful length is ever going to be a piece of the MAC. It is DRAM on a host, and the flop accounting above describes only the small control blocks that manage it.
Which gives the chapter's cost result in its true form.
| Instrument | Cost | Reaches |
|---|---|---|
| Chapter 19.7's 27 counters | 2 672 flops, 18.9% of the datapath, always on | the aggregate, over all time |
| a capture | 8 025 control flops plus gigabytes of DRAM, plus a maintenance window | 84 ms, and 40.4% of the faults |
Both rows are true simultaneously and that is the whole of Module 21's instrumentation argument. The counters are cheap, permanent, and blind to order, spacing and pairing. The capture is expensive, brief, and the only instrument that has them. Chapter 21.1 §12 put it sixth in the procedure; this accounting is why sixth is right, and Section 8 is why it is on the list at all.
20. Properties Worth Asserting, and One Worth Refusing
Every property in this section has the apparatus as its subject. That is not a stylistic choice — it is the only subject available, because the link has no specification the capture can check against.
Group A — the apparatus's own bookkeeping.
// A1. Seen equals written plus self-dropped. The instrument's
// conservation law, and the only one it fully controls.
p_cap_conservation: assert property (@(posedge clk) disable iff (!rst_n)
c_seen == c_written + c_self_dropped);
// A2. Records are never invented.
p_cap_no_invention: assert property (@(posedge clk) disable iff (!rst_n)
record_written |-> frame_at_point || $past(frame_at_point));
// A3. A drop happens only when the buffer is full.
p_cap_drop_needs_full: assert property (@(posedge clk) disable iff (!rst_n)
self_drop |-> buffer_full);
// A4. Occupancy never exceeds the buffer.
p_cap_occ_bounded: assert property (@(posedge clk) disable iff (!rst_n)
occupancy_mb <= BUFFER_MBYTES);
// A5. Occupancy falls only by at most the sink's per-tick capacity.
p_cap_drain_bounded: assert property (@(posedge clk) disable iff (!rst_n)
(occupancy_mb < $past(occupancy_mb)) |->
(($past(occupancy_mb) - occupancy_mb) <= SINK_MB_PER_MS));
// A6. Once full and still arriving, the apparatus MUST report a drop.
// This is the property that makes the loss visible. Note that it
// constrains the REPORT, not the completeness.
p_cap_full_reports: assert property (@(posedge clk) disable iff (!rst_n)
(buffer_full && frame_at_point && !record_written) |-> self_drop);A6 is the constructive replacement for the refused property and it is worth naming now rather than at the end. It does not say the apparatus keeps every frame. It says that when the apparatus does not keep one, it says so — which is a property of the instrument's design, is checkable, and is exactly the guarantee an operator needs.
Group B — the interpretability verdict.
// B1. Any self-drop disqualifies the "the link lost frames" conclusion.
p_cap_selfdrop_disqualifies: assert property (@(posedge clk) disable iff (!rst_n)
(c_self_dropped != 0) |-> !result_is_interpretable);
// B2. No independent completeness reference disqualifies it too.
p_cap_unknown_disqualifies: assert property (@(posedge clk) disable iff (!rst_n)
coverage_unknown |-> !result_is_interpretable);
// B3. The verdict is sticky within a capture: it never recovers.
p_cap_verdict_sticky: assert property (@(posedge clk) disable iff (!rst_n)
(!result_is_interpretable && !arm) |=> !result_is_interpretable);
// B4. Arming is the only thing that may clear it.
p_cap_verdict_cleared_only_by_arm: assert property (@(posedge clk) disable iff (!rst_n)
($rose(result_is_interpretable)) |-> $past(arm));
// B5. The caveat bits are monotone within a capture.
p_cap_caveats_monotone: assert property (@(posedge clk) disable iff (!rst_n)
!arm |=> ((o_caveats & $past(o_caveats)) == $past(o_caveats)));Group C — the two-point join.
// C1. Matches never exceed frames recorded at the near point.
p_join_matches_bounded: assert property (@(posedge clk) disable iff (!rst_n)
c_matched <= c_written);
// C2. Ambiguity is a subset of matching, not a separate outcome.
p_join_ambiguous_subset: assert property (@(posedge clk) disable iff (!rst_n)
c_ambiguous <= c_matched);
// C3. A match yields a NON-NEGATIVE transit time. A negative one means
// the two clocks are not comparable -- Section 6's premise, made
// checkable.
p_join_transit_nonneg: assert property (@(posedge clk) disable iff (!rst_n)
matched |-> (b_time_ns >= a_time_at_match));
// C4. A match consumes exactly one window entry.
p_join_consumes_one: assert property (@(posedge clk) disable iff (!rst_n)
matched |=> (window_occupancy == $past(window_occupancy) - 1));
// C5. An entry evicted while still occupied is counted as lost, once.
p_join_evict_counts: assert property (@(posedge clk) disable iff (!rst_n)
(a_valid && a_occ_at_wp) |=> (c_lost == $past(c_lost) + 1));
// C6. Lost plus matched never exceeds what point A saw.
p_join_accounting: assert property (@(posedge clk) disable iff (!rst_n)
(c_lost + c_matched) <= c_a_seen);C3 is the most useful property in the group and it is a premise check rather than a design check. A negative transit time is impossible if the clocks are related and commonplace if they are not, so the property fires exactly when Section 6's clock assumption has been violated — and it is far more likely to fire on a real deployment than any of the design properties around it.
Group D — the relationship extractor.
// D1. Ordering verdicts require the sequence field.
p_rel_order_needs_seq: assert property (@(posedge clk) disable iff (!rst_n)
out_of_order |-> f_seq_valid);
// D2. So do duplicate verdicts.
p_rel_dup_needs_seq: assert property (@(posedge clk) disable iff (!rst_n)
duplicate_seq |-> f_seq_valid);
// D3. Order and duplicate are mutually exclusive on one frame.
p_rel_order_dup_exclusive: assert property (@(posedge clk) disable iff (!rst_n)
!(out_of_order && duplicate_seq));
// D4. The burst peak is monotone within a capture.
p_rel_peak_monotone: assert property (@(posedge clk) disable iff (!rst_n)
!arm |=> (burst_peak_bytes >= $past(burst_peak_bytes)));
// D5. Back-to-back implies a gap at or below the rate's floor.
p_rel_b2b_gap: assert property (@(posedge clk) disable iff (!rst_n)
back_to_back |-> (gap_ns <= MIN_FRAME_PLUS_GAP_NS));
// D6. A gap is never negative -- the single-point clock is monotone.
p_rel_gap_nonneg: assert property (@(posedge clk) disable iff (!rst_n)
f_valid && have_last |-> (f_time_ns >= last_time));Group E — the trigger.
// E1. Completion never precedes a trigger.
p_trig_complete_after: assert property (@(posedge clk) disable iff (!rst_n)
capture_complete |-> triggered);
// E2. A trigger fires only while armed.
p_trig_needs_armed: assert property (@(posedge clk) disable iff (!rst_n)
$rose(triggered) |-> $past(armed));
// E3. Exactly one trigger per arming.
p_trig_once: assert property (@(posedge clk) disable iff (!rst_n)
triggered && !arm |=> triggered);
// E4. A short pre-trigger window is reported, not hidden.
p_trig_context_reported: assert property (@(posedge clk) disable iff (!rst_n)
($rose(triggered) && (ring_fill < PRE_TARGET)) |-> trigger_lost_context);
// E5. Post-trigger frames are bounded by the post-trigger target.
p_trig_post_bounded: assert property (@(posedge clk) disable iff (!rst_n)
post_frames_taken <= POST_TARGET);
// E6. Arming empties the ring's accounting.
p_trig_arm_resets: assert property (@(posedge clk) disable iff (!rst_n)
arm |=> (ring_fill == 0 && post_frames_taken == 0));Group F — the observable set.
// F1. Nothing outside IN_A_CAPTURE is ever claimed available.
p_obs_no_extra: assert property (@(posedge clk) disable iff (!rst_n)
(available & NOT_IN_CAPTURE) == 9'b0);
// F2. A missing trailing-bit count makes alignment undiagnosable, and
// the block must say so.
p_obs_alignment_flag: assert property (@(posedge clk) disable iff (!rst_n)
!available[OBS_TRAILING] |-> alignment_undiagnosable);
// F3. A passive tap does not perturb; anything else does.
p_obs_perturb: assert property (@(posedge clk) disable iff (!rst_n)
perturbs_the_system == (POINT != PT_TAP));
// F4. The gap is never presented as an octet count.
p_obs_gap_is_time: assert property (@(posedge clk) disable iff (!rst_n)
gap_is_a_timestamp_difference);
// F5. Frames captured only ever increases.
p_obs_count_monotone: assert property (@(posedge clk) disable iff (!rst_n)
c_frames_captured >= $past(c_frames_captured));Thirty-four properties, and every one has the instrument as its subject. Add the conformance monitor's ten and the chapter carries forty-four — and not one of them says anything about the link. That is the honest position: the capture has no specification for the link to violate, so a property about the link would be a property about the analysis tool's opinion, which is Chapter 21.2 §3's two-authority problem.
21. Verification Scenarios
Fifty-eight scenarios. The subject under test is the apparatus, and the hardest scenarios are the ones where the apparatus is wrong and looks right.
Group 1 — the sink and its buffer (12).
| # | Scenario | Expect |
|---|---|---|
| 1 | sink at 12.5 GB/s, buffer never fills | self_drop never asserts; coverage 100% |
| 2 | sink at 3 GB/s, 8 GB buffer, 500 ms capture | no drop — buffer fills at 842 ms |
| 3 | the same, 1 000 ms | 1.50 GB dropped, self_drop_pct_x10 = 120 |
| 4 | sink at 1 GB/s, 1 000 ms | 3.50 GB dropped, 28.0% |
| 5 | sink at 0.125 GB/s, 1 000 ms | 4.37 GB dropped, 35.0% |
| 6 | sink rate halves mid-capture (SLC exhaustion) | drop begins partway; the rate is not constant |
| 7 | offered load at 50% of line | buffer drains; no drop with a 6 GB/s sink |
| 8 | offered load 50%, sink 1 GB/s | still drops — 6.25 exceeds 1.00 |
| 9 | buffer full, then link goes idle | occupancy falls at the sink rate, not instantly |
| 10 | BUFFER_MB = 0 | every frame drops; p_cap_drop_needs_full still holds |
| 11 | occupancy forced above BUFFER_MBYTES | p_cap_occ_bounded fires |
| 12 | a drop injected with the buffer not full | p_cap_drop_needs_full fires |
Group 2 — the self-drop detector and interpretability (10).
| # | Scenario | Expect |
|---|---|---|
| 13 | no port counter available | coverage_unknown; result_is_interpretable low |
| 14 | port counter available, counts agree | coverage 100%; interpretable high |
| 15 | port counter read one minute after the capture stops | apparent self-drop that is entirely the minute |
| 16 | port counter snapshotted before and after | the difference is correct; coverage is real |
| 17 | one frame self-dropped, then a clean hour | verdict stays false — B3 |
| 18 | re-arm after a self-drop | verdict may clear — B4 |
| 19 | a caveat bit set, then its input deasserts | the bit stays set — B5 |
| 20 | c_seen incremented without c_written or self-drop | p_cap_conservation fires |
| 21 | a record written with no frame at the point | p_cap_no_invention fires |
| 22 | span port drops before the capture point | the detector sees nothing — the known hole |
Group 3 — the two-point join (12).
| # | Scenario | Expect |
|---|---|---|
| 23 | every frame appears at both points, in order | c_matched = frames; c_lost = 0 |
| 24 | one frame lost between the points | one eviction counted in c_lost |
| 25 | frames reordered by 8 within a window of 64 | all match; transit times vary |
| 26 | reordered by 80 within a window of 64 | evictions; false losses |
| 27 | two identical frames inside the window | c_ambiguous increments |
| 28 | a 32-bit digest collision, forced | a wrong match; transit time meaningless |
| 29 | B's clock 1 ms ahead of A's | p_join_transit_nonneg holds but the value is wrong |
| 30 | B's clock 1 ms behind A's | p_join_transit_nonneg fires — the useful case |
| 31 | a frame at B with no A record | b_without_a |
| 32 | contents altered between A and B | no digest match — reported as content_differs |
| 33 | c_matched forced above c_written | p_join_matches_bounded fires |
| 34 | c_ambiguous forced above c_matched | p_join_ambiguous_subset fires |
Group 4 — relationships (10).
| # | Scenario | Expect |
|---|---|---|
| 35 | frames with no sequence field, reordered | out_of_order never asserts — D1 |
| 36 | sequence present, one frame late | out_of_order once |
| 37 | the same sequence twice | duplicate_seq; not out_of_order — D3 |
| 38 | sequence wraps at 2^32 | a false out_of_order — the known simplification |
| 39 | back-to-back minimum frames at 100 Gb/s | back_to_back every frame; gap 6.72 ns |
| 40 | the same at 1 Gb/s with the 100 Gb/s constant | back_to_back never asserts — miscalibrated |
| 41 | an 88.9 µs burst of 1 518-octet frames | burst_peak_bytes ≈ 1.11 MB |
| 42 | the burst straddling the window boundary | two half-peaks — the jumping-window artefact |
| 43 | burst_peak_bytes forced to decrease | p_rel_peak_monotone fires |
| 44 | a timestamp that goes backwards | p_rel_gap_nonneg fires |
Group 5 — the trigger (8).
| # | Scenario | Expect |
|---|---|---|
| 45 | arm, fill the ring, trigger on a bad check sequence | 75% pre, 25% post; trigger_lost_context low |
| 46 | trigger 10 frames after arming | trigger_lost_context high |
| 47 | a burst of 100 bad frames | one trigger; trigger_was_first_of_a_burst |
| 48 | the link idles after the trigger | completion waits — the frames-not-time flaw |
| 49 | completion forced before a trigger | p_trig_complete_after fires |
| 50 | a trigger while not armed | p_trig_needs_armed fires |
| 51 | post_frames_taken forced above the target | p_trig_post_bounded fires |
| 52 | re-arm mid-capture | ring accounting resets — E6 |
Group 6 — the observable set and the perturbation (6).
| # | Scenario | Expect |
|---|---|---|
| 53 | trailing bits not recorded | alignment_undiagnosable high — F2 |
| 54 | POINT = PT_TAP | perturbs_the_system low; sees_before_phy high |
| 55 | POINT = PT_SPAN | perturbs_the_system high |
| 56 | available forced to claim OBS_SYMBOL | p_obs_no_extra fires |
| 57 | a capture path that strips the VLAN tag | the frame reads untagged — Section 18's row three |
| 58 | a snap length of 64 octets | payload absent; the check sequence unrecomputable |
Scenario 15 is the one that catches real tooling and scenario 22 is the one that cannot be caught at all.
The directed test random stimulus will not produce
Random stimulus explores frame content, frame timing and apparatus state independently. This test requires all three to be arranged against each other, in a specific order, and the point of the test is that the apparatus's output is CORRECT-LOOKING and WRONG.
The arrangement:
| Step | Action |
|---|---|
| 1 | a two-point setup, A a passive tap and B the host's receive queue, clocks disciplined |
| 2 | the sink is set to 3 GB/s and the buffer to 8 GB — Section 10's row three |
| 3 | the link runs at full 100 Gb/s line rate with every frame correct |
| 4 | the capture runs for 1 000 ms, so the apparatus drops 12.0% after 842 ms |
| 5 | during the last 158 ms, a real fault is injected: 200 frames are genuinely lost between A and B |
| 6 | the port counter is NOT read — coverage_unknown is high throughout |
Random stimulus produces steps 3 and 5 readily and will not produce step 4 aligned with step 5, because the apparatus's overflow is a function of elapsed capture time and the fault injection is a function of the stimulus generator's own rate — two independent processes, and the test needs them overlapped in a specific 158-millisecond window. A generator that randomises both will hit the overlap with probability proportional to the window, and will never hit the part that makes the test diagnostic: that the genuine loss is a rounding error against the manufactured one.
The oracle is in four parts, and no single part is sufficient.
| Part | Signal | Required value | Why this part alone is not enough |
|---|---|---|---|
| 1 — the apparatus admits the drop | c_self_dropped | ≈ 1.5 GB worth, non-zero | says nothing about the genuine 200 |
| 2 — the verdict is disqualified | result_is_interpretable | 0, from cycle 842 ms onward | could be set by coverage_unknown alone |
| 3 — the join's loss count is inflated | c_lost | far above 200 | without part 1 it looks like a link fault |
| 4 — the genuine loss is NOT separable | c_lost − apparatus drop | not computable from the capture alone | this is the finding, and it is a NEGATIVE result |
Part 4 is the assertion that makes the test worth writing, and it is an assertion about what the apparatus cannot do. The 200 genuinely lost frames are real, they are in the window, and they are indistinguishable from the roughly 18 million frames the apparatus discarded in the same interval. A ratio of about 90 000 to one. The test passes when the apparatus refuses to report a link loss figure at all, and fails when it reports one — including when it reports one that happens to be close to 200, because a right answer produced by an instrument with no basis for it is the failure this chapter exists to prevent.
Run the test a second time with the port counter snapshotted at both ends. Part 2's verdict then flips on c_self_dropped alone, part 4 becomes computable — the port counter's delta minus c_written gives the apparatus's loss, and the residual is the link's — and the same 200 frames become a reportable finding. The difference between the two runs is one management command, and it is the difference between an uninterpretable capture and a diagnosis.
22. Debugging With a Capture
A procedure, in the order the arithmetic supports rather than the order the tools encourage.
| Step | Do | Because |
|---|---|---|
| 1 | read the port counters first, and snapshot them | Chapter 19.7's 27, free, already running, and the only completeness reference |
| 2 | decide what a capture could possibly show | Section 4 — 59.6% of Module 21's faults it cannot reach |
| 3 | if the answer is "nothing", stop | Chapter 21.4's fourteen sites are the clearest case |
| 4 | choose the point, and write down what it perturbs | Section 14 — a tap eats the link budget, a mirror drops bursts |
| 5 | size the capture against Section 12's table | 1 GB is 84 ms; a 1-in-10⁹ fault needs 80 GB |
| 6 | filter before the store if you have a hypothesis | the 1/p multiplier — 84 ms becomes 23.3 hours |
| 7 | trigger if you do not | a ring plus a stop condition beats a longer file |
| 8 | capture, then read the port counters again | the delta is the only completeness evidence |
| 9 | check the apparatus's own drop counter before anything else | a non-zero value disqualifies every loss conclusion |
| 10 | analyse relationships before contents | Section 8 — relationships are what only this instrument has |
Step 1 before step 4 is the whole procedure's point. Chapter 21.1 §12 put the capture sixth; this puts the counters before the capture even gets configured, because the counters are what will decide whether the capture means anything.
Three specific readings, each of which has produced a wrong diagnosis in practice.
| Observation | Tempting reading | Check first |
|---|---|---|
| frames missing from the capture | the link dropped them | the apparatus's drop counter, and the port counter delta |
| check-sequence errors in the capture | the channel is bad | whether a tap was inserted without a budget check — Section 14 |
| a clean capture on a failing link | the fault is in software | FEC pre-correction counts — Section 17's group two |
Row three is the one that wastes the most time and the reason is in Chapter 3.7 §12. A link correcting six symbols per KR4 codeword delivers perfect frames and is one symbol from uncorrectable; the capture is clean, the frames are correct, and the link fails an hour later when the temperature rises. The instrument that would have shown it is the FEC decoder's own counter, not a bigger capture.
And one reading that is nearly always right.
| Observation | Reading |
|---|---|
| a burst peak far above the mean, with a drop counter moving | Chapter 21.6's tail drop, driven by a microburst |
That is the capture earning its cost. No counter in Chapter 19.7's 27 can show it — a sum over a second represents an 88.9-microsecond burst as 0.0089% — and the relationship extractor of Section 7 shows it from the timestamps the apparatus already recorded. One question, out of a module's worth, where this is the right instrument and nothing else is.
23. Misconceptions
Six, each in the form: the wrong model, what believing it costs, and the corrected model.
Misconception 1 — "a capture shows you everything on the wire."
Wrong model: the apparatus is a transparent window; what was on the wire is in the file.
What it costs: every conclusion about loss. Section 10's arithmetic says that at 100 Gb/s an ordinary apparatus discards 12.0% to 35.0% of a one-second capture, and nothing in the file marks them. A missing frame in the file is read as a missing frame on the link, which is Chapter 21.6's hardest question answered by the instrument's own limitation.
Corrected model: the file is what the apparatus kept, which at 100 Gb/s is what its storage path could absorb. Completeness is not a property of the file — it is a comparison between the file and the port counters, and without that comparison there is no completeness evidence at all.
Misconception 2 — "a bigger buffer fixes it."
Wrong model: the apparatus drops because the buffer is small; buy a bigger one.
What it costs: money, and the wrong instrument. A buffer only postpones: 8 GB lasts 842 ms with a 3 GB/s sink, 64 GB lasts 6.74 s, and a terabyte of DRAM would last 105 seconds. The excess is 9.5 GB/s forever, so every buffer is a countdown. And Section 12 shows a terabyte is 84 seconds of wire time anyway, so the buffer was never the limit on what a capture could see.
Corrected model: the fix is on the arrival side, not the storage side. A pre-store filter divides the arrival rate by the selectivity, and at a 1-in-10⁶ fault rate it turns 84 milliseconds into 23.3 hours on the same gigabyte. Filtering is a factor of a million; buffering is a factor of eight.
Misconception 3 — "the capture is objective; the counters can lie."
Wrong model: counters are derived quantities computed by the design under suspicion; the capture is raw evidence.
What it costs: the completeness check, and it inverts the actual reliability. Chapter 19.7's counters run continuously, cost 2 672 flops, and have seen 9.00 × 10¹³ frames over a week. A 1 GB capture holds 1.25 × 10⁷ — 7.2 million times fewer — and was taken by an apparatus with a known, unreported loss rate. The raw evidence is the smaller and less trustworthy sample.
Corrected model: the two instruments are complementary and neither dominates. Counters have the population; captures have the relationships. Section 19's table is the honest comparison, and the right procedure uses the counters to validate the capture rather than the other way round.
Misconception 4 — "put a tap in; it's passive, so it changes nothing."
Wrong model: passive means non-intrusive.
What it costs: the link's error budget and therefore the diagnosis. A 50/50 tap costs about 3 dB on the through path and the entire channel insertion-loss allowance for 100GBASE-SR4 over 100 m of OM4 is under 2 dB. So the tap takes the link out of spec, errors appear, and they appear in the capture — which is consistent with the channel having been marginal all along. Chapter 21.2's channel class, manufactured by the instrument.
Corrected model: passive describes the tap's electronics, not its effect on the optical budget. A tap is a component of the link and belongs in the link budget, exactly like a patch panel. Re-compute the budget before inserting one, and if there is no margin, use a span port and accept its bias instead.
Misconception 5 — "a capture at the receiver tells you what the sender sent."
Wrong model: one point is enough, because frames do not change in transit.
What it costs: Chapter 21.7's entire fault group, and any statement about delivery. Section 6's table: four of Module 21's nine partial faults become reachable only with a second point, and all of them are questions of the form did what arrived match what left. A single point answers what was here; it never answers what happened between here and there.
Corrected model: transit questions need two observations and a join, the join needs a key neither end can mint — Chapter 20.6 §4's problem — and the join's ambiguity rate must be published alongside its match rate, or a digest collision reports a delivery that did not happen.
Misconception 6 — "the capture was clean, so the link is healthy."
Wrong model: correct frames imply a correct physical layer.
What it costs: the warning. Section 17's group two: FEC exists to make the wire look perfect. Chapter 3.7 §4's KR4 code corrects up to seven symbol errors per codeword; a link correcting six delivers bit-perfect frames and is one symbol from the cliff. A capture of a million perfect frames is exactly what a link about to fail produces, and Chapter 3.7 §15 named the instrument that does show it.
Corrected model: a capture observes the wire after the physical layer has repaired it, so it reports the PHY's output, never its margin. The margin lives in the FEC decoder's pre-correction symbol counts, which Chapter 3.7 §10 built and which cost almost nothing to read. Read the slope, not the cliff — and a capture only ever shows the cliff.
24. Interview Questions
Six, with what a strong answer contains.
1. Why do the debugging chapters all put a packet capture last?
A strong answer gives the arithmetic rather than the folklore. A capture reaches 24.6% of Module 21's fault inventory outright and 40.4% including partials, costs a maintenance window, and covers 84 milliseconds per gigabyte at 100 Gb/s. The counters that would be consulted first are free, already running, and have a sample 7.2 million times larger. A stronger answer adds the exception: relationships between frames are the one thing no counter can ever supply, so the capture is last on the list and it is genuinely on the list.
2. A capture shows 3% of frames missing. What do you conclude?
The expected answer is the link is dropping. The correct answer is: nothing, until the apparatus's own drop counter and the port counter delta are known. At 100 Gb/s an ordinary apparatus loses 12% or more, so 3% is comfortably inside the instrument's noise floor — and a strong answer names the procedure that resolves it: snapshot the port counters immediately before and after, and compare against c_written plus c_self_dropped.
3. Why is a sum unable to detect reordering, however many sums you keep?
Because addition is commutative. Every permutation of a frame sequence produces identical totals, so the information is destroyed by the function rather than by the implementation — no counter width, resolution or count changes it. A strong answer names the alternative: a block that keeps the previous sequence value, which is state proportional to the number of flows, and prices it — 10 000 flows at about 130 flops each is 1.3 million flops, 92 times the entire receive datapath.
4. You insert an optical tap and check-sequence errors appear. What happened?
Most likely the tap. A 50/50 tap costs ≈3 dB and the 100GBASE-SR4 channel allowance over 100 m OM4 is under 2 dB, so the insertion took the link out of spec. A strong answer notes that the evidence is symmetric — the capture cannot distinguish a marginal channel from a channel the instrument made marginal — and that the resolution is to remove the tap and see whether the port's error counters return to zero, which is an experiment on the apparatus rather than on the link.
5. What single change most increases a capture's diagnostic power?
A second capture point — Section 6's table turns four unreachable questions into reachable ones. A strong answer names the cost honestly: twice the storage, a common time reference, and a join whose ambiguity must be measured — and notes the alternative answer, a pre-store filter, which multiplies covered wire time by 1/p and is cheaper, but requires a hypothesis you may not have.
6. The capture is clean and the link keeps failing. Where do you look?
Below the frame. FEC repairs the wire before anything a capture can see; a link correcting six of a possible seven symbols per KR4 codeword produces perfect frames. A strong answer names the instrument — pre-correction symbol counts from the FEC decoder — and adds the two other below-the-frame sources a capture misses: symbol errors in the interframe gap, and block-lock or alignment-marker state. The strongest answer observes that this is the failure mode where a capture is worse than no instrument, because it returns a confident clean result.
25. Questions and Answers
26. What's Next
Module 21 is complete. Nine chapters, 57 enumerated faults, and one instrument chapter that spends most of its length explaining what the instrument cannot do.
| Chapter | Its question |
|---|---|
| Chapter 21.1 | where in the receive path, and how do you narrow it |
| Chapter 21.2 | which class of error, and who decides the class |
| Chapter 21.3 | what a check sequence's one bit does and does not tell you |
| Chapter 21.4 | fourteen sites, no frames, and what MDIO separates |
| Chapter 21.5 | a mismatch priced against load, and which counters move at each end |
| Chapter 21.6 | the frames that arrived nowhere, without the optional counters |
| Chapter 21.7 | the descriptor ownership space, and the consumer stall |
| Chapter 21.8 | three ceilings below the line rate, and the lowest one |
| Chapter 21.9 | what the instrument of last resort is actually for |
The module's closing result is a division of labour. Chapter 19.7's counters hold the population — every frame, over all time, for 2 672 flops. A capture holds the relationships — order, spacing and pairing — for 84 milliseconds and a maintenance window. Neither substitutes for the other, and the commonest debugging error in the module is reaching for the second when the first has not been read.
Module 22 turns to a setting where almost every assumption in Modules 19 to 21 changes. Automotive Ethernet runs over a single twisted pair, at rates chosen for cost rather than throughput, in a system where a frame's deadline is a safety property rather than a performance target, and where the link is expected to survive fifteen years and 125 °C rather than a data centre's three-year refresh. The counters, the taxonomy, and the instruments all follow — and the first question is why a whole separate physical layer was necessary at all.
Continue learning
Related tutorials
- Related topic
A Method for Debugging Ethernet
The receive path has twelve fault sites and the RMON-required counters separate them into seven classes, one holding five — so the method's ceiling is knowable from a datasheet.
- Related topic
The Ethernet Error Taxonomy
Seven of the twelve reachable frame shapes belong to more than one error class, so exclusivity comes from a priority rule — and the two obvious rules disagree on exactly half the space.
- Related topic
CRC Errors
A channel fault's error rate is proportional to frame length and every logic fault's is flat, so the ratio is 23.72 or 1.00 — measurable on counters a MAC already computes.
- Related topic
Link Failures
A down link has no frames, so every instrument in Module 21 is unavailable at once; the replacement space has fourteen sites and MDIO separates nine classes of them.
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.
