Ethernet · Module 6
What Error Detection Must Guarantee
A detector's specification is a set of bounded guarantees plus a probability, and neither can be stated without an error model — including the guarantee everybody cites and this polynomial does not provide.
Chapter 5.8 established exactly what the frame check sequence covers — a range of octets, between an append point and a check point, one hop at a time. It stopped short of what the check guarantees, and deliberately: the guarantee is a different kind of question, and answering it needs something the last chapter did not have.
It needs an error model.
"The CRC detects errors" is the sentence everybody knows and it is not a specification. It does not say which errors, or how many, or with what certainty, or over what length — and every one of those qualifiers turns out to matter, because for each of them there is a real class of corruption that passes the check.
A specification looks like this instead: all bursts up to 32 bits; all error patterns of weight three or fewer at any standard frame length, and of weight four at frames up to about 375 octets; and for everything else, a residual probability of about one in four billion. That is a claim somebody can check, budget against, and be wrong about.
Notice what is missing from that list, because almost every account of the Ethernet CRC includes it: all odd numbers of bit errors. That guarantee does not hold for this polynomial, and Section 3 shows why in one line of arithmetic.
And it cannot be stated at all without first saying what kind of errors the link produces, because the guarantee is conditional on the error's shape rather than on its size.
1. Scope — What This Chapter Owns
This chapter owns the error model and the shape of the guarantee. What actually goes wrong on a real link, how those failures are distributed, what a detector can promise deterministically and what only probabilistically, why frame length enters the promise, and what "undetected error probability" means as a number a design can budget against.
It does not own the mathematics, and the separation is strict. How CRC-32 is generated as polynomial division in a shift register is Chapter 6.2. The residue property that lets a receiver check without comparing is Chapter 6.3. Building a byte- or word-wide parallel engine from the serial definition is Chapter 6.4. This chapter uses one property of the arithmetic — stated, not derived — in Section 13, and nothing else.
Chapter 5.8 owns the covered range, and everything here assumes it: the guarantees below apply to corruption inside that range, between the append point and the check point. Corruption outside it is not detected at any probability, because the mechanism never looks.
Chapter 3.7 owns forward error correction. This chapter takes its output as an input — Section 10 is about what FEC does to the error model that reaches the CRC, which is a different question from how FEC works.
The question this chapter answers that its neighbours do not: what would it mean for a detector to be adequate, stated precisely enough that a design could fail to meet it?
2. What Actually Goes Wrong
Three modules of this track have described physical links. Collect what they found, because the collection is the error model.
Random, independent bit errors are the textbook case and the least representative. They arise where thermal noise dominates and the channel is otherwise clean — a regime that a well-designed link at moderate rate does operate in, and a marginal one does not.
Correlated errors from a marginal channel. Chapter 3.3 showed the eye closing under intersymbol interference, crosstalk and reflections. When the eye is marginal, the bits that fail are not independent: the same pattern-dependent effect that closed the eye for one bit closes it for its neighbours. Errors arrive in clusters, and the cluster length is set by the channel's memory, not by chance.
Symbol errors that flip several bits at once. Chapter 3.6's PAM4 carries two bits per symbol, so a symbol decided one level wrong corrupts two adjacent bits together — and with a sensible mapping, usually exactly one of them. A four-level decision has three thresholds and the outer levels are more robust than the inner ones, so the errors are not even uniformly distributed across symbols.
A residue left by correction. Chapter 3.7 showed forward error correction removing the overwhelming majority of channel errors. What it leaves behind is not a thinned-out version of what entered — it is what happens when a codeword exceeds the correction capability, which typically corrupts a whole block rather than a bit.
And errors that are not the channel's at all. Chapter 5.8 §5 established that a frame's octets pass through memory before the append point and after the check point. Those errors are single-bit, uncorrelated with anything on the wire, and outside the covered range entirely.
3. The Guarantee, Stated Properly
Take the deterministic guarantees one at a time, because each has a different reason and a different bound.
All bursts up to 32 bits. A burst is a corruption confined to a span, and a check of m bits detects every single burst of length m or less, at any position and in a frame of any length. The bound is the number of check bits — which is why it is 32 and not 31 or 40, and why the shortest error pattern that can slip through is 33 bits long, a fact Section 13 turns into a directed test.
All patterns of weight up to dmin − 1, within a length. The code's minimum distance — the smallest number of bit flips that turns one valid frame into another valid frame — sets how many independent errors are always caught, and it falls as the protected length rises. Section 7 gives the profile; the headline is that every Ethernet frame gets weight 3, and frames up to roughly 375 octets get weight 4.
And then the line that is not a guarantee at all. Any pattern outside those classes is detected with probability approximately 1 − 2⁻³², because the check value is 32 bits and a corrupted frame's value is effectively an arbitrary one. About one such pattern in four billion produces a matching value, and the frame is delivered as correct.
That number is small and it is not zero, and the difference between small and zero is what the rest of this chapter is about.
4. Why a Probability Is the Right Form of the Answer
Engineers are uncomfortable with a mechanism whose specification is a probability, and the discomfort is worth addressing directly, because it drives the rejected property of Section 13.
There is no detector with a finite check that catches everything. A check of n bits partitions all possible frames into 2ⁿ classes; two different frames in the same class are indistinguishable to it. Since there are vastly more possible frames than classes, collisions exist by counting, and no cleverness in the choice of function removes them.
So the design question was never "can we catch everything". It was: given that some corrupted frames will pass, how often, and is that acceptable?
And that question has an answer that can be budgeted. Take a link, a frame rate, a bit error rate, and a fraction of errors falling outside the deterministic guarantees, and the expected number of undetected corrupt frames per year falls out — a number to compare against what the system above can tolerate.
Illustrative, and the arithmetic is the point rather than the values. Suppose a link carries 10⁹ frames per day, and suppose one frame in 10⁶ arrives corrupted. That is 10³ corrupted frames per day, of which — for those outside the deterministic classes — about 2⁻³² pass, giving roughly one undetected frame every four thousand days.
Now change one input. Raise the corrupted-frame rate to one in 10⁴ — a link that is degrading but still working, with an error rate a hundred times worse — and the interval falls to about forty days. The undetected rate is linear in the error rate, so a channel that gets a hundred times worse produces a hundred times more silently corrupt frames, and the frames that are detected are the only symptom anybody sees.
5. RTL 1 — An Injector Parameterised by Error Model
// VERIFICATION COMPONENT. NOT FOR SYNTHESIS.
//
// Injects errors according to a SELECTABLE model, because the whole
// argument of this chapter is that the model changes the answer.
//
// Almost every verification environment injects uniform independent bit
// errors. That model is the one a real link least resembles, and it is
// also the model under which a check sequence looks strongest -- so the
// default choice flatters the mechanism in exactly the way that matters.
package errmodel_pkg;
typedef enum logic [2:0] {
EM_UNIFORM, // independent bit flips -- the textbook model
EM_BURST, // a contiguous run, length drawn from a range
EM_SYMBOL, // pairs of adjacent bits, as PAM4 produces
EM_BLOCK, // a whole block corrupted, as a FEC failure produces
EM_ADVERSARIAL // a pattern chosen to be undetectable (Section 13)
} err_model_e;
endpackage
module error_injector
import errmodel_pkg::*;
#(
parameter int unsigned MAX_FRAME_BITS = 12144,
parameter int unsigned BURST_MIN = 2,
parameter int unsigned BURST_MAX = 40,
parameter int unsigned BLOCK_BITS = 5140
) (
input err_model_e model,
// Errors per million bits. The SAME rate across models, so that the
// comparison isolates the model's shape from its intensity.
input int unsigned rate_ppm,
input int unsigned frame_bits,
output bit [MAX_FRAME_BITS-1:0] error_mask,
output int unsigned injected_weight,
output int unsigned injected_span
);
function automatic void build();
int unsigned start;
int unsigned len;
int unsigned n;
error_mask = '0;
injected_weight = 0;
injected_span = 0;
case (model)
EM_UNIFORM: begin
// Independent flips. Weight is binomial; span is the whole frame.
for (int unsigned i = 0; i < frame_bits; i++)
if (($urandom_range(999_999, 0)) < rate_ppm) begin
error_mask[i] = 1'b1;
injected_weight++;
end
injected_span = (injected_weight == 0) ? 0 : frame_bits;
end
EM_BURST: begin
// A contiguous run. Note BURST_MAX exceeds 32 deliberately: a
// generator capped at the guarantee can never produce the case
// the guarantee does not cover.
len = $urandom_range(BURST_MAX, BURST_MIN);
start = $urandom_range(frame_bits - len, 0);
for (int unsigned i = 0; i < len; i++) begin
error_mask[start + i] = 1'b1;
injected_weight++;
end
injected_span = len;
end
EM_SYMBOL: begin
// PAM4: a mis-decided symbol corrupts two ADJACENT bits. Note the
// consequence -- a handful of symbol errors reaches weight 8 while
// spanning the whole frame, so neither the burst bound nor the
// low-weight bound covers it.
n = 1 + ($urandom_range(3, 0));
for (int unsigned k = 0; k < n; k++) begin
start = $urandom_range(frame_bits - 2, 0) & ~32'd1;
error_mask[start] = 1'b1;
error_mask[start + 1] = 1'b1;
injected_weight += 2;
end
injected_span = frame_bits;
end
EM_BLOCK: begin
// A FEC codeword that exceeded its correction capability. The
// residue is a long, dense corruption -- far beyond the 32-bit
// burst guarantee, which is the point.
start = $urandom_range((frame_bits > BLOCK_BITS) ? (frame_bits - BLOCK_BITS) : 0, 0);
len = (frame_bits < BLOCK_BITS) ? frame_bits : BLOCK_BITS;
for (int unsigned i = 0; i < len; i++)
if ($urandom_range(1, 0)) begin
error_mask[start + i] = 1'b1;
injected_weight++;
end
injected_span = len;
end
EM_ADVERSARIAL: begin
// Filled by the directed test of Section 13, which constructs a
// pattern the check provably cannot detect.
injected_span = 33;
end
default: ;
endcase
endfunction
always_comb build();
endmoduleClassification: verification component, not for synthesis.
What it teaches: that the injection model is a parameter of the experiment and is usually left at its least representative setting. At a realistic error rate, uniform independent flips produce almost entirely weight-one and weight-two patterns — which sit deep inside the low-weight guarantee and are caught with certainty. So a suite built on uniform injection reports essentially perfect detection, and reports it because of the stimulus, not because of the design.
Deliberately simplified: the models are caricatures of the physics. EM_SYMBOL flips two adjacent bits every time, where a real PAM4 error usually flips one; EM_BLOCK corrupts uniformly at random within a block. The caricature preserves the property that matters — weight parity and span — which is what the guarantees are sensitive to.
Production implication: BURST_MAX is 40, above the 32-bit guarantee, and that single choice is the difference between a suite that tests the guarantee and one that tests inside it. A generator whose range stops at the bound can never produce a case the bound does not cover, so every burst it makes is caught, and the environment concludes the mechanism is stronger than it is. It is the same trap Chapter 5.8 §13 named for injection confined to the covered range.
6. RTL 2 — Measuring the Residual, Rather Than Assuming It
// SYNTHESIZABLE INSTRUMENTATION.
//
// Estimates the UNDETECTED error rate, which is the number a check
// sequence's specification is actually made of.
//
// It cannot be computed from the check alone -- a receiver that could
// tell a corrupted frame from a clean one without the check would not
// need the check. So this module takes an INDEPENDENT truth input:
//
// in a lab, from the injector that created the error;
// in production, from an end-to-end integrity check above this layer.
//
// A design without either cannot measure its own residual, and a design
// that reports one anyway has computed it from an assumption.
module undetected_error_estimator
#(
parameter int unsigned CNT_W = 40
) (
input logic clk,
input logic rst_n,
input logic clear,
input logic frame_done,
input logic fcs_ok,
// Independent truth. Held low when unavailable, which is the common
// case and must be distinguishable from "the frame was fine".
input logic truth_valid,
input logic truth_corrupt,
output logic [CNT_W-1:0] c_frames,
output logic [CNT_W-1:0] c_detected, // corrupt and caught
output logic [CNT_W-1:0] c_undetected, // corrupt and PASSED
output logic [CNT_W-1:0] c_false_alarm, // clean and rejected
output logic [CNT_W-1:0] c_unknown, // no independent truth
// Undetected frames per billion corrupt frames, in the window that
// closed. The residual, expressed so it can be compared against the
// 2^-32 the code predicts -- about 0.23 per billion.
output logic [CNT_W-1:0] residual_per_billion,
output logic residual_valid,
output logic undetected_seen // sticky
);
logic [CNT_W-1:0] win_corrupt_q, win_missed_q;
logic [19:0] win_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
c_frames <= '0;
c_detected <= '0;
c_undetected <= '0;
c_false_alarm <= '0;
c_unknown <= '0;
win_corrupt_q <= '0;
win_missed_q <= '0;
win_q <= '0;
residual_per_billion <= '0;
residual_valid <= 1'b0;
undetected_seen <= 1'b0;
end else begin
residual_valid <= 1'b0;
if (clear) begin
c_frames <= '0;
c_detected <= '0;
c_undetected <= '0;
c_false_alarm <= '0;
c_unknown <= '0;
// undetected_seen deliberately survives. A single undetected
// corrupt frame is not a statistic -- it is the event the whole
// mechanism exists to make rare, and it must not be cleared away.
end else if (frame_done) begin
c_frames <= c_frames + 1'b1;
if (!truth_valid) begin
// NOT counted as a pass. Nothing independent looked at it.
c_unknown <= c_unknown + 1'b1;
end else if (truth_corrupt && !fcs_ok) begin
c_detected <= c_detected + 1'b1;
win_corrupt_q <= win_corrupt_q + 1'b1;
end else if (truth_corrupt && fcs_ok) begin
c_undetected <= c_undetected + 1'b1;
win_corrupt_q <= win_corrupt_q + 1'b1;
win_missed_q <= win_missed_q + 1'b1;
undetected_seen <= 1'b1;
end else if (!truth_corrupt && !fcs_ok) begin
// A clean frame rejected. NOT a property of the code -- the
// code has no false alarms. This is a check that is broken,
// or a range that disagrees (Chapter 5.8 §10).
c_false_alarm <= c_false_alarm + 1'b1;
end
end
if (&win_q) begin
residual_per_billion <= (win_corrupt_q == '0) ? '0
: ((win_missed_q * 1_000_000_000) / win_corrupt_q);
residual_valid <= 1'b1;
win_corrupt_q <= '0;
win_missed_q <= '0;
win_q <= '0;
end else begin
win_q <= win_q + 1'b1;
end
end
end
endmoduleClassification: synthesizable instrumentation.
What it teaches: that c_false_alarm should always be zero, and a counter for it is still worth having. A CRC has no false alarms — a clean frame produces a matching value by construction, so a rejection of an uncorrupted frame is never the code's behaviour. It is a checker fault: a covered range that disagrees, an initial-value convention mismatch, a bit-ordering error. A counter that must read zero is a conformance check disguised as a statistic, and it costs one register.
Deliberately simplified: the truth input is assumed reliable. In a lab it is; in production it is an end-to-end check that has its own residual, so the measurement is really a comparison between two detectors and inherits the weaker one's blind spots.
Production implication: c_unknown is the largest counter in almost every deployment, and refusing to fold it into "passed" is the module's most important design decision. A frame that passed with no independent verification has not been shown correct — it has been shown that the one mechanism looking at it did not object, which is the same distinction Chapter 5.8 §12 drew with its unverified-pass counter. Reporting a residual computed over frames that include unknowns produces a number that is too good and cannot be corrected later.
7. Length Changes the Guarantee
One guarantee depends on length and one does not, and it is worth being exact about which.
The burst guarantee is unconditional. Thirty-two check bits detect every single burst of 32 bits or fewer, wherever it falls and however long the frame. Length does not enter.
The weight guarantee depends on length, through the code's minimum distance — how many independent bit flips can be applied before one valid frame becomes another valid frame. The published distance profile for the standard 802.3 CRC-32 is a staircase, and here is the part of it that Ethernet frames occupy:
| Codeword length n (bits) | Frame size | dmin | Independent errors always detected |
|---|---|---|---|
| 512 – 3006 | 64 – 375 octets | 5 | 4 |
| 3007 – 12144 | 376 – 1518 octets | 4 | 3 |
| beyond, to about 91 600 bits | up to about 11 450 octets | 4 | 3 |
Read the third row against the second, because it settles a question raised earlier in the track. A 9018-octet jumbo frame is 72 144 bits — still inside the distance-four region. Raising the frame ceiling to jumbo sizes does not drop the weight guarantee below three; the distance stays at four until roughly 11 450 octets, which is beyond any deployed jumbo value.
So the length effect is real and it is one step, not a slide. The step happens at about 375 octets, well below the standard maximum — which means the frames that lose the fourth-error guarantee are ordinary full-size frames, not jumbo ones.
And there is a second published result worth having, from the same analysis: for codeword lengths from 512 to 12 144 bits — every standard Ethernet frame from the minimum to the maximum — the undetected error probability is bounded by 2⁻³² for any bit error probability, not merely for small ones. That is a stronger statement than the usual hand-wave, and it is what makes the budget arithmetic of Section 4 legitimate rather than approximate.
8. RTL 3 — A Budget, Not a Counter
// SYNTHESIZABLE INSTRUMENTATION.
//
// Maintains a BUDGET for undetected corruption, derived from what the
// link is actually doing.
//
// The chain, stated so each step is visible:
//
// detected corrupt frames per window <- observed
// x the code's miss ratio (2^-32) <- the code's property
// = expected undetected frames <- a PREDICTION
//
// Comparing the prediction against an independently measured residual is
// how a wrong error model is discovered. A model that says errors are
// uniform and independent predicts one number; a channel producing
// correlated bursts produces another; and the discrepancy is the finding.
module detection_budget_monitor
#(
parameter int unsigned CNT_W = 48,
// 2^-32 expressed per billion, rounded. Illustrative precision: the
// point is the RATIO, not the third significant figure.
parameter int unsigned MISS_PER_BILLION = 1,
parameter int unsigned WINDOW_BITS = 24
) (
input logic clk,
input logic rst_n,
input logic clear,
input logic frame_done,
input logic fcs_bad,
// Independently measured, from Section 6, where available.
input logic residual_valid,
input logic [CNT_W-1:0] residual_per_billion,
output logic [CNT_W-1:0] win_frames,
output logic [CNT_W-1:0] win_detected,
// The prediction: expected undetected frames per billion frames, given
// the observed detected rate.
output logic [CNT_W-1:0] predicted_undetected_ppb,
output logic prediction_valid,
// Set when the measured residual exceeds the prediction by more than a
// factor of two. NOT a fault -- a statement that the error model in use
// does not describe this link.
output logic model_disagrees,
output logic [CNT_W-1:0] worst_detected_ppb
);
logic [WINDOW_BITS-1:0] win_q;
logic [CNT_W-1:0] f_q, d_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
win_q <= '0;
f_q <= '0;
d_q <= '0;
win_frames <= '0;
win_detected <= '0;
predicted_undetected_ppb <= '0;
prediction_valid <= 1'b0;
model_disagrees <= 1'b0;
worst_detected_ppb <= '0;
end else begin
prediction_valid <= 1'b0;
if (clear) begin
f_q <= '0;
d_q <= '0;
// worst_detected_ppb and model_disagrees deliberately survive.
end else if (frame_done) begin
f_q <= f_q + 1'b1;
if (fcs_bad) d_q <= d_q + 1'b1;
end
if (&win_q) begin
automatic logic [CNT_W-1:0] det_ppb =
(f_q == '0) ? '0 : ((d_q * 1_000_000_000) / f_q);
win_frames <= f_q;
win_detected <= d_q;
// The detected rate, scaled by the code's miss ratio. This is the
// number that should be showing up in Section 6's residual.
predicted_undetected_ppb <= (det_ppb * CNT_W'(MISS_PER_BILLION)) / 1_000_000_000;
prediction_valid <= 1'b1;
if (det_ppb > worst_detected_ppb) worst_detected_ppb <= det_ppb;
// A measured residual well above the prediction means the errors
// reaching the check are not the shape the model assumed -- which
// is a finding about the CHANNEL, discovered from the detector.
if (residual_valid &&
(residual_per_billion >
(2 * ((det_ppb * CNT_W'(MISS_PER_BILLION)) / 1_000_000_000))))
model_disagrees <= 1'b1;
f_q <= '0;
d_q <= '0;
win_q <= '0;
end else begin
win_q <= win_q + 1'b1;
end
end
end
endmoduleClassification: synthesizable instrumentation.
What it teaches: that a prediction turns a counter into a hypothesis that can be wrong. A detected-error count on its own is a number that goes up. Multiplied by the code's miss ratio it becomes a claim — this many corrupt frames should be getting through — and a claim can be compared against measurement. A model that does not describe the link produces a discrepancy, and there is no other way for a wrong model to announce itself, because the model's failures are precisely the frames nothing detects.
Deliberately simplified: a fixed miss ratio and a factor-of-two threshold. Both are illustrative; a real design derives the ratio from the code and the frame-length distribution, and sets the threshold from how noisy the residual measurement is.
Production implication: worst_detected_ppb survives clear for the reason Chapter 5.6 §8 gave for its smallest-frame register. The worst error rate a link has ever shown is the input to a risk calculation — it says how bad the undetected rate got at its peak — and a routine counter clear must not erase the only record of it.
9. Correction Changes the Model, Not Just the Rate
Read the middle of that figure carefully, because the intuitive reading is wrong.
The intuitive reading is that correction thins the error population — the same kinds of errors, far fewer of them. What actually happens is that correction removes the errors it can handle entirely, and leaves only the events that exceeded its capability.
Those events are qualitatively different. A codeword with more errors than the code can correct does not emerge slightly wrong; it emerges as a block-length corruption, dense and long. So the errors arriving at the check sequence are:
- far rarer — by many orders of magnitude;
- far longer — spanning a whole correction block rather than a few bits;
- of unconstrained weight parity — so the odd-weight guarantee applies to only about half of them.
And the burst guarantee is the one that suffers. It covers bursts up to 32 bits. A correction block is far longer than that, so a post-correction residue is exactly the case the deterministic guarantee does not reach — leaving the 2⁻³² probability as the only protection.
Which produces the summary worth carrying: forward error correction makes undetected corruption much rarer and no longer deterministically excluded. The rate falls by orders of magnitude; the guarantee weakens from "always caught" to "caught four billion times out of four billion and one".
That is an excellent trade and it is a trade. A design that assumes correction makes the check sequence redundant has it backwards: correction is what makes the check sequence's probabilistic regime the operative one.
10. RTL 4 — Correlating Before and After Correction
// SYNTHESIZABLE INSTRUMENTATION.
//
// Watches the relationship between the PRE-correction error rate and the
// frames the check sequence rejects, because the two together say
// something neither says alone:
//
// pre-FEC rate rising, CRC rejections flat -> the channel is degrading
// and correction is still absorbing it. THE EARLY WARNING.
//
// pre-FEC rate high, CRC rejections rising -> correction capability is
// being exceeded. The margin is gone.
//
// pre-FEC rate low, CRC rejections rising -> the corruption is NOT
// coming from the channel. Chapter 5.8's uncovered regions, or a
// range mismatch with a peer.
//
// That third row is the one a design without this correlation cannot
// reach, and it is the one that saves the most time.
module pre_post_fec_correlator
#(
parameter int unsigned CNT_W = 40,
parameter int unsigned WINDOW_BITS = 22
) (
input logic clk,
input logic rst_n,
input logic clear,
// From the FEC decoder (Chapter 3.7): symbols it had to correct.
input logic fec_symbol_corrected,
input logic fec_uncorrectable,
input logic frame_done,
input logic fcs_bad,
output logic [CNT_W-1:0] win_corrected,
output logic [CNT_W-1:0] win_uncorrectable,
output logic [CNT_W-1:0] win_fcs_bad,
output logic win_valid,
// The three conclusions above, decoded rather than left to a reader.
output logic channel_degrading,
output logic margin_exhausted,
output logic corruption_not_from_channel
);
logic [WINDOW_BITS-1:0] win_q;
logic [CNT_W-1:0] c_q, u_q, b_q;
logic [CNT_W-1:0] prev_c_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
win_q <= '0; c_q <= '0; u_q <= '0; b_q <= '0; prev_c_q <= '0;
win_corrected <= '0; win_uncorrectable <= '0; win_fcs_bad <= '0;
win_valid <= 1'b0;
channel_degrading <= 1'b0;
margin_exhausted <= 1'b0;
corruption_not_from_channel <= 1'b0;
end else begin
win_valid <= 1'b0;
if (clear) begin
c_q <= '0; u_q <= '0; b_q <= '0;
channel_degrading <= 1'b0;
margin_exhausted <= 1'b0;
corruption_not_from_channel <= 1'b0;
end else begin
if (fec_symbol_corrected) c_q <= c_q + 1'b1;
if (fec_uncorrectable) u_q <= u_q + 1'b1;
if (frame_done && fcs_bad) b_q <= b_q + 1'b1;
end
if (&win_q) begin
win_corrected <= c_q;
win_uncorrectable <= u_q;
win_fcs_bad <= b_q;
win_valid <= 1'b1;
// Rising correction load with the check still clean: the channel
// is getting worse and nothing has been lost yet. This is the
// window in which action is cheap.
channel_degrading <= (c_q > prev_c_q) && (b_q == '0);
// Correction is failing, and the check is now the thing catching
// what gets through.
margin_exhausted <= (u_q != '0) && (b_q != '0);
// Rejections with no correction load at all. The corruption did
// not come from the channel this decoder is watching.
corruption_not_from_channel <= (b_q != '0) && (c_q == '0) && (u_q == '0);
prev_c_q <= c_q;
c_q <= '0; u_q <= '0; b_q <= '0; win_q <= '0;
end else begin
win_q <= win_q + 1'b1;
end
end
end
endmoduleClassification: synthesizable instrumentation.
What it teaches: that the useful signal is the pre-correction rate, and it is the one that gets discarded. A design reporting only what the check sequence rejected sees nothing at all until correction has already failed — which is Chapter 3.7's argument, and this module is where it connects to the check sequence. channel_degrading is the output with the most value and the fewest consequences: it fires while everything still works.
Deliberately simplified: a single-window comparison for the trend. A real design keeps several windows and requires a sustained rise, because a single window's variation is noise.
Production implication: corruption_not_from_channel is the row a design cannot reach without both inputs, and it is worth the wiring on its own. Check-sequence rejections with no correction activity mean the corruption happened somewhere the decoder never saw — inside a device, in one of Chapter 5.8 §11's uncovered regions, or in a peer whose covered range disagrees. Every one of those has a fix that has nothing to do with the link, and without this correlation all of them look like link errors.
11. RTL 5 — Making the Claim Explicit
// SYNTHESIZABLE CONFORMANCE CHECKER.
//
// A design's detection claim is usually implicit: it uses CRC-32, so it
// "detects errors". This module makes the claim EXPLICIT, as parameters,
// and then checks the design's configuration against it.
//
// The point is not that the checks are hard. It is that stating the claim
// forces somebody to notice when the configuration stops supporting it --
// which happens quietly, when a frame ceiling is raised or a check is
// moved, and never announces itself.
module detector_claim_checker
#(
// THE CLAIM, stated as parameters so it appears in the source.
parameter int unsigned CLAIM_BURST_BITS = 32,
parameter int unsigned CLAIM_WEIGHT = 3,
parameter int unsigned CLAIM_LENGTH_BITS = 12144, // 1518 octets
parameter int unsigned CLAIM_MISS_PER_BILLION = 1,
// THE CONFIGURATION, which may drift away from the claim.
parameter int unsigned CHECK_BITS = 32,
parameter int unsigned MAX_FRAME_BITS = 12144
) (
input logic clk,
input logic rst_n,
input logic frame_done,
input logic [15:0] frame_octets,
output logic frame_within_claim,
output logic claim_exceeded_seen, // sticky
output logic [15:0] largest_beyond_claim
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
frame_within_claim <= 1'b1;
claim_exceeded_seen <= 1'b0;
largest_beyond_claim <= '0;
end else if (frame_done) begin
// A frame longer than the length the weight guarantee was claimed
// for is NOT an error. It is a frame protected to a weaker
// standard than the design's stated claim, and the distinction is
// the reason this reports rather than rejects.
frame_within_claim <= ((frame_octets * 8) <= 16'(CLAIM_LENGTH_BITS));
if ((frame_octets * 8) > 16'(CLAIM_LENGTH_BITS)) begin
claim_exceeded_seen <= 1'b1;
if (frame_octets > largest_beyond_claim)
largest_beyond_claim <= frame_octets;
end
end
end
// The burst guarantee IS the number of check bits. A claim above it is
// arithmetic that does not hold, and it is exactly the kind of thing a
// specification document asserts and nothing verifies.
// synopsys translate_off
a_burst_claim_supported: assert final (CLAIM_BURST_BITS <= CHECK_BITS)
else $fatal(1, "claimed burst detection exceeds the check width");
// A design that accepts frames longer than its claim covers is not
// wrong -- but somebody should have decided that deliberately.
a_length_claim_consistent: assert final (MAX_FRAME_BITS <= CLAIM_LENGTH_BITS)
else $warning("frame ceiling exceeds the length the weight claim covers");
// synopsys translate_on
endmoduleClassification: synthesizable conformance checker.
What it teaches: that a claim written as a parameter can be checked and a claim written in a document cannot. The failure this catches is entirely mundane: somebody raises the frame ceiling for jumbo frames — a Chapter 5.7 change, made for throughput reasons, by someone not thinking about error detection — and the design's weight guarantee silently now covers a fraction of the frames it used to.
Deliberately simplified: the length claim is a single number. The real relationship between length and guaranteed weight is a table, and a rigorous version would carry it and report the guarantee per frame rather than a boolean.
Production implication: frame_within_claim reports and does not reject, and getting that wrong would be worse than not having the module. A frame longer than the claim is not invalid — it is protected to a weaker standard, which is a decision somebody may legitimately have made. Rejecting it would break every jumbo deployment in the name of a document, and the correct behaviour is to make the consequence visible so the decision is informed rather than accidental.
12. Assertions — About Guarantees, Not About Outcomes
// ---------------------------------------------------------------------
// P1 -- THE UNCONDITIONAL ONE. Every single burst within the check width
// is detected, at any frame length and any position. This is the only
// detection guarantee with no length qualifier on it.
//
// NOTE what is deliberately NOT here: a property that every odd-weight
// error is detected. That guarantee requires a generator divisible by
// (x+1), and the 802.3 generator has fifteen terms -- odd parity -- so it
// is not. Section 3 shows the arithmetic and Section 13 constructs an
// undetected odd-weight counterexample.
// ---------------------------------------------------------------------
property p_burst_within_width_detected;
@(posedge clk) disable iff (!rst_n)
(result_valid && truth_corrupt &&
(injected_span != 0) && (injected_span <= CHECK_BITS)) |-> !fcs_ok;
endproperty
a_burst_within_width_detected: assert property (p_burst_within_width_detected)
else $error("a burst within the check width passed -- the check is not CRC-32");
// ---------------------------------------------------------------------
// P2 -- Every burst within the claimed span is detected.
// ---------------------------------------------------------------------
property p_burst_within_span_detected;
@(posedge clk) disable iff (!rst_n)
(result_valid && truth_corrupt &&
(injected_span <= CLAIM_BURST_BITS) && (injected_span != 0)) |-> !fcs_ok;
endproperty
a_burst_within_span_detected: assert property (p_burst_within_span_detected);
// ---------------------------------------------------------------------
// P3 -- Every error of weight within the claim, at a length within the
// claim, is detected. Note BOTH bounds: the property is false if either
// is dropped -- weight 4 holds only to about 375 octets (Section 7).
// ---------------------------------------------------------------------
property p_low_weight_detected_within_length;
@(posedge clk) disable iff (!rst_n)
(result_valid && truth_corrupt &&
(injected_weight <= CLAIM_WEIGHT) &&
(frame_bits <= CLAIM_LENGTH_BITS)) |-> !fcs_ok;
endproperty
a_low_weight_detected_within_length: assert property (p_low_weight_detected_within_length);
// ---------------------------------------------------------------------
// P4 -- NO FALSE ALARMS. A clean frame never fails. This one IS absolute,
// and a violation means the checker is broken -- not the code.
// ---------------------------------------------------------------------
property p_clean_frame_never_fails;
@(posedge clk) disable iff (!rst_n)
(result_valid && truth_valid && !truth_corrupt) |-> fcs_ok;
endproperty
a_clean_frame_never_fails: assert property (p_clean_frame_never_fails)
else $error("a clean frame was rejected -- checker fault, not code behaviour");
// ---------------------------------------------------------------------
// P5 -- The false-alarm counter must stay at zero. A statistic that is a
// conformance check.
// ---------------------------------------------------------------------
property p_no_false_alarms_counted;
@(posedge clk) disable iff (!rst_n)
(c_false_alarm == '0);
endproperty
a_no_false_alarms_counted: assert property (p_no_false_alarms_counted);
// ---------------------------------------------------------------------
// P6 -- A frame with no independent truth is counted as UNKNOWN, never
// as a pass. The blind spot must not be folded into the good population.
// ---------------------------------------------------------------------
property p_unknown_not_counted_as_pass;
@(posedge clk) disable iff (!rst_n)
(frame_done && !truth_valid)
|=> ($stable(c_detected) && $stable(c_undetected) &&
(c_unknown == $past(c_unknown) + 1));
endproperty
a_unknown_not_counted_as_pass: assert property (p_unknown_not_counted_as_pass);
// ---------------------------------------------------------------------
// P7 -- The four outcome classes partition every frame with truth.
// ---------------------------------------------------------------------
property p_outcomes_partition;
@(posedge clk) disable iff (!rst_n)
(frame_done && truth_valid) |=> (counter_sum == $past(counter_sum) + 1);
endproperty
a_outcomes_partition: assert property (p_outcomes_partition);
// ---------------------------------------------------------------------
// P8 -- An undetected frame is sticky. It is not a statistic; it is the
// event the mechanism exists to make rare.
// ---------------------------------------------------------------------
property p_undetected_is_sticky;
@(posedge clk) disable iff (!rst_n)
undetected_seen |=> undetected_seen;
endproperty
a_undetected_is_sticky: assert property (p_undetected_is_sticky);
// ---------------------------------------------------------------------
// P9 -- The residual is a fraction of the corrupt population, so it can
// never exceed a billion per billion.
// ---------------------------------------------------------------------
property p_residual_bounded;
@(posedge clk) disable iff (!rst_n)
residual_valid |-> (residual_per_billion <= CNT_W'(1_000_000_000));
endproperty
a_residual_bounded: assert property (p_residual_bounded);
// ---------------------------------------------------------------------
// P10 -- The prediction is derived from the DETECTED rate, so a window
// with no detections predicts nothing.
// ---------------------------------------------------------------------
property p_no_detections_no_prediction;
@(posedge clk) disable iff (!rst_n)
(prediction_valid && (win_detected == '0)) |-> (predicted_undetected_ppb == '0);
endproperty
a_no_detections_no_prediction: assert property (p_no_detections_no_prediction);
// ---------------------------------------------------------------------
// P11 -- model_disagrees is a statement about the MODEL, so it may only
// be set when an independent residual was available to compare against.
// ---------------------------------------------------------------------
property p_disagreement_needs_measurement;
@(posedge clk) disable iff (!rst_n)
$rose(model_disagrees) |-> $past(residual_valid);
endproperty
a_disagreement_needs_measurement: assert property (p_disagreement_needs_measurement);
// ---------------------------------------------------------------------
// P12 -- The three correlator conclusions are mutually exclusive.
// ---------------------------------------------------------------------
property p_correlator_conclusions_exclusive;
@(posedge clk) disable iff (!rst_n)
win_valid |=> $onehot0({channel_degrading, margin_exhausted,
corruption_not_from_channel});
endproperty
a_correlator_conclusions_exclusive: assert property (p_correlator_conclusions_exclusive);
// ---------------------------------------------------------------------
// P13 -- "Corruption not from the channel" requires NO correction
// activity. With any correction load the conclusion is unsupported.
// ---------------------------------------------------------------------
property p_not_from_channel_requires_quiet_fec;
@(posedge clk) disable iff (!rst_n)
corruption_not_from_channel |-> ((win_corrected == '0) && (win_uncorrectable == '0));
endproperty
a_not_from_channel_requires_quiet_fec: assert property (p_not_from_channel_requires_quiet_fec);
// ---------------------------------------------------------------------
// P14 -- A frame beyond the claimed length is REPORTED, never rejected.
// Rejecting it would break every jumbo deployment in the name of a
// document.
// ---------------------------------------------------------------------
property p_beyond_claim_is_reported_not_rejected;
@(posedge clk) disable iff (!rst_n)
!frame_within_claim |-> !frame_rejected_by_claim_checker;
endproperty
a_beyond_claim_is_reported_not_rejected: assert property (p_beyond_claim_is_reported_not_rejected);
// ---------------------------------------------------------------------
// P15 -- Extremes and sticky flags survive a counter clear.
// ---------------------------------------------------------------------
property p_extremes_survive_clear;
@(posedge clk) disable iff (!rst_n)
clear |=> ($stable(undetected_seen) && $stable(worst_detected_ppb) &&
$stable(claim_exceeded_seen));
endproperty
a_extremes_survive_clear: assert property (p_extremes_survive_clear);
// ---------------------------------------------------------------------
// P16 -- COVERAGE. Every error model exercised. An environment that only
// ran EM_UNIFORM has tested the mechanism in its most flattering regime.
// ---------------------------------------------------------------------
c_all_models_seen: cover property (
@(posedge clk) disable iff (!rst_n) all_error_models_exercised
);
// ---------------------------------------------------------------------
// P17 -- COVERAGE. A burst LONGER than the guarantee. If this never
// happened, P2 was never tested at its boundary.
// ---------------------------------------------------------------------
c_burst_beyond_guarantee: cover property (
@(posedge clk) disable iff (!rst_n)
(result_valid && truth_corrupt && (injected_span > CLAIM_BURST_BITS))
);
// ---------------------------------------------------------------------
// P18 -- COVERAGE. An error of weight ABOVE the claim. Uniform injection
// at a realistic rate produces weight one and two almost exclusively, so
// this bucket stays empty unless the stimulus is built for it.
// ---------------------------------------------------------------------
c_weight_beyond_claim_seen: cover property (
@(posedge clk) disable iff (!rst_n)
(result_valid && truth_corrupt && (injected_weight > CLAIM_WEIGHT))
);13. Verification — Twenty-Two Scenarios and an Error That Is Provably Invisible
| # | Scenario | Stimulus | What must be observed |
|---|---|---|---|
| 1 | Clean frame | no injection, truth says clean | fcs_ok; c_false_alarm stays zero (P4) |
| 2 | Single bit error | weight 1, any frame size | detected — inside every guarantee |
| 3 | Three bit errors | weight 3, 1518-octet frame | detected (P3) — dmin is 4 here |
| 4 | Four bit errors, short frame | weight 4, 300-octet frame | detected — dmin is 5 below ~375 octets |
| 5 | Four bit errors, full frame | weight 4, 1518-octet frame | not guaranteed — the step at 375 octets |
| 6 | Burst of 8 | contiguous span 8 | detected (P2) |
| 7 | Burst of exactly 32 | contiguous span 32 | detected — the boundary of the guarantee |
| 8 | Burst of 33 | contiguous span 33 | not guaranteed — the first span outside the promise |
| 9 | Burst of 40 | span 40 | not guaranteed; c_burst_beyond_guarantee covered (P17) |
| 10 | PAM4 symbol error | two adjacent bits, span 2 | detected — inside both the burst and weight bounds |
| 11 | Four symbol errors | weight 8, spread across the frame | outside every deterministic class (P18) |
| 12 | FEC block residue | EM_BLOCK, span 5140 | far outside the burst guarantee |
| 13 | Error outside the covered range | corrupt the preamble | fcs_ok — Chapter 5.8's range, not a detection failure |
| 14 | Jumbo frame, weight 3 | 9018 octets, three errors | detected — dmin is still 4 at this length; frame_within_claim low |
| 15 | Odd-weight counterexample | the constructed 33-bit pattern, weight 15 | passes — the odd-weight claim is false (Section 3) |
| 16 | No independent truth | truth_valid low | c_unknown increments; nothing else does (P6) |
| 17 | Residual measurement | 10⁹ corrupt frames, mixed models | residual_per_billion near the predicted value |
| 18 | Budget prediction | steady detected rate | predicted_undetected_ppb tracks the detected rate linearly |
| 19 | Model disagreement | inject only EM_BLOCK at high rate | measured residual exceeds the prediction; model_disagrees sets |
| 20 | Channel degrading | rising correction load, no rejections | channel_degrading high; the other two low (P12) |
| 21 | Corruption not from the channel | rejections with zero correction activity | corruption_not_from_channel high (P13) |
| 22 | Clear behaviour | assert clear after all of the above | counters zero; undetected_seen, worst_detected_ppb, claim_exceeded_seen survive (P15) |
14. Debugging — What an Error Rate Is Telling You
Symptom — check-sequence rejections rising, with no correction activity at all.
The corruption is not coming from the channel. Section 10's corruption_not_from_channel decodes exactly this, and the candidates are all off the wire: corruption in one of Chapter 5.8 §11's uncovered regions, a peer whose covered range disagrees with yours, or a checker configured with the wrong convention. The confirming test is the difference pattern — a dense one, with about half the bits set, means two implementations were never computing the same thing.
Symptom — correction load climbing steadily and no rejections.
The best possible signal, and the one most likely to be ignored because nothing is failing. The channel is degrading and correction is absorbing it. Act here, while the cost is a scheduled inspection rather than an outage; once margin_exhausted sets, the margin is gone and frames are already being lost.
Symptom — an application reports rare, unexplained data corruption and every link counter is clean.
Two candidates and they are distinguished by the counters that are not clean elsewhere. If some link on the path shows check-sequence rejections, this may be the 2⁻³² residual — compute Section 8's budget from that link's detected rate and see whether the observed rate of application-level corruption is in the same range. If no link anywhere shows rejections, the budget predicts essentially zero undetected frames, and the corruption is in one of the uncovered regions instead — Chapter 5.8 §11's elimination applies and the links are ruled out.
Symptom — the measured residual is far above the predicted one.
model_disagrees, and it is a finding about the channel, not the detector. The errors arriving are not the shape the prediction assumed — most often because they are long bursts from correction failures rather than scattered bit errors, and long bursts fall outside the deterministic guarantees while the scattered low-weight errors the prediction assumed do not. The fix is not to the check sequence; it is to whatever is producing block-level failures.
Symptom — a jumbo deployment and no change in error counters.
Expected, and it does not mean the guarantee is unchanged. claim_exceeded_seen from Section 11 will be set, and largest_beyond_claim names the size. The weakening is in a guarantee, not in a rate — the number of independent errors always caught fell, while the probabilistic protection did not move at all. Nothing will show it in a counter, which is precisely why the claim is worth stating as a parameter.
Symptom — a verification suite reports perfect detection and the field does not.
Check the injection model before anything else. At any realistic rate, uniform independent bit errors produce almost entirely weight-one and weight-two patterns, which sit deep inside the low-weight guarantee — so the suite reports perfection because of the stimulus. Check the burst generator's range too: one whose maximum stops at 32 can never produce a case the guarantee does not cover, and 33 is the length at which escapes are twice as likely as anywhere else.
15. Common Misconceptions
"CRC-32 detects all odd numbers of bit errors."
The wrong model: an unconditional guarantee that holds at any length, offered as reassurance whenever the other guarantees look uncomfortable.
What it costs: it is the guarantee people lean on hardest and it is not there. A design that believes it will conclude that a class of errors — including several shapes a real channel produces — is impossible rather than merely rare, and will not budget for them at all.
The corrected model: that guarantee requires a generator divisible by (x + 1), which holds exactly when the polynomial has an even number of terms. The 802.3 generator has fifteen — odd — so it is not, and Section 13 constructs an undetected error of weight fifteen to prove it. The polynomial is primitive instead, which buys length reach at each minimum distance. It is a deliberate trade, not an oversight.
"Detects errors" is a specification.
The wrong model: naming the mechanism describes the guarantee.
What it costs: two links running identical hardware, one clean and one marginal, are protected to different standards by the same four octets — and nothing in the specification says so. There is no number to design against and no way to be wrong.
The corrected model: a detector's specification is a set of deterministic guarantees with bounds attached, plus a probability for everything outside them — and it cannot be stated without an error model, because the guarantees are conditional on the error's shape, not on its size.
"A frame that passes the check is intact."
The wrong model: the check is a decision procedure.
What it costs: Section 12's rejected property, and then the reasoning it licenses — that an end-to-end integrity check above is unnecessary, that the residual need not be measured, and that silent corruption need not be budgeted for at all.
The corrected model: about one corrupted frame in four billion passes, outside the deterministic classes. No finite check catches everything: a check of n bits partitions all frames into 2ⁿ classes, there are vastly more frames than classes, and collisions exist by counting rather than by weakness.
"FEC makes the CRC redundant."
The wrong model: correction removes the errors, so the check has nothing left to do.
What it costs: precisely backwards reasoning about which regime you are in. Correction removes the errors the check would have caught deterministically and leaves the ones it can only catch probabilistically.
The corrected model: correction removes the errors it can handle entirely and leaves the events that exceeded its capability — which emerge as block-length corruptions, far beyond the 32-bit burst guarantee. The rate falls by orders of magnitude and the guarantee weakens from "always caught" to 2⁻³². Correction is what makes the check sequence's probabilistic regime the operative one.
"Injecting random bit errors tests the detector."
The wrong model: uniform independent flips are a neutral stimulus.
What it costs: a suite that reports perfect detection because of its stimulus rather than its design. At any realistic rate, uniform injection produces almost entirely weight-one and weight-two patterns, which sit deep inside the guarantee.
The corrected model: the injection model is a parameter of the experiment, and it should include the shapes the channel actually produces — correlated clusters, paired symbol errors, block-length residues — plus bursts longer than 32 bits. A burst generator capped at the guarantee can never produce a case the guarantee does not cover.
16. Interview Reasoning
"What does the Ethernet CRC guarantee?"
The weak answer is "it detects errors". The answer that ends the topic gives the guarantees with their bounds: every single burst up to 32 bits, at any length; every error of weight three at any standard frame size, and weight four below about 375 octets; and about 2⁻³² for everything else. The payoff is what is absent: the commonly cited "all odd numbers of bit errors" does not hold here, because that requires a generator divisible by (x + 1) and the 802.3 polynomial has fifteen terms.
"Why is the undetected error probability the right specification?"
Because no finite check catches everything, and the reason is counting rather than weakness: n check bits partition all frames into 2ⁿ classes, two frames in a class are indistinguishable, and there are far more frames than classes. So the design question was never "can we catch everything" but "how often do we not, and is that acceptable". The strong close is that the number is budgetable — frame rate, error rate and the miss ratio give an expected count of silently corrupt frames per year, which is a figure a system can be designed against.
"Does FEC make the frame check sequence redundant?"
No, and the interesting part is why not. Correction removes the errors it can handle completely and leaves the codewords that exceeded its capability — which emerge as long, dense, block-length corruptions rather than as thinned-out bit errors. Those are exactly the shape the 32-bit burst guarantee does not cover. So correction makes undetected corruption much rarer and no longer deterministically excluded, moving the check sequence from its deterministic regime into its probabilistic one.
"Would you assert that a frame passing its check is uncorrupted?"
No, and the danger is not that it fails. It passes — about four billion times out of four billion and one — so no achievable amount of stimulus distinguishes it from a true property, and its green result is then read as corrupted frames do not get through. That justifies not building the end-to-end check, not measuring the residual, and not budgeting for silent corruption. The right shape is the deterministic guarantees with their bounds written into the properties, plus a measured residual and a budget — because a probability is a number to compare against, not a property to assert.
17. Understanding Check
Because it names the mechanism and states no guarantee.
A usable specification has two parts. The deterministic guarantees, each with a bound: every single burst up to 32 bits at any length; every error of weight three at standard frame sizes; every error of weight four below about 375 octets. And a probability for everything outside them — about 2⁻³².
And neither part can be stated without an error model, because the guarantees are conditional on the error's shape rather than its size. A detector that catches every burst up to 32 bits is excellent on a channel producing bursts of 20 and provides no deterministic guarantee at all on one producing bursts of 40.
Which is the uncomfortable consequence: two links running identical hardware, one clean and one marginal, are protected to different standards by the same four octets — and the specification that says "CRC-32" does not distinguish them.
The model is the half nobody states, and it is the half that changes between deployments.
18. What's Next
The claim this chapter defended: "detects errors" is not a specification, and the specification that replaces it is a set of bounded guarantees plus a probability — neither of which can be stated without an error model.
The guarantees are exact and they are narrower than the folklore. Every single burst up to 32 bits, at any length. Every error of weight three at any standard frame size, and weight four below about 375 octets. And not every odd number of bit errors — that guarantee needs a generator divisible by (x + 1), and the 802.3 polynomial has fifteen terms, so it is absent by construction and a 33-bit counterexample can be built in four steps.
Everything else is caught about four billion times out of four billion and one. That number is small, not zero, linear in the link's error rate, and therefore budgetable — which makes the detected error rate worth trending as a free proxy for the silent failures nobody can see. And forward error correction does not remove the problem: it removes the errors that were deterministically excluded and leaves the ones that are not.
Chapter 6.2 — CRC-32 Generation turns to the mechanism this chapter has been specifying. It builds the check from polynomial division in a shift register, and its subject is the part that makes independent implementations disagree: the initial value, the reflection of input and output bits, and the final complement. None of those affect any guarantee in this chapter — they are conventions, not properties — and every one of them will make two correct engines produce different values for the same frame.
Which is the failure Chapter 5.8 §7 was already able to recognise without knowing its cause: a dense difference pattern, roughly half the bits set, appearing at 100% against one peer and 0% against every other. 6.2 supplies the cause.
The full path is on the Ethernet curriculum index.
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
Forward Error Correction
FEC converts a gradual degradation into a cliff and hides the gradient behind it. The pre-correction error rate is the link's health metric and gives months of warning; the corrected output reads zero until the moment it collapses, and can be silently wrong when a decoder miscorrects.
- Related topic
CRC-32 Generation
The arithmetic is a shift register performing polynomial division. The four conventions wrapped around it — initial value, input reflection, output reflection, final complement — change no guarantee and every value, which is where interoperability fails.
- Related topic
CRC Checking and the Residue
A receiver feeds the data and the check sequence through one division and tests for a fixed constant — no held value, no captured FCS, no need to know where the payload ended. And that constant is a fingerprint of all four conventions at once.
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.
