Skip to content
VLSI Mentor

Ethernet · Module 9

200G to 800G — PAM4, FEC and the Modern Data-Centre Link

PAM4 halves the baud rate and costs 9.54 dB, so RS(544,514) becomes mandatory — and the FEC's waterfall means a link with full margin and one about to fail report identical post-correction statistics.

Chapter 9.5 made a link faster by making it wider — twenty PCS lanes, striped, marked and deskewed — while every lane still signalled the way 10GBASE-R did.

Above 100 Gigabit that stops working, because the lanes themselves have to get faster and NRZ runs out of channel.

PAM4 is the answer, and it is a trade rather than an improvement. Four amplitude levels instead of two carry two bits per symbol, so a given bit rate needs half the baud — and half the baud is half the bandwidth the channel has to support, which is the entire point.

What it costs is margin. Four levels in the same voltage swing means three eyes where there was one, each a third of the height. Which is a signal-to-noise penalty of

20 × log₁₀(3) = 9.54 dB

And at that penalty the raw link no longer meets Ethernet's error expectation on its own. So forward error correction stops being an option a medium may specify and becomes mandatory, in the base specification, for every PAM4 interface.

1. Scope — What This Chapter Owns

This chapter owns PAM4 and the error correction it makes compulsory.

It covers the SNR penalty and where it comes from, the transcoding that pays for the FEC's parity, the rate build-up for 200, 400 and 800 Gigabit, the RS(544,514) code and why it rather than the weaker one, what the FEC adds to a latency budget, and — most of all — what a design must measure to know its own margin, given that the FEC is built to hide exactly that.

It does not re-derive what other chapters own. Chapter 3.6 owns multi-level signalling as a mechanism; Chapter 9.5 owns lane striping and deskew, which this chapter's lanes still use; Chapter 8.4 owns the latency decomposition this chapter adds a term to. Chapter 10.1 opens the module that owns the MAC/PHY interfaces in their own right.

The claim this chapter defends: once correction is mandatory, the link's error rate stops being an indicator — because the correction was designed to make it insensitive to margin — and the design's obligation moves to measuring the input the correction consumes.

2. PAM4 — Two Bits per Symbol, and What They Cost

Non return to zero signalling uses two amplitude levels and therefore has one eye occupying the full voltage swing, carrying one bit per symbol. Pulse amplitude modulation with four levels places four levels in the same swing, which creates three eyes each one third of the height, and carries two bits per symbol. Halving the number of symbols needed for a given bit rate halves the baud rate and therefore halves the channel bandwidth required. The price is a signal to noise ratio penalty of twenty times the base ten logarithm of three, which is nine point five four decibels, and that penalty is what makes forward error correction mandatory rather than optional.NRZ: 2 levels1 eye, full swingPAM4: 4 levels3 eyes, ⅓ height each2 bits per symbolhalf the baudHalf the bandwidthwhat was bought20·log₁₀(3) = 9.54dBwhat was paidFEC becomesmandatorythe consequence12
Figure 1 — four levels in the same voltage swing give three eyes of a third the height, which is a 9.54 dB penalty bought in exchange for half the baud rate.

The whole trade is one division and one logarithm.

NRZ has two levels and one eye, which occupies the entire voltage swing the transmitter can produce. One symbol carries one bit.

PAM4 puts four levels in the same swing. Four levels create three eyes, and they share the swing, so each is one third of the height an NRZ eye had.

A third of the amplitude is a signal-to-noise penalty of

20 × log₁₀(1/3) = −9.542 dB

And two bits per symbol means half the symbols, so a 100 Gb/s lane needs 50 Gbaud in NRZ and 25 Gbaud in PAM4 — half the bandwidth from the channel.

NRZPAM4
levels24
eyes13
eye heightfull swing⅓ of the swing
bits per symbol12
baud for a given bit rateRR/2
SNR relative to NRZreference−9.54 dB

Which is the deal in one sentence: 9.54 dB of margin for a factor of two in bandwidth.

And it is worth taking, because the two quantities do not cost the same. Above about 25 Gbaud, channel loss on copper and board rises steeply with frequency, so the last factor of two in bandwidth is enormously expensive — better materials, shorter reach, more retimers. Ten decibels of SNR, by contrast, can be bought back by a code, and RS(544,514) buys most of it at 5.84% of the line rate.

3. The Rate Build-Up, Computed

A PAM4 lane rate is built in four steps, and two of them are overheads that pay for each other.

Step 1 — transcode. Chapter 9.4's 64B/66B costs 3.125%, which is a lot of line rate to give to a FEC that also needs parity. So four 66-bit blocks are transcoded into one 257-bit block:

4 × 66 = 264 bits, carrying 4 × 64 = 256 payload bits 264 → 257 bits, a saving of 7 bits per 256 payload bits

The overhead falls from 66/64 = 3.125% to 257/256 = 0.3906% — and the freed 2.73 percentage points are most of what the FEC's parity will cost.

Step 2 — add FEC parity. RS(544,514) adds 30 parity symbols to 514, an overhead of 30/514 = 5.8366%.

Step 3 — divide by the lane count. Step 4 — divide by two, because PAM4 carries two bits per symbol.

VariantAggregate× 257/256× 544/514LanesGb/s per lane÷ 2 → baud
200GBASE-R4200 G200.7813212.5453.12526.5625 GBd
400GBASE-R8400 G401.5625425.0853.12526.5625 GBd
800G, 8 lanes800 G803.1250850.08106.2553.125 GBd

Read the first two rows together. 200 and 400 Gigabit have identical per-lane rates — 53.125 Gb/s, 26.5625 GBd — and differ only in how many lanes. This is Chapter 9.5's strategy again: a proven serialiser, replicated, with the aggregate as the quotient.

And 800 Gigabit is where the per-lane rate finally doubles, to 53.125 GBd — which is why 800G needed a new generation of SerDes and 400G did not.

4. RTL 1 — Three Eyes Are Not One Eye Measured Three Times

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE.
//
// Per-eye margin for a PAM4 lane.
//
// PAM4 has FOUR levels and therefore THREE decision thresholds:
//
//   level 3  ----   eye 2 (upper)
//   level 2  ----   eye 1 (middle)
//   level 1  ----   eye 0 (lower)
//   level 0  ----
//
// The three eyes are NOT interchangeable, and a single aggregate margin
// destroys the distinction:
//
//   ALL THREE EQUALLY SMALL -- loss, reach, or additive noise. A link
//        level problem.
//   OUTER EYES WORSE        -- transmitter compression or a nonlinear
//        driver: the extreme levels are squeezed toward each other.
//   MIDDLE EYE WORSE        -- level mismatch or a DC offset: the
//        middle levels have drifted off their nominal spacing.
//
// Same aggregate margin, three different work orders. Which is why the
// counters below are per eye and never summed inside this module.
package pam4_link_pkg;
 
  localparam int unsigned EYES   = 3;
  localparam int unsigned LEVELS = 4;
 
  // 20*log10(3) = 9.5424 dB, in hundredths, for reporting.
  localparam int unsigned PAM4_SNR_PENALTY_CDB = 954;
 
  // RS(544,514) over 10-bit symbols (KP4).
  localparam int unsigned FEC_N       = 544;
  localparam int unsigned FEC_K       = 514;
  localparam int unsigned FEC_SYM_BITS = 10;
  localparam int unsigned FEC_T       = (FEC_N - FEC_K) / 2;   // 15
 
endpackage
 
module pam4_level_monitor
  import pam4_link_pkg::*;
#(
  parameter int unsigned MARGIN_W = 12,
  parameter int unsigned CNT_W    = 24,
  parameter int unsigned WINDOW_BITS = 20,
  // Below this, an eye is closing rather than merely small.
  parameter int unsigned EYE_FLOOR = 12'd200
) (
  input  logic clk,
  input  logic rst_n,
  input  logic clear,
 
  input  logic [EYES-1:0]               eye_sample_valid,
  input  logic [EYES-1:0][MARGIN_W-1:0] eye_margin,
 
  output logic [EYES-1:0][MARGIN_W-1:0] worst_eye_margin,
  output logic [EYES-1:0]               eye_closing,
  output logic                          window_valid,
 
  // WHICH eye is worst, and the shape of the degradation. These two
  // outputs are the module: the shape names the cause.
  output logic [1:0]                    worst_eye,
  output logic                          outer_eyes_worse,
  output logic                          middle_eye_worse,
  output logic                          all_eyes_equal,
 
  output logic [EYES-1:0]               ever_closing
);
 
  logic [WINDOW_BITS-1:0] win_q;
  integer i;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      win_q <= '0; window_valid <= 1'b0; eye_closing <= '0;
      ever_closing <= '0;
      for (i = 0; i < EYES; i = i + 1) worst_eye_margin[i] <= '1;
    end else if (clear) begin
      win_q <= '0; window_valid <= 1'b0; eye_closing <= '0;
      for (i = 0; i < EYES; i = i + 1) worst_eye_margin[i] <= '1;
      // ever_closing survives: an eye that has closed is a fact about
      // this transmitter, receiver or channel, and reading a counter
      // did not change any of them.
    end else begin
      win_q <= win_q + 1'b1;
      if (&win_q) window_valid <= 1'b1;
 
      for (i = 0; i < EYES; i = i + 1) begin
        if (eye_sample_valid[i]) begin
          if (eye_margin[i] < worst_eye_margin[i])
            worst_eye_margin[i] <= eye_margin[i];
          if (eye_margin[i] < MARGIN_W'(EYE_FLOOR)) begin
            eye_closing[i]  <= 1'b1;
            ever_closing[i] <= 1'b1;
          end
        end
      end
    end
  end
 
  // THE SHAPE. Comparing the outer eyes (0 and 2) with the middle (1)
  // is what turns three numbers into a cause.
  always_comb begin
    logic [MARGIN_W-1:0] outer_min, mid;
    outer_min = (worst_eye_margin[0] < worst_eye_margin[2])
                  ? worst_eye_margin[0] : worst_eye_margin[2];
    mid       = worst_eye_margin[1];
 
    worst_eye = (outer_min < mid)
                  ? ((worst_eye_margin[0] < worst_eye_margin[2]) ? 2'd0 : 2'd2)
                  : 2'd1;
 
    // A quarter difference is a shape; anything smaller is measurement
    // noise on three nominally identical eyes.
    outer_eyes_worse = window_valid && (mid       > (outer_min + (outer_min >> 2)));
    middle_eye_worse = window_valid && (outer_min > (mid       + (mid       >> 2)));
    all_eyes_equal   = window_valid && !outer_eyes_worse && !middle_eye_worse;
  end
 
endmodule

Classification: synthesizable.

What it teaches: that the shape of the degradation names the cause, and an aggregate margin destroys the shape. Three equally small eyes is loss or additive noise — a link-level fact. Outer eyes worse is transmitter compression, because a nonlinear driver squeezes the extreme levels toward each other. Middle eye worse is a level or offset error. All three report the same worst-case margin, and only the comparison separates them.

Deliberately simplified: eye_margin arrives as a per-eye number. In a real receiver it comes from a slicer's soft information or from an on-die eye scan, and the scan is a mode rather than a continuous measurement.

Production implication: ever_closing is per eye and survives clear. A link that intermittently closes its upper eye and only its upper eye has a transmitter that is compressing when it warms up — and that is a completely different investigation from a link whose three eyes shrink together in the afternoon. The aggregate number is identical in both cases; the per-eye history is the whole diagnosis.

5. Why the Three Eyes Diverge

Section 4 asserts that the eyes degrade differently. Here is why, mechanism by mechanism, because the shapes are what make the monitor worth building.

Additive noise closes all three equally. Thermal noise, crosstalk and jitter-induced amplitude noise do not care which level a symbol is at. Three equal reductions, and the cause is the channel or the environment.

Transmitter nonlinearity closes the outer eyes. A driver approaching its supply rails compresses: the distance from level 2 to level 3 shrinks more than the distance from level 1 to level 2, because the top of the swing is where the amplifier runs out of headroom. The outer eyes go first, and they go first on the transmitter's side of the link.

Level mismatch closes the middle eye. The four levels are meant to be equally spaced; a DC offset or a mis-set level ratio moves the middle two toward each other while leaving the outer gaps alone.

And there is a fourth asymmetry that is designed in. Real PAM4 transmitters deliberately do not space the levels equally — the outer levels are pushed slightly apart to compensate for the nonlinearity above. So a receiver measuring three exactly equal eyes on a real link is measuring something wrong, and the monitor's tolerance band exists partly for that reason.

Shape observedMechanismWhere the fault is
all three equally smalladditive noise, loss, reachthe channel or environment
outer eyes worsedriver compression, nonlinearitythe far transmitter
middle eye worselevel mismatch, DC offsetlevel setting, either end
one outer eye onlyasymmetric clippingone rail of the far driver

6. RTL 2 — Building the Codeword

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE shell around a Reed-Solomon encoder; the Galois-field
// arithmetic is abstracted and the FRAMING is the subject.
//
// RS(544,514) over GF(2^10) -- "KP4", and mandatory for every PAM4
// Ethernet interface.
//
//   symbol size    : 10 bits
//   message        : 514 symbols = 5140 bits
//   parity         :  30 symbols =  300 bits
//   codeword       : 544 symbols = 5440 bits
//   corrects       : t = (544-514)/2 = 15 symbols
//   worst-case burst it can absorb: 15 x 10 = 150 bits, IF the errors
//                    fall inside 15 distinct symbols
//   parity overhead: 30/514 = 5.8366%
//
// THE PART THAT MATTERS FOR RTL: the encoder cannot emit anything until
// it has 514 message symbols, and the decoder cannot correct anything
// until it has all 544. That is not an implementation choice -- it is
// what a block code is, and it is where the latency in Section 8 comes
// from.
module rs_fec_codeword_builder
  import pam4_link_pkg::*;
#(
  parameter int unsigned CNT_W = 24
) (
  input  logic clk,
  input  logic rst_n,
 
  input  logic [FEC_SYM_BITS-1:0] sym_in,
  input  logic                    sym_valid,
 
  output logic [FEC_SYM_BITS-1:0] sym_out,
  output logic                    sym_out_valid,
  output logic                    is_parity,
 
  // Where in the codeword we are. Exported because the deskew and
  // latency blocks both need to know when a codeword boundary passes.
  output logic [9:0]              sym_index,
  output logic                    codeword_start,
  output logic                    codeword_end,
 
  // The encoder is mid-codeword and the source has stalled. A block
  // code cannot emit a partial codeword, so the stall propagates -- and
  // a design that does not report this attributes the resulting gap to
  // the wrong stage.
  output logic                    stalled_mid_codeword,
 
  output logic [CNT_W-1:0]        c_codewords
);
 
  logic [9:0]  idx_q;
  logic [FEC_SYM_BITS-1:0] parity_q [30];
  integer i;
 
  assign sym_index      = idx_q;
  assign codeword_start = sym_valid && (idx_q == 10'd0);
  assign codeword_end   = sym_out_valid && (idx_q == 10'(FEC_N - 1));
 
  // Placeholder for the GF(2^10) parity accumulation. The real encoder
  // is a linear feedback structure over the code's generator
  // polynomial; what matters at this level is that parity is a function
  // of ALL 514 message symbols and therefore cannot be emitted early.
  function automatic logic [FEC_SYM_BITS-1:0] parity_step
    (input logic [FEC_SYM_BITS-1:0] acc, input logic [FEC_SYM_BITS-1:0] s);
    parity_step = {acc[FEC_SYM_BITS-2:0], acc[FEC_SYM_BITS-1]} ^ s;
  endfunction
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      idx_q <= 10'd0; sym_out <= '0; sym_out_valid <= 1'b0;
      is_parity <= 1'b0; stalled_mid_codeword <= 1'b0;
      c_codewords <= '0;
      for (i = 0; i < 30; i = i + 1) parity_q[i] <= '0;
    end else begin
      sym_out_valid <= 1'b0;
 
      // A block code has no partial output. If the source stalls
      // mid-codeword the whole pipeline stalls, and saying so is what
      // stops the gap being blamed on the MAC.
      stalled_mid_codeword <= !sym_valid && (idx_q != 10'd0)
                                         && (idx_q < 10'(FEC_K));
 
      if (sym_valid && (idx_q < 10'(FEC_K))) begin
        // MESSAGE SYMBOLS pass through unchanged and feed the parity.
        sym_out       <= sym_in;
        sym_out_valid <= 1'b1;
        is_parity     <= 1'b0;
        for (i = 0; i < 30; i = i + 1)
          parity_q[i] <= parity_step(parity_q[i], sym_in);
        idx_q <= idx_q + 10'd1;
 
      end else if (idx_q >= 10'(FEC_K)) begin
        // PARITY SYMBOLS. Emitted without needing input, because they
        // are already a function of everything that came before.
        sym_out       <= parity_q[idx_q - 10'(FEC_K)];
        sym_out_valid <= 1'b1;
        is_parity     <= 1'b1;
 
        if (idx_q == 10'(FEC_N - 1)) begin
          idx_q <= 10'd0;
          for (i = 0; i < 30; i = i + 1) parity_q[i] <= '0;
          if (!(&c_codewords)) c_codewords <= c_codewords + 1'b1;
        end else begin
          idx_q <= idx_q + 10'd1;
        end
      end
    end
  end
 
endmodule

Classification: synthesizable shell; the Galois-field arithmetic is abstracted.

What it teaches: that a block code's boundary is a hard structural fact with consequences in three other blocks. Parity is a function of all 514 message symbols, so nothing can be emitted early, and the decoder cannot correct until it holds all 544. That single property produces the latency of Section 8, the stall behaviour above, and the reason the error statistics of Section 10 are naturally per codeword rather than per bit.

Deliberately simplified: the parity accumulation is a placeholder, and one symbol is processed per cycle. At 425 Gb/s a real encoder processes many symbols per clock, which changes the arithmetic but not the boundary.

Production implication: stalled_mid_codeword exists because a block code turns an upstream stall into a downstream gap at a place nothing else would attribute correctly. Without it, a MAC that pauses mid-codeword produces idle time on the wire that looks like a PHY problem — and a throughput investigation starts at the wrong end of the pipeline. The block boundary is invisible above the FEC and it shapes everything below it.

7. Why KP4 Rather Than KR4

Two Reed–Solomon codes appear in Ethernet, they differ only in parity, and PAM4 is the reason the stronger one won.

KR4 — RS(528,514)KP4 — RS(544,514)
message symbols514514
parity symbols1430
codeword528 symbols = 5280 bits544 symbols = 5440 bits
corrects(528−514)/2 = 7 symbols(544−514)/2 = 15 symbols
worst-case burst absorbed70 bits150 bits
parity overhead14/514 = 2.7237%30/514 = 5.8366%

KP4 costs 3.11 percentage points more of line rate and corrects more than twice as many symbols. On an NRZ link with a raw error rate low enough that KR4 has ample headroom, that extra parity is a waste. On a PAM4 link it is the difference between working and not.

And the reason is the 9.54 dB. A PAM4 link at the same received power as an NRZ link has a raw error rate orders of magnitude higher — around 2.4 × 10⁻⁴ at the specified operating point, roughly one bit in four thousand. Seven correctable symbols per 544 is not enough margin at that input rate; fifteen is.

Which is why FEC stops being a medium's choice. In earlier generations a medium specification could require FEC or not, depending on its reach and loss. With PAM4 the raw link does not meet the error expectation on any conforming channel, so the correction moves into the base specification and every PAM4 interface has it.

8. RTL 3 — Accounting for the Latency the FEC Adds

At the transmitter, parity is a function of all five hundred and fourteen message symbols, so no parity can be emitted until the whole message has been accumulated. At the receiver, the decoder cannot correct any symbol until it holds the complete five hundred and forty four symbol codeword, because a single late symbol can change which symbols the decoder concludes were wrong. These two fills are the structural floor on the delay a block code adds. Real implementations add a decoder pipeline and interleaving on top of that floor, which is why the realised contribution is several times the computed minimum.514 message symbolsmust all arriveParity computedover all of them544 symbols heldbefore any correctionStructural floortwo codeword fillsDecoder pipelinesyndromes, roots, valuesRealised delayseveral times the floor12
Figure 2 — a block code cannot correct until it holds a whole codeword, so latency is structural rather than an implementation cost.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE.
//
// Measures what the FEC actually costs in delay, rather than quoting a
// datasheet number.
//
// WHY IT IS STRUCTURAL, not an implementation cost:
//   ENCODE -- parity is a function of all 514 message symbols, so the
//        encoder holds a whole message before it emits any parity.
//   DECODE -- the decoder cannot correct ANY symbol until it holds all
//        544, because a symbol arriving last can change which symbols
//        the decoder concludes were wrong.
//
// THE FLOOR, computed:
//   codeword                    = 544 x 10 = 5440 bits
//   400G line rate (after both overheads) = 425 Gb/s
//   one fill                    = 5440 / 425e9 = 12.8 ns
//   encode fill + decode fill   = 25.6 ns
//
// And a real KP4 contribution is several times that, because the
// decoder's syndrome, root-finding and error-evaluation stages are
// pipelines of their own. So the module MEASURES rather than assumes.
module fec_latency_accountant
  import pam4_link_pkg::*;
#(
  parameter int unsigned TS_W  = 32,
  parameter int unsigned CNT_W = 24
) (
  input  logic clk,
  input  logic rst_n,
  input  logic clear,
 
  // A symbol entering the encoder and the same symbol leaving the
  // decoder, tagged so the two can be matched.
  input  logic          tag_in_valid,
  input  logic [TS_W-1:0] tag_in_time,
  input  logic          tag_out_valid,
  input  logic [TS_W-1:0] tag_out_time,
 
  output logic [TS_W-1:0] last_latency,
  output logic [TS_W-1:0] worst_latency,
  output logic [TS_W-1:0] best_latency,
 
  // Accumulated, so a mean can be computed by whoever reads -- keeping
  // a divider out of a path that runs at line rate.
  output logic [TS_W+CNT_W-1:0] total_latency,
  output logic [CNT_W-1:0]      c_samples,
 
  // The measured delay exceeded the budget this stage was allotted in
  // the system's latency decomposition. Reported, because a FEC that
  // silently costs more than its budget shows up as a system-level
  // latency miss with no stage attached to it.
  input  logic [TS_W-1:0]       budget_cycles,
  output logic                  over_budget,
  output logic                  ever_over_budget
);
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      last_latency <= '0; worst_latency <= '0; best_latency <= '1;
      total_latency <= '0; c_samples <= '0;
      over_budget <= 1'b0; ever_over_budget <= 1'b0;
    end else if (clear) begin
      worst_latency <= '0; best_latency <= '1;
      total_latency <= '0; c_samples <= '0; over_budget <= 1'b0;
      // ever_over_budget survives.
    end else begin
      over_budget <= 1'b0;
 
      if (tag_out_valid) begin
        automatic logic [TS_W-1:0] d = tag_out_time - tag_in_time;
        last_latency  <= d;
        total_latency <= total_latency + (TS_W+CNT_W)'(d);
        if (!(&c_samples)) c_samples <= c_samples + 1'b1;
        if (d > worst_latency) worst_latency <= d;
        if (d < best_latency)  best_latency  <= d;
        if (d > budget_cycles) begin
          over_budget      <= 1'b1;
          ever_over_budget <= 1'b1;
        end
      end
    end
  end
 
endmodule

Classification: synthesizable.

What it teaches: that worst_latency and best_latency must both be kept, because the FEC's delay is not constant. A codeword that needed no correction leaves the decoder earlier than one that consumed the full error-evaluation pipeline — so a link with rising error rates has rising latency, and the two numbers together show it. A single mean hides it entirely.

Deliberately simplified: the tag arrives with a matching timestamp. Real measurement uses a periodic marked symbol and matches at the far end, which is what makes the number a system measurement rather than a block one.

Production implication: over_budget connects this block to Chapter 8.4's decomposition. A FEC that quietly costs more than its allotted term produces a system-level latency miss with no stage attached to it — and 8.4's argument was precisely that a latency budget is only useful if each term names its owner. A stage that can exceed its budget without saying so removes itself from the decomposition.

9. The Latency Cost, Against a Real Budget

Compute the structural floor first, because it is the part nobody can implement away.

StepWorkingResult
codeword size544 × 105440 bits
400G line rate400 × 257/256 × 544/514425 Gb/s
one codeword fill5440 ÷ 425 × 10⁹12.8 ns
encode fill + decode fill2 × 12.825.6 ns
at 800G (850 Gb/s)2 × 5440 ÷ 850 × 10⁹12.8 ns

And the realised figure is several times that — KP4 is commonly cited at roughly 100 to 200 ns — because the decoder's syndrome computation, root finding and error evaluation are pipelines on top of the fill, and interleaving adds more.

Now put 100 ns beside Chapter 8.4's other terms at 400 Gigabit.

TermValue at 400GWorking
serialization of a 1518-octet frame30.36 ns1518 × 8 ÷ 400 × 10⁹
FEC contribution≈ 100 ns3.3× a whole maximum frame
propagation over 100 m of fibre489.5 ns100 ÷ (0.681 × 3 × 10⁸)
the FEC expressed as fibre≈ 20.4 m100 × 10⁻⁹ × 0.681 × 3 × 10⁸

Read the second row against the first. At 400 Gigabit, the FEC costs more than three times the time it takes to serialise an entire maximum-length frame. Chapter 8.4 established that serialization shrinks with rate while the other terms do not — and the FEC is a term that appeared as the rate rose, and which does not shrink with it in the same way.

And read it against the third. The FEC is worth about 20 metres of fibre — trivial in a long-haul link, and significant inside a data-centre row where 20 metres is a meaningful fraction of the total path.

10. RTL 4 — Measuring the Input the Correction Consumes

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE.
//
// The margin reporter, and the most important module in this chapter.
//
// THE PROBLEM IT SOLVES:
//   Post-FEC errors on a healthy 400G link are approximately zero, and
//   they stay approximately zero as the link degrades -- until they
//   don't, and then the link is gone. Section 11 computes the curve.
//
//   So "no errors" is not a margin measurement. It is the answer a
//   perfect link and an almost-dead link both give.
//
// WHAT DOES CARRY MARGIN INFORMATION:
//   the number of symbols the decoder CORRECTED per codeword.
//   That number is monotone in the pre-FEC error rate, moves smoothly,
//   and is available every codeword. A histogram of it is the link's
//   margin, directly.
//
//   t = 15, so a codeword correcting 2 symbols has enormous headroom
//   and one correcting 14 is one bad symbol from being lost -- and both
//   report zero post-FEC errors.
module fec_margin_reporter
  import pam4_link_pkg::*;
#(
  parameter int unsigned CNT_W = 24,
  parameter int unsigned BINS  = FEC_T + 2      // 0..15 corrected, plus uncorrectable
) (
  input  logic clk,
  input  logic rst_n,
  input  logic clear,
 
  input  logic       cw_done,
  input  logic [4:0] corrected_symbols,   // 0..15
  input  logic       uncorrectable,
 
  // The histogram IS the margin. Exported whole, because a summary of
  // it is exactly the information this module exists to preserve.
  output logic [BINS-1:0][CNT_W-1:0] hist,
 
  output logic [4:0]       worst_corrected,
  output logic [CNT_W-1:0] c_codewords,
  output logic [CNT_W-1:0] c_uncorrectable,
 
  // Corrected symbols summed, so a pre-FEC symbol error rate can be
  // derived: total_corrected / (c_codewords x 544).
  output logic [CNT_W+5:0] total_corrected,
 
  // The link is running close to the code's limit. This is the ALARM
  // that a post-FEC error count cannot produce, because by the time a
  // post-FEC error appears the margin is already gone.
  output logic             near_limit,
  output logic             ever_near_limit,
  output logic             ever_uncorrectable
);
 
  // Within two symbols of t is close enough that ordinary variation
  // will reach it.
  localparam int unsigned NEAR_LIMIT_AT = FEC_T - 2;   // 13
 
  integer i;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      worst_corrected <= 5'd0; c_codewords <= '0; c_uncorrectable <= '0;
      total_corrected <= '0; near_limit <= 1'b0;
      ever_near_limit <= 1'b0; ever_uncorrectable <= 1'b0;
      for (i = 0; i < BINS; i = i + 1) hist[i] <= '0;
    end else if (clear) begin
      worst_corrected <= 5'd0; c_codewords <= '0; c_uncorrectable <= '0;
      total_corrected <= '0; near_limit <= 1'b0;
      for (i = 0; i < BINS; i = i + 1) hist[i] <= '0;
      // ever_near_limit and ever_uncorrectable survive. They are the
      // two facts a later reader needs and the two a clear destroys.
    end else begin
      near_limit <= 1'b0;
 
      if (cw_done) begin
        if (!(&c_codewords)) c_codewords <= c_codewords + 1'b1;
 
        if (uncorrectable) begin
          // The overflow bin. An uncorrectable codeword is not a bigger
          // version of a corrected one -- the decoder does not know how
          // many symbols were wrong, only that it was more than t.
          if (!(&hist[BINS-1])) hist[BINS-1] <= hist[BINS-1] + 1'b1;
          if (!(&c_uncorrectable)) c_uncorrectable <= c_uncorrectable + 1'b1;
          ever_uncorrectable <= 1'b1;
        end else begin
          if (!(&hist[corrected_symbols]))
            hist[corrected_symbols] <= hist[corrected_symbols] + 1'b1;
          total_corrected <= total_corrected + (CNT_W+6)'(corrected_symbols);
          if (corrected_symbols > worst_corrected)
            worst_corrected <= corrected_symbols;
          if (corrected_symbols >= 5'(NEAR_LIMIT_AT)) begin
            near_limit      <= 1'b1;
            ever_near_limit <= 1'b1;
          end
        end
      end
    end
  end
 
endmodule

Classification: synthesizable.

What it teaches: that the corrected-symbol count is the margin and the post-FEC error count is not. corrected_symbols is monotone in the pre-FEC error rate, moves smoothly, and is available on every codeword. A histogram concentrated at 1 or 2 is a link with enormous headroom; one with mass at 13 and 14 is a link about to fail — and both report zero uncorrectable codewords.

Deliberately simplified: the decoder supplies a correction count. Real RS decoders produce it naturally as the degree of the error-locator polynomial, so this is one of the few telemetry outputs that is genuinely free.

Production implication: ever_near_limit is the alarm the post-FEC counter cannot give. By the time an uncorrectable codeword appears the margin is already gone, because Section 11's curve is a cliff rather than a slope. A histogram sampled weekly shows the mass drifting from bin 2 toward bin 13 over months, which is a maintenance window; an uncorrectable-codeword counter shows nothing, nothing, nothing, and then an outage.

11. The Waterfall

As the pre forward error correction bit error rate rises, two observable quantities behave completely differently. The average number of symbols the decoder corrects per codeword rises smoothly and proportionally, so it tracks the link's margin continuously and can be trended over weeks. The probability that a codeword is uncorrectable stays effectively at zero across the entire useful operating range and then rises almost vertically over a very narrow band of input error rate. Therefore a link with generous margin and a link on the edge of failure produce identical post correction statistics, and only the corrected symbol distribution distinguishes them.Pre-FEC error raterises as margin fallsSymbols correctedrises smoothlyTrendable over weeksa maintenance windowUncorrectablecodewordsflat at zeroThen verticalan outageHealthy and dyingagreeboth report zero12
Figure 3 — the corrected-symbol count moves smoothly with margin; the uncorrectable-codeword count is flat at zero and then vertical.

Compute the curve, because the shape is the argument.

For RS(544,514) over 10-bit symbols, a bit error rate p gives a symbol error rate of 1 − (1 − p)¹⁰, and a codeword is lost when more than 15 of its 544 symbols are wrong.

Pre-FEC BERSymbol error rateMean errors per codewordP(uncorrectable codeword)
1 × 10⁻⁵1.00 × 10⁻⁴0.0542.1 × 10⁻³⁴
5 × 10⁻⁵5.00 × 10⁻⁴0.2722.7 × 10⁻²³
1 × 10⁻⁴1.00 × 10⁻³0.5441.4 × 10⁻¹⁸
2.4 × 10⁻⁴2.40 × 10⁻³1.3048.2 × 10⁻¹³
5 × 10⁻⁴4.99 × 10⁻³2.7142.8 × 10⁻⁸
1 × 10⁻³9.96 × 10⁻³5.4161.5 × 10⁻⁴
2 × 10⁻³1.98 × 10⁻²10.7837.9 × 10⁻²
4 × 10⁻³3.93 × 10⁻²21.3729.1 × 10⁻¹

Read the last column down the page. From a pre-FEC BER of 10⁻⁴ to 2 × 10⁻³ — a factor of 20 in the input — the codeword loss probability rises from 1.4 × 10⁻¹⁸ to 7.9 × 10⁻², a factor of about 6 × 10¹⁶.

That is not a slope. It is a cliff, and the useful operating range sits entirely on the flat part.

Now read the fourth column instead. Mean errors per codeword goes 0.054 → 0.272 → 0.544 → 1.30 → 2.71 → 5.42 → 10.78. Smooth, monotone, and proportional to the input over the whole range.

Which is the entire design consequence. A link at 10⁻⁴ and a link at 5 × 10⁻⁴ — a fivefold difference in margin — both report zero uncorrectable codewords for as long as anyone will watch. They report 0.54 and 2.71 mean corrected symbols, and that difference is visible in seconds.

12. RTL 5 — Per-Lane, Per-Eye, Because the Codeword Is Striped

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE.
//
// Combines Chapter 9.5's per-lane attribution with this chapter's
// per-eye attribution, because at 400G BOTH have been destroyed by the
// time anything above the FEC can see an error.
//
// THE TWO COMBINING STEPS, and what each one erases:
//   1. The FEC decoder turns "which symbols were wrong" into "corrected
//      or not". Symbol identity is gone.
//   2. The re-interleaver turns 8 lanes into one stream (Chapter 9.5).
//      Lane identity is gone.
//   3. And within a lane, min() over three eyes destroys the shape
//      (Section 4). Eye identity is gone.
//
// So a design that wants to know WHERE its margin went must capture all
// three below their respective combines. There is no fourth chance.
module pam4_eye_margin_reporter
  import pam4_link_pkg::*;
#(
  parameter int unsigned LANES    = 8,
  parameter int unsigned MARGIN_W = 12,
  parameter int unsigned CNT_W    = 20
) (
  input  logic clk,
  input  logic rst_n,
  input  logic clear,
 
  input  logic [LANES-1:0]                      lane_sample_valid,
  input  logic [LANES-1:0][EYES-1:0][MARGIN_W-1:0] lane_eye_margin,
  input  logic [LANES-1:0][4:0]                 lane_corrected_symbols,
  input  logic [LANES-1:0]                      lane_cw_done,
 
  output logic [LANES-1:0][EYES-1:0][MARGIN_W-1:0] worst,
  output logic [LANES-1:0][CNT_W-1:0]           lane_corrected_total,
 
  // The two comparisons that name a cause.
  output logic [2:0] worst_lane,
  output logic [1:0] worst_eye_of_worst_lane,
  output logic       single_lane_outlier,
  output logic       same_eye_across_lanes,
 
  output logic       window_valid,
  output logic [LANES-1:0] ever_lane_degraded
);
 
  logic [CNT_W-1:0] win_q;
  integer l, e;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      win_q <= '0; window_valid <= 1'b0; ever_lane_degraded <= '0;
      for (l = 0; l < LANES; l = l + 1) begin
        lane_corrected_total[l] <= '0;
        for (e = 0; e < EYES; e = e + 1) worst[l][e] <= '1;
      end
    end else if (clear) begin
      win_q <= '0; window_valid <= 1'b0;
      for (l = 0; l < LANES; l = l + 1) begin
        lane_corrected_total[l] <= '0;
        for (e = 0; e < EYES; e = e + 1) worst[l][e] <= '1;
      end
      // ever_lane_degraded survives.
    end else begin
      win_q <= win_q + 1'b1;
      if (&win_q) window_valid <= 1'b1;
 
      for (l = 0; l < LANES; l = l + 1) begin
        if (lane_sample_valid[l]) begin
          for (e = 0; e < EYES; e = e + 1)
            if (lane_eye_margin[l][e] < worst[l][e])
              worst[l][e] <= lane_eye_margin[l][e];
        end
        if (lane_cw_done[l]) begin
          if (!(&lane_corrected_total[l]))
            lane_corrected_total[l] <= lane_corrected_total[l]
                                       + CNT_W'(lane_corrected_symbols[l]);
        end
      end
    end
  end
 
  // Two questions, two answers, and they point at different hardware.
  always_comb begin
    logic [CNT_W-1:0] hi, lo;
    logic [2:0] hi_idx;
    logic [MARGIN_W-1:0] wm;
    logic [1:0] we;
    int same_count;
 
    hi = '0; lo = '1; hi_idx = 3'd0;
    for (int p = 0; p < LANES; p = p + 1) begin
      if (lane_corrected_total[p] > hi) begin
        hi = lane_corrected_total[p]; hi_idx = 3'(p);
      end
      if (lane_corrected_total[p] < lo) lo = lane_corrected_total[p];
    end
    worst_lane = hi_idx;
    // ONE lane far worse than the rest: that lane's optics or path.
    single_lane_outlier = window_valid && (hi > (lo << 2));
 
    wm = '1; we = 2'd0;
    for (int q = 0; q < EYES; q = q + 1)
      if (worst[hi_idx][q] < wm) begin wm = worst[hi_idx][q]; we = 2'(q); end
    worst_eye_of_worst_lane = we;
 
    // THE SAME eye worst on every lane: not a lane fault at all. It is
    // a level-setting or driver characteristic shared by the whole
    // device, and replacing a fibre will not touch it.
    same_count = 0;
    for (int r = 0; r < LANES; r = r + 1) begin
      logic [MARGIN_W-1:0] m; logic [1:0] idx;
      m = '1; idx = 2'd0;
      for (int q2 = 0; q2 < EYES; q2 = q2 + 1)
        if (worst[r][q2] < m) begin m = worst[r][q2]; idx = 2'(q2); end
      if (idx == we) same_count = same_count + 1;
    end
    same_eye_across_lanes = window_valid && (same_count == int'(LANES));
  end
 
endmodule

Classification: synthesizable.

What it teaches: that same_eye_across_lanes distinguishes a device problem from a path problem, and nothing else does. If the upper eye is worst on every lane, the cause is shared — a level-setting choice, a driver characteristic, a supply — and replacing a fibre will not change it. If one lane is worse and its worst eye differs from the others', the cause is that lane's optics or path.

Deliberately simplified: eight lanes and a fixed outlier ratio. Production telemetry reports per-lane pre-FEC error rates and per-lane eye scans directly, and the comparison is made in software.

Production implication: three combining steps sit between a PAM4 eye and a frame error — min() over eyes, the FEC decoder, and the lane re-interleaver — and each one destroys an attribution permanently. A frame's FCS failure at 400 Gigabit carries no lane, no eye and no symbol, so all three have to be captured below their respective combines. This is the fourth time the track reaches that conclusion, after Chapter 9.3's pairs, Chapter 9.4's byte lanes and Chapter 9.5's PCS lanes — and here three of them are stacked on one link.

13. Properties Worth Asserting, and One Worth Refusing

The organising split here is between the codeword's structure, which this design produces and controls, and the link's error statistics, which it only observes. Everything in the first group is unconditionally assertable. Everything in the second group can only be asserted as a property of the reporting.

Codeword structure

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P1. A codeword is exactly FEC_N symbols. The index never runs past
// the code's length, whatever the source does.
property p_codeword_length;
  @(posedge clk) disable iff (!rst_n)
  sym_index < 10'(FEC_N);
endproperty
a_codeword_length: assert property (p_codeword_length);
 
// P2. The first FEC_K symbols are message, the rest parity. The
// boundary is structural and never moves.
property p_parity_after_message;
  @(posedge clk) disable iff (!rst_n)
  sym_out_valid |-> (is_parity == (sym_index >= 10'(FEC_K)));
endproperty
a_parity_after_message: assert property (p_parity_after_message);
 
// P3. No parity is emitted before the whole message has been consumed.
// The property that makes the Section 9 latency structural rather than
// an implementation choice.
property p_no_early_parity;
  @(posedge clk) disable iff (!rst_n)
  is_parity |-> ($past(sym_index) >= 10'(FEC_K - 1));
endproperty
a_no_early_parity: assert property (p_no_early_parity);
 
// P4. A codeword boundary is a pulse, once per FEC_N symbols.
property p_codeword_end_periodic;
  @(posedge clk) disable iff (!rst_n)
  codeword_end |=> (sym_index == 10'd0);
endproperty
a_codeword_end_periodic: assert property (p_codeword_end_periodic);
 
// P5. A stall mid-codeword is REPORTED. A block code turns an upstream
// pause into a downstream gap that nothing else attributes correctly.
property p_stall_reported;
  @(posedge clk) disable iff (!rst_n)
  (!sym_valid && (sym_index != 10'd0) && (sym_index < 10'(FEC_K)))
    |=> stalled_mid_codeword;
endproperty
a_stall_reported: assert property (p_stall_reported);

Margin reporting — the honesty properties

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P6. Corrected symbols never exceed the code's capability. A decoder
// reporting 18 corrections on a t=15 code has miscounted, and the
// number is being used as a margin measure.
property p_corrections_within_t;
  @(posedge clk) disable iff (!rst_n)
  (cw_done && !uncorrectable) |-> (corrected_symbols <= 5'(FEC_T));
endproperty
a_corrections_within_t: assert property (p_corrections_within_t);
 
// P7. Corrected and uncorrectable are exclusive. An uncorrectable
// codeword has NO correction count -- the decoder does not know how
// many symbols were wrong, only that it was more than t.
property p_uncorrectable_has_no_count;
  @(posedge clk) disable iff (!rst_n)
  (cw_done && uncorrectable) |=> $stable(total_corrected);
endproperty
a_uncorrectable_no_count: assert property (p_uncorrectable_has_no_count);
 
// P8. Every completed codeword lands in exactly one histogram bin. The
// histogram is the margin, so a codeword that misses it is margin
// information destroyed.
property p_every_codeword_binned;
  @(posedge clk) disable iff (!rst_n)
  cw_done |=> (c_codewords == $past(c_codewords) + 1'b1);
endproperty
a_every_codeword_binned: assert property (p_every_codeword_binned);
 
// P9. THE ALARM PROPERTY. A codeword correcting within two of the limit
// raises near_limit -- which is the warning a post-FEC error count
// cannot give, because by then the margin is gone.
property p_near_limit_raised;
  @(posedge clk) disable iff (!rst_n)
  (cw_done && !uncorrectable && (corrected_symbols >= 5'(FEC_T - 2)))
    |=> near_limit;
endproperty
a_near_limit_raised: assert property (p_near_limit_raised);
 
// P10. Margin history survives clear. ever_near_limit and
// ever_uncorrectable are the two facts a later reader needs and the two
// a counter reset destroys.
property p_margin_history_sticky;
  @(posedge clk) disable iff (!rst_n)
  ever_near_limit |=> ever_near_limit;
endproperty
a_margin_history_sticky: assert property (p_margin_history_sticky);

Per-eye and per-lane attribution

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P11. The worst eye is always a real eye. A shape report that names
// nothing is an aggregate margin with extra steps.
property p_worst_eye_valid;
  @(posedge clk) disable iff (!rst_n)
  window_valid |-> (worst_eye < 2'(EYES));
endproperty
a_worst_eye_valid: assert property (p_worst_eye_valid);
 
// P12. The three shape classifications are mutually exclusive -- a
// degradation has one shape, and a design reporting two has a
// comparison bug rather than an interesting link.
property p_shapes_exclusive;
  @(posedge clk) disable iff (!rst_n)
  $onehot0({outer_eyes_worse, middle_eye_worse});
endproperty
a_shapes_exclusive: assert property (p_shapes_exclusive);
 
// P13. A worst-eye margin only ever falls. It is a running minimum, and
// a minimum that rises has been reset by something that should not
// have.
property p_worst_margin_monotone;
  @(posedge clk) disable iff (!rst_n)
  !clear |=> (worst_eye_margin[0] <= $past(worst_eye_margin[0]));
endproperty
a_worst_margin_monotone: assert property (p_worst_margin_monotone);
 
// P14. Eye-closure history survives clear, because reading a counter
// did not change the transmitter.
property p_eye_history_sticky;
  @(posedge clk) disable iff (!rst_n)
  (|ever_closing) |=> (|ever_closing);
endproperty
a_eye_history_sticky: assert property (p_eye_history_sticky);
 
// P15. The worst lane is always named when an outlier is declared.
property p_outlier_names_lane;
  @(posedge clk) disable iff (!rst_n)
  single_lane_outlier |-> (worst_lane < 3'(LANES));
endproperty
a_outlier_names_lane: assert property (p_outlier_names_lane);

Latency accounting

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P16. Worst is never below best. The two bounds must bracket, or the
// FEC's variable delay is being reported by a broken pair of registers.
property p_latency_bounds_ordered;
  @(posedge clk) disable iff (!rst_n)
  (c_samples > 0) |-> (worst_latency >= best_latency);
endproperty
a_latency_bounds_ordered: assert property (p_latency_bounds_ordered);
 
// P17. Exceeding the allotted budget is REPORTED. A stage that can
// overrun silently has removed itself from Chapter 8.4's decomposition.
property p_over_budget_reported;
  @(posedge clk) disable iff (!rst_n)
  (tag_out_valid && ((tag_out_time - tag_in_time) > budget_cycles))
    |=> over_budget;
endproperty
a_over_budget_reported: assert property (p_over_budget_reported);

14. Verification Scenarios

Group by what the environment is doing to the link, because at 400 Gigabit the design's own logic is simple and the interesting failures are all about what it observes and reports.

Codeword structure

  1. A continuous symbol stream — codewords of exactly 544 symbols, 514 message then 30 parity, forever.
  2. A stall at message symbol 0 — no stalled_mid_codeword; a gap between codewords is legal.
  3. A stall at message symbol 300stalled_mid_codeword high, and no parity is emitted until the message completes.
  4. A stall during the parity symbols — parity continues, because it needs no input. The distinction the module's else if encodes.
  5. sym_index across a full codeword — 0 to 543, then 0. Never 544.
  6. Back-to-back codewords with no gapcodeword_end and the next codeword_start in consecutive cycles.
  7. Reset mid-codeword — the index and parity accumulator both clear; a partial codeword is abandoned, not resumed.

Margin reporting

  1. Codewords with 0 corrections — bin 0 fills, total_corrected unchanged, near_limit low.
  2. Codewords with exactly t = 15 corrections — bin 15, near_limit high, and not counted as uncorrectable. The boundary that must be corrected.
  3. Codewords reported uncorrectable — the overflow bin, c_uncorrectable rises, and total_corrected does not move. P7 directly: an uncorrectable codeword has no correction count.
  4. A decoder reporting 16 corrections on a t = 15 code — P6 fires. The property exists because this number is used as a margin measure and a miscount corrupts the measure.
  5. 13 correctionsnear_limit high at FEC_T − 2, two symbols before the limit.
  6. 12 correctionsnear_limit low. The alarm's boundary, tested from both sides.
  7. A mix drifting from bin 2 to bin 13 over a long run — the histogram's mass moves; c_uncorrectable stays at zero throughout. The scenario that demonstrates why the histogram exists.
  8. clear after a near_limit — bins and counters clear, ever_near_limit survives.

Per-eye shape

  1. All three eyes reduced equallyall_eyes_equal high, neither shape flag set.
  2. Outer eyes 30% worse than the middleouter_eyes_worse, worst_eye naming eye 0 or 2. Transmitter compression.
  3. Middle eye 30% worsemiddle_eye_worse, worst_eye = 1. Level mismatch.
  4. A 10% difference between outer and middleneither shape flag. Inside the tolerance band, because real PAM4 levels are deliberately unequal.
  5. Only eye 2 degradedworst_eye = 2, outer_eyes_worse. Asymmetric clipping on one rail.
  6. worst_eye_margin under a rising then falling input — monotone non-increasing. P13.
  7. clear after eye closureever_closing survives per eye.

Latency and multi-lane

  1. Codewords needing no correction versus codewords needing 15best_latency and worst_latency differ, because the error-evaluation pipeline runs only when there is something to evaluate.
  2. A measured delay one cycle over budget_cyclesover_budget pulses, ever_over_budget sticks.
  3. One lane correcting four times as many symbols as the otherssingle_lane_outlier high, worst_lane naming it.
  4. All eight lanes correcting similarlysingle_lane_outlier low, with the identical aggregate correction count as scenario 25. Same total, opposite diagnosis.
  5. The upper eye worst on all eight lanessame_eye_across_lanes high. A device characteristic, not a path fault.
  6. The worst eye differing per lane, one lane an outliersame_eye_across_lanes low, single_lane_outlier high. That lane's optics.

15. Debugging: Nothing Above the FEC Tells You Anything

At 400 Gigabit, three combining steps sit between a physical defect and a frame error, and each one erases an attribution. The table sorts faults by which telemetry, below which combine, distinguishes them.

ObservationLikely causeThe distinguishing check
link up, zero errors, no margin data collectedunknown — and it will stay unknownthere is no diagnosis available; start collecting the histogram
histogram mass at bins 0–1a healthy link with full marginworst_corrected low; nothing to do
histogram mass drifting toward 13–15 over weeksmargin being consumed — ageing, temperature, a connectortrend total_corrected ÷ c_codewords; schedule before the cliff
near_limit firing, c_uncorrectable still zerothe last warning the link will giveact now; the next step is an outage, not more errors
c_uncorrectable non-zeroalready past the cliffthe margin went some time ago and nothing was watching
one lane correcting 4× the othersthat lane's optics, fibre or connectorsingle_lane_outlier, worst_lane
all lanes correcting similarlyreach, plant, temperature, supplysingle_lane_outlier low — same total, different work order
the same eye worst on every lanea device characteristic — levels, driver, supplysame_eye_across_lanes high; replacing a fibre changes nothing
outer eyes worse on one lanethat lane's transmitter compressingouter_eyes_worse with worst_lane
middle eye worselevel setting or DC offsetmiddle_eye_worse
system latency misses its target, no stage owns the missthe FEC exceeding its budgetworst_latency against budget_cycles; ever_over_budget

Three habits.

First, never treat a zero error count as a margin measurement. Section 11 computed it: a fivefold loss of margin leaves c_uncorrectable at zero for any window an operator watches. The clean counter is the FEC succeeding, and it will keep succeeding until it does not.

Second, trend the corrected-symbol mean, and write down its value when the link was commissioned. total_corrected ÷ c_codewords is one division of two exported registers, it is monotone in margin, and the difference between 0.5 and 2.7 is the difference between a link with headroom and a link on borrowed time. Neither shows up anywhere else.

Third, ask whether the worst eye is the same eye on every lane before touching any fibre. Same eye everywhere is a device — levels, driver, supply — and no amount of re-cabling will move it. A different worst eye on an outlier lane is that path.

16. Common Misconceptions

"PAM4 is a better modulation than NRZ."

The wrong model: a technical improvement, adopted because it is superior.

What it costs: you cannot explain why FEC became mandatory at the same moment, or why NRZ is still used wherever the channel allows it.

The corrected model: it is a trade, and an expensive one. Two bits per symbol halves the baud rate and therefore the bandwidth the channel must carry — and it costs 20 × log₁₀(3) = 9.54 dB of signal-to-noise ratio, because four levels in one voltage swing give three eyes of a third the height. It is taken because above about 25 Gbaud the last factor of two in channel bandwidth is enormously expensive and ten decibels of SNR can be bought back by a code.

"FEC is an optional extra that improves reliability."

The wrong model: a safety margin bolted on top of a working link.

What it costs: you misread the link's error statistics completely, because you expect a healthy link to have a low raw error rate.

The corrected model: the raw link does not work. A conforming 400 Gigabit link runs at a pre-FEC bit error rate of about 2.4 × 10⁻⁴ — roughly one bit in four thousand — and that is the operating point, not a fault. RS(544,514) is mandatory in the base specification for every PAM4 interface, and its 5.84% is not a tax on a working link: it is part of the mechanism that makes one.

"Zero post-FEC errors means the link is healthy."

The wrong model: a clean error counter is a clean link.

What it costs: everything — it is the reason margin loss is discovered as an outage.

The corrected model: the FEC was built to make its output insensitive to its input, and it succeeds. Section 11's table: from a pre-FEC BER of 10⁻⁴ to 5 × 10⁻⁴ — a fivefold loss of margin — the codeword loss probability goes from 10⁻¹⁸ to 10⁻⁸, both of which read as zero on any dashboard. The metric that moves is the corrected-symbol count, which goes from 0.54 to 2.71 per codeword over the same range.

"An uncorrectable codeword is a slightly worse corrected one."

The wrong model: a continuum, so the counters can be added together.

What it costs: a corrupted margin measure, because the two events carry different information.

The corrected model: a corrected codeword tells you exactly how many symbols were wrong; an uncorrectable one tells you only that it was more than 15. The decoder does not know the count, and inventing one — or folding it into a total — poisons the only monotone margin signal the link produces. Which is what P7 asserts: an uncorrectable codeword contributes nothing to total_corrected.

"All three PAM4 eyes are the same, so one margin number is enough."

The wrong model: three measurements of the same thing.

What it costs: every PAM4 fault gets diagnosed as "loss," because the aggregate cannot distinguish the causes.

The corrected model: the shape names the cause. Three eyes equally small is additive noise or reach — the channel. Outer eyes worse is driver compression, on the far transmitter. Middle eye worse is a level or offset error. All three report the same minimum margin, and only the comparison separates them — and if the same eye is worst on every lane, the cause is the device and no fibre change will touch it.

17. Interview Reasoning

"Why did the industry move to PAM4, and what did it cost?"

The weak answer says it is faster. The answer that ends the topic states it as a trade with both sides computed. Two bits per symbol halves the baud rate for a given bit rate, which halves the bandwidth the channel must support — and four levels in one voltage swing give three eyes of a third the height, which is 20 × log₁₀(3) = 9.54 dB of SNR. The reason the trade is worth taking is that the two quantities do not cost the same: above about 25 Gbaud, channel loss rises steeply with frequency so the last factor of two in bandwidth is very expensive, while ten decibels can be bought back by a code at 5.84% of the line rate. The finishing point: the penalty is a ratio of amplitudes, so it is exactly 9.54 dB on every medium at every rate — and it is a floor, with nonlinearity and level-dependent noise adding to it.

"Why is FEC mandatory at 400 Gigabit when it was optional before?"

Because the 9.54 dB penalty puts the raw link's error rate at about 2.4 × 10⁻⁴ on a conforming channel — one bit in four thousand — which is not an Ethernet link. In earlier generations a medium specification could require FEC or not, depending on its reach; with PAM4 no conforming channel meets the error expectation unaided, so the correction moves into the base specification. The strong answer adds why it is KP4 rather than KR4: RS(544,514) corrects 15 symbols per codeword against RS(528,514)'s 7, for 5.84% overhead instead of 2.72%, and seven is not enough headroom at a 10⁻⁴ input rate. The finishing observation: the transcoding from 64B/66B to 256B/257B frees 2.73 points, so the FEC costs about 3.1 points net rather than 5.84.

"How much latency does the FEC add, and where does it come from?"

Structurally, from the block. Parity is a function of all 514 message symbols, so the encoder holds a whole message before emitting any; and the decoder cannot correct any symbol until it holds all 544, because a symbol arriving last can change which symbols the decoder concludes were wrong. Compute the floor: the codeword is 544 × 10 = 5440 bits, the 400G line rate after both overheads is 400 × 257/256 × 544/514 = 425 Gb/s, so one fill is 12.8 ns and encode-plus-decode is 25.6 ns. The realised KP4 figure is several times that — commonly cited around 100 ns — because the syndrome, root-finding and error-evaluation stages are pipelines. Put it in context: 100 ns is 3.3 times the 30.36 ns it takes to serialise a whole 1518-octet frame at 400G, and equivalent to about 20 metres of fibre. The finishing point: it is the first latency term Ethernet added rather than inherited, and it does not shrink with rate the way serialization does.

"Would you assert that the post-FEC error rate meets the target?"

No, on two grounds, and the second is the one that matters. First, it is unevaluable — 10⁻¹² needs 10¹² codewords, so the property passes because nothing ran long enough to fail it. Second, and worse: the metric is flat across the entire operating range. From a pre-FEC BER of 10⁻⁴ to 5 × 10⁻⁴ — a link that has lost most of its margin — uncorrectable codewords stay at zero for any window anyone watches. The FEC was built to make its output insensitive to its input, so asserting on the output asserts on the one quantity the mechanism was designed to flatten. And a design graded on that property has no reason to build the corrected-symbol histogram, which is the only warning the link will ever give. Assert the reporting instead: corrections never exceed t, an uncorrectable codeword contributes no count, every codeword is binned, near_limit fires two symbols before the limit, and the history survives a clear.

18. Understanding Check

Half the bandwidth, for 9.54 dB of margin.

What is bought. PAM4 carries two bits per symbol, so a given bit rate needs half the baud — a 100 Gb/s lane is 50 Gbaud in NRZ and 25 Gbaud in PAM4 — and baud rate is what the channel has to support.

What is paid. Four levels share the same voltage swing, so there are three eyes where there was one, each one third of the height:

20 × log₁₀(1/3) = −9.542 dB

And the trade is worth taking because the two are not equally expensive. Above about 25 Gbaud, channel loss on copper and board rises steeply with frequency, so the last factor of two in bandwidth costs better materials, shorter reach and more retimers. Ten decibels of SNR, by contrast, is recoverable by a code — RS(544,514) at 5.84% of the line rate.

Two more things worth knowing. The penalty is a ratio of amplitudes, so it is exactly 9.54 dB on every medium at every rate, with no qualification. And it is a floor — real PAM4 adds level-dependent noise, transmitter nonlinearity and tighter timing sensitivity on top, which is why Section 4 measures the three eyes separately rather than assuming they match.

19. What's Next

The claim this chapter defended: once correction is mandatory, the link's error rate stops being an indicator.

PAM4 buys half the channel bandwidth and pays 20 × log₁₀(3) = 9.54 dB for it, because three eyes share the swing one eye used to have. At that penalty a conforming 400 Gigabit link runs at a raw error rate of about 2.4 × 10⁻⁴ — one bit in four thousand — which is the operating point, not a fault. So RS(544,514) is in the base specification for every PAM4 interface, the transcoding to 256B/257B frees 2.73 points to help pay for its 5.84, and the line rate works out at 400 × 257/256 × 544/514 = 425 Gb/s, or 53.125 Gb/s and 26.5625 GBd on each of eight lanes.

The code adds a latency term Ethernet never had, and it is structural: a block code cannot correct until it holds a whole codeword, giving a floor of 25.6 ns at 400G and a realised figure several times larger — 3.3 times the time it takes to serialise a maximum-length frame.

And it flattens the metric everyone watches. From a pre-FEC BER of 10⁻⁴ to 2 × 10⁻³, uncorrectable codewords move by 6 × 10¹⁶ while reading as zero across the entire useful range — so a link with full margin and a link one step from an outage report the same thing. The quantity that moves smoothly is the corrected-symbol count, which is why the design's real obligation is to keep its distribution and why the rejected property here asserts on the output the mechanism was built to make constant.

Module 9 is complete. Six generations, from a shared coax to eight PAM4 lanes, and the pattern held throughout: mechanisms attached to a medium died with it, and mechanisms attached to a frame were inherited by every link that carries frames.

Chapter 10.1 — MII opens the module that owns the MAC/PHY interfaces in their own right.

Module 9 cited them in passing — Chapter 9.3 §11 gave GMII's width arithmetic, Chapter 9.4 §4 gave XGMII's. Module 10 gives the pin lists, the clocking, the latency each one adds, and the bring-up failures each one is famous for — starting with the original 4-bit MII, its two clock domains, and the signals that carry carrier sense and collision to a MAC that, on most links since, has had no use for either.

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.