Ethernet · Module 21
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.
A rising check-sequence error count is the best single observation in Chapter 21.1's method — 0.98 bits — and it leaves five candidate sites. This chapter separates them, and every separator is arithmetic over counters a MAC already has.
| Fault | FCS error rate | Depends on frame size? | Signature |
|---|---|---|---|
| the channel, random bits | low | yes — exactly proportional | 23.72× between 64 and 1 518 octets |
| the channel, failed FEC | low, clustered | yes | bursts in time, not in size |
| the PHY's lane alignment | high | no | alignment errors move too |
| the xMII interface | high | no | the PHY's own count disagrees |
| the parser's offsets | 100% | no | every frame, and frames_in is healthy |
| the check engine's own logic | 1.56% | no | a comb: every 64th length |
Row one is the chapter's central measurement and it is exact rather than statistical. A random bit error damages a frame with probability proportional to the frame's length in bits — so the per-frame error rate at 1 518 octets is 1 518/64 = 23.72 times the rate at 64 octets, and Chapter 19.7 §2's RMON size histogram already contains the data to measure it.
Row six is the most specific signature in Module 21. Chapter 19.4 §4's engine has 64 partial-word residue classes; a fault in one of them fails 1.56% of frames, and the failing frames are the ones whose length is congruent to a fixed value modulo 64 — 23 of the 1 455 legal sizes, evenly spaced.
A channel fault is a slope. A logic fault is a comb. Nothing else in the taxonomy is that distinguishable.
1. Scope, and Five Sites One Counter Cannot Separate
Scope: the frames that fail their check sequence — what that verdict does and does not mean, and how to tell which of five sites produced it.
Not in scope: the engine itself. Chapter 19.4 builds it and this chapter reads its output as evidence. Nor the other error classes — Chapter 21.2 is the catalogue, and Chapter 21.1 is the method this chapter is one step of.
Start from where Chapter 21.1 leaves off.
| From Chapter 21.1 | |
|---|---|
c_crc_errors is the best single question | 0.980 bits — the most even split available |
| it moves for | 5 of the 12 sites |
| four of those five are | inside the chip |
| what the method does next | c_alignment_errors, then frames_in |
Those two next observations take five candidates to one or two, and this chapter is about what to do when they do not — and about the three measurements that separate the remaining sites without any further counters at all.
The five, with what each one physically does to a frame:
| # | Site | What it does |
|---|---|---|
| 1 | the channel | flips bits in transit — Chapter 3.3 |
| 2 | the PHY's lane alignment | presents octets in the wrong order — Chapter 3.4 |
| 3 | the xMII interface | loses or duplicates octets at the boundary — Chapter 4.2 |
| 5 | the parser's offsets | computes the check over the wrong octets — Chapter 19.2 §3 |
| 6 | the check engine's logic | computes the right octets wrongly — Chapter 19.4 |
Sites 1, 2, 3 and 5 damage or misread the frame. Site 6 does neither — the frame is intact, the octets are the right octets, and the verdict is wrong. That distinction is Section 2's subject and it is what makes this chapter's rejected property the one it is.
2. What a Check Sequence Actually Guarantees
A frame check sequence is a statement about a span, not about a frame — and every misreading in this chapter comes from forgetting which span.
A matching check sequence says: these octets are the same octets that were present when the value was appended. It says nothing about whether they were correct then, and nothing about what happens to them afterwards.
Draw the span explicitly.
| Stage | Inside the protected span? |
|---|---|
| the host's memory | no |
| the transmit DMA read — Chapter 18.4 | no |
| the transmit assembler before the append | no |
| the append point — Chapter 19.3 §4 | the span begins |
| the wire | yes |
| the receiving PHY | yes |
| the xMII | yes |
| the receive parser | yes |
| the check point — Chapter 19.4 | the span ends |
| the receive FIFO | no |
| the receive DMA write | no |
| host memory | no |
Four stages are protected and eight are not. Everything before the append and everything after the check is covered by nothing — which is why Chapter 18.6 and Chapter 19.6 carry their own integrity mechanisms and why a frame can arrive in host memory corrupt with a perfectly valid check sequence.
And that gives three distinct ways the verdict and the truth diverge.
| The frame was | The check says | Where | |
|---|---|---|---|
| 1 | corrupt before the append | valid | before the span |
| 2 | intact, the checker is broken | invalid | at the span's end |
| 3 | corrupt in an undetectable pattern | valid | inside the span — Section 12 |
| 4 | corrupted after the check | valid | after the span |
Rows one and four are the reason a check sequence is not an end-to-end guarantee, and they are why Ethernet's FCS coexists with IP and TCP checksums that cover a different, overlapping span. Row two is site 6 and is this chapter's hardest separation. Row three is 2^-32 and Section 12 says how often that is.
One more property of the span, because it decides Section 10's burst analysis.
| CRC-32 detects | |
|---|---|
| any single bit error | always |
| any odd number of bit errors | always — the polynomial has x + 1 as a factor |
| any burst of 32 bits or fewer | always |
| any burst longer than 32 bits | with probability 1 − 2^-32 |
Row four is the one that matters on a modern link. Chapter 3.7's forward error correction operates on blocks of thousands of bits, and a block the FEC cannot correct is damaged across its whole length — far beyond 32 bits. So exactly the failure mode a high-rate link produces is the one the check sequence's burst guarantee does not cover, and it falls back to the 2^-32 random bound.
3. RTL 1 — The Diagnostic Package and the Size-Rate Cross
// ---------------------------------------------------------------------
// crcdiag_pkg -- the five candidate sites of Section 1, and the three
// measurements that separate them.
//
// The measurements are a SLOPE, a COMB and a BURST. None of them needs
// a counter a MAC does not already have; all three need a counter
// CROSSED with something, and that is the whole cost.
// ---------------------------------------------------------------------
package crcdiag_pkg;
typedef enum logic [2:0] {
SRC_CHANNEL_RANDOM = 3'd0,
SRC_CHANNEL_BURST = 3'd1,
SRC_PHY_LANES = 3'd2,
SRC_XMII = 3'd3,
SRC_PARSER = 3'd4,
SRC_CRC_LOGIC = 3'd5,
SRC_UNDECIDED = 3'd7
} crc_source_e;
// Chapter 19.7 Section 2's RMON histogram, which already exists and
// whose buckets are the x-axis of Section 4's measurement.
localparam int N_BUCKETS = 7;
// The representative length of each bucket, for the slope. Bucket 0
// is a single size; the rest are geometric-ish spans and the
// midpoint is close enough for a ratio test.
localparam int BUCKET_MID [N_BUCKETS] = '{64, 96, 191, 383, 767, 1271, 4000};
// Section 6: if the damage is random bits, the per-frame error rate
// is proportional to the frame's length in bits. Bucket 5 against
// bucket 0 is therefore 1271/64 = 19.86 for a channel fault and
// 1.00 for every logic fault in Section 1.
localparam int SLOPE_CHANNEL_X100 = 1986;
localparam int SLOPE_LOGIC_X100 = 100;
// Chapter 19.4 Section 4's residue classes. A fault in one of them
// is Section 8's comb.
localparam int N_RESIDUES = 64;
// Section 12: CRC-32's random escape probability.
localparam int ESCAPE_SHIFT = 32;
function automatic int ratio_x100(int num, int den);
return (den == 0) ? 0 : ((num * 100) / den);
endfunction
endpackageClassification: a package whose constants are two predicted slopes and one modulus.
What it teaches: that the whole diagnosis is a ratio test with a predicted value. A channel fault predicts 19.86 between bucket 5 and bucket 0; every logic fault predicts 1.00. Those are not thresholds somebody chose — they are the ratio of two frame lengths, and the prediction is exact in the way a physical law is exact, because the mechanism really is "each bit has an independent chance of flipping."
And it teaches why the RMON histogram is the right x-axis. Chapter 19.7 §2's seven buckets already exist in every conformant MAC — the measurement needs the error counter crossed with them and nothing else. A cross of 7 by 1 is six extra counters, about 594 flops, against a diagnosis that currently requires a capture.
Deliberately simplified: BUCKET_MID uses midpoints where the correct value is the mean length of the frames actually in each bucket, which the traffic decides — so the predicted 19.86 is accurate to the traffic's shape rather than exactly. The jumbo bucket's midpoint of 4 000 is a guess. SLOPE_CHANNEL_X100 assumes independent bit errors, which Section 10 shows is false for a FEC-protected link. And ratio_x100 truncates.
Production implication: the two predicted slopes are far enough apart that the measurement tolerates a great deal of imprecision. 19.86 against 1.00 is a factor of twenty, so a bucket-midpoint estimate that is wrong by 30% still separates the hypotheses unambiguously. That is the property that makes this diagnosis practical rather than academic: it does not need a good model of the traffic, only a model good enough to distinguish twenty from one.
// ---------------------------------------------------------------------
// size_rate_cross -- check-sequence errors, crossed with Chapter 19.7
// Section 2's size buckets.
//
// Six extra counters, and they turn the single most ambiguous counter
// in Ethernet into a measurement with a predicted value.
// ---------------------------------------------------------------------
module size_rate_cross
import crcdiag_pkg::*;
(
input logic clk,
input logic rst_n,
input logic frame_end,
input logic [2:0] size_bucket, // from Chapter 19.7's histogram
input logic fcs_mismatch,
output logic [31:0] c_frames_in_bucket [N_BUCKETS],
output logic [31:0] c_errors_in_bucket [N_BUCKETS],
output logic [31:0] rate_ppm_in_bucket [N_BUCKETS],
output logic cross_populated
);
logic [N_BUCKETS-1:0] seen;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
for (int i = 0; i < N_BUCKETS; i++) begin
c_frames_in_bucket[i] <= '0;
c_errors_in_bucket[i] <= '0;
end
seen <= '0;
end else if (frame_end) begin
c_frames_in_bucket[size_bucket] <= c_frames_in_bucket[size_bucket] + 32'd1;
seen[size_bucket] <= 1'b1;
if (fcs_mismatch)
c_errors_in_bucket[size_bucket] <= c_errors_in_bucket[size_bucket] + 32'd1;
end
end
// A rate per bucket, which is the quantity Section 4 compares. A
// COUNT per bucket says only what the traffic mix was.
always_comb
for (int i = 0; i < N_BUCKETS; i++)
rate_ppm_in_bucket[i] = (c_frames_in_bucket[i] == 32'd0) ? 32'd0
: ((c_errors_in_bucket[i] * 32'd1000000) / c_frames_in_bucket[i]);
// Section 6: at least two buckets must have traffic or there is no
// slope to measure, whatever the counts say.
assign cross_populated = ($countones(seen) >= 2);
endmoduleClassification: a two-dimensional counter, and the second dimension is one a MAC already computes.
What it teaches: that a rate per bucket is the quantity and a count per bucket is not. Minimum-size frames dominate most traffic mixes, so a raw error count is highest in bucket 0 on a channel fault and on a logic fault alike — the count follows the traffic, not the fault. The rate divides that out, and the rate is what has a predicted value.
And it teaches that cross_populated has to gate every conclusion. A link carrying one frame size has one populated bucket and no slope at all — and Chapter 20.1 §17 showed that fixed-size streams are exactly what stress tests produce. The measurement needs a mixed traffic mix, which real links have and synthetic ones often do not.
Deliberately simplified: the rates divide combinationally, seven times, which is seven 32-bit dividers. A real implementation exports the counts and divides in software. size_bucket is an input rather than derived, so the block trusts Chapter 19.7's boundaries — and Chapter 21.2 §18 showed those boundaries move with the VLAN configuration. And there is no windowing: the counts are since reset, so a fault that started an hour ago is diluted by an hour of health.
Production implication: the missing windowing is the one to fix first. A slope computed over all time answers "has this link ever had a channel fault"; a slope over the last minute answers "does it have one now." Chapter 19.7 §15's telemetry has the same shape and the same gap, and the fix is the same: a shadow copy and a subtraction, which is 224 more flops for seven buckets and turns a historical record into a measurement.
4. The Error Rate Is Proportional to Length — If It Is the Channel
A random bit error damages a frame if it lands anywhere in it. So the probability a frame of L octets fails its check is:
P(fail) = 1 − (1 − p)^(8L) ≈ 8 · L · p for small pThe approximation is excellent at every bit error rate a working link has, and it says the per-frame rate is linear in the frame's length.
| Bit error rate | 64 octets | 1 518 octets | 9 000 octets | 1 518 / 64 |
|---|---|---|---|---|
| 10⁻¹² | 5.12 × 10⁻¹⁰ | 1.21 × 10⁻⁸ | 7.20 × 10⁻⁸ | 23.72 |
| 10⁻¹⁰ | 5.12 × 10⁻⁸ | 1.21 × 10⁻⁶ | 7.20 × 10⁻⁶ | 23.72 |
| 10⁻⁸ | 5.12 × 10⁻⁶ | 1.21 × 10⁻⁴ | 7.20 × 10⁻⁴ | 23.72 |
| 10⁻⁶ | 5.12 × 10⁻⁴ | 1.21 × 10⁻² | 6.95 × 10⁻² | 23.58 |
The last column is constant across four orders of magnitude of channel quality, and it is exactly 1 518 / 64. The ratio does not depend on how bad the channel is; it depends only on the two frame lengths, which is what makes it a usable test: you do not need to know the bit error rate to run it.
Now every one of the other four sites.
| Site | What it damages | Rate depends on length? |
|---|---|---|
| the PHY's lane alignment | the octet ordering of every frame | no — a whole frame or none |
| the xMII | an octet at a boundary, per frame | no |
| the parser's offsets | the octets selected, per frame | no |
| the check engine's logic | the computation, per frame | no — but see Section 8 |
All four are per-frame mechanisms. A lane-deskew fault misorders a frame whether it is 64 octets or 9 000; a parser offset error selects the wrong octets for every frame equally. So all four predict a slope of 1.00, and the channel predicts 23.72.
| Predicted ratio, bucket 5 against bucket 0 | |
|---|---|
| the channel, random bits | 19.86 — the midpoints' ratio |
| any of the four logic faults | 1.00 |
| the gap | a factor of 20 |
A factor of twenty is not a threshold anybody has to argue about, and that is the practical value of the measurement: it survives a bad estimate of the bucket midpoints, a skewed traffic mix and a short window. Any measured slope above about 5 is the channel; anything below about 2 is logic; and the region between is where the windowing of Section 3 matters.
One caveat, and it is the one that will bite.
| Problem | |
|---|---|
| the traffic mix decides which buckets are populated | cross_populated |
| a fixed-size stress test | one bucket, no slope |
| an all-minimum-size flood | bucket 0 only |
| the fix | measure on production traffic, not on a test pattern |
A synthetic test is the worst possible input to this measurement and production traffic is the best, which inverts the usual relationship between a diagnostic and a lab — and is the reason this diagnosis belongs in a monitoring system rather than in a bring-up script.
And the slope has a second use that costs nothing extra: it estimates the bit error rate.
Once the fault is known to be the channel, the per-bucket rate inverts directly:
p ≈ rate_in_bucket / (8 × bucket_midpoint)| Measured rate in bucket 5 | Implied bit error rate | What it means |
|---|---|---|
| 1 in 10⁶ frames | 9.8 × 10⁻¹¹ | a good link |
| 1 in 10⁴ | 9.8 × 10⁻⁹ | marginal, and degrading |
| 1 in 10² | 9.8 × 10⁻⁷ | unusable for bulk transfer |
| 1 in 10 | 9.8 × 10⁻⁶ | far outside any specification |
Every Ethernet specification states a target bit error rate — 10⁻¹² for most copper and optical variants — and the frame counters are the only instrument most deployments have that can estimate it. The estimate is crude: it assumes independent bits, which Section 10 says is false on a FEC-protected link, and it ignores the errors the check sequence misses, which Section 12 says are 2^-32 of them. But it is right to within a factor of two on a link whose damage is random, and a factor of two is enough to say whether a link meets its specification.
| What the two numbers give | |
|---|---|
| the slope | is it the channel? |
| the rate, inverted | and if so, how far outside spec? |
Those two questions are the entire content of a channel-fault escalation, and neither of them needs anything the MAC does not already count.
5. RTL 2 — The Length-Slope Estimator
// ---------------------------------------------------------------------
// length_slope_estimator -- reduce Section 3's seven rates to one
// number and compare it against two predictions.
//
// The output is not a probability or a confidence. It is a ratio with
// two predicted values twenty apart, and the verdict is which one it
// is nearer to.
// ---------------------------------------------------------------------
module length_slope_estimator
import crcdiag_pkg::*;
#(
parameter int MIN_ERRORS = 32 // below this, the ratio is noise
)(
input logic clk,
input logic rst_n,
input logic [31:0] rate_ppm_in_bucket [N_BUCKETS],
input logic [31:0] c_errors_in_bucket [N_BUCKETS],
input logic cross_populated,
output logic [31:0] slope_x100,
output logic slope_valid,
output logic looks_like_channel,
output logic looks_like_logic,
output logic [2:0] lo_bucket,
output logic [2:0] hi_bucket
);
logic [31:0] total_errors;
logic enough;
// Use the LOWEST and HIGHEST populated buckets that have enough
// errors to be meaningful, rather than fixed buckets 0 and 5 --
// the traffic decides which buckets exist.
always_comb begin
lo_bucket = 3'd0;
hi_bucket = 3'd0;
total_errors = 32'd0;
for (int i = 0; i < N_BUCKETS; i++) begin
total_errors += c_errors_in_bucket[i];
if (c_errors_in_bucket[i] >= 32'(MIN_ERRORS)) begin
if (lo_bucket == 3'd0) lo_bucket = 3'(i);
hi_bucket = 3'(i);
end
end
enough = (total_errors >= 32'(MIN_ERRORS)) && cross_populated &&
(hi_bucket > lo_bucket);
slope_x100 = enough
? 32'(ratio_x100(int'(rate_ppm_in_bucket[hi_bucket]),
int'(rate_ppm_in_bucket[lo_bucket])))
: 32'd0;
slope_valid = enough;
// Section 4: the two predictions are 19.86 and 1.00. Anything
// above 5 is the channel and anything below 2 is logic; between
// them the measurement has not decided and must say so.
looks_like_channel = enough && (slope_x100 >= 32'd500);
looks_like_logic = enough && (slope_x100 <= 32'd200);
end
endmoduleClassification: a ratio and two comparisons, and the honesty is in the gap between them.
What it teaches: that looks_like_channel and looks_like_logic are not complements, and that the region between them is a real state. A slope of 3.5 has not decided; reporting it as one or the other would be a coin flip with a twenty-fold separation available, which means the right response is more traffic or a longer window, not a verdict.
And it teaches why the buckets are chosen dynamically. Fixed buckets 0 and 5 fail on any link whose traffic does not populate both — and Chapter 20.1 §17's fixed-size streams populate exactly one. Choosing the lowest and highest populated buckets makes the measurement work on whatever mix the link actually carries, at the cost of a slope whose predicted value changes with the buckets selected.
Deliberately simplified: the predicted channel slope is computed for buckets 5 and 0 and is not adjusted when other buckets are chosen — so a measurement over buckets 1 and 4 has a predicted channel value of 767/96 = 7.99, not 19.86, and the fixed threshold of 5.00 is then much closer to the prediction than it should be. lo_bucket's "first populated" test misbehaves when bucket 0 has no errors, because its initial value is already 0. And MIN_ERRORS of 32 is a judgement, where a statistician would want a confidence interval.
Production implication: the threshold-versus-prediction mismatch is the block's real defect and it is worth fixing rather than documenting. The predicted slope should be BUCKET_MID[hi] / BUCKET_MID[lo], computed from the buckets actually used, and the verdict should be whether the measurement is nearer that or nearer 1.00. That is one division more and it makes the block correct for every traffic mix instead of for the one its constants were written against — which is Chapter 20.6 §6's lesson about constants that are secretly parameters, arriving in a diagnostic.
6. Twenty-Three Point Seven, and What It Means to Measure It
The slope is the chapter's primary separator, so it is worth being precise about what a measured value means and what it does not.
What it is: the ratio of the per-frame check-failure rate at one length to the rate at another. Under random bit damage it equals the ratio of the two lengths, because each bit is an independent opportunity to fail.
| Value | |
|---|---|
| 1 518 / 64 | 23.72 |
| 9 000 / 64 | 140.63 |
| bucket 5 midpoint / bucket 0 | 19.86 |
| any per-frame logic fault | 1.00 |
What it is not: a measure of how bad the channel is. The slope is 23.72 at a bit error rate of 10⁻¹² and 23.72 at 10⁻⁸ — a factor of ten thousand in link quality with no change in the slope at all. The slope says what kind of fault; the absolute rate says how bad. Two numbers, two questions, and conflating them is Section 23's fourth misconception.
| Says | |
|---|---|
| the slope | channel or logic |
| the absolute rate in any bucket | how severe |
| the two together | the diagnosis and its urgency |
And there is a third thing the slope is not, which is a statement about a single frame. A 1 518-octet frame that failed was not damaged more than a 64-octet frame that failed; it was 23.72 times more likely to be damaged at all. The per-frame verdict carries no length information; only the population does — which is why this measurement cannot be made on a capture of a handful of frames and can be made on counters that have been running for an hour.
One genuinely useful corollary, because it inverts a common expectation.
On a channel fault, the frames that fail are disproportionately the large ones — so the bytes lost grow as the square of the length, not linearly.
| Length | Failure rate at BER 10⁻⁸ | Octets lost per frame sent |
|---|---|---|
| 64 | 5.12 × 10⁻⁶ | 3.28 × 10⁻⁴ |
| 1 518 | 1.21 × 10⁻⁴ | 0.184 |
| 9 000 | 7.20 × 10⁻⁴ | 6.48 |
Between minimum and maximum size the failure rate rises 23.7× and the octet loss rises 561×, because a longer frame is both more likely to be hit and larger when it is. A link whose measured frame-loss rate looks tolerable on minimum-size traffic can be unusable for bulk transfer, and the single number in a status page does not distinguish the two.
7. RTL 3 — The Residue Histogram
// ---------------------------------------------------------------------
// fcs_residue_histogram -- failing frames, binned by Chapter 19.4
// Section 4's partial-word residue.
//
// If the check engine has a fault in one of its 64 residue paths, the
// failures land in ONE bin. Nothing else in Section 1's list produces
// a non-uniform residue distribution, so this is the sharpest
// separator in the chapter.
// ---------------------------------------------------------------------
module fcs_residue_histogram
import crcdiag_pkg::*;
(
input logic clk,
input logic rst_n,
input logic frame_end,
input logic [13:0] wire_len,
input logic fcs_mismatch,
output logic [31:0] c_by_residue [N_RESIDUES],
output logic [31:0] c_frames_by_residue [N_RESIDUES],
output logic [5:0] peak_residue,
output logic [31:0] peak_count,
output logic [31:0] total_errors,
output logic comb_detected
);
logic [5:0] residue;
// Chapter 19.4 Section 4: the residue is the pre-FCS length modulo
// the beat width. Chapter 20.6 Section 6 -- this 64 is the beat
// width and moves with the interface.
assign residue = 6'((wire_len - 14'd4) % 14'd64);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
for (int i = 0; i < N_RESIDUES; i++) begin
c_by_residue[i] <= '0;
c_frames_by_residue[i] <= '0;
end
total_errors <= '0;
end else if (frame_end) begin
c_frames_by_residue[residue] <= c_frames_by_residue[residue] + 32'd1;
if (fcs_mismatch) begin
c_by_residue[residue] <= c_by_residue[residue] + 32'd1;
total_errors <= total_errors + 32'd1;
end
end
end
always_comb begin
peak_residue = 6'd0;
peak_count = 32'd0;
for (int i = 0; i < N_RESIDUES; i++)
if (c_by_residue[i] > peak_count) begin
peak_count = c_by_residue[i];
peak_residue = 6'(i);
end
// A uniform distribution puts 1/64 of the errors in the peak bin.
// Half of them in one bin is a comb, and a comb is a logic fault
// in exactly one of Chapter 19.4's 64 paths.
comb_detected = (total_errors > 32'd256) &&
(peak_count > (total_errors >> 1));
end
endmoduleClassification: a 64-bin histogram whose interesting output is a single concentration test.
What it teaches: that Chapter 19.4 §4's 64 residue classes are a diagnostic axis and not only an implementation detail. The engine has sixty-four distinct partial-word paths; a fault in one of them fails only the frames that take it, which is 1.56% of a uniform traffic mix. Nothing else in Section 1's list is residue-selective — a channel does not know about beat boundaries, and a lane-deskew fault damages every frame equally.
And it teaches that c_frames_by_residue is needed as well as c_by_residue. Traffic is not uniform over residues: Chapter 20.1 §17's fixed-size streams occupy one residue entirely, so a raw error histogram is a picture of the traffic. The concentration test should be on the rate, and this block computes it on the count — which Section 22's third complaint is about.
Deliberately simplified: the modulus is a hardcoded 64, which Chapter 20.6 §6 spent a section arguing is really the beat width — at 8 octets per beat there are 8 residue classes and this block's 64 bins are 56 dead ones. The peak search is 64 comparators, combinational. comb_detected uses a half-of-all-errors threshold where a uniform distribution's peak bin would hold about 1.56% plus noise, so the test is very conservative and will miss a fault that affects a few residues rather than one.
Production implication: the conservatism is the right default and the failure it misses is worth naming. A fault in Chapter 19.4's correction barrel affects a range of residues rather than one — the barrel shifts by a residue-dependent amount, so a stuck bit in its control affects half of them. That produces a bimodal histogram, not a comb, and comb_detected stays low while the distribution is obviously non-uniform. A chi-squared test over 64 bins would catch both, costs nothing in hardware because the bins are already exported, and belongs in the software that reads them.
8. A Comb in the Length Distribution
A check engine fault in one residue path produces a failure set with a shape nothing else produces: a regular comb in the frame length.
Chapter 19.4 §4's residue is (L − 4) mod 64, so the frames taking residue path r are exactly those with:
L ≡ r + 4 (mod 64)| Value | |
|---|---|
| legal wire lengths, 64 to 1 518 | 1 455 |
| lengths congruent to any fixed value mod 64 | 22 or 23 |
| share of all lengths | 1.56% |
| spacing between failing lengths | exactly 64 octets |
Twenty-three lengths, evenly spaced sixty-four apart, and every frame of those lengths fails while every other frame passes. On a uniform size distribution that is a 1.56% error rate; on production traffic it is whatever share those lengths happen to carry.
And it is the only mechanism in Section 1's list that produces a regular pattern.
| Fault | The set of failing lengths |
|---|---|
| the channel | all lengths, weighted by length |
| the PHY's lanes | all lengths, uniformly |
| the xMII | all lengths, uniformly |
| the parser's offsets | all lengths, uniformly |
| one residue path | a comb: 23 lengths, spaced 64 apart |
Which makes the diagnosis visible by eye on a scatter plot of failing lengths, and computable without one: Section 7's histogram concentrates in one bin, and no other fault concentrates anywhere.
Two related signatures worth knowing, because the barrel is the more likely fault.
Chapter 19.4's correction barrel shifts the accumulated remainder by an amount that depends on the residue. A stuck bit in the shift amount affects every residue whose binary representation has that bit set — which is half of them, and the failing set is then:
| A single-path fault | A barrel control fault | |
|---|---|---|
| residues affected | 1 of 64 | 32 of 64 |
| error rate on uniform traffic | 1.56% | 50% |
| histogram shape | one spike | half the bins high, half at zero |
comb_detected fires? | yes | no — no single peak |
| is it obvious? | only with the histogram | yes — half of all frames fail |
Row five is why the barrel fault is the easier of the two in practice and the harder for Section 7's test. A 50% error rate is impossible to miss; the histogram's shape is what says it is the barrel rather than the parser, and comb_detected is the wrong test for it. A chi-squared statistic over the 64 bins detects both, and the bins are already exported.
And the third variant, which is the one that survives for years.
| A fault in the residue-63 path only | |
|---|---|
| share of lengths | 1.56% |
| which lengths | L ≡ 3 (mod 64) — Chapter 19.3 §20's spill sizes |
| how often production traffic hits them | rarely — 1.6% at best |
| what a test suite of round sizes reaches | none of them |
Residue 63 is Chapter 19.4's spill case, where the check value crosses a beat boundary — the hardest path in the engine, the one Chapter 20.1 §15 built a whole coverage goal around, and the one a test suite built from 64, 128, 256, 512, 1 024 and 1 518 octets never exercises. A fault there fails 1.56% of production frames, forever, and no conformance test finds it.
And one variant of the comb that is not a fault at all, which matters because it will be seen.
Chapter 20.1 §17 showed that a fixed-size stream has a fixed wire period, and Chapter 19.3 §6's deficit makes that period a multiple of four. So a link carrying a small number of distinct frame sizes — which is most links, because protocols use a handful of sizes — has a residue distribution that is a comb before any fault is present.
| Traffic | Residues occupied |
|---|---|
| a 1 518-octet bulk stream | 1 — residue 42 |
| 64-octet acknowledgements | 1 — residue 60 |
| the two together | 2 |
| a real mix with a hundred sizes | up to 64, unevenly |
Errors then land in the residues the traffic occupies, whatever the fault is — so an error histogram on such a link is a comb produced by the traffic and not by the engine. The correction is the one Section 7 already names: divide by c_frames_by_residue and compare rates. On a link occupying two residues the rate test still works; on a link occupying one it cannot work at all, and cross_populated's equivalent for residues is the check that should gate it.
A comb at every 64th length is a check engine. A slope with length is a channel. A uniform 100% is a parser. Those three sentences are the chapter — and each of them is a rate, never a count.
9. RTL 4 — The Burst Detector
// ---------------------------------------------------------------------
// error_burst_detector -- are the failures spread in time or clustered?
//
// Section 10: a random channel produces independent failures; a
// failing FEC block, a connector that moves, or an interference source
// produces clusters. The distinction is not about which SITE is at
// fault -- both are the channel -- it is about what to do next.
// ---------------------------------------------------------------------
module error_burst_detector
import crcdiag_pkg::*;
#(
parameter int WINDOW_FRAMES = 1024
)(
input logic clk,
input logic rst_n,
input logic frame_end,
input logic fcs_mismatch,
output logic [31:0] c_errors,
output logic [31:0] c_windows,
output logic [31:0] c_windows_with_error,
output logic [31:0] max_in_window,
output logic clustered,
output logic [31:0] expected_windows_x100
);
logic [31:0] frames_in_window, errors_in_window;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
c_errors <= '0; c_windows <= '0; c_windows_with_error <= '0;
max_in_window <= '0;
frames_in_window <= '0; errors_in_window <= '0;
end else if (frame_end) begin
frames_in_window <= frames_in_window + 32'd1;
if (fcs_mismatch) begin
c_errors <= c_errors + 32'd1;
errors_in_window <= errors_in_window + 32'd1;
end
if (frames_in_window == 32'(WINDOW_FRAMES) - 32'd1) begin
c_windows <= c_windows + 32'd1;
if (errors_in_window != 32'd0)
c_windows_with_error <= c_windows_with_error + 32'd1;
if (errors_in_window > max_in_window)
max_in_window <= errors_in_window;
frames_in_window <= 32'd0;
errors_in_window <= 32'd0;
end
end
end
// Independent errors spread across windows: with E errors and W
// windows, about min(E, W) windows are touched. Clustering puts the
// same E errors into far fewer.
always_comb begin
expected_windows_x100 = (c_errors < c_windows)
? (c_errors * 32'd100)
: (c_windows * 32'd100);
clustered = (c_errors > 32'd64) &&
((c_windows_with_error * 32'd400) < expected_windows_x100);
end
endmoduleClassification: a clustering test, and the only block in the chapter that measures time rather than shape.
What it teaches: that clustering does not change which site is at fault and does change what to do about it. Random and bursty damage are both the channel — Section 4's slope is high in both cases. But random damage means a marginal signal-to-noise ratio, which is a cable, a connector or an optic, and clustered damage means an intermittent event: a connector that moves, an interference source, a FEC block that fails under a specific pattern. One replaces a component; the other looks for a correlation with something else.
And it teaches why the test is "how many windows were touched" rather than a variance. With E independent errors spread over W windows, about min(E, W) windows contain at least one. Clustering puts the same errors into far fewer windows, and the ratio needs no distributional assumption at all — which matters because the alternative, a variance test, needs one.
Deliberately simplified: the window is a fixed number of frames, not of time, so a link whose rate varies has windows of varying duration — and an interference source correlates with time rather than with frames. The factor of four in clustered is a judgement. max_in_window is recorded and never used. And the block cannot distinguish a burst of errors inside one frame — which the check sequence reports as one failure — from separate frames failing.
Production implication: the last simplification is a real limit and it points at where the evidence actually is. A single 5 140-bit FEC block spans several minimum-size frames at 100 Gb/s, so one failed correction damages several consecutive frames — which this block sees as a cluster and which the PHY's own uncorrected-block counter sees directly. Chapter 3.7's FEC counters are the better evidence and they are behind MDIO, which is the same reason Chapter 21.2 §9's symbol counters go unread.
10. Random Bits, Bursts, and Failed FEC
The check sequence's detection guarantee is conditional on the shape of the damage, and the shape a modern link produces is the one the guarantee does not cover.
| Damage | CRC-32 detects | Why |
|---|---|---|
| one bit | always | Hamming distance |
| any odd number of bits | always | the polynomial has x + 1 as a factor |
| a burst of ≤ 32 bits | always | the remainder cannot be zero |
| a burst of > 32 bits | with probability 1 − 2^-32 | it falls back to chance |
Rows three and four are the transition, and where a link sits on it is decided by its line coding.
| Link | The damage unit | Bits | Inside the burst guarantee? |
|---|---|---|---|
| 1 Gb/s, 8B/10B | a code group | 8 to 10 | yes |
| 10 Gb/s, 64B/66B | a block | 64 | NO |
| 25 Gb/s and above, with RS-FEC | an uncorrectable codeword | thousands | NO |
A 1 Gb/s copper link damages octets and CRC-32 catches every such burst with certainty. A 25 Gb/s link with Reed-Solomon FEC either corrects the damage completely or fails a whole codeword, and a failed codeword is damage across thousands of bits — far outside the 32-bit guarantee, so detection falls back to 1 − 2^-32.
Which changes what a check sequence means as the rate rises.
| At 1 Gb/s | At 100 Gb/s with FEC | |
|---|---|---|
| channel damage the MAC sees | small bursts, always detected | corrected, or enormous and probabilistically detected |
| an FCS error therefore suggests | the channel | something past the FEC |
| the first counter to read | c_crc_errors | the PHY's uncorrected-codeword count |
Row two is Chapter 21.2 §17's inversion stated as a diagnostic step. On a FEC-protected link the channel's errors are corrected before the MAC sees them — so a frame arriving with damage arrived after correction, which points inward. The exception is a codeword the FEC could not correct, and that has its own counter, in the PHY.
And the shape of the damage explains a signature that otherwise looks like a logic fault.
| A failed FEC codeword | |
|---|---|
| bits damaged | thousands, contiguous |
| frames affected at 100 Gb/s minimum size | several consecutive |
| Section 4's slope | still high — more frames of any size are hit |
| Section 9's clustering | strongly clustered |
| looks like | a channel fault that comes and goes |
Slope high and clustering high together is the FEC signature, and it is distinguishable from a marginal channel — slope high, clustering low — by a test that costs one counter and a division.
And there is a third damage shape worth naming because its signature looks like a logic fault and is not.
| A stuck or intermittent lane | |
|---|---|
| what it damages | one lane of several, continuously |
| at 100 Gb/s, four lanes | a quarter of every frame's octets |
| Section 4's slope | 1.00 — every frame is hit |
| alignment errors | often, because lane order breaks too |
| looks like | a logic fault, and it is the PHY |
A lane that is dead rather than noisy damages every frame regardless of its length, so the slope reports 1.00 and the measurement points inward — correctly, because the fault is inside the PHY rather than on the fibre. The site is still 2 rather than 1, and that is the right answer: Chapter 21.1 §2's site 2 is "the PHY's lane alignment" and a dead lane is exactly that.
Which completes the slope's interpretation and it is not quite what Section 4 implied.
| Slope | Means |
|---|---|
| high, spread | the medium itself — bits damaged in transit |
| high, clustered | the medium intermittently, or failed FEC |
| 1.00, with alignment errors | the PHY or the interface — a per-frame mechanism |
| 1.00, no alignment errors | the parser or the check engine |
The slope separates "damage proportional to bits on the wire" from "damage per frame" — and that is a cleaner statement than "channel versus logic," because a dead lane is neither the medium nor logic and lands correctly in row three.
11. RTL 5 — The Undetected-Error Estimator
// ---------------------------------------------------------------------
// fcs_escape_estimator -- how many corrupted frames got through.
//
// It is an ESTIMATE and it cannot be anything else: an undetected
// error is by definition invisible. What the block computes is the
// expected number, from the detected rate and CRC-32's 2^-32 bound --
// and the useful output is an interval, not a count.
// ---------------------------------------------------------------------
module fcs_escape_estimator
import crcdiag_pkg::*;
(
input logic clk,
input logic rst_n,
input logic [31:0] c_frames,
input logic [31:0] c_errors,
input logic bursty, // from Section 9
output logic [31:0] escapes_per_exa, // expected escapes per 10^18 frames
output logic [63:0] frames_per_escape,
output logic escape_plausible,
output logic guarantee_is_probabilistic
);
// Detected errors are the frames the checker caught. For each of
// them, about 2^-32 of the SAME damage pattern would have escaped.
// So the expected escape count is the detected count shifted right
// by 32 -- which is zero for any realistic counter width, and that
// is the point.
always_comb begin
escapes_per_exa = (c_frames == 32'd0) ? 32'd0
: 32'((c_errors * 32'd1000000000) /
(c_frames == 32'd0 ? 32'd1 : c_frames));
frames_per_escape = (c_errors == 32'd0) ? 64'hFFFF_FFFF_FFFF_FFFF
: (64'(c_frames) << ESCAPE_SHIFT) / 64'(c_errors);
// One escape becomes plausible once the frames seen approach the
// frames-per-escape figure.
escape_plausible = (64'(c_frames) > (frames_per_escape >> 4));
// Section 10: on a burst-damaged link the 32-bit burst guarantee
// does not apply, so the 2^-32 bound is the ONLY bound and the
// detection is probabilistic rather than certain.
guarantee_is_probabilistic = bursty;
end
endmoduleClassification: an estimator whose honest output is a rate rather than a count.
What it teaches: that frames_per_escape is the number to report and it is derived, not measured. For every detected error there are about 2^-32 as many undetected ones with the same damage statistics — so the frames-per-escape figure is the frames seen, times 2^32, divided by the errors seen. A link with one error in a million frames escapes about once every 4.29 × 10¹⁵ frames, which is a number a person can reason about.
And it teaches that guarantee_is_probabilistic is the most important bit in the block. On a link whose damage is small bursts, CRC-32's guarantee is absolute and there are no escapes at all — the 2^-32 figure does not apply. On a link whose damage is failed FEC codewords, thousands of bits wide, the guarantee is gone and 2^-32 is the only bound there is. Section 9's clustering test is what decides which regime the link is in, and the escape estimate is meaningless without it.
Deliberately simplified: escapes_per_exa is computed and is wrong — the expression is the detected error rate per billion, not an escape rate, and it is left in as the block's own bug because it is the one somebody writes. The shift-by-32 in frames_per_escape overflows 64 bits for large frame counts. And the estimate assumes the escape probability of the detected damage applies to the undetected damage, which is the standard assumption and is unverifiable by construction.
Production implication: the honest use of this block is to answer a question nobody asks until an audit. "How often does a corrupted frame reach host memory with a valid check sequence?" At 100 Gb/s, 1 518-octet frames, a bit error rate of 10⁻¹⁰: 1.21 × 10⁻⁶ of frames are damaged, 2^-32 of those escape, 8.13 million frames per second — one escape every 13.8 years. That is the number that justifies a higher-layer checksum for data that matters and justifies not having one for data that does not.
12. One Escape Every Fourteen Years
The undetected-error rate is the number that decides whether the check sequence is enough, and it is computable exactly for any link.
Three quantities multiply.
escapes per second = (frames per second)
× P(the frame is damaged)
× 2^-32| Value at 100 Gb/s, 1 518 octets, BER 10⁻¹⁰ | |
|---|---|
| frames per second | 8.127 × 10⁶ |
| P(damaged) | 1.214 × 10⁻⁶ |
| 2^-32 | 2.328 × 10⁻¹⁰ |
| escapes per second | 2.298 × 10⁻⁹ |
| mean time between escapes | 4.35 × 10⁸ s — 13.8 years |
Thirteen point eight years per port, which is why a single Ethernet link's check sequence is usually considered adequate on its own. And the number moves in ways worth knowing.
| Change | New MTBF | Factor |
|---|---|---|
| a worse channel: BER 10⁻⁸ | 50 days | 100× worse |
| minimum-size frames instead | 13.8 years — unchanged | 1× |
| ten thousand ports in a fleet | 12 hours | 10 000× worse |
| the burst guarantee no longer applies | as computed | it already assumed that |
Row two is the one that surprises. Minimum-size frames are 23.72× less likely to be damaged and there are 18.3× more of them per second — and the two factors nearly cancel, because both are proportional to the frame's length. The escape rate is a property of the bits on the wire, not of the frames, and no frame-size decision changes it.
Row three is the one that matters operationally. Thirteen point eight years is a comfortable number for one link and twelve hours is not a comfortable number for a data centre. At fleet scale the check sequence is provably insufficient, which is why storage and database traffic carry their own integrity checks and why Chapter 18.4's DMA path has its own.
And there is a fourth change that is not in the table because it is not a change in the link.
| Effect on the escape rate | |
|---|---|
| a switch that regenerates the check sequence | each hop gets its own 13.8 years |
| N hops | N times the escape rate |
| a store-and-forward switch with a corrupt buffer | the escape is created at the hop |
Row three is the failure mode the arithmetic does not cover and is the real-world cause of most observed corruption. A switch that validates, stores, and re-appends a check sequence protects the frame's octets across each hop and not across the path — so a bit flipped in the switch's own memory leaves the switch with a perfectly valid check sequence computed over corrupt data. No amount of link-level integrity detects it, and the span argument of Section 2 is exactly why.
A check sequence's 13.8 years is per link, per port, and per hop — and it protects a span that ends at every switch.
One more comparison, because it is the one that decides whether a higher-layer check is worth its cost.
| Mechanism | Width | Escape probability | Span |
|---|---|---|---|
| the Ethernet FCS | 32 bits | 2^-32 | one hop |
| the IPv4 header checksum | 16 bits | 2^-16 | end to end, header only |
| the TCP checksum | 16 bits | 2^-16 | end to end, header and payload |
| a 32-bit application digest | 32 bits | 2^-32 | end to end |
Rows two and three are sixteen bits and escape sixty-five thousand times more often than the FCS — one in 65 536 of the corrupted frames they see — and they are the only mechanisms in the table whose span is end to end. So the layering is not redundant: the strong check covers a short span and the weak checks cover a long one, which is exactly backwards from what a designer starting today would choose and is what history produced.
| Consequence | |
|---|---|
| corruption inside a hop | caught by the FCS, 2^-32 |
| corruption inside a switch's buffer | caught only by TCP, 2^-16 |
| a fleet of switches | the weak check is the one that matters |
Row two is why a storage or database workload carries its own digest and why the argument for one is not "Ethernet is unreliable." Ethernet's check is the strong one; it is the span that is short — and the mechanism protecting the long span is the sixteen-bit one, which escapes once in every 65 536 corrupted segments.
13. RTL 6 — The Self-Check, Used as a Diagnostic
// ---------------------------------------------------------------------
// crc_equivalence_probe -- Chapter 19.4 Section 14's equivalence
// checker, repurposed.
//
// In that chapter it proves a parallel implementation matches a serial
// reference. Here it answers a diagnostic question: is the check
// engine computing the right value for THIS frame's residue class?
// One known-good frame per residue is enough, and there are 64.
// ---------------------------------------------------------------------
module crc_equivalence_probe
import crcdiag_pkg::*;
(
input logic clk,
input logic rst_n,
input logic probe_en,
input logic [5:0] probe_residue, // which of the 64 to test
input logic probe_frame_end,
input logic [31:0] engine_result, // what Chapter 19.4 computed
input logic [31:0] reference_result, // a serial reference
output logic [63:0] residue_tested,
output logic [63:0] residue_failed,
output logic [5:0] first_bad_residue,
output logic engine_faulty,
output logic probe_complete
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
residue_tested <= '0; residue_failed <= '0;
first_bad_residue <= 6'd0; engine_faulty <= 1'b0;
end else if (probe_en && probe_frame_end) begin
residue_tested[probe_residue] <= 1'b1;
if (engine_result != reference_result) begin
residue_failed[probe_residue] <= 1'b1;
if (!engine_faulty) first_bad_residue <= probe_residue;
engine_faulty <= 1'b1;
end
end
end
// Section 8: all 64 residue classes, or the probe has not tested
// the one that matters. The spill class, residue 63, is the one a
// round-number test suite never reaches.
assign probe_complete = (residue_tested == 64'hFFFF_FFFF_FFFF_FFFF);
endmoduleClassification: an equivalence checker used as an instrument rather than as a verification component.
What it teaches: that the check engine is the one site in Section 1's list that can be tested directly, and that the test is sixty-four frames. Chapter 19.4 §14's reference already exists in the verification environment; running it against the shipped engine on one frame per residue class settles site 6 completely, with no inference and no statistics. None of the other four sites has an equivalent — you cannot ask a cable whether it is working.
And it teaches that probe_complete is the output that decides whether a negative result means anything. Sixty-three residues tested and clean says nothing about the sixty-fourth, and Chapter 19.3 §20's spill class — residue 63 — is the hardest path and the one a suite of round frame sizes never reaches. A probe that reports "no faults found" without probe_complete has tested the easy paths.
Deliberately simplified: the block needs a serial reference implementation present in the design, which no shipping MAC contains — so this is a bring-up and a test-mode instrument, not a field one. residue_tested is set on any probe frame, with no check that the frame actually had that residue. And engine_faulty is sticky with no way to clear it short of reset, which is correct for a verdict and inconvenient for a test loop.
Production implication: the absence of a reference in shipped silicon is the whole reason this chapter's other five sections exist. If a MAC carried a second, differently-implemented check engine, site 6 would be decidable in one cycle — and the cost would be Chapter 19.4's 8 512 XOR terms again, which nobody will spend. So site 6 is diagnosed statistically, by Section 7's histogram, and the histogram is 64 counters against a second engine's thousands of gates — which is the trade the chapter is built on.
14. What a CRC Diagnosis Must Never Do
Six prohibitions. Three are about the span and three are about the statistics, and every one of them produces a confident wrong site.
| Never | Because | |
|---|---|---|
| 1 | read a failing check as "the channel" | it is five sites, four of them inside the chip |
| 2 | read a passing check as "the frame is correct" | Section 2 — the span starts after the transmitter's memory |
| 3 | compare error counts across size buckets | the count follows the traffic mix — Section 3 |
| 4 | run the slope test on a fixed-size stream | one bucket, no slope |
| 5 | conclude from fewer than a few dozen errors | the ratio is noise |
| 6 | apply the 2^-32 escape bound on a small-burst link | there, detection is certain — Section 10 |
Row two is the one with consequences outside this chapter. A valid check sequence means these octets are the octets that were present at the append point — not that they were the right octets then, and not that they survived the receive FIFO, the DMA and host memory. Chapter 18.4's read path is entirely outside the span, and a bit flipped there is protected by nothing and detected by nothing.
Row six is a prohibition in the opposite direction from the usual. On a 1 Gb/s copper link damaging code groups of eight to ten bits, CRC-32 detects every single burst with certainty — the escape probability is exactly zero, not 2^-32. Quoting the probabilistic bound there overstates the risk, which leads to a higher-layer checksum being added where the arithmetic did not call for one.
And the two that look like statistical pedantry and are not:
| Why it is a prohibition | |
|---|---|
| row three | minimum-size frames dominate, so the count is highest in bucket 0 either way |
| row four | Chapter 20.1 §17 — a stress test is exactly one bucket |
Both make the chapter's primary measurement return a number that is not a slope, which is what the six have in common: each one produces a value that looks like the measurement and is not it.
15. RTL 7 — Diagnostic Telemetry
// ---------------------------------------------------------------------
// crcdiag_telemetry -- three measurements, one verdict, and the
// conditions under which the verdict is worth having.
//
// The verdict is a SITE, which is what Chapter 21.1's method wanted
// and could not get from c_crc_errors alone.
// ---------------------------------------------------------------------
module crcdiag_telemetry
import crcdiag_pkg::*;
(
input logic clk,
input logic rst_n,
input logic [31:0] slope_x100,
input logic slope_valid,
input logic looks_like_channel,
input logic looks_like_logic,
input logic comb_detected,
input logic [5:0] peak_residue,
input logic clustered,
input logic [31:0] c_errors,
input logic [31:0] c_frames,
input logic alignment_errors_present,
input logic frames_in_healthy,
input logic engine_faulty,
output crc_source_e verdict,
output logic verdict_confident,
output logic [15:0] error_rate_ppm,
output logic [31:0] slope_reported,
output logic need_more_traffic,
output logic need_mdio
);
always_comb begin
error_rate_ppm = (c_frames == 32'd0) ? 16'd0
: 16'((c_errors * 32'd1000000) / c_frames);
slope_reported = slope_x100;
// The order is by DECREASING certainty, which is the opposite of
// Chapter 21.2 Section 7's mistake: here the most specific
// evidence really is the strongest, because each test is a
// different measurement rather than a different predicate on one.
if (engine_faulty) verdict = SRC_CRC_LOGIC;
else if (comb_detected) verdict = SRC_CRC_LOGIC;
else if (error_rate_ppm > 16'd900000) verdict = SRC_PARSER;
else if (alignment_errors_present) verdict = SRC_PHY_LANES;
else if (looks_like_channel && clustered)verdict = SRC_CHANNEL_BURST;
else if (looks_like_channel) verdict = SRC_CHANNEL_RANDOM;
else if (looks_like_logic && frames_in_healthy) verdict = SRC_CRC_LOGIC;
else verdict = SRC_UNDECIDED;
verdict_confident = (verdict != SRC_UNDECIDED) &&
(engine_faulty || comb_detected || slope_valid);
need_more_traffic = !slope_valid && (c_errors < 32'd32);
// Sections 2 and 3 of Chapter 21.2: separating the PHY from the
// xMII needs the PHY's own counters, and those are behind MDIO.
need_mdio = (verdict == SRC_PHY_LANES);
end
endmoduleClassification: a verdict block whose priority order is by measurement strength rather than by predicate specificity.
What it teaches: that this chain's order is defensible where Chapter 21.2 §7's was not, and the difference is worth stating precisely. There, the arms tested different predicates on one object and the order decided a classification. Here each arm is a different measurement — an equivalence probe, a histogram shape, an error rate, a slope — and they genuinely differ in strength. An equivalence failure is a proof; a slope is an inference. Ordering by strength is correct; ordering predicates by specificity was not.
And it teaches that verdict_confident is not the same as verdict != SRC_UNDECIDED. The chain always produces a value; only the first three arms rest on evidence strong enough to act on alone. The alignment-error arm, for instance, is Chapter 21.1 §4's class B — two sites, not one — and reporting SRC_PHY_LANES for it is a shorthand that need_mdio immediately qualifies.
Deliberately simplified: the 90% threshold for SRC_PARSER assumes a parser fault fails essentially every frame, which a fault in one offset path would not. alignment_errors_present collapses Chapter 21.2's whole alignment class into one bit. SRC_UNDECIDED is reached by falling off the end, so it conflates "no evidence" with "contradictory evidence". And slope_reported is passed through unchanged, so a reader has to know Section 5's threshold to interpret it.
Production implication: need_mdio is the output that turns a verdict into a next action, and it is the one most likely to go unimplemented. Separating the PHY's lane alignment from the xMII needs Chapter 21.2 §9's cross-chip comparison — the PHY's symbol count against the MAC's RX_ER count — and that is an MDIO transaction. A monitoring system that does not make MDIO transactions stops at "the PHY or the interface", which is two sites, and that is the ceiling this chapter reaches on such a platform.
16. RTL 8 — The Diagnostic Conformance Monitor
// ---------------------------------------------------------------------
// crcdiag_conformance_monitor -- six verdicts about the DIAGNOSIS, and
// four of them say the measurement is not yet worth reading.
// ---------------------------------------------------------------------
module crcdiag_conformance_monitor
import crcdiag_pkg::*;
#(
parameter int MIN_ERRORS = 32,
parameter int MIN_BUCKETS = 2
)(
input logic clk,
input logic rst_n,
input logic [31:0] c_errors,
input logic [31:0] c_frames,
input logic cross_populated,
input logic slope_valid,
input logic [31:0] slope_x100,
input logic [63:0] residue_tested,
input logic probe_complete,
input logic bursty,
input crc_source_e verdict,
output logic too_few_errors,
output logic one_bucket_only,
output logic slope_ambiguous,
output logic probe_incomplete,
output logic escape_bound_misapplied,
output logic verdict_unsupported,
output logic diagnosis_sound
);
assign too_few_errors = (c_errors < 32'(MIN_ERRORS));
assign one_bucket_only = !cross_populated;
// Section 5: between 2.00 and 5.00 the measurement has not decided,
// and saying so is the block's most useful output.
assign slope_ambiguous = slope_valid &&
(slope_x100 > 32'd200) && (slope_x100 < 32'd500);
// Section 13: residue 63 is the spill class and a round-number test
// suite never reaches it.
assign probe_incomplete = !probe_complete && !residue_tested[63];
// Section 10 and Section 14's row six: on a small-burst link the
// detection is CERTAIN and quoting 2^-32 overstates the risk.
assign escape_bound_misapplied = !bursty;
assign verdict_unsupported = (verdict != SRC_UNDECIDED) &&
(too_few_errors || one_bucket_only);
assign diagnosis_sound = !verdict_unsupported && !slope_ambiguous;
always_ff @(posedge clk) begin
if (rst_n && probe_incomplete)
$display("[crcdiag] residue 63 untested -- the spill class is the hardest path");
end
endmoduleClassification: an auditor of a measurement rather than of a design.
What it teaches: that verdict_unsupported is the verdict that protects the chapter from itself. Section 15's chain always produces a value, and on thirty errors spread across one bucket that value is a guess with a slope computed from noise. The monitor's job is to say so — and the two conditions it checks are exactly the two Section 14's rows three, four and five prohibit.
And it teaches that probe_incomplete singles out one residue by name. Sixty-three of sixty-four tested is 98.4% complete and 0% complete on the path that matters: Chapter 19.4's spill class, where the check value crosses a beat boundary, and Chapter 19.3 §20's 23 lengths that a suite of round sizes never produces. A verification suite that is one residue short is one residue short of the hard one, not of an average one.
Deliberately simplified: escape_bound_misapplied asserts on every non-bursty link, which is most of them — it is a reminder rather than a fault, and it should be a report rather than a verdict. MIN_BUCKETS is declared and unused. And diagnosis_sound omits probe_incomplete, so a diagnosis can be sound while the strongest available test has not been run on the path most likely to fail.
Production implication: the omission in diagnosis_sound is the one to argue about. Including probe_incomplete would make almost every real diagnosis unsound, because the equivalence probe needs a reference implementation that shipped silicon does not carry — so the flag would fire always and be ignored always. Leaving it out makes the verdict usable and silently accepts that site 6 is diagnosed statistically. Section 13 is explicit about the trade; the monitor's parameter list is where a team decides whether to accept it.
17. The Five Sites, Separated
Everything in the chapter, as one procedure over three measurements and two counters that already exist.
| Step | Measure | Separates |
|---|---|---|
| 1 | c_alignment_errors | sites 1, 2, 3 from sites 5, 6 |
| 2 | frames_in | sites 1, 2, 3 (counted) from 5, 6 (not) |
| 3 | the error rate | site 5 — a parser fault is near 100% |
| 4 | Section 4's slope | site 1 (high) from sites 2, 3, 5, 6 (flat) |
| 5 | Section 7's histogram | site 6 — a comb |
| 6 | Section 9's clustering | random channel from bursty channel |
| 7 | Chapter 21.2 §9's MDIO comparison | site 2 from site 3 |
And the procedure has one property worth stating before the table: every step before the last is a division, and the last is a transaction.
| Step | Instrument | Time |
|---|---|---|
| 1 to 3 | three register reads | microseconds |
| 4 to 6 | arithmetic over counters already running | none — the data is there |
| 7 | one MDIO transaction | milliseconds |
Steps 4 to 6 take no time at all, because the counters have been accumulating since the link came up — the measurement is a division over numbers already collected, and that is why the whole diagnosis can run inside a monitoring system on every port continuously. Chapter 21.1 §12 made the same argument about its first five steps; this chapter extends it to the three measurements that actually name a site.
Steps 1 to 3 are Chapter 21.1's method and cost three register reads. Steps 4 to 6 are this chapter's and cost six counters and sixty-four bins — about 594 and 6 336 flops — and no equipment at all. Step 7 is one MDIO transaction.
The full separation, as a decision table:
| Evidence | Site |
|---|---|
alignment errors present, frames_in healthy, slope high | the channel |
| the same, slope high and clustered | the channel, intermittently or via FEC |
| alignment errors present, PHY count agrees | the PHY's lanes |
| alignment errors present, PHY count disagrees | the xMII |
no alignment errors, rate near 100%, frames_in healthy | the parser's offsets |
| no alignment errors, rate 1.56%, one residue bin | the check engine |
| no alignment errors, rate 50%, half the bins | the check engine's correction barrel |
Seven rows, five sites, and every discriminator is arithmetic over counters. Compare what the chapter started with:
| Before | After | |
|---|---|---|
| candidates | 5 | 1 |
| observations | 1 | 7 |
| equipment | a capture, three hours | none |
| added hardware | — | ~6 930 flops, 48.9% of the datapath |
The last row is the honest cost and it is much larger than Chapter 21.1's or Chapter 21.2's, because the residue histogram is 64 counters. And that is the right place to make a choice: the six size-bucket counters are 594 flops, 4.2%, and buy the channel-versus-logic separation, which is the split that decides whether a maintenance crew is dispatched. The 64 residue bins are 6 336 flops, 44.7%, and buy the check engine's comb — a fault that occurs once in a product's life.
| Addition | Flops | Share | Buys |
|---|---|---|---|
| six size-bucket error counters | ~594 | 4.2% | channel against logic |
| sixty-four residue bins | ~6 336 | 44.7% | which residue path |
| one burst-window counter pair | ~198 | 1.4% | random against clustered |
Rows one and three together are 5.6% and separate four of the five sites. Row two separates the fifth and costs eight times as much as the other two combined — which is the argument for exporting the residue as a field in a failure log rather than binning it in hardware. One 6-bit field per failing frame, written to the same place a MAC already logs a bad frame, and the histogram is built in software for nothing.
And the whole of Module 21, as one table, because the three chapters have been building one instrument.
| From | Adds | Flops | Separates |
|---|---|---|---|
| Chapter 21.1 §19 | c_filtered, c_vlan_discards, c_fifo_drops, c_desc_errors | ~297 | class G's five sites |
| Chapter 21.2 §19 | c_unclassified, c_self_inflicted, c_symbol_in_gap, the order pair | ~426 | the unnamed shape, our truncation, the earliest warning |
| this chapter | six size-bucket rates, one burst pair | ~792 | channel from logic, random from clustered |
| this chapter, logged | the residue, 6 bits per failing frame | ~0 | which residue path |
| total | — | ~1 515 — 10.7% | all twelve sites |
Ten point seven per cent of a datapath separates every fault site in the receive path, and the same twelve sites currently resolve to seven classes with the largest holding five. Chapter 21.1 §8's twelve boundary counters would take the number of observations from seven to four for another 8.4%; this table takes the answer from a class of five to a site for 10.7%. The second is worth more and neither is built.
| Flops | What it buys | |
|---|---|---|
| Chapter 19.4's correction barrels | 5 397 XOR terms | correctness — built |
| Chapter 19.7's shadow bank | 992 | a common reading instant — built |
| all of Module 21 | ~1 515 | every fault site named — not built |
Module 21's entire instrumentation is one and a half times Chapter 19.7's shadow bank, which was built without argument because a common reading instant is a correctness requirement. The pattern holds across all three chapters and it is the module's closing observation: the thing that decides whether a mechanism ships is not its cost and not its value — it is whether it is a correctness requirement.
18. What the Diagnosis Assumes
Nine assumptions. Three are about the damage, three about the traffic and three about the instrumentation — and the first one is false on every high-rate link.
| Assumption | From | If false | |
|---|---|---|---|
| 1 | bit errors are independent | Section 4's model | the slope holds; the escape bound does not |
| 2 | the damage is inside the protected span | Section 2 | a valid check over corrupt data |
| 3 | the traffic populates two size buckets | nothing | no slope — cross_populated |
| 4 | the size buckets mean what Chapter 19.7 §2 says | the MTU | VLAN tags move the top boundary |
| 5 | the residue modulus is 64 | Chapter 19.4 §4 | Chapter 20.6 §6 — it is the beat width |
| 6 | a parser fault fails essentially every frame | Section 15 | a single-offset fault fails a fraction |
| 7 | one fault at a time | convenience | Chapter 21.1 §17 — the earlier one masks |
| 8 | the counters are not read-to-clear | the platform | the second read is a delta of a delta |
| 9 | the window is long enough | Chapter 21.1 §18's row nine | a zero that is not a zero |
Row one is false on every FEC-protected link and the consequence is asymmetric, which is the useful part. Reed-Solomon FEC either corrects a codeword or fails it entirely, so errors arrive in blocks of thousands of bits and are emphatically not independent.
| Under independent bits | Under failed codewords | |
|---|---|---|
| Section 4's slope | exactly the length ratio | still high — a longer frame spans more codewords |
| Section 12's 2^-32 bound | correct | the only bound there is, and the guarantee is gone |
| Section 9's clustering | low | high |
The slope survives the assumption's failure and the escape bound depends on it, which is why Section 4 is the chapter's primary measurement and Section 12 is a caveat. A longer frame spans more codewords in exactly the same proportion as it spans more bits, so the ratio test is robust to the damage's shape; the detection probability is not.
Row five is the assumption Chapter 20.6 spent a chapter on. The residue modulus is the beat width, not the constant 64: at 8 octets per beat there are 8 residue classes, not 64, and Section 7's histogram has 56 permanently empty bins. The comb's spacing is the beat width too — 8 octets rather than 64 — so the diagnostic signature changes shape across the xMII family and the constant does not.
| Beat width | Residue classes | Comb spacing | Share of lengths per class |
|---|---|---|---|
| 1 octet | 1 | none — every length | 100% |
| 8 octets | 8 | 8 octets | 12.5% |
| 64 octets | 64 | 64 octets | 1.56% |
| 128 octets | 128 | 128 octets | 0.78% |
At GMII the comb does not exist at all — one residue class, so a check engine fault fails everything and is indistinguishable from a parser fault by this test. The sharpest diagnostic in the chapter is sharpest at the highest rate and absent at the lowest, which is the opposite of the usual relationship and follows directly from the beat width.
And three things deliberately not assumed:
| Not assumed | Why not |
|---|---|
| that a failing check means damage | Section 2 — site 6 is the checker |
| that a passing check means integrity | Section 2 — the span |
| that the slope decides how bad the fault is | Section 6 — it decides what kind |
Row three is the separation the chapter keeps returning to. The slope is constant across four orders of magnitude of channel quality; the absolute rate is what moves. Two numbers, two questions — and a status page that shows one of them is showing the wrong one about half the time.
And one assumption that is not in the table because it is about the reader rather than the design.
Every measurement in this chapter is a population statistic and every instinct about CRC errors is about a frame.
| A frame-level question | The population answer | |
|---|---|---|
| "was this frame damaged?" | the check says yes | and by which of five sites, unknown |
| "is this link bad?" | unanswerable from one frame | the slope and the rate together |
| "how big was the damage?" | unknowable — the check is one bit | the clustering test infers it |
| "which frames fail?" | the ones that failed | the comb says which lengths |
Row three is the one that trips people who reach for a capture. A check sequence's verdict is one bit: it says the octets changed and not how many, not where, and not in what pattern. A capture of a hundred failing frames tells you a hundred times that one bit — and the damage's shape, which is what separates a burst from random bits, is not in any of them. Chapter 21.2 §9's PHY symbol counters have it and the frames do not.
| Where the shape information is | |
|---|---|
| in the failing frames | nowhere — the check is one bit |
| in the frames' lengths | the slope and the comb |
| in the failures' timing | the clustering test |
| in the PHY | the symbol and FEC counters, over MDIO |
Three of the four rows are counters and one is a capture that does not help, which is the chapter's practical thesis and the reason Chapter 21.1 §12 puts a capture at step 6.
19. The Cost, Accounted
The diagnosis is counters. Two of the three measurements are cheap and one is not.
| Block | Flops | Nature |
|---|---|---|
crcdiag_pkg | 0 | two predicted slopes and a modulus |
size_rate_cross — 7 × 2 × 32 | ~455 | the slope's data |
length_slope_estimator | 0 | combinational |
fcs_residue_histogram — 64 × 2 × 32 | ~4 105 | the comb's data |
error_burst_detector | ~161 | four counters and a window |
fcs_escape_estimator | 0 | combinational |
crc_equivalence_probe | ~135 | two 64-bit masks |
crcdiag_telemetry | 0 | combinational |
crcdiag_conformance_monitor | 0 | combinational |
| total | ~4 856 flops |
Four of the nine blocks are pure combinational logic and one block is 85% of the cost. The residue histogram's 128 counters — sixty-four error bins and sixty-four frame bins — are the whole expense, and Section 17 already argued they should not be counters at all.
| Flops | Share of the 14 166-flop datapath | |
|---|---|---|
| everything except the histogram | ~751 | 5.3% |
| the histogram | ~4 105 | 29.0% |
| the histogram as a logged field instead | 6 bits per failing frame | ~0% |
Row three is the design change this chapter argues for and it is almost free. A MAC that logs a bad frame at all already has a place to put a few bits about it; adding the residue is six bits in an existing record. The histogram is then built by whatever reads the log, in software, for nothing — and the diagnostic that costs 29% of a datapath in hardware costs six bits in a log entry.
Module 21's three chapters, together:
| Chapter | Flops if built in hardware | Flops if logged instead |
|---|---|---|
| Chapter 21.1 — four optional counters | ~297 | ~297 — they must be counters |
| Chapter 21.2 — four more | ~426 | ~426 |
| this chapter | ~4 856 | ~751 |
| total | ~5 579 — 39.4% | ~1 474 — 10.4% |
Ten point four per cent buys every diagnosis in Module 21, and the difference between the two columns is entirely the decision to bin a value in hardware rather than to log it. Chapter 21.1's counters cannot be logged — they count events that have no frame to attach to — and this chapter's histogram is a property of a specific failing frame and can.
Count what has no frame. Log what has one. That single rule takes Module 21's instrumentation from 39.4% of a datapath to 10.4%.
20. Properties Worth Asserting, and One Worth Refusing
Thirty-three properties and eight covers, in four groups: the span, the slope, the comb and the escape bound.
Group one — the span, which is what Section 2 is about.
// A check sequence characterises a SPAN. These properties say what
// happens at each end of it and nothing about the frame's truth.
p_check_after_append: assert property (@(posedge clk) disable iff (!rst_n)
fcs_checked |-> $past(fcs_appended_upstream));
p_span_ends_here: assert property (@(posedge clk) disable iff (!rst_n)
fcs_checked |-> !fifo_write_done);
p_valid_not_truth: assert property (@(posedge clk) disable iff (!rst_n)
(fcs_ok && tx_memory_corrupt) |-> 1'b1); // deliberately vacuous
p_mismatch_5_sites: assert property (@(posedge clk) disable iff (!rst_n)
fcs_mismatch |-> (candidate_sites == 12'b0000_0011_0111));
p_engine_in_span: assert property (@(posedge clk) disable iff (!rst_n)
engine_faulty |-> fcs_mismatch_possible_without_damage);
p_post_check_unprot: assert property (@(posedge clk) disable iff (!rst_n)
fifo_write_done |-> !fcs_protects_from_here);
p_hop_scoped: assert property (@(posedge clk) disable iff (!rst_n)
switch_regenerates_fcs |-> new_span_begins);
p_probe_needs_ref: assert property (@(posedge clk) disable iff (!rst_n)
probe_en |-> reference_present);Group two — the slope.
p_rate_not_count: assert property (@(posedge clk) disable iff (!rst_n)
slope_valid |-> (rate_ppm_in_bucket[hi_bucket] != 32'd0));
p_slope_needs_two: assert property (@(posedge clk) disable iff (!rst_n)
slope_valid |-> cross_populated);
p_slope_needs_errors: assert property (@(posedge clk) disable iff (!rst_n)
slope_valid |-> (c_errors >= 32'd32));
p_channel_excl_logic: assert property (@(posedge clk) disable iff (!rst_n)
!(looks_like_channel && looks_like_logic));
p_gap_is_undecided: assert property (@(posedge clk) disable iff (!rst_n)
(slope_valid && slope_x100 > 32'd200 && slope_x100 < 32'd500)
|-> slope_ambiguous);
p_buckets_ordered: assert property (@(posedge clk) disable iff (!rst_n)
slope_valid |-> (hi_bucket > lo_bucket));
p_rate_bounded: assert property (@(posedge clk) disable iff (!rst_n)
error_rate_ppm <= 16'd1000000);
p_one_bucket_flagged: assert property (@(posedge clk) disable iff (!rst_n)
!cross_populated |-> one_bucket_only);
p_slope_stable: assert property (@(posedge clk) disable iff (!rst_n)
($stable(rate_ppm_in_bucket) && $stable(c_errors_in_bucket))
|-> $stable(slope_x100));Group three — the comb.
p_residue_range: assert property (@(posedge clk) disable iff (!rst_n)
frame_end |-> (residue < 6'(N_RESIDUES)));
p_residue_is_len: assert property (@(posedge clk) disable iff (!rst_n)
frame_end |-> (residue == 6'((wire_len - 14'd4) % 14'd64)));
p_bins_sum: assert property (@(posedge clk) disable iff (!rst_n)
(c_by_residue.sum() == total_errors));
p_peak_is_max: assert property (@(posedge clk) disable iff (!rst_n)
(c_by_residue[peak_residue] == peak_count));
p_comb_needs_peak: assert property (@(posedge clk) disable iff (!rst_n)
comb_detected |-> (peak_count > (total_errors >> 1)));
p_comb_needs_data: assert property (@(posedge clk) disable iff (!rst_n)
comb_detected |-> (total_errors > 32'd256));
p_probe_marks: assert property (@(posedge clk) disable iff (!rst_n)
(probe_en && probe_frame_end) |=>
residue_tested[$past(probe_residue)]);
p_probe_63: assert property (@(posedge clk) disable iff (!rst_n)
probe_complete |-> residue_tested[63]);
p_faulty_is_sticky: assert property (@(posedge clk) disable iff (!rst_n)
engine_faulty |=> always engine_faulty);Group four — the escape bound and the verdict.
p_escape_needs_burst: assert property (@(posedge clk) disable iff (!rst_n)
!bursty |-> escape_bound_misapplied);
p_small_burst_certain:assert property (@(posedge clk) disable iff (!rst_n)
(burst_bits <= 32) |-> fcs_mismatch);
p_odd_errors_caught: assert property (@(posedge clk) disable iff (!rst_n)
(damaged_bits[0] == 1'b1) |-> fcs_mismatch);
p_single_bit_caught: assert property (@(posedge clk) disable iff (!rst_n)
(n_damaged_bits == 32'd1) |-> fcs_mismatch);
p_verdict_supported: assert property (@(posedge clk) disable iff (!rst_n)
(verdict != SRC_UNDECIDED) |-> !verdict_unsupported);
p_confident_needs_ev: assert property (@(posedge clk) disable iff (!rst_n)
verdict_confident |->
(engine_faulty || comb_detected || slope_valid));
p_mdio_for_phy: assert property (@(posedge clk) disable iff (!rst_n)
(verdict == SRC_PHY_LANES) |-> need_mdio);
p_sound_excludes: assert property (@(posedge clk) disable iff (!rst_n)
diagnosis_sound |-> (!verdict_unsupported && !slope_ambiguous));
p_parser_is_total: assert property (@(posedge clk) disable iff (!rst_n)
(verdict == SRC_PARSER) |-> (error_rate_ppm > 16'd900000));And eight covers, because six of these states are what a reviewer needs to have seen.
c_slope_high: cover property (@(posedge clk) looks_like_channel);
c_slope_flat: cover property (@(posedge clk) looks_like_logic);
c_slope_middle: cover property (@(posedge clk) slope_ambiguous);
c_comb: cover property (@(posedge clk) comb_detected);
c_residue_63: cover property (@(posedge clk) residue == 6'd63 && fcs_mismatch);
c_clustered: cover property (@(posedge clk) clustered);
c_probe_all_64: cover property (@(posedge clk) probe_complete);
c_every_verdict: cover property (@(posedge clk) verdicts_seen == 7'h7F);21. Verification Scenarios
Fifty-eight scenarios, plus a five-run directed test that a random generator cannot produce because its variable is a fault mechanism.
The span — 9 scenarios.
| # | Scenario | Expected |
|---|---|---|
| 1 | a frame corrupted before the append | the check passes; the data is wrong |
| 2 | a frame corrupted on the wire | the check fails |
| 3 | a frame corrupted after the check | the check passed; the data is wrong |
| 4 | a check engine that inverts its result | every frame fails; none was damaged |
| 5 | a switch that regenerates the FCS | a new span begins |
| 6 | a bit flipped in that switch's buffer | valid check over corrupt data |
| 7 | N hops | N independent spans, N times the escape rate |
| 8 | a frame that never reaches the check | no verdict — not a pass |
| 9 | the probe with no reference present | probe_en must not assert |
The slope — 12 scenarios.
| # | Scenario | Expected |
|---|---|---|
| 10 | BER 10⁻¹⁰, mixed sizes | slope ≈ 19.86; looks_like_channel |
| 11 | BER 10⁻⁸, the same mix | slope ≈ 19.86 — unchanged |
| 12 | BER 10⁻¹², the same mix | slope ≈ 19.86, too few errors |
| 13 | a lane-deskew fault | slope ≈ 1.00; looks_like_logic |
| 14 | a parser offset fault | slope ≈ 1.00; rate near 100% |
| 15 | a single residue path fault | slope ≈ 1.00; rate 1.56% |
| 16 | all 1 518-octet frames | one bucket; !cross_populated |
| 17 | all 64-octet frames | one bucket; no slope |
| 18 | two buckets, 8 errors | too_few_errors |
| 19 | two buckets, 40 errors | a slope, and it may be noisy |
| 20 | slope of 3.5 | slope_ambiguous — no verdict |
| 21 | buckets 1 and 4 chosen | predicted 7.99, not 19.86 — Section 5's defect |
The comb — 11 scenarios.
| # | Scenario | Expected |
|---|---|---|
| 22 | a fault in residue path 17 | all errors in bin 17 |
| 23 | the failing lengths | L ≡ 21 (mod 64) |
| 24 | how many legal lengths | 23 of 1 455 — 1.56% |
| 25 | comb_detected | asserts above 256 errors |
| 26 | a barrel control fault | 32 bins high, 32 empty |
| 27 | comb_detected on that | does NOT assert — no single peak |
| 28 | a chi-squared test on that | detects it |
| 29 | a fixed-size stream, healthy | one bin, all frames, no errors |
| 30 | a fixed-size stream, channel fault | one bin, and it looks like a comb |
| 31 | the rate per bin instead of the count | uniform — the fix for scenario 30 |
| 32 | a fault in residue 63 only | the spill class; a round-size suite misses it |
Bursts and escapes — 10 scenarios.
| # | Scenario | Expected |
|---|---|---|
| 33 | independent bit errors | clustered low |
| 34 | one failed FEC codeword | several consecutive frames fail |
| 35 | clustered on that | asserts |
| 36 | slope on that | still high — it is still the channel |
| 37 | an 8-bit burst | always detected — no escape |
| 38 | a 32-bit burst | always detected |
| 39 | a 33-bit burst | detected with probability 1 − 2^-32 |
| 40 | an odd number of flipped bits | always detected |
| 41 | escape_bound_misapplied on a 1 Gb/s link | asserts — detection is certain there |
| 42 | the escape MTBF at 100 Gb/s, BER 10⁻¹⁰ | 13.8 years |
The verdict — 8 scenarios.
| # | Scenario | Expected |
|---|---|---|
| 43 | engine_faulty set | SRC_CRC_LOGIC, confident |
| 44 | comb, no probe | SRC_CRC_LOGIC, confident |
| 45 | rate 95%, frames_in healthy | SRC_PARSER |
| 46 | alignment errors present | SRC_PHY_LANES, need_mdio |
| 47 | slope high, clustered | SRC_CHANNEL_BURST |
| 48 | slope high, not clustered | SRC_CHANNEL_RANDOM |
| 49 | 20 errors, one bucket | SRC_UNDECIDED, verdict_unsupported |
| 50 | slope 3.5 and no comb | SRC_UNDECIDED |
The probe and the cost — 8 scenarios.
| # | Scenario | Expected |
|---|---|---|
| 51 | 64 probe frames, one per residue | probe_complete |
| 52 | 63 of them, missing residue 63 | probe_incomplete, and a $display |
| 53 | a round-number size suite | reaches 2 of 64 residues — 3.1% |
| 54 | engine_faulty then reset | clears; sticky within a run |
| 55 | the histogram in hardware | ~4 105 flops, 29.0% |
| 56 | the residue as a 6-bit log field | effectively free |
| 57 | the size-bucket cross only | ~594 flops, 4.2%, four of five sites |
| 58 | at 8 octets per beat | 8 residue classes; 56 bins dead |
And the directed test, because random stimulus will not produce it.
The case: one link, five fault mechanisms, and a diagnosis that must name a different site each time using only counters.
A constrained-random generator cannot reach this, because the variable is not a field of any frame — it is which component is broken. Each run needs a different injected defect, and three of the five are inside the design rather than on the wire: Chapter 20.5 §10's split, arriving in a diagnostic chapter.
| Run | The injected fault | Where it lives |
|---|---|---|
| A | random bit errors at BER 10⁻⁸ | the wire — an impairment |
| B | one FEC codeword in 10⁶ uncorrectable | the wire — a patterned impairment |
| C | a lane-deskew error in the PHY model | the PHY model |
| D | a stuck bit in the parser's offset | inside the design |
| E | a wrong constant in residue path 17 | inside the design |
The oracle is four-part and no part of it requires knowing which run is which.
| Part | A | B | C | D | E |
|---|---|---|---|---|---|
| slope | 19.86 | high | 1.00 | 1.00 | 1.00 |
| clustering | low | HIGH | low | low | low |
| error rate | 1.2 × 10⁻⁴ | ~10⁻⁶ | high | ~100% | 1.56% |
| residue histogram | uniform | uniform | uniform | uniform | ONE BIN |
Every column is distinct and no two rows are needed to separate all five, which is the test's point: four measurements, five faults, and a unique signature for each. Runs A and B differ only in clustering; C and D differ only in rate; D and E differ only in rate and histogram. Remove any one row and two runs collapse.
Runs D and E are the two that need a fault injected into the design rather than onto the wire, which is Chapter 20.5 §10's category and is why this test belongs to a verification environment rather than to a lab bench. Runs A, B and C can be produced with a channel impairment and a PHY model; D and E need a parameter in the RTL.
And run E is the one that would never be run. A wrong constant in one of sixty-four residue paths fails 1.56% of production frames, passes every conformance suite built from round frame sizes, and is exactly what Section 13's equivalence probe exists to find — one frame per residue, sixty-four frames, and the thing is settled.
22. Debugging a CRC Diagnosis
Six complaints.
Complaint 1 — "the slope says channel but we replaced the cable and nothing changed."
| Check | If yes | Meaning |
|---|---|---|
| is the link FEC-protected? | yes | the channel's errors are corrected — Section 10 |
| is the PHY's uncorrected-codeword count moving? | no | then the channel is fine |
| is the slope still high after the swap? | yes | it is not the cable |
| is the damage inside the PHY, past the FEC? | likely | a lane, an internal path, a SerDes |
Row four is the one the slope cannot see and it is the chapter's main blind spot. The slope separates length-dependent damage from per-frame damage; it does not say where the length-dependent damage happens. A fault inside the PHY that damages bits — after the FEC and before the xMII — produces a perfect channel signature and is not the channel. The separating evidence is the PHY's own counters, over MDIO.
Complaint 2 — "the histogram shows a comb but the engine is fine."
| Check | If yes | Meaning |
|---|---|---|
| is the traffic one frame size? | yes | one residue bin, always — scenario 30 |
is c_frames_by_residue uniform? | no | the histogram is a picture of the traffic |
| compute the rate per bin | uniform | no comb at all |
cross_populated? | irrelevant here | this is a residue problem, not a size one |
The comb test measures a count where it should measure a rate, and Section 7 names this as a simplification. A 1 518-octet fixed-size stream puts every frame in residue bin 42 — (1518 − 4) mod 64 — so every error lands there too and comb_detected fires on a channel fault. Dividing by c_frames_by_residue removes it entirely.
Complaint 3 — "the slope is 3.5 and we need an answer."
| Check | If yes | Meaning |
|---|---|---|
| how many errors? | 40 | barely above the floor |
| which buckets? | 1 and 4 | predicted channel slope is 7.99, not 19.86 |
| is 3.5 nearer 7.99 or 1.00? | nearer 1.00 | but not by much |
| wait for more traffic? | yes | the only correct answer |
Row two is Section 5's known defect and it changes the reading. The threshold of 5.00 was chosen against a predicted 19.86; with buckets 1 and 4 the prediction is 7.99 and the midpoint is about 4.5. A slope of 3.5 is then weak evidence for logic, not the strong evidence the fixed threshold implies — and the fix is to compute the prediction from the buckets in use.
Complaint 4 — "the FCS error rate doubled but the link is unchanged."
| Check | If yes | Meaning |
|---|---|---|
| did the traffic's size mix change? | yes | the rate follows the mix |
| did the slope change? | no | the fault is the same fault |
| did the per-bucket rates change? | no | nothing about the link moved |
| what changed? | more large frames | and large frames fail 23.7× as often |
An aggregate error rate is a weighted average over the size distribution, so a traffic mix that shifts towards large frames doubles the reported rate with no change in the channel at all. The per-bucket rates are invariant under that change and the aggregate is not — which is the same argument Chapter 20.4 §19 made about denominators, in a different vocabulary.
Complaint 5 — "we see corruption in host memory and the FCS never fails."
| Check | If yes | Meaning |
|---|---|---|
| is the corruption after the check point? | likely | Section 2 — outside the span |
| how many hops? | three | three spans, none end-to-end |
| does any switch store and forward? | yes | its buffer is unprotected |
| is the escape bound plausible? | 13.8 years per port | so it is not an escape |
Row four is the arithmetic that rules out the explanation everybody reaches for. An undetected error is once every 13.8 years per port; corruption seen weekly is not a 2^-32 event. It is unprotected memory somewhere in the path — a switch's buffer, a DMA, a host page — and Section 2's span table says which stages those are.
Complaint 6 — "the equivalence probe passes and frames still fail."
| Check | If yes | Meaning |
|---|---|---|
probe_complete? | no | some residue was not tested |
residue_tested[63]? | no | the spill class, and it is the hard one |
| what sizes did the probe use? | round numbers | which reach two residues, not sixty-four |
| add one frame of 1 475 octets | residue 63 is covered | Chapter 19.3 §20's size |
Sixty-three of sixty-four is 98.4% and zero per cent of the path that matters — and a round-number suite does far worse than that.
| Size | Residue (L − 4) mod 64 |
|---|---|
| 64 | 60 |
| 128 | 60 |
| 256 | 60 |
| 512 | 60 |
| 1 024 | 60 |
| 1 518 | 42 |
Six sizes, two residues. The five powers of two all land on residue 60 because they are congruent modulo 64, so the classic suite exercises 2 of the engine's 64 paths — 3.1% — and reaches residue 63 never. The 23 lengths that do are 67, 131, 195 and so on to 1 475, and no test suite anybody writes contains one.
Complaint 7 — "the residue histogram is completely flat and the engine is definitely broken."
| Check | If yes | Meaning |
|---|---|---|
| is the error rate about 50%? | yes | a barrel control fault — Section 8 |
| are exactly half the bins high? | look again | flat and bimodal look alike on a bar chart |
| sort the bins by value | two clusters | the finding |
is comb_detected low? | yes, correctly | it tests for one peak, not two clusters |
A correction-barrel fault affects every residue whose binary representation has a particular bit set, which is thirty-two of sixty-four — so the histogram is two flat plateaus rather than a comb, and a test looking for a single spike reports nothing. Sorting the bins makes it obvious in one step, and a chi-squared statistic over the 64 bins detects both shapes. The bins are already exported; the test belongs in software.
Complaint 8 — "the diagnosis says the check engine and we replaced the whole ASIC."
| Check | If yes | Meaning |
|---|---|---|
| did the new part behave the same? | yes | it is not a manufacturing defect |
| is it the same RTL? | yes | so it is a logic bug, in every part ever made |
| which residue? | 63 | the spill class |
| does a firmware path exist? | sometimes | a cut-through disable, an MTU change |
A fault in one residue path is a design bug and not a part bug, so every device from that mask set has it. Replacing the part changes nothing, and the useful responses are a workaround — avoid the 23 frame lengths, which usually means an MTU change — or an erratum. Section 8's comb is the evidence that distinguishes a design bug from a defective unit, and it is the only evidence available without a lab.
And the three symptoms this chapter is systematically blamed for:
| Symptom | Blamed on | Usually is |
|---|---|---|
| FCS errors on a good cable | the cable | four sites inside the chip |
| a rate that changed with no cause | the link | the traffic's size mix |
| corruption with a valid check | the check sequence | a stage outside its span |
23. Misconceptions
Misconception 1 — "CRC errors mean a bad cable."
The wrong model: the check sequence protects the frame across the medium, so a failing check means the medium damaged it.
What it costs: c_crc_errors moves for five sites and four of them are inside the chip — the PHY's lane alignment, the xMII, the parser's offsets and the check engine's own logic. Replacing a cable is right about one time in five, and the four wrong times each consume a maintenance window. On a FEC-protected link it is worse than one in five, because the channel's errors are corrected before the MAC sees them.
The corrected model: a failing check narrows the space to five and the slope narrows it to one. Section 4's ratio is 19.86 for the channel and 1.00 for every logic fault, a factor of twenty, measurable on counters that already exist.
Misconception 2 — "a valid check sequence means the frame is correct."
The wrong model: the FCS is an integrity guarantee, so a frame that passes it is intact.
What it costs: the span begins at Chapter 19.3 §4's append point and ends at the check. Eight of the twelve stages in the path are outside it — the host's memory, the transmit DMA, the receive FIFO, the receive DMA, host memory — and a switch that regenerates the check sequence starts a new span at every hop. A bit flipped in a store-and-forward switch's buffer leaves with a perfectly valid check sequence over corrupt data.
The corrected model: a matching check says these octets are the octets that were present when the value was appended. Not that they were right then; not that they survive afterwards. Section 2's table is which stages are covered and which are not.
Misconception 3 — "undetected errors are why we see corruption."
The wrong model: CRC-32 escapes at 2^-32, so some corruption is inevitable and unexplained corruption is that.
What it costs: the arithmetic does not support it. At 100 Gb/s, 1 518-octet frames, a bit error rate of 10⁻¹⁰: one escape every 13.8 years per port. Corruption seen weekly is not a 2^-32 event, and attributing it to one stops the investigation at the only explanation that requires no action.
The corrected model: compute the escape rate before invoking it. It rises with the error rate and with the fleet size — ten thousand ports is twelve hours — and it does not rise with the traffic's frame size at all, because the two size effects cancel. When the number does not support the explanation, the corruption is in an unprotected stage.
Misconception 4 — "a bigger slope means a worse channel."
The wrong model: the slope is a measure of the fault's severity.
What it costs: the slope is 23.72 at a bit error rate of 10⁻¹² and 23.72 at 10⁻⁸ — a factor of ten thousand in link quality, identical slope. It says what kind of fault; it says nothing about how bad. A team that escalates on the slope escalates on a number that does not move with severity, and one that ignores the absolute rate ignores the only number that does.
The corrected model: two numbers. The slope decides channel-or-logic; the absolute rate in any bucket decides how urgent. Together they are a diagnosis and a priority, and neither alone is either.
Misconception 5 — "CRC-32 catches all burst errors up to 32 bits, so bursts are fine."
The wrong model: the burst guarantee covers the damage a real link produces.
What it costs: it did at 1 Gb/s, where 8B/10B damage is eight to ten bits. At 25 Gb/s and above, Reed-Solomon FEC either corrects a codeword or fails it entirely, and a failed codeword is damage across thousands of bits — far outside the guarantee, where detection falls back to 1 − 2^-32. The damage mode a high-rate link produces is exactly the one the guarantee does not cover.
The corrected model: the guarantee is a function of the damage's shape, and the shape is set by the line coding. Section 9's clustering test says which regime a link is in, and the escape bound applies only in one of them.
Misconception 6 — "our conformance suite covers the check engine."
The wrong model: a suite of 64, 128, 256, 512, 1 024 and 1 518 octets exercises the frame sizes that matter.
What it costs: those six sizes reach two of Chapter 19.4 §4's sixty-four residue paths — 3.1% — because the five powers of two are all congruent modulo 64 and land on residue 60. Residue 63, the spill class where the check value crosses a beat boundary, is the engine's hardest path and is reached by none of them. A fault there fails 1.56% of production frames forever.
The corrected model: cover the residues, not the sizes. Sixty-four frames, one per class, is Chapter 19.4 §14's equivalence check run to completion — and Chapter 20.1 §15's residues_count == 64 is the coverage goal that says so.
And the same argument one step further, because it explains why the goal was written in the first place.
| Frames needed | |
|---|---|
| six round sizes | 6, reaching 2 residues |
| a uniform sweep to all 64 residues | Chapter 20.1 §6's 304 frames |
| the same with a 50% weight on the spill set | 172 |
| the coupon-collection ceiling | 86 |
Three hundred and four frames reaches every residue path in the engine, which is a fraction of a second of traffic at any rate — and the six-size suite that reaches two has been the industry default for thirty years. The goal in Chapter 20.1 §15's five-bit list exists because somebody counted the paths and then counted what a conventional suite reaches, and the gap between 64 and 2 is the entire justification.
24. Interview Questions
Question 1 — "The CRC error counter is climbing. Where do you look?"
What the answer should establish: five places, and four of them are inside the chip. A strong answer immediately narrows: c_alignment_errors separates the three physical sites from the two logical ones, and frames_in separates those counted at the port from those that were not. The strongest answer names the measurement that finishes the job: cross the errors with the RMON size histogram and read the slope — 19.86 for the channel, 1.00 for anything else.
Question 2 — "Why is the error rate proportional to frame length for a channel fault?"
What the answer should establish: each bit is an independent opportunity to fail, so a frame of L octets fails with probability about 8·L·p. The ratio between 1 518 and 64 octets is exactly 23.72 — the length ratio — at every bit error rate, which is what makes it usable: you do not need to know p. A strong answer contrasts: every logic fault is per-frame and predicts 1.00, a factor of twenty away.
Question 3 — "What does a valid frame check sequence guarantee?"
What the answer should establish: a span, not a frame. These octets are the octets that were present at the transmitter's append point; nothing about whether they were right then, and nothing about what happens after the check. A strong answer names what is outside: the host's memory, both DMA paths, the receive FIFO — eight of twelve stages — and a new span at every store-and-forward hop. The strongest answer draws the operational conclusion: end-to-end integrity needs an end-to-end check.
Question 4 — "How often does a corrupted frame get through with a valid check?"
What the answer should establish: frames per second, times the probability the frame is damaged, times 2^-32. At 100 Gb/s with 1 518-octet frames and a bit error rate of 10⁻¹⁰ that is one every 13.8 years per port. A strong answer notes what moves it: a hundred times worse channel is fifty days; ten thousand ports is twelve hours; the frame size does not move it at all, because the two size effects cancel exactly.
Question 5 — "Your check engine has a fault in one residue path. What do you see?"
What the answer should establish: a comb. Chapter 19.4 §4's residue is (L − 4) mod 64, so the failing frames are those whose length is congruent to a fixed value modulo 64 — 23 of the 1 455 legal sizes, evenly spaced, 1.56% of uniform traffic. A strong answer says why it survives: a round-number test suite reaches two residues of sixty-four, so the fault passes conformance and fails in production forever.
Question 6 — "Which of the five sites can you test directly?"
What the answer should establish: only the check engine, and only with a reference implementation present. Chapter 19.4 §14's equivalence checker against the shipped engine, one frame per residue class, sixty-four frames, and site 6 is settled with no statistics at all. A strong answer notes the catch: shipped silicon does not carry a second engine, so in the field site 6 is diagnosed by Section 7's histogram — 64 counters instead of 8 512 XOR terms, which is the trade the whole chapter rests on.
25. Questions and Answers
26. What's Next
Module 21 has six chapters left and this one has set the pattern for all of them: take a class from Chapter 21.2's table, find the measurements that separate its causes, and price them.
| Chapter | Takes | The separation it needs |
|---|---|---|
| Chapter 21.4 | a link that never comes up | a fault space with no frames in it |
| Chapter 21.5 | negotiation | Chapter 11.4's asymmetric symptoms |
| Chapter 21.6 | classes 8 to 12 | the five Chapter 21.1 could not separate |
| Chapter 21.7 | the DMA path | Chapter 19.6's descriptors |
| Chapter 21.8 | a healthy link that is slow | no errors at all, which is a different problem |
| Chapter 21.9 | the capture | step 6, and what it cannot show |
Chapter 21.4 is the interesting inversion. Every measurement in this chapter is a statistic over frames; a link that will not come up has no frames, so the whole apparatus of slopes, combs and clustering is unavailable. The evidence there is the PHY's state machine and the counters behind MDIO — Chapter 4.5 — which is the one source this chapter kept needing and kept noting was unread.
Chapter 21.8 is the other inversion and the harder one. A link with no errors that is nonetheless slow has no error counter to read at all; the evidence is Chapter 8.3's efficiency arithmetic, Chapter 19.5's occupancy and Chapter 19.6's outstanding counts — a completely different instrument set for a complaint that arrives through the same ticket queue.
And the series is now ninety-six classes long. Chapter 20.2 §8's six-group taxonomy has taken four extensions in six chapters, and the last four — 93, 94, 95 and 96 — form the eighth group Chapter 21.2 §26 named: a property bound to the wrong side of a function. A width at a seam; a search against its sensors; a classification's output against its input; and now a detector's verdict against the object rather than the span. Four members in four consecutive chapters is not a coincidence — it is what happens when a track stops writing properties about a design and starts writing them about the instruments that observe one.
Continue learning
Related tutorials
- Related topic
Frame Check Sequence
The check sequence protects a range, and the range is shorter than the frame's journey — appended at one point in a transmitter, verified at one point in the next receiver, and recomputed at every hop, so a device's own memory is covered by nothing the frame carries.
- 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
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.
