Skip to content
VLSI Mentor

Ethernet · Module 3

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.

Chapter 3.6 ended owing a debt it could not pay. PAM4 costs about 9.54 dB of vertical margin by arithmetic that no circuit recovers, its least significant bit is roughly twice as error-prone as its most significant, and Chapter 3.5 §12 showed a self-synchronising descrambler tripling whatever the channel delivered before anything downstream sees it.

The result is a link with a raw error rate far above what earlier generations tolerated, and the shortfall has to be recovered somewhere.

Forward error correction is that somewhere. Add redundancy at the transmitter, and the receiver can not merely detect a limited number of errors but repair them, with no retransmission and no round trip.

But describing FEC as "it fixes errors" misses the thing that matters operationally, and misses it in a way that has cost people real outages.

Before FEC, a link had one error rate, and it was both the health indicator and the verdict. Errors rose gradually as margin fell, and the number you measured told you how the link was doing.

With FEC there are two error rates, and they behave completely differently. The rate entering the decoder still tracks the physical link's health, degrading smoothly as margin erodes. The rate leaving it is essentially zero — until the errors exceed what the code can repair, at which point it becomes catastrophic almost immediately.

FEC converts a slope into a cliff, and then hides the slope behind it.

What does forward error correction actually buy, what does it cost, and why is the corrected error rate the wrong thing to monitor?

1. Scope — What This Chapter Owns

This chapter owns: what redundancy buys and in what units; Reed-Solomon at the symbol level including a small but genuinely correct field implementation; the two Ethernet codes and why there are two; where FEC sits relative to the PCS of Chapter 3.4; the latency floor derived rather than asserted; the pre-correction and post-correction distinction as an operational discipline; miscorrection; and the bypass decision.

This chapter does not own: the algebra of decoding. Berlekamp-Massey, Chien search and Forney's algorithm are a coding-theory subject, and Section 9 models the decoder's verdict interface rather than pretending to implement them — with that boundary stated in the source. Nor does it own the block codes (Chapter 3.5) or the modulation that made FEC necessary (Chapter 3.6). Alignment marker interaction with codeword boundaries belongs to Chapter 3.8.

The debts it repays: Chapter 3.1 §3 named the LDPC code in 10GBASE-T without explaining what a code of that kind does. Chapter 3.4 §9 named FEC placement as deferred. Chapter 3.5 §13 argued that error correction became the cheaper purchase than a longer block. Chapter 3.6 §13 showed why PAM4 makes it mandatory. All four converge here.

2. What FEC Changes

The single most important diagram in this chapter is a comparison of two curves, and it is worth stating in prose before any code appears.

Without FEC, as a link's margin erodes — a channel ageing, a connector loosening, temperature rising — the bit error rate climbs smoothly. Ten to the minus twelve, then minus eleven, then minus ten. Every one of those is measurable, every one is a warning, and the progression takes weeks or months.

With FEC, the same physical degradation happens and the same raw errors arrive. But as long as the errors in each codeword stay at or below t, every one of them is repaired, and the corrected output has an error rate so low it is effectively unmeasurable. The monitoring system reports a perfect link.

Then the raw rate crosses the point where codewords routinely contain more than t errors, and the corrected rate goes from unmeasurable to catastrophic over a very small change in the underlying physics.

Physical degradation raises the raw error rate smoothly. That raw rate is visible as the pre-correction error rate, which gives gradual warning. The decoder repairs up to t errors per codeword, so the post-correction rate stays at zero until the raw rate crosses the correction threshold, after which it collapses. Monitoring only the post-correction rate provides no warning.Margin erodesageing, heat, a looseconnectorRaw errors risesmoothly, over weeksPre-FEC ratethe health metric — aslopeMonths of warningact before anything failsDecoder repairsup to t errors percodewordPost-FEC ratethe verdict — a cliffNo warning at allzero, then the link isdown12
Figure 1 — the same degradation, seen from two places, behaving completely differently.

Read the two rows as two monitoring strategies. The top row is a link operator who instrumented the pre-correction rate and has been watching it climb for six weeks. The bottom row is one who instrumented the corrected output and is being paged at three in the morning.

Both are looking at the same link. The physics is identical; only the measurement point differs.

3. Reed-Solomon at the Symbol Level

Reed-Solomon operates on symbols, not bits, and that choice is the whole reason it suits a high-rate serial link.

A symbol is a group of m bits treated as a single element of a finite field. Ethernet's RS-FEC uses m = 10, so a symbol is 10 bits and the field has 1024 elements.

The code is written RS(n, k): k data symbols in, n total symbols out, with n − k parity symbols appended. Because the code is systematic, the k data symbols appear unchanged in the codeword and the parity is added — which means a receiver that trusts the link can simply take the data and skip decoding entirely.

The correction capability is exact:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
t = (n − k) / 2

Two parity symbols per correctable symbol error, and the reason is worth understanding rather than memorising: one to locate the error and one to determine its value. A decoder that already knows where the errors are — from some outside indication — can repair twice as many, which is a real technique and is why erasure information is valuable.

4. The Two Ethernet Codes, and Why There Are Two

Chapter 3.6 §13 introduced them as a price list. Here they are properly.

KR4KP4
codeRS(528, 514)RS(544, 514)
data symbols514514
parity symbols1430
correctable symbol errors t715
symbol size10 bits10 bits
targeted atNRZ modulationPAM4 modulation
clauseIEEE 802.3 Clause 91Clause 91, and 802.3cd Clause 134

Both carry the same 514 data symbols. The entire difference is parity — 14 against 30 — and therefore correction capability: 7 against 15.

Why two codes exist rather than one: because the raw error rate they face differs, and matching the code to the rate is the whole engineering decision. KR4 is targeted at NRZ, whose eyes are full height. KP4 is targeted at PAM4, whose eyes are one third that and whose least significant bit is twice as error-prone. More than twice the correction capability, for a modulation with substantially more raw errors — the clause structure recording exactly the trade Chapter 3.6 derived.

And the overhead arithmetic is worth having:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
KR4: 528/514 = 1.02724  ->  about 2.72%
KP4: 544/514 = 1.05837  ->  about 5.84%

5. Where FEC Sits, and Why

Chapter 3.4 §9 built a PCS pipeline and named FEC's placement as deferred. Here is the answer, and the reasoning matters more than the position.

FEC must sit below the block code and above the serialiser.

Below the block code, because the decoder needs to repair errors before anything tries to interpret the bits. Chapter 3.5 §9's alignment search treats an invalid pattern as evidence about offset; if uncorrected errors reach it, it interprets channel noise as evidence the alignment is wrong and can lose a lock that was correct.

Above the serialiser, because the codeword is a structure over symbols, and below the PMA there are no symbols — only bits on lanes, and then physical levels. Chapter 3.4 §3's criterion applies directly: FEC operates on the symbol unit, so it belongs on the side of the boundary where symbols exist.

On receive, the deserialiser produces symbols, the FEC decoder repairs them, the descrambler follows, and only then does the block decoder interpret the bits and the alignment search run. FEC sits below the block code because errors must be repaired before anything interprets the bits, and above the serialiser because a codeword is a structure over symbols.PMAbits become symbolsFEC decoderepair, before anythingparsesDescrambleafter decode, not beforeBlock decodelegality, control,boundariesToward the MACoctets, believedSymbols exist herea codeword needs themAlignment runshereand must not see rawerrors12
Figure 2 — below the code so errors are gone before anything parses, above the serialiser because symbols exist there.

But there is a complication that placement alone does not resolve, and it is the interaction with the scrambler.

6. The Latency Cost, Derived

FEC's cost is not only overhead. It is latency, and the floor is structural rather than implementation-dependent.

The reason is simple and unavoidable: syndromes are computed over the whole codeword. Every one of the n symbols contributes, so the decoder cannot begin until the last symbol has arrived. That is a store-and-decode structure, and it imposes a floor of at least one codeword time before any data emerges.

The derivation, with each term named:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
codeword bits   = n × m
codeword time   = (n × m) / lane_rate
total FEC delay ≈ codeword time (fill)
                + decode pipeline
                + codeword time (drain, transmit side)

An illustrative calculation for KP4:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
n = 544 symbols, m = 10 bits  ->  5,440 bits per codeword
 
At a lane rate of 26.5625 Gb/s:
  codeword time = 5440 / 26.5625e9 = 2.048e-7 s = about 205 ns

Roughly 205 nanoseconds to fill one codeword, and a comparable time on the transmit side, plus the decoder's own pipeline. These are illustrative figures computed from the stated formula at a stated lane rate; the actual latency of any implementation depends on its lane count, its pipelining and its clock, and normative latency limits are clause-specific.

What is not illustrative is the shape: latency scales with codeword length, and codeword length is what buys correction capability. You cannot have more correction without more delay.

7. RTL 1 — Finite Field Arithmetic, Correctly

Reed-Solomon is arithmetic in a finite field, and there is no way to model it honestly without the field. So this section implements one correctly — small enough to be verifiable by hand, and structurally identical to the one Ethernet uses.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Genuine GF(2^M) arithmetic.
//
// This is real finite-field arithmetic, not an approximation. It is shown in
// GF(2^4) so every one of the 16 elements can be enumerated and checked by
// hand; Ethernet's RS-FEC uses GF(2^10) with 10-bit symbols, and the ONLY
// differences are M and the reduction polynomial -- both parameters here.
//
// Why a field at all: Reed-Solomon's correction guarantee rests on the fact
// that a polynomial of degree d over a field has at most d roots. That
// theorem needs a FIELD -- addition, multiplication, and multiplicative
// inverses for every non-zero element. Ordinary integer arithmetic modulo
// 16 is not a field (4 has no inverse), and the code would not work over it.
package gf_pkg;
  parameter int unsigned M = 4;                    // bits per symbol
  parameter int unsigned N_ELEM = (1 << M) - 1;    // 15 non-zero elements
 
  // Primitive polynomial x^4 + x + 1, as the low M bits of 0b1_0011.
  // For GF(2^10), Ethernet uses a degree-10 primitive polynomial instead.
  parameter logic [M-1:0] POLY = 4'b0011;
 
  typedef logic [M-1:0] gf_t;
endpackage
 
module gf_mul
  import gf_pkg::*;
(
  input  gf_t a,
  input  gf_t b,
  output gf_t product
);
 
  // Carry-less multiply, then reduce modulo the primitive polynomial.
  //
  // ADDITION IN GF(2^M) IS XOR. There is no carry, which is why this is
  // combinational and cheap in hardware -- and why finite-field arithmetic
  // suits a high-rate pipeline in a way integer arithmetic would not.
  always_comb begin
    automatic gf_t acc = '0;
    automatic gf_t tmp = a;
 
    for (int unsigned i = 0; i < M; i++) begin
      if (b[i]) acc = acc ^ tmp;
      // Multiply tmp by x, reducing if it overflows the field.
      if (tmp[M-1]) tmp = gf_t'((tmp << 1) ^ POLY);
      else          tmp = gf_t'(tmp << 1);
    end
 
    product = acc;
  end
 
endmodule
 
 
// SYNTHESIZABLE. Powers of the primitive element alpha.
//
// Syndrome computation evaluates the received polynomial at alpha^i for
// i = 0 .. 2t-1, so those powers are needed repeatedly. A small table is
// the right structure; for GF(2^10) it is 1023 entries, still a table.
module gf_alpha_pow
  import gf_pkg::*;
(
  input  logic [$clog2(N_ELEM+1)-1:0] exponent,
  output gf_t                         value
);
 
  // alpha = x = 4'b0010 under this polynomial. Successive powers, generated
  // by repeated multiplication by x with reduction -- the same operation
  // gf_mul performs, unrolled into a constant table.
  always_comb begin
    case (exponent % N_ELEM)
      0:  value = 4'h1;  1:  value = 4'h2;  2:  value = 4'h4;
      3:  value = 4'h8;  4:  value = 4'h3;  5:  value = 4'h6;
      6:  value = 4'hC;  7:  value = 4'hB;  8:  value = 4'h5;
      9:  value = 4'hA;  10: value = 4'h7;  11: value = 4'hE;
      12: value = 4'hF;  13: value = 4'hD;  14: value = 4'h9;
      default: value = 4'h1;
    endcase
  end
 
endmodule

Classification: synthesizable, mathematically correct.

What it teaches: that Reed-Solomon needs a field, not merely arithmetic, and why. The correction guarantee rests on a polynomial of degree d having at most d roots — a theorem that requires multiplicative inverses for every non-zero element. Integers modulo 16 do not form a field, and the code simply would not work over them.

Deliberately simplified: GF(2⁴) rather than GF(2¹⁰), so the whole field fits in a table a reader can check. The structure, the reduction step, and the XOR-as-addition property are identical at any width.

Production implication: addition in GF(2^m) is XOR — no carry, no propagation delay across the width. That is why finite-field arithmetic pipelines so well at high rates, and it is a large part of why Reed-Solomon is practical at 400 Gb/s at all. A designer who expects field arithmetic to be as expensive as integer arithmetic will over-budget the area substantially.

Later ownership: the decoder's algebra — Berlekamp-Massey, Chien search, Forney — is a coding-theory subject and is explicitly outside this chapter's scope. Section 9 states that boundary in the source.

8. RTL 2 — The Systematic Encoder

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Systematic Reed-Solomon encoder, RS(15,11) over GF(2^4).
//
// SYSTEMATIC means the k data symbols pass through UNCHANGED and the n-k
// parity symbols are appended. That property is what makes Section 12's
// bypass path possible: a receiver that trusts the link can take the data
// symbols directly and never invoke the decoder at all.
//
// The structure is polynomial division. The generator polynomial is
//   g(x) = (x - alpha^0)(x - alpha^1) ... (x - alpha^(2t-1))
// and the parity is the remainder of the message (shifted up by n-k)
// divided by g(x). An LFSR whose taps are g's coefficients computes that
// remainder as the message streams through, one symbol per cycle.
module rs_encoder
  import gf_pkg::*;
#(
  parameter int unsigned N = 15,          // total symbols
  parameter int unsigned K = 11,          // data symbols
  parameter int unsigned PARITY = N - K,  // 4 -> t = 2
  // Generator coefficients for t=2 over this field, low order first.
  // These are the coefficients of (x-a^0)(x-a^1)(x-a^2)(x-a^3).
  parameter gf_t G0 = 4'hC,
  parameter gf_t G1 = 4'h3,
  parameter gf_t G2 = 4'h1,
  parameter gf_t G3 = 4'h4
) (
  input  logic clk,
  input  logic rst_n,
 
  input  logic start,          // begin a new codeword
  input  logic in_valid,
  input  gf_t  in_symbol,      // one of K data symbols
 
  output logic out_valid,
  output gf_t  out_symbol,     // data passes through, then parity
  output logic out_is_parity,
  output logic codeword_done
);
 
  gf_t  lfsr [PARITY];
  logic [$clog2(N+1)-1:0] count_q;
  logic                   in_parity_q;
 
  gf_t feedback_c;
  gf_t mul0_c, mul1_c, mul2_c, mul3_c;
 
  // Feedback is the incoming symbol XORed with the top of the register.
  // XOR because addition in GF(2^m) is XOR -- Section 7's point, used here.
  assign feedback_c = in_parity_q ? gf_t'(0)
                                  : (in_symbol ^ lfsr[PARITY-1]);
 
  gf_mul u_m0 (.a(feedback_c), .b(G0), .product(mul0_c));
  gf_mul u_m1 (.a(feedback_c), .b(G1), .product(mul1_c));
  gf_mul u_m2 (.a(feedback_c), .b(G2), .product(mul2_c));
  gf_mul u_m3 (.a(feedback_c), .b(G3), .product(mul3_c));
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      for (int unsigned i = 0; i < PARITY; i++) lfsr[i] <= '0;
      count_q       <= '0;
      in_parity_q   <= 1'b0;
      out_valid     <= 1'b0;
      out_symbol    <= '0;
      out_is_parity <= 1'b0;
      codeword_done <= 1'b0;
    end else if (start) begin
      for (int unsigned i = 0; i < PARITY; i++) lfsr[i] <= '0;
      count_q       <= '0;
      in_parity_q   <= 1'b0;
      out_valid     <= 1'b0;
      codeword_done <= 1'b0;
    end else if (in_valid || in_parity_q) begin
      if (!in_parity_q) begin
        // Data phase: the symbol passes through untouched -- systematic.
        out_symbol    <= in_symbol;
        out_is_parity <= 1'b0;
        lfsr[0] <= mul0_c;
        lfsr[1] <= lfsr[0] ^ mul1_c;
        lfsr[2] <= lfsr[1] ^ mul2_c;
        lfsr[3] <= lfsr[2] ^ mul3_c;
      end else begin
        // Parity phase: shift the remainder out, top symbol first.
        out_symbol    <= lfsr[PARITY-1];
        out_is_parity <= 1'b1;
        lfsr[3] <= lfsr[2];
        lfsr[2] <= lfsr[1];
        lfsr[1] <= lfsr[0];
        lfsr[0] <= '0;
      end
 
      out_valid <= 1'b1;
      count_q   <= count_q + 1'b1;
 
      if (count_q == ($clog2(N+1))'(K - 1)) in_parity_q   <= 1'b1;
      if (count_q == ($clog2(N+1))'(N - 1)) codeword_done <= 1'b1;
    end else begin
      out_valid     <= 1'b0;
      codeword_done <= 1'b0;
    end
  end
 
endmodule

Classification: synthesizable, a correct systematic encoder for the stated code.

What it teaches: that the encoder is polynomial division, computed by an LFSR as the message streams through, and that systematic is a load-bearing property rather than a convenience. Because the data symbols pass through unchanged, a receiver can extract them without decoding — which is what makes the bypass of Section 12 possible and what makes FEC a runtime decision rather than a compile-time one.

Deliberately simplified: RS(15,11) over GF(2⁴) with t = 2, so the whole codeword can be traced by hand. Ethernet's KP4 is RS(544,514) over GF(2¹⁰) with t = 15 — 30 LFSR stages rather than 4, 10-bit symbols rather than 4-bit, and a generator polynomial of degree 30. The structure does not change.

Production implication: the generator coefficients are parameters and must match the decoder's. They are derived from the code definition, so both ends compute the same ones — but a design that hard-codes them in two places, or derives them with two different conventions for the primitive element, produces an encoder and decoder that disagree. The symptom is every codeword reporting uncorrectable errors on a perfectly clean channel, which sends people to the physical layer for a parameter mismatch.

Later ownership: the decoder's algebra is outside this chapter, and Section 9 makes that boundary explicit.

9. RTL 3 — Syndromes, and the Three-Way Verdict

The decoder's first step is exact, cheap, and fully modellable. The steps after it are not, and this section is careful about which is which.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE (syndromes) + INTERFACE MODEL (verdict).
//
// ── WHAT IS EXACT ───────────────────────────────────────────────────────────
// Syndrome computation. S_i = r(alpha^i) for i = 0 .. 2t-1, evaluated by
// Horner's method as the codeword streams in. All-zero syndromes mean no
// error was detected. This is correct and complete.
//
// ── WHAT IS NOT IMPLEMENTED ────────────────────────────────────────────────
// Everything after the syndromes: the error-locator polynomial, its roots,
// and the error values. Those are Berlekamp-Massey, Chien search and
// Forney, and they are outside this chapter. `n_errors_found` and
// `locator_failed` are INPUTS here, supplied by an unmodelled solver.
//
// ── WHY THE VERDICT INTERFACE IS THE POINT ─────────────────────────────────
// Every RS decoder produces one of THREE outcomes, and a design that
// collapses them into "ok / not ok" has destroyed the only early-warning
// signal the link has:
//
//   CLEAN        -- syndromes all zero. Nothing was wrong.
//   CORRECTED    -- errors found and repaired. THE DATA IS GOOD, and the
//                   count is the pre-correction health metric of Section 2.
//   UNCORRECTABLE-- more than t errors. The data is bad and known to be bad.
//
// CLEAN and CORRECTED both deliver good data. Conflating them makes the
// pre-correction error rate unmeasurable, which is the mistake this whole
// chapter exists to prevent.
package rsdec_pkg;
  typedef enum logic [1:0] {
    VERDICT_CLEAN         = 2'd0,
    VERDICT_CORRECTED     = 2'd1,
    VERDICT_UNCORRECTABLE = 2'd2
  } verdict_e;
endpackage
 
module rs_syndrome
  import gf_pkg::*;
  import rsdec_pkg::*;
#(
  parameter int unsigned N       = 15,
  parameter int unsigned K       = 11,
  parameter int unsigned T       = (N - K) / 2,   // 2
  parameter int unsigned NSYND   = 2 * T          // 4
) (
  input  logic clk,
  input  logic rst_n,
 
  input  logic start,
  input  logic in_valid,
  input  gf_t  in_symbol,
 
  output logic       syndromes_valid,
  output gf_t        syndrome [NSYND],
  output logic       any_error_detected,
 
  // From the unmodelled error-locator solver. Stated as inputs so the
  // boundary is impossible to miss.
  input  logic                     locator_valid,
  input  logic [$clog2(N+1)-1:0]   n_errors_found,
  input  logic                     locator_failed,
 
  output verdict_e verdict,
  output logic     verdict_valid,
  output logic [$clog2(N+1)-1:0] errors_corrected
);
 
  gf_t                    synd_q [NSYND];
  logic [$clog2(N+1)-1:0] count_q;
 
  gf_t alpha_i   [NSYND];
  gf_t mul_out   [NSYND];
 
  // Horner's method: S_i = ((r_0 * a^i + r_1) * a^i + r_2) ... Each
  // syndrome needs one multiply and one XOR per received symbol.
  generate
    for (genvar i = 0; i < NSYND; i++) begin : g_synd
      gf_alpha_pow u_pow (.exponent(($clog2(N_ELEM+1))'(i)), .value(alpha_i[i]));
      gf_mul       u_mul (.a(synd_q[i]), .b(alpha_i[i]), .product(mul_out[i]));
    end
  endgenerate
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n || start) begin
      for (int unsigned i = 0; i < NSYND; i++) synd_q[i] <= '0;
      count_q         <= '0;
      syndromes_valid <= 1'b0;
    end else if (in_valid) begin
      for (int unsigned i = 0; i < NSYND; i++)
        synd_q[i] <= mul_out[i] ^ in_symbol;
 
      count_q <= count_q + 1'b1;
      // Syndromes are complete only when the LAST symbol has arrived. This
      // single fact is the store-and-decode latency floor of Section 6.
      syndromes_valid <= (count_q == ($clog2(N+1))'(N - 1));
    end else begin
      syndromes_valid <= 1'b0;
    end
  end
 
  always_comb begin
    any_error_detected = 1'b0;
    for (int unsigned i = 0; i < NSYND; i++)
      if (synd_q[i] != '0) any_error_detected = 1'b1;
  end
 
  assign syndrome = synd_q;
 
  // The three-way verdict.
  always_comb begin
    verdict_valid    = 1'b0;
    verdict          = VERDICT_CLEAN;
    errors_corrected = '0;
 
    if (syndromes_valid && !any_error_detected) begin
      // Zero syndromes: nothing detected. Fast path, no solver needed.
      verdict_valid = 1'b1;
      verdict       = VERDICT_CLEAN;
    end else if (locator_valid) begin
      verdict_valid = 1'b1;
      if (locator_failed || (n_errors_found > ($clog2(N+1))'(T))) begin
        verdict = VERDICT_UNCORRECTABLE;
      end else begin
        verdict          = VERDICT_CORRECTED;
        errors_corrected = n_errors_found;
      end
    end
  end
 
endmodule

Classification: syndrome computation synthesizable and exact; the verdict path an interface model with the solver as an input.

What it teaches: two things, and the second is the operational one.

First, the latency floor is visible in the code. syndromes_valid asserts only when count_q reaches N − 1. Every symbol contributes to every syndrome, so the decoder genuinely cannot start until the codeword is complete. Section 6's store-and-decode floor is not an implementation choice — it is that line.

Second, the verdict is three-valued and must stay that way. CLEAN and CORRECTED both deliver good data, and a design that reports "good" for both has made the pre-correction error rate unmeasurable — destroying the only early warning the link has. Section 10's counters depend entirely on this distinction surviving.

Deliberately simplified: the error-locator solver. Berlekamp-Massey, Chien search and Forney are a coding-theory subject; approximating them would teach an algorithm that does not work. They are inputs, and the boundary is stated in the source so nobody mistakes the model for an implementation.

Production implication: the CLEAN fast path — zero syndromes, no solver invoked — is worth having explicitly. On a healthy link the overwhelming majority of codewords are clean, so the solver is idle almost always, and the decoder's typical latency is much lower than its worst case. A design that runs the full pipeline unconditionally pays worst-case latency on every codeword for no benefit.

Later ownership: the decoding algebra belongs to a coding-theory reference, not to an Ethernet chapter that would have to abbreviate it.

10. RTL 4 — Pre-Correction and Post-Correction Telemetry

This is the chapter's most important block, and it is instrumentation.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE INSTRUMENTATION. Outside the datapath.
//
// THE CENTRAL DISCIPLINE OF THIS CHAPTER, IN COUNTERS.
//
//   PRE-CORRECTION  : symbol errors the decoder REPAIRED. Tracks the
//                     physical link's health. Degrades smoothly. Gives
//                     weeks or months of warning. THIS IS THE HEALTH METRIC.
//
//   POST-CORRECTION : codewords the decoder COULD NOT repair. Reads zero
//                     until the raw rate crosses the threshold, then goes
//                     catastrophic. THIS IS ONLY THE VERDICT.
//
// A monitoring system watching only the second has no early warning at all.
module fec_telemetry
  import rsdec_pkg::*;
#(
  parameter int unsigned T     = 15,   // correction capability
  parameter int unsigned CNT_W = 32,
  parameter int unsigned WINDOW = 1_000_000,
  parameter int unsigned WIN_W  = $clog2(WINDOW + 1)
) (
  input  logic clk,
  input  logic rst_n,
  input  logic clear,
 
  input  logic                   verdict_valid,
  input  verdict_e               verdict,
  input  logic [$clog2(T*2+2)-1:0] errors_corrected,
 
  // ── Codeword accounting: exhaustive, so the three close on the total ────
  output logic [CNT_W-1:0] c_codewords,
  output logic [CNT_W-1:0] c_clean,
  output logic [CNT_W-1:0] c_corrected,
  output logic [CNT_W-1:0] c_uncorrectable,
 
  // ── PRE-correction: total symbol errors repaired. The health metric. ────
  output logic [CNT_W-1:0] c_symbols_corrected,
 
  // Histogram of errors per codeword, 0..T and an overflow bin. This is
  // strictly better than a total: the SHAPE says how close to the cliff the
  // link is running, which a mean cannot.
  output logic [CNT_W-1:0] hist_errors [T+2],
 
  // The largest correction seen in the window that just closed. When this
  // approaches T, the link is near the edge -- and it gets there long
  // before any codeword becomes uncorrectable.
  output logic [$clog2(T*2+2)-1:0] window_max_errors,
  output logic                     window_valid,
 
  // Survives `clear`: the closest this link has ever come to the cliff.
  output logic [$clog2(T*2+2)-1:0] worst_ever_errors,
 
  // Consecutive uncorrectable codewords. One is an event; a run is an
  // outage, and they need different responses.
  output logic [15:0] longest_uncorrectable_run
);
 
  logic [$clog2(T*2+2)-1:0] run_max_q;
  logic [WIN_W-1:0]         win_q;
  logic [15:0]              unc_run_q;
 
  function automatic logic [CNT_W-1:0] bump(input logic [CNT_W-1:0] v,
                                            input logic             en);
    bump = (en && !(&v)) ? (v + 1'b1) : v;
  endfunction
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      c_codewords         <= '0; c_clean         <= '0;
      c_corrected         <= '0; c_uncorrectable <= '0;
      c_symbols_corrected <= '0;
      for (int unsigned i = 0; i < T+2; i++) hist_errors[i] <= '0;
      run_max_q                 <= '0;
      win_q                     <= '0;
      window_max_errors         <= '0;
      window_valid              <= 1'b0;
      worst_ever_errors         <= '0;
      unc_run_q                 <= '0;
      longest_uncorrectable_run <= '0;
    end else begin
      if (clear) begin
        c_codewords         <= '0; c_clean         <= '0;
        c_corrected         <= '0; c_uncorrectable <= '0;
        c_symbols_corrected <= '0;
        for (int unsigned i = 0; i < T+2; i++) hist_errors[i] <= '0;
        longest_uncorrectable_run <= '0;
        // worst_ever_errors deliberately NOT cleared: it is a property of
        // the installed link, not of a window an operator happened to pick.
      end else if (verdict_valid) begin
        c_codewords <= bump(c_codewords, 1'b1);
 
        unique case (verdict)
          VERDICT_CLEAN: begin
            c_clean        <= bump(c_clean, 1'b1);
            hist_errors[0] <= bump(hist_errors[0], 1'b1);
            unc_run_q      <= '0;
          end
 
          VERDICT_CORRECTED: begin
            c_corrected         <= bump(c_corrected, 1'b1);
            // Symbols, not codewords. A codeword with 9 corrected errors
            // says far more about link health than one with 1, and a
            // codeword count treats them identically.
            c_symbols_corrected <= (c_symbols_corrected + CNT_W'(errors_corrected));
            hist_errors[errors_corrected] <= bump(hist_errors[errors_corrected], 1'b1);
            if (errors_corrected > run_max_q)         run_max_q         <= errors_corrected;
            if (errors_corrected > worst_ever_errors) worst_ever_errors <= errors_corrected;
            unc_run_q <= '0;
          end
 
          default: begin  // VERDICT_UNCORRECTABLE
            c_uncorrectable <= bump(c_uncorrectable, 1'b1);
            hist_errors[T+1] <= bump(hist_errors[T+1], 1'b1);
            unc_run_q <= unc_run_q + 1'b1;
            if (unc_run_q + 1'b1 > longest_uncorrectable_run)
              longest_uncorrectable_run <= unc_run_q + 1'b1;
          end
        endcase
 
        if (win_q == WIN_W'(WINDOW - 1)) begin
          window_max_errors <= run_max_q;
          window_valid      <= 1'b1;
          run_max_q         <= '0;
          win_q             <= '0;
        end else begin
          win_q <= win_q + 1'b1;
        end
      end
    end
  end
 
endmodule

Classification: synthesizable instrumentation.

What it teaches: that the useful measurement is the histogram, not the total. c_symbols_corrected alone gives a rate; the distribution across hist_errors gives the shape, and the shape says how close to the cliff the link is running. A link averaging 2 errors per codeword against t = 15 is comfortable; one averaging 2 but with a tail reaching 13 is about to fail, and the two have identical means.

Deliberately simplified: no time-stamping, and no per-lane breakdown. Both matter in production, the second especially on a multi-lane link where one lane can be responsible for most of the correction load.

Production implication: window_max_errors approaching T is the alarm to configure, and it fires long before c_uncorrectable moves. That is the entire operational value of this module: a link correcting 14 of a possible 15 is still delivering perfect data and is one small degradation from an outage. c_uncorrectable will not warn you; the histogram tail will.

And counting symbols rather than codewords is deliberate. A codeword needing 9 corrections and one needing 1 are equally "corrected", and a codeword-only count treats them identically — throwing away the magnitude information that makes the metric a health indicator at all.

11. RTL 5 — The Latency Accountant and the Bypass Path

Section 6 derived the latency floor. This block makes it visible at runtime and makes the bypass decision explicit rather than a synthesis-time constant.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. FEC enable/bypass with explicit latency accounting.
//
// Bypass is possible ONLY because the code is systematic (Section 8). The k
// data symbols appear unchanged in the codeword, so a receiver that trusts
// the link can take them and never invoke the decoder.
//
// What bypass gives up is not merely correction. It gives up the ENTIRE
// pre-correction health metric of Section 10 -- because if nothing decodes,
// nothing counts corrections, and the link's only early-warning signal
// disappears. That trade must be made deliberately.
module fec_bypass_ctrl
  import rsdec_pkg::*;
#(
  parameter int unsigned N            = 544,
  parameter int unsigned M            = 10,
  // Codeword fill time in clock cycles, from N, M and the datapath width.
  parameter int unsigned FILL_CYCLES  = 544,
  parameter int unsigned DECODE_CYCLES = 64
) (
  input  logic clk,
  input  logic rst_n,
 
  input  logic fec_enable,        // policy, from software or negotiation
 
  input  logic       cw_start,
  input  logic       verdict_valid,
  input  verdict_e   verdict,
 
  // Data path selection. Systematic coding is what makes this legal.
  output logic       take_decoded,    // use the decoder's output
  output logic       take_raw,        // take data symbols directly
 
  // Latency in cycles for the path currently selected. Exposed at runtime
  // so a system can BUDGET rather than assume -- and so the cost of
  // enabling FEC is a number an operator can see.
  output logic [15:0] current_latency_cycles,
  output logic [15:0] bypass_latency_cycles,
  output logic [15:0] fec_latency_cycles,
 
  // When bypassed, the pre-correction health metric does not exist. A
  // system that does not know this will read c_symbols_corrected as zero
  // and conclude the link is perfect. Say so explicitly.
  output logic       health_metric_unavailable,
 
  // Sticky: FEC was disabled at some point. Survives a counter clear,
  // because a telemetry window that spans a bypass period is not
  // comparable with one that does not.
  output logic       bypass_was_used
);
 
  assign take_decoded = fec_enable;
  assign take_raw     = !fec_enable;
 
  // Bypass still pays the codeword fill, because the framing is built
  // around codeword boundaries -- it saves the DECODE pipeline, not the
  // store. That distinction is routinely got wrong when budgeting.
  assign bypass_latency_cycles  = 16'(FILL_CYCLES);
  assign fec_latency_cycles     = 16'(FILL_CYCLES + DECODE_CYCLES);
  assign current_latency_cycles = fec_enable ? fec_latency_cycles
                                             : bypass_latency_cycles;
 
  assign health_metric_unavailable = !fec_enable;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) bypass_was_used <= 1'b0;
    else if (!fec_enable) bypass_was_used <= 1'b1;
  end
 
endmodule

Classification: synthesizable.

What it teaches: that bypass costs more than correction. Disabling FEC also removes the pre-correction health metric entirely — nothing decodes, so nothing counts corrections, and Section 10's counters read zero. A monitoring system unaware of the bypass reads zero corrections and concludes the link is perfect, which is why health_metric_unavailable is an explicit output rather than something software is expected to infer.

Deliberately simplified: fill and decode times are parameters rather than derived from the datapath width and clock. A real design computes them from its own structure.

Production implication: the distinction between bypass_latency_cycles and fec_latency_cycles is the one people get wrong when budgeting. Bypass does not remove the codeword fill time — the framing is built around codeword boundaries, so the store still happens. It saves the decode pipeline only. A budget assuming bypass eliminates all FEC latency is wrong by the larger of the two terms.

12. Miscorrection — The Failure Worse Than Not Correcting

Every discussion of FEC covers "errors within t are corrected" and "errors beyond t are not". There is a third case, it is the dangerous one, and it is why t is chosen with margin rather than tightly.

When a codeword contains more than t errors, the decoder does not always know it.

The decoder's job is to find the nearest valid codeword to what it received. With at most t errors that codeword is provably the transmitted one — the correction guarantee. With more than t errors, the received word can be nearer to some other valid codeword, and the decoder will find that one, "correct" toward it, and emit the result with a CORRECTED verdict.

The output is wrong, and it is flagged as good.

A received codeword takes one of three paths. With at most t errors it is provably corrected to the transmitted codeword. With more than t errors it is usually detected as uncorrectable and flagged. But sometimes more than t errors place the received word nearer a different valid codeword, and the decoder corrects toward that one and reports success, producing silent corruption.What a decoder does with a damaged codeword1Codeword arriveswith some number of symbol errors2At most t errorsprovably corrected — data is right3More than t, detectedflagged uncorrectable — data is known bad4More than t, nearer anothercorrected to the WRONG codeword5Reported as correctedsilent corruption, no flag raised
Figure 3 — three outcomes, and only two of them announce themselves.

Steps 3 and 4 both have more than t errors. The difference is geometric — whether the damage happened to carry the received word closer to a different valid codeword — and the decoder cannot distinguish them, because from its position both look like a codeword with a small number of errors.

Three consequences that shape real designs:

  1. Miscorrection probability falls as the excess over t grows in detectability, not simply as errors grow. Codewords with a few more than t errors are the risky region; heavily damaged ones are almost always detected. Which means the dangerous operating point is a link running just past its correction capability — exactly where a degrading link spends time on its way down.
  2. t is chosen with margin over the expected error rate, not tightly against it. Section 10's window_max_errors approaching T is an alarm precisely because it means the link is entering the miscorrection-prone region.
  3. The layer above must not treat CORRECTED as a guarantee. Ethernet's frame check sequence — the FCS of Chapter 2.5 — is the backstop. A miscorrected codeword produces a frame whose FCS fails, and the frame is dropped. FEC and the FCS are independent mechanisms and the second is not redundant, which is a good answer to why the FCS was not removed when FEC arrived.

13. Assertions

Some properties below are genuine code properties — the correction bound t = (n−k)/2, and the behaviour of syndromes on an undamaged codeword, are consequences of the mathematics rather than of any implementation. Others are properties of these teaching models. Each is labelled.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─── REAL CODE PROPERTY: a clean codeword has zero syndromes ───────────────
// Mathematics, not implementation: a valid codeword is divisible by the
// generator, so it evaluates to zero at every root. Catches: a syndrome
// engine with the wrong alpha powers, which reports errors on perfect data
// and makes every downstream metric meaningless.
property p_clean_codeword_zero_syndromes;
  @(posedge clk) disable iff (!rst_n)
  (syndromes_valid && no_errors_injected) |-> !any_error_detected;
endproperty
 
// ─── REAL CODE PROPERTY: correction never exceeds t ────────────────────────
// t = (n-k)/2 is a bound from the code definition. A decoder claiming more
// corrections than t has not corrected -- it has miscorrected, and reporting
// the count honestly is what lets Section 12's alarm work.
property p_corrections_within_t;
  @(posedge clk) disable iff (!rst_n)
  (verdict_valid && (verdict == VERDICT_CORRECTED)) |-> (errors_corrected <= T);
endproperty
 
// ─── REAL CODE PROPERTY: systematic means data passes through ──────────────
// The property Section 11's bypass depends on. Catches: an encoder that
// modifies data symbols, which makes bypass silently deliver wrong data.
property p_encoder_is_systematic;
  @(posedge clk) disable iff (!rst_n)
  (out_valid && !out_is_parity) |-> (out_symbol == $past(in_symbol));
endproperty
 
// ─── Mutual exclusion: exactly one verdict ─────────────────────────────────
// Catches: a decoder reporting both corrected and uncorrectable, after which
// two downstream consumers disagree about whether the data is usable.
property p_verdict_onehot;
  @(posedge clk) disable iff (!rst_n)
  verdict_valid |-> $onehot({(verdict == VERDICT_CLEAN),
                             (verdict == VERDICT_CORRECTED),
                             (verdict == VERDICT_UNCORRECTABLE)});
endproperty
 
// ─── Causation: a clean verdict requires zero syndromes ────────────────────
// Catches: a fast path that reports CLEAN without actually checking, which
// makes the pre-correction metric read zero on a degrading link.
property p_clean_implies_zero_syndromes;
  @(posedge clk) disable iff (!rst_n)
  (verdict_valid && (verdict == VERDICT_CLEAN)) |-> !any_error_detected;
endproperty
 
// ─── Ordering: syndromes complete only at the end of a codeword ────────────
// The latency floor of Section 6, asserted. Catches: a design that claims
// syndrome validity early, which would decode on a partial codeword and
// produce confident nonsense.
property p_syndromes_need_full_codeword;
  @(posedge clk) disable iff (!rst_n)
  syndromes_valid |-> ($past(count_q) == (N - 1));
endproperty
 
// ─── Conservation: the three verdicts close on the total ───────────────────
// Catches: counters that stop closing, which means they are lying -- and a
// lying pre-correction metric is worse than none, because it is trusted.
property p_verdict_counters_close;
  @(posedge clk) disable iff (!rst_n)
  (!(&c_codewords)) |->
    (c_codewords == c_clean + c_corrected + c_uncorrectable);
endproperty
 
// ─── Conservation: the histogram closes on the codeword count ──────────────
// Catches: a histogram that drops entries, biasing the shape that Section 12
// uses as its alarm.
property p_histogram_closes;
  @(posedge clk) disable iff (!rst_n)
  (!(&c_codewords)) |-> (c_codewords == hist_sum);
endproperty
 
// ─── Safety: symbols corrected is at least codewords corrected ─────────────
// Every corrected codeword had at least one error. Catches: symbol counting
// that increments by codeword rather than by error count, which discards
// the magnitude information the metric exists for.
property p_symbols_at_least_codewords;
  @(posedge clk) disable iff (!rst_n)
  (c_symbols_corrected >= c_corrected);
endproperty
 
// ─── Stability: the worst-ever correction survives a clear ─────────────────
// Catches: worst_ever folded into the clear branch, destroying the record of
// how close this link has come to its correction limit.
property p_worst_ever_survives_clear;
  @(posedge clk) disable iff (!rst_n)
  clear |=> (worst_ever_errors >= $past(worst_ever_errors));
endproperty
 
// ─── Causation: bypass declares the metric unavailable ─────────────────────
// Catches: a bypass path that leaves monitoring believing zero corrections
// means a perfect link, when it actually means nothing is being measured.
property p_bypass_flags_metric_unavailable;
  @(posedge clk) disable iff (!rst_n)
  !fec_enable |-> health_metric_unavailable;
endproperty
 
// ─── Safety: bypass still pays the fill time ───────────────────────────────
// Catches: a latency budget that assumes bypass removes all FEC delay. It
// removes the decode pipeline only; the codeword store remains.
property p_bypass_latency_is_fill_only;
  @(posedge clk) disable iff (!rst_n)
  (bypass_latency_cycles == FILL_CYCLES)
    && (fec_latency_cycles == FILL_CYCLES + DECODE_CYCLES);
endproperty
 
// ─── Bounded response: a verdict always arrives ────────────────────────────
// Catches: a solver that can stall, leaving a codeword permanently
// undecided and stalling the datapath behind it.
property p_verdict_is_bounded;
  @(posedge clk) disable iff (!rst_n)
  syndromes_valid |-> ##[0:DECODE_CYCLES+1] verdict_valid;
endproperty

14. Verification

Scenarios

  1. A clean codeword end to end. Encode 11 data symbols, verify all syndromes are zero, the verdict is CLEAN, no solver is invoked, and hist_errors[0] advances.
  2. Systematic pass-through. Verify every data symbol emerges unchanged from the encoder. This is what Section 11's bypass depends on, and a failure here makes bypass silently deliver wrong data.
  3. One symbol error. Inject a single error, verify non-zero syndromes, the verdict is CORRECTED with errors_corrected == 1, and the recovered data matches the original exactly.
  4. Exactly t symbol errors. The boundary on the safe side. Verify CORRECTED with the right count and correct recovered data.
  5. Exactly t + 1 symbol errors. The boundary on the dangerous side, and the case Scenario 18 develops. Verify the verdict is not CLEAN and that whatever it is, is reported honestly.
  6. A burst of errors inside one symbol. Corrupt all m bits of one symbol and verify it consumes exactly one of the t budget. This is Section 3's central claim about why symbol-level correction suits this channel.
  7. The same number of bit errors, scattered across m symbols. Verify it consumes m of the budget. Run 6 and 7 as a pair — the contrast is the whole argument.
  8. Errors in parity symbols only. Verify they are corrected exactly like data errors. Parity is not privileged, and a design that treats it specially fails here.
  9. Field arithmetic, exhaustively. Multiply every pair of GF(2⁴) elements — 256 cases — and check against an independently computed table. This is exhaustive and cheap, and a field bug corrupts everything above it.
  10. Field identities. Verify a × 1 == a and a × 0 == 0 for all 16 elements, and that every non-zero element has a multiplicative inverse. The last one is what makes it a field rather than merely a ring.
  11. Alpha powers cycle. Verify alpha^15 == alpha^0 and that the 15 non-zero powers are distinct — the primitive element genuinely generating the field.
  12. Generator polynomial agreement. Verify the encoder's coefficients produce codewords whose syndromes are zero. A mismatch here makes every codeword report uncorrectable on a perfect channel.
  13. Verdict counter closure. Run a long mixed stream and verify c_codewords equals the sum of the three verdict counters at every sample point, not just at end of test.
  14. Histogram closure. Verify the histogram bins sum to the codeword count, including the overflow bin.
  15. Symbol against codeword counting. Inject codewords needing 1 and 9 corrections and verify c_corrected advances by 2 while c_symbols_corrected advances by 10. A design counting codewords only fails this and loses the magnitude information.
  16. Bypass. Enable bypass, verify raw data is taken, health_metric_unavailable asserts, correction counters stop advancing, and bypass_was_used sticks.
  17. Latency accounting. Verify bypass_latency_cycles equals the fill time alone and fec_latency_cycles includes the decode pipeline. Then verify a measured end-to-end latency matches.

What the checker must own

  • An independently computed field table, generated from the primitive polynomial by a different method than the design uses. A scoreboard sharing the design's gf_mul verifies only self-consistency.
  • A software reference encoder and decoder implementing the same code independently. It is the only way to check recovered data rather than verdicts, and Scenario 18's oracle needs exactly that.
  • An exhaustive miscorrection search over the small field. Feasible at RS(15,11), infeasible at RS(544,514) — which is precisely why the small field is the right place to build and validate the search, and why Section 7 uses one.
  • Coverage crosses of errors-per-codeword against verdict. The bin (errors == t, CORRECTED) must be well populated — that is the boundary of the guarantee — and (errors <= t, UNCORRECTABLE) must be unreachable, because a decoder failing inside its guarantee is a design fault.

15. Debugging — Read the Slope, Not the Cliff

The symptom: an intermittent link, or one that failed suddenly with no warning.

Step 1 — read c_symbols_corrected and the histogram, not c_uncorrectable. This is the whole method in one instruction, and it inverts what most people do first.

Histogram shapeWhat it meansAction
almost all in bin 0the link is comfortablenothing — look elsewhere
a small mound near the low binsnormal operation with real errorsbaseline it and trend
a tail pressing toward Tnear the correction limitact now — miscorrection is already possible
bins at T + 1 movingpast the limitthe link is failing, and has been for a while

Row three is the finding, and it is invisible to c_uncorrectable, which still reads zero.

Step 2 — compare window_max_errors against T over successive windows. A single reading has no context. A max that was 3 six weeks ago and is 11 now is a link that will fail, and the trend is the finding rather than either value. This is the same discipline Chapter 3.3 §17 applied to margin, one layer up.

Step 3 — if corrections are high, ask whether the ordering is right. Chapter 3.5 §12's error multiplication means a descramble-before-decode ordering presents the decoder with three times the channel's errors. Read the multiplication factor from that chapter's monitor. A pre-FEC rate three times higher than the physical layer predicts is an ordering bug, not a channel problem — and it is the explanation for an otherwise inexplicable measurement.

Step 4 — check whether the correction load is uniform across lanes. On a multi-lane link, one lane carrying most of the corrections is a claim about that lane's physical path, and it points straight at Chapter 3.2's or Chapter 3.1's per-lane methods. Uniform load points at something shared — the modulation choice, the reference clock, the environment.

Step 5 — if corrections are near zero and the link still errors, check bypass. health_metric_unavailable and bypass_was_used exist for exactly this. Zero corrections may mean a perfect link or a disabled decoder, and those are opposite conclusions from the same reading.

Step 6 — frames failing their FCS while FEC reports success. This is Section 12's miscorrection signature, and it is the one case where the FEC counters are actively misleading. Cross-check the FCS failure rate against c_uncorrectable: FCS failures substantially exceeding uncorrectable codewords means miscorrection, and it means the link is operating past its correction capability even though every FEC metric reports success.

The method stated once: the histogram tail is the health metric and c_uncorrectable is the obituary. Trend the tail against T, check the multiplication factor before blaming the channel, check bypass before believing a zero, and cross-check FCS failures to catch miscorrection that the FEC counters cannot see.

16. Common Misconceptions

"FEC means errors do not matter."

The wrong model: correction removes the error problem, so the physical layer no longer needs attention.

What it costs: you stop monitoring the physical layer, you have no idea how much margin the link has, and the first indication of trouble is an outage. You also cannot answer whether a link is one week or one year from failing, which makes capacity and maintenance planning guesswork.

The corrected model: FEC buys a countable budget of t symbol errors per codeword and nothing beyond it. Errors matter exactly as much as before — what changed is that their effect is deferred until the budget is exhausted, and the budget's consumption is measurable. The pre-correction rate is the physical layer's health, unchanged in meaning.

"A link with no uncorrectable codewords is a healthy link."

The wrong model: zero uncorrectable means everything is fine.

What it costs: this is Section 13's rejected property, and it is the most expensive misconception in the chapter. A link one small degradation from failure reads exactly the same as a comfortable one — and a link already silently corrupting data through miscorrection reads the same as both, because miscorrected codewords are reported as CORRECTED.

The corrected model: c_uncorrectable is the cliff and reads zero right up until failure. The histogram tail against T is the slope, and it is the only pre-correction signal that gives warning. Alarm at a fraction of T, never on the first uncorrectable codeword.

"FEC always fails safely — it either corrects or reports failure."

The wrong model: two outcomes, both honest.

What it costs: you treat a CORRECTED verdict as a guarantee that the data is right, and you may conclude the FCS is redundant now that FEC exists. Removing it would let miscorrected data through undetected.

The corrected model: there are three outcomes. Beyond t errors the received word can be nearer a different valid codeword, and the decoder will correct toward that one and report success. Miscorrection is silent, it does not appear in c_uncorrectable, and the FCS of Chapter 2.5 is the independent backstop that catches it.

"More FEC is always better."

The wrong model: a stronger code is a strict improvement.

What it costs: you specify a long codeword on a latency-sensitive link and blow the budget, or you enable FEC on a clean short channel and pay hundreds of nanoseconds per hop for correction that was never needed.

The corrected model: correction capability scales with codeword length, and codeword length is the store-and-decode latency floor. The two cannot be separated. The engineering question is whether this channel's raw error rate needs the correction and whether this application can afford the codeword time — which is why Section 11's bypass exists and why lower-latency FEC variants were specified for applications that need them.

"Bypassing FEC just gives up error correction."

The wrong model: disabling FEC costs correction and saves latency, and nothing else.

What it costs: you disable it on a link you believe is clean, monitoring reads zero corrections, and you conclude the link is perfect. You have removed the instrument, not confirmed the reading — and you will not notice degradation at all.

The corrected model: bypass removes the entire pre-correction health metric, because nothing decodes and nothing counts. health_metric_unavailable exists to say so explicitly. And bypass does not remove the codeword fill time — the framing is built around codeword boundaries — so it saves the decode pipeline only, which is the smaller of the two latency terms.

17. Interview Reasoning

"What does forward error correction actually buy?"

The weak answer is "it fixes errors". The answer that ends the topic gives the budget — t = (n−k)/2 symbol errors per codeword, countable and monitorable — and then immediately names what changed operationally: there are now two error rates, the pre-correction rate is the health metric because it degrades smoothly, and the post-correction rate is a cliff that gives no warning. Naming the monitoring consequence unprompted is what signals someone has operated a link rather than only read about one.

"Why are there two RS-FEC codes in Ethernet rather than one?"

Because the raw error rate differs by modulation and the code is matched to it. KR4 is RS(528,514) with t = 7, targeted at NRZ. KP4 is RS(544,514) with t = 15, targeted at PAM4 — more than twice the correction, because PAM4's eye is one third as tall and its LSB is twice as error-prone. The strong version connects it back: the clause structure is recording the 9.54 dB trade that Chapter 3.6 derived.

"Why is the frame check sequence still needed once FEC exists?"

Because FEC can fail silently. Beyond t errors a decoder can land on a different valid codeword and report CORRECTED while emitting wrong data — and that miscorrection never appears in the uncorrectable counter. The FCS is an independent check one layer up, and it is what catches it. A candidate who reaches miscorrection unprompted has understood the failure model rather than the happy path.

18. Understanding Check

It creates two error rates that behave completely differently, and only one of them is a health metric.

Pre-correction — the symbol errors the decoder repaired. This tracks the physical link's condition and degrades smoothly as margin erodes, exactly as an uncorrected link's error rate would. It gives weeks or months of warning.

Post-correction — the codewords that could not be repaired. This reads zero while the link degrades from comfortable to marginal to nearly-failing, because every one of those states is successfully corrected. It becomes non-zero only after the link has already crossed the point of failure.

So a monitoring system watching only the corrected output has no early warning at all. It reports a perfect link right up to the outage.

This is Chapter 3.3's argument one layer up, and the pattern repeats:

LayerThe slope — measure thisThe cliff — alarm of last resort
analogmarginraw bit errors
codedpre-FEC error ratepost-FEC error rate

The follow-up to be ready for: what specifically do you monitor? Not the total — the histogram of errors per codeword. A link averaging 2 against t = 15 is comfortable; one averaging 2 with a tail reaching 13 is about to fail, and the two have identical means.

19. What's Next

The claim this chapter defended: forward error correction does not remove errors. It converts a gradual degradation into a cliff, and hides the gradient behind it.

What FEC buys is a countable budget — t = (n−k)/2 symbol errors per codeword, matched to the modulation, which is why Ethernet specifies KR4 with t = 7 for NRZ and KP4 with t = 15 for PAM4. What it costs is overhead and, more importantly, a structural latency floor: syndromes span the whole codeword, so the decoder cannot start until the last symbol arrives.

And what it changes operationally is the measurement. The pre-correction rate is the slope and gives months of warning; the post-correction rate is the cliff and gives none. Beyond t, a decoder can miscorrect — landing on a different valid codeword and reporting success — which is why the frame check sequence of Chapter 2.5 remained necessary and why the histogram tail, not the uncorrectable count, is the alarm.

Module 3 now has every piece. Chapter 3.1 and Chapter 3.2 gave the two media, Chapter 3.3 the channel, Chapter 3.4 the sublayer split, Chapter 3.5 the codes, Chapter 3.6 the modulation, and this chapter the correction.

Chapter 3.8 — Link Establishment assembles them. Every chapter in this module built a piece of what happens when a link comes up — signal detect at the PMD, clock lock at the PMA, block lock from the code's invalid patterns, alignment and deskew across lanes, and now FEC codeword lock. That is a sequence with a strict order, each stage depending on the ones below it, and the practical question is always the same one: which stage did it stall at, and for how long?

The full path is on the Ethernet curriculum index.

Continue learning

Standards & specifications

Governing standard
IEEE Std 802.3 (Ethernet)(opens IEEE in a new tab)

Defines the Ethernet MAC, the media-independent interfaces and the physical-layer sublayers, including framing, access control, auto-negotiation and per-rate PHY specifications. VLAN tagging, priority and time-sensitive shaping are defined by IEEE 802.1, not by 802.3.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the Ethernet curriculum.