Ethernet · Module 3
Line Modulation — NRZ and PAM4
PAM4 does not create bandwidth. At the same data rate it halves the symbol rate, recovering horizontal margin from frequency-dependent loss, and pays for it by cutting the vertical eye to one third — a 9.54 dB penalty that no circuit recovers and that forward error correction must.
Chapter 3.5 established what a line code buys, and it was careful to say what a code is not. A code decides what a bit sequence means. It says nothing about how many volts carry a bit, or how many bits ride on one symbol.
That is modulation, and Chapter 3.4 insisted the two be kept apart. This chapter is why the insistence matters.
The usual account of PAM4 is that it "doubles the bandwidth" by carrying two bits per symbol instead of one. That account is wrong in a way that makes every downstream decision wrong with it, because it treats an extra bit as something you can simply have.
You cannot. Chapter 3.3 established that a receiver decides against a threshold with finite margin, and that the eye has exactly two dimensions. Four levels in the same voltage swing means three thresholds instead of one, and each gap is a third the size. The extra bit is bought, and the currency is vertical margin.
But that is only half the transaction, and the half everyone quotes. At the same data rate, four levels means half the symbol rate — and Chapter 3.3 §6 showed that a channel's loss rises with frequency. Halving the symbol rate moves the signal's energy into a range the channel treats far more kindly, which buys horizontal margin back.
What does a second bit per symbol actually cost, what does it buy back, and which channels does the trade favour?
1. Scope — What This Chapter Owns
This chapter owns: the separation between coding and modulation stated operationally rather than as a definition; NRZ as the baseline; PAM4's four levels, three eyes and two bits; the vertical penalty derived rather than quoted; the horizontal gain and why it is channel-dependent; Gray mapping and precisely what it buys; the asymmetry between the two bits of a symbol; and why the trade forces forward error correction.
This chapter does not own: the block codes — Chapter 3.5 owns those, and a PAM4 link still needs one. Nor the channel itself: attenuation, reflections, inter-symbol interference and jitter are Chapter 3.3's, and this chapter uses its two-axis eye budget as a given. Forward error correction is Chapter 3.7, though Section 14 explains why this chapter makes it mandatory.
The debt it repays: Chapter 3.3 built a symbol_slicer parameterised on level count and said plainly that what each additional level costs in vertical margin belonged here. Section 5 is that answer, and Section 10 extends that slicer to three thresholds.
2. Modulation Is Not Coding — Stated Operationally
The distinction is easy to assert and easy to blur. Here it is in a form you can apply.
| Question | Answered by |
|---|---|
| What bit sequence represents this octet? | the code — Chapter 3.5 |
| Is this bit pattern legal? | the code |
| How does the receiver find a block boundary? | the code, via its invalid-pattern space |
| How many voltage levels exist? | modulation |
| How many bits does one symbol carry? | modulation |
| How much margin separates two adjacent decisions? | modulation, against the channel |
The operational test: change one and ask whether the other must change. Swapping 64b/66b for 8B/10B on a PAM4 link changes the overhead, the alignment behaviour and the control encoding — and leaves the four voltage levels, the three thresholds and the 9.54 dB penalty exactly as they were. Swapping PAM4 for NRZ changes every one of those and leaves the code untouched.
They compose because they answer different questions. Chapter 3.4 §4's combinatorial argument applies here in miniature: N codes and M modulations need N plus M pieces, not N times M.
3. NRZ — The Baseline
Non-return-to-zero: two levels, one bit per symbol. A high level is one value, a low level the other, and the symbol occupies the full unit interval.
Its virtues are all consequences of having one threshold.
- One decision per symbol, midway between the two levels, with the full swing available as margin.
- The maximum possible vertical margin for a given swing. Nothing carries more margin per volt, because there is nothing to divide.
- A simple receiver. One comparator, one threshold, one adaptation loop.
Its cost is the symbol rate. One bit per symbol means the symbol rate equals the bit rate, and Chapter 3.3 §6 established that the channel attenuates high frequencies more. Push the bit rate high enough and the loss at the required symbol rate closes the eye horizontally no matter how much vertical margin the two-level scheme preserved.
That is the wall PAM4 exists to get around, and naming it properly is the key to the whole chapter: NRZ does not fail because two levels are insufficient. It fails because at high enough rates the symbol rate itself becomes the problem.
4. PAM4 — Four Levels, Three Eyes, Two Bits
Pulse amplitude modulation with four levels. The same voltage swing is divided into four, each level carrying one of four symbol values, and four values carry two bits.
Conceptual — NRZ against PAM4 over the same swing
8 cyclesThis figure is conceptual and labelled so. A digital waveform cannot render four analog levels; what it shows correctly is the information structure — one NRZ bit per symbol against two PAM4 bits per symbol, and one threshold against three.
Three thresholds means three eyes, stacked vertically, and the published description of the transition structure is worth carrying: four levels produce 12 distinct signal transitions, six rising and six falling, against NRZ's two. Every one of those transitions has its own rise time, its own settling behaviour, and its own contribution to the eye's closure — which is one reason PAM4 receivers are so much more complex than the level count alone suggests.
5. The Vertical Cost, Derived
This is the number, and it should be derived rather than memorised.
Take a peak-to-peak swing of V.
- NRZ places two levels at the extremes. The single eye's height is the full swing,
V. The decision threshold sits at the midpoint, so the margin from either level to the threshold isV/2. - PAM4 places four equally spaced levels across the same swing. The spacing between adjacent levels is
V/3. Each of the three eyes has heightV/3, and the margin from a level to its nearest threshold isV/6.
The ratio is exactly one third, and expressed in decibels as an amplitude ratio:
penalty = 20 · log10(1/3)
= 20 · (−0.4771)
= −9.54 dBThis matches the standard result — the eye height for PAM4 is one third that of NRZ, giving a link-budget penalty of about 9.54 dB.
6. The Horizontal Gain
Now the half that is usually missing.
At the same data rate, PAM4's symbol rate is half NRZ's, because each symbol carries two bits instead of one.
data rate R, NRZ : symbol rate = R, UI = 1/R
data rate R, PAM4 : symbol rate = R/2, UI = 2/RThe unit interval doubles. Chapter 3.3 §8 defined eye width as roughly UI − TJ(BER) — so with the same total jitter, doubling the UI more than doubles the horizontal margin.
And the channel treats the signal far better. Chapter 3.3 §6 established that conductor loss rises with the square root of frequency and dielectric loss roughly linearly with it. Halving the symbol rate halves the fundamental frequency, and the insertion loss at that lower frequency is substantially smaller.
An illustrative calculation, with the caveat stated first: the loss recovered depends entirely on the channel's own frequency response, so no universal number exists. But the shape is instructive. If a channel's insertion loss grows roughly linearly with frequency over the range of interest — a reasonable approximation where dielectric loss dominates — then halving the frequency roughly halves the loss in dB. A channel losing 30 dB at the NRZ symbol rate might lose around 15 dB at PAM4's, recovering about 15 dB.
Against a 9.54 dB vertical cost, that is a net gain of roughly 5 dB — and the link that could not close at NRZ closes at PAM4.
Now run the same arithmetic on a short channel. A channel losing 6 dB at the NRZ rate might lose 3 dB at PAM4's, recovering 3 dB against a 9.54 dB cost — a net loss of about 6.5 dB. On that channel PAM4 is strictly worse, and NRZ is the right answer.
These figures are illustrative and the linear-loss approximation is deliberately crude. What is not crude is the conclusion: the trade favours PAM4 precisely when the channel is lossy enough at the NRZ symbol rate, and it favours NRZ otherwise. That is why the transition happened at a particular point in the rate progression rather than as soon as PAM4 was understood.
7. The Trade, on Both Axes
| NRZ | PAM4 | Who wins | |
|---|---|---|---|
| bits per symbol | 1 | 2 | PAM4 |
| symbol rate at data rate R | R | R/2 | PAM4 |
| decision thresholds | 1 | 3 | NRZ |
| eye height | full swing | one third | NRZ, by 9.54 dB |
| unit interval | 1/R | 2/R | PAM4, doubled |
| channel loss at the symbol rate | at R | at R/2 | PAM4, channel-dependent |
| distinct transitions | 2 | 12 | NRZ |
| receiver complexity | one comparator | three, plus level adaptation | NRZ |
| error structure | uniform | asymmetric across bits | NRZ |
The bottom row is the one this chapter's RTL is about, and Section 8 explains it.
8. Gray Coding — What the Mapping Buys
Four levels must be assigned to four two-bit values, and there are 24 ways to do it. The assignment is not arbitrary, and the reason is a direct consequence of how errors happen.
How a symbol error occurs: the received sample lands on the wrong side of a threshold. Chapter 3.3 §11's slicer decides against the nearest threshold, so the overwhelmingly likely error is to an adjacent level — the sample crossed one threshold, not two. Crossing two thresholds requires an excursion three times larger, which on a Gaussian noise distribution is enormously less likely.
So the design question is: when a symbol becomes its neighbour, how many bits flip?
A naive binary mapping assigns levels 0, 1, 2, 3 to 00, 01, 10, 11:
| Level pair | Bits | Bits flipped |
|---|---|---|
| 0 to 1 | 00 to 01 | 1 |
| 1 to 2 | 01 to 10 | 2 |
| 2 to 3 | 10 to 11 | 1 |
The middle transition flips both bits. One threshold crossing, two bit errors.
Gray mapping assigns them 00, 01, 11, 10:
| Level pair | Bits | Bits flipped |
|---|---|---|
| 0 to 1 | 00 to 01 | 1 — the LSB |
| 1 to 2 | 01 to 11 | 1 — the MSB |
| 2 to 3 | 11 to 10 | 1 — the LSB |
Every adjacent-level error flips exactly one bit. That is the entire purpose of Gray coding here, and the published effect is that it reduces the error to one bit per symbol, roughly halving the overall bit error rate relative to a mapping where the middle transition flips two.
9. RTL 1 — The PAM4 Mapper
// SYNTHESIZABLE. PAM4 symbol mapping and demapping, Gray coded.
//
// The mapping is the whole module, and it is not arbitrary. Errors move a
// sample across ONE threshold, so the design goal is that adjacent levels
// differ in exactly one bit. Gray coding achieves that for all three
// adjacent pairs; a naive binary mapping fails at the middle pair, where
// 01 -> 10 flips both bits from a single threshold crossing.
package pam4_pkg;
// Level index, ascending in amplitude: L0 lowest, L3 highest.
typedef logic [1:0] level_t;
// Gray map. Read the second column downward and check that each adjacent
// pair differs in exactly one position:
//
// L0 = 00
// L1 = 01 (LSB changed)
// L2 = 11 (MSB changed)
// L3 = 10 (LSB changed)
//
// Which also says WHICH bit each threshold owns -- the fact Section 12
// turns into separate error accounting.
localparam logic [1:0] GRAY_L0 = 2'b00;
localparam logic [1:0] GRAY_L1 = 2'b01;
localparam logic [1:0] GRAY_L2 = 2'b11;
localparam logic [1:0] GRAY_L3 = 2'b10;
// Which threshold separates which pair. Crossing THR_MID flips the MSB;
// crossing THR_LOW or THR_HIGH flips the LSB.
typedef enum logic [1:0] {
THR_LOW = 2'd0, // between L0 and L1
THR_MID = 2'd1, // between L1 and L2
THR_HIGH = 2'd2
} threshold_e;
endpackage
module pam4_mapper
import pam4_pkg::*;
(
input logic clk,
input logic rst_n,
input logic in_valid,
input logic [1:0] in_bits, // {msb, lsb}
output logic in_ready,
input logic out_ready,
output logic out_valid,
output level_t out_level // amplitude index, NOT the bit pair
);
assign in_ready = out_ready;
// Bits to level. The inverse of the Gray map above.
function automatic level_t map_bits(input logic [1:0] b);
case (b)
GRAY_L0: map_bits = 2'd0;
GRAY_L1: map_bits = 2'd1;
GRAY_L2: map_bits = 2'd2;
default: map_bits = 2'd3; // GRAY_L3
endcase
endfunction
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
out_valid <= 1'b0;
out_level <= 2'd0;
end else if (out_ready) begin
out_valid <= in_valid;
if (in_valid) out_level <= map_bits(in_bits);
end
end
endmodule
// SYNTHESIZABLE. The inverse. Kept as a separate module deliberately.
//
// A single module holding both directions invites someone to "simplify" the
// two case statements into one shared table -- after which a mapping change
// silently applies to only one direction, and the link carries data that
// decodes to something else. Keeping them separate and asserting the round
// trip (Section 15) is cheaper than the bug.
module pam4_demapper
import pam4_pkg::*;
(
input logic clk,
input logic rst_n,
input logic in_valid,
input level_t in_level,
output logic out_valid,
output logic [1:0] out_bits
);
function automatic logic [1:0] unmap_level(input level_t l);
case (l)
2'd0: unmap_level = GRAY_L0;
2'd1: unmap_level = GRAY_L1;
2'd2: unmap_level = GRAY_L2;
default: unmap_level = GRAY_L3;
endcase
endfunction
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
out_valid <= 1'b0;
out_bits <= 2'b00;
end else begin
out_valid <= in_valid;
if (in_valid) out_bits <= unmap_level(in_level);
end
end
endmoduleClassification: synthesizable.
What it teaches: that the mapping is a design decision with a measurable consequence, not a labelling convention. Gray coding makes every adjacent-level error flip exactly one bit; a naive binary mapping makes the middle crossing flip two, roughly doubling the bit error rate for the same symbol error rate.
Deliberately simplified: the module produces a level index, not an amplitude. Turning an index into four analog levels — including any deliberate unequal spacing to compensate transmitter nonlinearity — is a driver function that RTL does not represent.
Production implication: keeping the two directions in separate modules is deliberate. A shared table looks like an obvious simplification and creates a defect class where a mapping change applies to one direction only — producing a link that carries data faithfully and decodes it to something else, with every status indicator healthy. Section 15's round-trip property is the guard.
Later ownership: how errors from these levels distribute into a forward-error-correction symbol is Chapter 3.7.
10. RTL 2 — The Three-Threshold Slicer
Chapter 3.3 §11 built a symbol_slicer parameterised on level count and left the consequences of raising that parameter to this chapter. This is the specialisation, with the parts PAM4 needs that NRZ does not.
// SYNTHESIZABLE. PAM4 slicing, with per-eye margin.
//
// The specialisation over Chapter 3.3's generic slicer: THREE EYES ARE NOT
// ONE EYE. They are routinely unequal in practice -- transmitter
// nonlinearity compresses the outer levels, and the middle eye often
// differs from both outers. A single "margin" output averages the healthy
// eyes with the failing one and reports a link that looks fine.
//
// So this module reports margin PER EYE, and names the worst.
module pam4_slicer
import pam4_pkg::*;
#(
parameter int unsigned SAMPLE_W = 8,
// How close to a threshold counts as low confidence, in sample units.
parameter int unsigned GUARD = 6
) (
input logic clk,
input logic rst_n,
input logic sample_valid,
input logic signed [SAMPLE_W-1:0] sample,
// Three thresholds, ascending. Inputs from the adaptation loop, exactly
// as in Chapter 3.3 -- where they come from is not an RTL question.
input logic signed [SAMPLE_W-1:0] thr_low,
input logic signed [SAMPLE_W-1:0] thr_mid,
input logic signed [SAMPLE_W-1:0] thr_high,
output logic sym_valid,
output level_t level,
output logic [1:0] bits, // Gray-demapped, for convenience
// Distance to the threshold this decision was made against.
output logic [SAMPLE_W-1:0] margin,
output threshold_e nearest_threshold,
output logic low_confidence,
// Which eye this decision lived in. Section 11 bins errors on this, and
// that binning is the chapter's main diagnostic.
output threshold_e eye_id
);
logic signed [SAMPLE_W:0] d_low, d_mid, d_high;
level_t level_c;
threshold_e near_c;
logic [SAMPLE_W:0] dist_c;
always_comb begin
// Decision: count thresholds exceeded. Same structure as Chapter 3.3's
// generic loop, unrolled for three.
if (sample > thr_high) level_c = 2'd3;
else if (sample > thr_mid) level_c = 2'd2;
else if (sample > thr_low) level_c = 2'd1;
else level_c = 2'd0;
d_low = (sample > thr_low) ? (SAMPLE_W+1)'(sample - thr_low)
: (SAMPLE_W+1)'(thr_low - sample);
d_mid = (sample > thr_mid) ? (SAMPLE_W+1)'(sample - thr_mid)
: (SAMPLE_W+1)'(thr_mid - sample);
d_high = (sample > thr_high) ? (SAMPLE_W+1)'(sample - thr_high)
: (SAMPLE_W+1)'(thr_high - sample);
// Nearest threshold, and its distance. The nearest threshold IDENTIFIES
// the eye this sample sits in, which is what Section 11 needs.
if ((d_low <= d_mid) && (d_low <= d_high)) begin
near_c = THR_LOW; dist_c = d_low;
end else if ((d_mid <= d_low) && (d_mid <= d_high)) begin
near_c = THR_MID; dist_c = d_mid;
end else begin
near_c = THR_HIGH; dist_c = d_high;
end
end
function automatic logic [1:0] to_bits(input level_t l);
case (l)
2'd0: to_bits = GRAY_L0;
2'd1: to_bits = GRAY_L1;
2'd2: to_bits = GRAY_L2;
default: to_bits = GRAY_L3;
endcase
endfunction
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
sym_valid <= 1'b0;
level <= 2'd0;
bits <= 2'b00;
margin <= '0;
nearest_threshold <= THR_LOW;
eye_id <= THR_LOW;
low_confidence <= 1'b0;
end else begin
sym_valid <= sample_valid;
if (sample_valid) begin
level <= level_c;
bits <= to_bits(level_c);
margin <= dist_c[SAMPLE_W-1:0];
nearest_threshold <= near_c;
eye_id <= near_c;
low_confidence <= (dist_c < (SAMPLE_W+1)'(GUARD));
end
end
end
endmoduleClassification: synthesizable.
What it teaches: that PAM4 margin is three numbers, not one. The eyes are routinely unequal — transmitter nonlinearity compresses the outer levels, and the middle eye frequently differs from both — and an aggregate margin averages the healthy eyes with the failing one, reporting a link that looks fine while one eye is closing.
Deliberately simplified: thresholds are inputs, exactly as in Chapter 3.3. Their adaptation is analog and control-theoretic. Also absent is any decision-feedback structure, which real PAM4 receivers need because the inter-symbol interference of Chapter 3.3 §6 is far more damaging when the margin is a third the size.
Production implication: eye_id is the output that makes the rest of the chapter's diagnostics possible. Without knowing which eye a decision lived in, per-eye error rates cannot be computed, and per-eye error rates are the only way to distinguish transmitter nonlinearity from a channel problem — Section 17's core partition.
Later ownership: the adaptation loop that sets three thresholds and keeps them centred is an analog and control-loop subject rather than an RTL one.
11. RTL 3 — Per-Eye and Per-Bit Error Attribution
Section 8 established that the three eyes feed the two bits unequally. This is the block that measures it, and it is the chapter's most useful piece of silicon.
// SYNTHESIZABLE INSTRUMENTATION. Outside the datapath.
//
// Two independent measurements, and each answers a question the other
// cannot:
//
// PER-EYE : are the three eyes equally healthy? An imbalance points at
// the TRANSMITTER -- nonlinearity compressing outer levels --
// or at threshold adaptation. Not at the channel.
//
// PER-BIT : is the LSB-to-MSB error ratio near two, as Gray mapping
// predicts? A ratio near ONE means the middle eye is
// anomalously bad. A ratio near FOUR means the outer eyes
// are. The ratio localises the problem to an eye without
// needing the per-eye counters at all -- useful when only
// post-demap errors are observable.
module pam4_error_attribution
import pam4_pkg::*;
#(
parameter int unsigned CNT_W = 24
) (
input logic clk,
input logic rst_n,
input logic clear,
input logic sym_valid,
input threshold_e eye_id,
input logic low_confidence,
input logic symbol_error, // from a downstream reference check
// Which bit or bits actually differed, from the same reference.
input logic msb_error,
input logic lsb_error,
// Symbols observed and errors seen, per eye.
output logic [CNT_W-1:0] c_eye_symbols [3],
output logic [CNT_W-1:0] c_eye_errors [3],
output logic [CNT_W-1:0] c_msb_errors,
output logic [CNT_W-1:0] c_lsb_errors,
// LSB-to-MSB error ratio, scaled by 256. Gray mapping predicts about 512
// (a ratio of two) when the three eyes are equally healthy.
output logic [15:0] lsb_msb_ratio_x256,
// Asserted when the ratio departs far from the predicted two, which means
// the eyes are unequal. This is a claim about the TRANSMITTER or the
// adaptation, not about the channel.
output logic eye_imbalance_suspected,
// A symbol error that flipped BOTH bits. With Gray mapping this requires
// crossing two thresholds, which is enormously less likely than crossing
// one -- so a non-trivial count here means the mapping is wrong, not that
// the channel is bad. Different fault entirely.
output logic [CNT_W-1:0] c_double_bit_errors
);
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; // saturating
endfunction
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n || clear) begin
for (int unsigned e = 0; e < 3; e++) begin
c_eye_symbols[e] <= '0;
c_eye_errors[e] <= '0;
end
c_msb_errors <= '0;
c_lsb_errors <= '0;
c_double_bit_errors <= '0;
end else if (sym_valid) begin
c_eye_symbols[eye_id] <= bump(c_eye_symbols[eye_id], 1'b1);
c_eye_errors[eye_id] <= bump(c_eye_errors[eye_id], symbol_error || low_confidence);
c_msb_errors <= bump(c_msb_errors, msb_error);
c_lsb_errors <= bump(c_lsb_errors, lsb_error);
// Both bits from one symbol error: two threshold crossings, which
// Gray mapping makes very rare. A real count means a mapping fault.
c_double_bit_errors <= bump(c_double_bit_errors, msb_error && lsb_error);
end
end
always_comb begin
lsb_msb_ratio_x256 = (c_msb_errors == 0) ? 16'd0
: 16'((c_lsb_errors * 256) / c_msb_errors);
// Gray mapping predicts 512. Flag a departure of more than 50 percent
// in either direction, once there is enough data to mean anything.
eye_imbalance_suspected = (c_msb_errors > 16'd64)
&& ((lsb_msb_ratio_x256 < 16'd256)
|| (lsb_msb_ratio_x256 > 16'd1024));
end
endmoduleClassification: synthesizable instrumentation.
What it teaches: that the Gray mapping's structure is itself a measurement instrument. Because the mapping is known, the predicted LSB-to-MSB error ratio is known — about two when the eyes are equal — and any departure from it localises the fault to an eye without needing per-eye counters. That is genuinely useful when only post-demapper errors are observable, which is common when the PHY is purchased IP.
Deliberately simplified: the reference check that produces symbol_error, msb_error and lsb_error is an input. In practice it comes from a known test pattern, from FEC correction results, or from a loopback comparison.
Production implication: c_double_bit_errors is a mapping check, not a channel one. With Gray coding, flipping both bits of a symbol requires crossing two thresholds — an excursion three times larger, and on a Gaussian distribution enormously less likely. A non-trivial count means the mapping is wrong at one end, which is a configuration or design fault that would otherwise present as an inexplicably high bit error rate.
Later ownership: how these error statistics feed forward error correction sizing is Chapter 3.7.
12. RTL 4 — Eye-Health Telemetry
// SYNTHESIZABLE INSTRUMENTATION.
//
// Chapter 3.3 §13 trended one margin. PAM4 has three, and the SPREAD
// between them is a measurement in its own right:
//
// all three falling together -> the channel is degrading
// one falling alone -> the transmitter or the adaptation
// outer two below the middle -> transmitter compression, the classic
// PAM4 signature
//
// A design that trends one averaged margin can distinguish none of these.
module pam4_eye_telemetry
import pam4_pkg::*;
#(
parameter int unsigned SAMPLE_W = 8,
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 sym_valid,
input threshold_e eye_id,
input logic [SAMPLE_W-1:0] margin,
// Minimum margin per eye over the window that just closed. Three trends.
output logic [SAMPLE_W-1:0] window_min [3],
output logic window_valid,
// Worst ever per eye. Survives `clear` -- a property of the installed
// link, not of a measurement window someone chose.
output logic [SAMPLE_W-1:0] worst_ever [3],
// Spread between the healthiest and the worst eye in the last window.
// Near zero means balanced eyes; large means one eye is failing alone.
output logic [SAMPLE_W-1:0] eye_spread,
output threshold_e worst_eye
);
logic [SAMPLE_W-1:0] run_min [3];
logic [WIN_W-1:0] win_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
for (int unsigned e = 0; e < 3; e++) begin
run_min[e] <= '1;
window_min[e] <= '1;
worst_ever[e] <= '1;
end
win_q <= '0;
window_valid <= 1'b0;
end else begin
if (clear) begin
for (int unsigned e = 0; e < 3; e++) run_min[e] <= '1;
win_q <= '0;
window_valid <= 1'b0;
// worst_ever deliberately NOT cleared.
end else if (sym_valid) begin
if (margin < run_min[eye_id]) run_min[eye_id] <= margin;
if (margin < worst_ever[eye_id]) worst_ever[eye_id] <= margin;
if (win_q == WIN_W'(WINDOW - 1)) begin
for (int unsigned e = 0; e < 3; e++) begin
window_min[e] <= (e == eye_id) && (margin < run_min[e]) ? margin
: run_min[e];
run_min[e] <= '1;
end
window_valid <= 1'b1;
win_q <= '0;
end else begin
win_q <= win_q + 1'b1;
end
end
end
end
// Spread and worst eye, from the last closed window.
logic [SAMPLE_W-1:0] max_c, min_c;
threshold_e worst_c;
always_comb begin
max_c = window_min[0];
min_c = window_min[0];
worst_c = THR_LOW;
for (int unsigned e = 1; e < 3; e++) begin
if (window_min[e] > max_c) max_c = window_min[e];
if (window_min[e] < min_c) begin
min_c = window_min[e];
worst_c = threshold_e'(e);
end
end
eye_spread = max_c - min_c;
worst_eye = worst_c;
end
endmoduleClassification: synthesizable instrumentation.
What it teaches: that with three eyes, the spread between them is a measurement the individual margins do not provide. Three eyes falling together is a channel story; one falling alone is a transmitter or adaptation story. Those send different people to different places, and only the spread separates them.
Deliberately simplified: one window and a minimum per eye. Production designs keep histograms per eye, which reveal distribution shape rather than only the worst point.
Production implication: the specific signature to watch for is the outer two eyes below the middle one. That is transmitter compression — the driver cannot reach the extreme levels linearly, so the top and bottom levels are pulled inward and the outer eyes shrink. It is common, it is correctable with transmitter equalisation or deliberate unequal level spacing, and it is completely invisible to an averaged margin.
13. Why This Makes Forward Error Correction Mandatory
The chapter's arithmetic has one more consequence, and it is the reason the next chapter exists.
Section 5 established a fixed 9.54 dB vertical cost. Section 6 showed the horizontal recovery can exceed it on a lossy channel. But "exceed it" is not "exceed it comfortably" — the links that adopted PAM4 did so because the trade was net favourable, not because it was generous, and what remains after the trade is a link operating with far less margin than an NRZ link of an earlier generation.
A link with less margin has a higher raw error rate. That is Chapter 3.3's entire argument: errors are the tail of a distribution, and a smaller margin puts the decision threshold closer to the distribution's body.
Three effects compound, and each is from a different chapter:
| Effect | Magnitude | Source |
|---|---|---|
| the eye is one third as tall | about 9.54 dB of margin | this chapter, Section 5 |
| the LSB sees two eyes | roughly twice the MSB's error rate | this chapter, Section 8 |
| a self-synchronising descrambler multiplies errors | one channel error becomes three | Chapter 3.5 §12 |
The raw error rate that reaches the layer above is far higher than an NRZ link's, and no amount of circuit improvement closes a 9.54 dB arithmetic gap.
So it has to be recovered downstream, and that is forward error correction. The published clause structure shows this decision made explicitly: for 100 Gb/s Ethernet the IEEE standardised two Reed-Solomon codes — KR4, which is RS(528,514), targeted at NRZ modulation, and KP4, which is RS(544,514), targeted at PAM4. The PAM4 code corrects 15 symbol errors per codeword against KR4's 7, using 30 parity symbols against 14.
Read that as the price list. The stronger code exists specifically because PAM4's raw error rate is higher, and its extra parity is the second instalment on the extra bit per symbol. The first instalment was 9.54 dB of margin; the second is FEC overhead and FEC latency.
Chapter 3.7 is where that instalment is paid.
14. Assertions
Some properties below are consequences of the Gray mapping, which is a design choice with a known structure rather than an IEEE requirement. Others are properties of these teaching models. Each is labelled, and none is a normative electrical requirement — no clause's eye mask or amplitude envelope is asserted here, because no RTL assertion can express one.
// ─── Conservation: the mapping round-trips ─────────────────────────────────
// Catches: mapper and demapper tables that disagree -- the defect that
// arises the moment someone "simplifies" the two case statements into one
// shared table and then changes only one direction.
property p_map_round_trip;
@(posedge clk) disable iff (!rst_n)
demap_valid |-> (out_bits == $past(in_bits, 2));
endproperty
// ─── MAPPING PROPERTY: adjacent levels differ in exactly one bit ───────────
// This is what Gray coding MEANS, and it is the property everything else in
// the chapter rests on. Catches: a table typo that reverts one pair to a
// binary mapping, doubling the bit error rate for that pair only -- which
// presents as an inexplicably elevated error rate with no other symptom.
property p_gray_adjacency;
@(posedge clk) disable iff (!rst_n)
1'b1 |-> ($countones(GRAY_L0 ^ GRAY_L1) == 1)
&& ($countones(GRAY_L1 ^ GRAY_L2) == 1)
&& ($countones(GRAY_L2 ^ GRAY_L3) == 1);
endproperty
// ─── MAPPING PROPERTY: the middle threshold owns the MSB ───────────────────
// Catches: a mapping that puts the MSB transition on an outer threshold,
// which inverts the error attribution of Section 11 and makes every
// diagnostic in Section 17 point the wrong way.
property p_mid_threshold_owns_msb;
@(posedge clk) disable iff (!rst_n)
1'b1 |-> ((GRAY_L1[1] ^ GRAY_L2[1]) == 1'b1)
&& ((GRAY_L0[1] ^ GRAY_L1[1]) == 1'b0)
&& ((GRAY_L2[1] ^ GRAY_L3[1]) == 1'b0);
endproperty
// ─── Safety: the slicer decision matches the thresholds ────────────────────
// Catches: an off-by-one in the three-way comparison, which shifts every
// decided level by one and corrupts all data while margin looks perfect.
property p_slicer_level_correct;
@(posedge clk) disable iff (!rst_n)
sym_valid |-> (level == ($past(sample) > $past(thr_high)) ? 2'd3 :
($past(sample) > $past(thr_mid)) ? 2'd2 :
($past(sample) > $past(thr_low)) ? 2'd1 : 2'd0);
endproperty
// ─── Safety: thresholds are ordered ────────────────────────────────────────
// ASSUMPTION on the adaptation loop, asserted at the boundary. Catches: an
// adaptation that crosses two thresholds, after which the decision logic is
// meaningless and produces plausible-looking garbage.
property p_thresholds_ordered;
@(posedge clk) disable iff (!rst_n)
sample_valid |-> (thr_low < thr_mid) && (thr_mid < thr_high);
endproperty
// ─── Causation: eye identity follows the nearest threshold ─────────────────
// Catches: eye_id derived from the decided LEVEL rather than the nearest
// threshold, which mis-bins every decision and silently invalidates the
// whole of Section 11's attribution.
property p_eye_id_is_nearest;
@(posedge clk) disable iff (!rst_n)
sym_valid |-> (eye_id == nearest_threshold);
endproperty
// ─── Safety: margin is a distance ──────────────────────────────────────────
// Catches: a signed/unsigned mix in the subtraction, which wraps a tiny
// distance into a huge one and reports excellent margin on a failing eye.
property p_margin_is_a_distance;
@(posedge clk) disable iff (!rst_n)
sym_valid |-> (margin <= (1 << (SAMPLE_W-1)));
endproperty
// ─── Conservation: every symbol lands in exactly one eye ───────────────────
// Catches: a binning path that drops symbols, which biases the per-eye
// rates in a way no downstream analysis can detect or correct.
property p_symbol_binned_once;
@(posedge clk) disable iff (!rst_n)
sym_valid |=> (c_eye_symbols[$past(eye_id)] == $past(c_eye_symbols[$past(eye_id)]) + 1);
endproperty
// ─── Conservation: eye errors never exceed eye symbols ─────────────────────
// Catches: counting an error without counting its symbol, producing an
// error rate above one and destroying trust in the whole measurement.
property p_eye_errors_bounded;
@(posedge clk) disable iff (!rst_n)
(c_eye_errors[0] <= c_eye_symbols[0])
&& (c_eye_errors[1] <= c_eye_symbols[1])
&& (c_eye_errors[2] <= c_eye_symbols[2]);
endproperty
// ─── Causation: a double-bit error implies two crossings ───────────────────
// Not a prohibition -- it can happen. But with Gray mapping it requires a
// three-times-larger excursion, so it must be counted separately rather
// than folded into a general error count.
property p_double_bit_counted_separately;
@(posedge clk) disable iff (!rst_n)
(sym_valid && msb_error && lsb_error) |=>
(c_double_bit_errors == $past(c_double_bit_errors) + 1);
endproperty
// ─── Stability: worst-ever margin survives a clear ─────────────────────────
// Catches: worst_ever folded into the clear branch, letting an operator
// destroy the installation's history without knowing they did.
property p_worst_ever_survives_clear;
@(posedge clk) disable iff (!rst_n)
clear |=> (worst_ever[0] <= $past(worst_ever[0]));
endproperty
// ─── Bounded response: a window always closes ──────────────────────────────
// Catches: a window counter that can be starved, so the three trends never
// produce a data point.
property p_window_closes;
@(posedge clk) disable iff (!rst_n)
$rose(sym_valid) |-> ##[1:WINDOW+1] window_valid;
endproperty15. Verification
Scenarios
- All four bit pairs through the mapper and back. Exhaustive — four cases is a complete proof of the mapping, and there is no reason to sample it.
- Gray adjacency, checked structurally. Verify each adjacent level pair differs in exactly one bit position. This is a static check on the constants and it catches a table typo that would otherwise present only as an elevated error rate.
- Middle-threshold ownership. Verify the MSB differs only between L1 and L2, and the LSB only between L0/L1 and L2/L3. Section 11's entire attribution rests on this.
- Slicing at each of the four level centres. Verify the correct level,
low_confidencedeasserted, and a large margin. - A sample exactly on each of the three thresholds. Three runs. Verify the decision is deterministic and
marginreads zero rather than wrapping. - A sample just inside and just outside the guard band, at each threshold. Six runs. Verify
low_confidencetracks the boundary at every threshold, not only the middle one. eye_idagainstnearest_threshold. Sweep the sample across the whole range and verify the reported eye is always the nearest threshold and never derived from the decided level.- Unequal thresholds. Set the three thresholds with deliberately unequal spacing and verify decisions and margins remain correct. This is a normal configuration, not an error, per Section 14's rejected property.
- Thresholds out of order. Drive
thr_midbelowthr_lowand verify the ordering assertion fires rather than the design producing plausible garbage. - Per-eye binning. Drive a known distribution of samples across the three eyes and verify each
c_eye_symbolsentry matches the driven count exactly. - The LSB-to-MSB ratio with equal eyes. Inject errors uniformly across the three eyes and verify
lsb_msb_ratio_x256converges near 512 — a ratio of two, as Gray mapping predicts. - The ratio with the middle eye degraded. Inject errors only in the middle eye and verify the ratio falls toward zero and
eye_imbalance_suspectedasserts. - The ratio with the outer eyes degraded. Inject only in the outer eyes and verify the ratio rises well above 512, with the flag asserting. Together with 11 and 12 this proves the ratio localises rather than merely detecting.
- A double-bit error. Force a two-threshold crossing and verify
c_double_bit_errorsadvances whilec_msb_errorsandc_lsb_errorsboth advance too. This is a mapping-fault detector, not a channel one. - Eye spread with balanced eyes, and with one eye compressed. Verify
eye_spreadis near zero in the first case and names the rightworst_eyein the second. - Clear during operation. Verify counters and window minima zero, and that
worst_everdoes not. - Reset mid-window. Verify no partial window is reported valid and that no eye's minimum leaks across the reset.
What the checker must own
- An independent Gray table transcribed from the specification. A scoreboard sharing the design's constants verifies only that the design agrees with itself.
- A per-eye error model in the reference, so the expected LSB-to-MSB ratio can be computed for any injected eye distribution and compared against the design's.
- Level-spacing configurability in the stimulus, not just noise amplitude. Without it Scenario 18 cannot be set up at all, and the most common real fault class goes untested.
- Coverage crosses of eye identity against margin band against error. The bin
(outer eye, small margin, no aggregate alarm)must be well populated — that is the compressed-transmitter state, and a run that never reaches it has not exercised the attribution logic.
16. Debugging — Which Eye, and Therefore Which End
The symptom: a PAM4 link with a higher error rate than expected, and no obvious cause.
Step 1 — read the LSB-to-MSB error ratio. One number, and it partitions the problem before any per-eye counter is consulted. Gray mapping predicts about two when the eyes are equal:
| Ratio | What it means | Where to look |
|---|---|---|
| near 2 | the eyes are roughly equal | the channel — all three degrading together |
| well below 2 | the middle eye is anomalously bad | threshold adaptation, or middle-level placement |
| well above 2 | the outer eyes are anomalously bad | transmitter compression — the classic PAM4 fault |
| near 1 with high double-bit count | the eyes are not the issue | the mapping is wrong at one end |
The fourth row is worth its own note. With Gray coding a double-bit error requires crossing two thresholds, which is enormously less likely than crossing one. A non-trivial c_double_bit_errors therefore means the mapping is not Gray at one end — a configuration or design fault that presents as an inexplicably doubled error rate with no eye asymmetry at all.
Step 2 — read the three window minima and the spread. This confirms or refutes step 1 with direct evidence:
- All three falling together — the channel is degrading. Go to Chapter 3.3's margin method, and the modulation is not the issue.
- Outer two below the middle — transmitter compression. Correctable with transmitter equalisation or deliberate unequal level spacing.
- One eye alone falling — threshold adaptation for that eye, or a level-placement fault.
Step 3 — compare the spread against its baseline, not against zero. Section 14's rejected property exists because some spread is normal and its value is a characteristic of this transmitter on this channel. The diagnostic is change, so a link without a spread measurement recorded at installation has thrown away the reference this step needs.
Step 4 — if the eyes are balanced and the rate is still high, ask whether PAM4 was the right choice. Section 6's arithmetic is the check: estimate the channel's insertion loss at the NRZ symbol rate and at half it. If the difference is comfortably above 9.54 dB, PAM4 is right and the problem is elsewhere. If it is below, the modulation is the problem — a short, clean channel running PAM4 has paid 9.54 dB and recovered less, and NRZ at double the symbol rate would close with more margin.
Step 5 — only now attach an instrument. A PAM4 eye measurement is the right tool, and steps 1 through 4 tell you which eye to examine and what you expect to see. Attaching a scope to a three-eye diagram without a hypothesis is a slow way to reach the same place.
The method stated once: the LSB-to-MSB ratio is a single number that names which eye is failing, the per-eye minima confirm it, and the spread against its baseline separates degradation from normal asymmetry. Two register reads decide whether the problem is the channel, the transmitter, or the choice of modulation itself.
17. Common Misconceptions
"PAM4 doubles the bandwidth."
The wrong model: four levels means twice the data for free.
What it costs: you budget a PAM4 link as though it were an NRZ link at twice the rate, and it does not close. You cannot explain why PAM4 was not adopted years earlier, or why some links still use NRZ. You size FEC as though the error rate were unchanged.
The corrected model: PAM4 doubles bits per symbol and pays 9.54 dB of vertical margin for it, by arithmetic on level spacing. What it buys back is horizontal: half the symbol rate, so much less channel loss. It is a trade between Chapter 3.3's two eye axes, and whether it is favourable depends entirely on the channel's loss at the NRZ symbol rate.
"The 9.54 dB penalty can be engineered away."
The wrong model: a better driver, a lower-noise receiver or a newer process will recover it.
What it costs: you plan a design cycle around closing a gap that cannot close, and you skip the FEC the link actually needs.
The corrected model: the penalty is 20·log10(1/3) on level spacing. Nothing about circuit quality appears in that derivation. Better circuits improve absolute margin at both NRZ and PAM4 and leave the ratio exactly where it was. The gap is recovered downstream, by error correction — which is why PAM4 links are specified with a stronger code than NRZ links of the same rate.
"Gray coding is just a labelling convention."
The wrong model: which two-bit value goes on which level is arbitrary.
What it costs: you use a binary mapping and roughly double the bit error rate for the same symbol error rate, because the middle transition flips both bits from one threshold crossing. And you lose the entire diagnostic of Section 16, because the predicted LSB-to-MSB ratio only exists under a known mapping.
The corrected model: errors move a sample across one threshold, so the design goal is that adjacent levels differ in one bit. Gray coding achieves that for all three pairs; binary fails at the middle one. The published effect is that Gray reduces the error to one bit per symbol, roughly halving the bit error rate.
"The three eyes are the same, so one margin number is enough."
The wrong model: equal level spacing gives equal eyes, so reporting the aggregate loses nothing.
What it costs: you cannot detect transmitter compression, which is one of the most common real PAM4 faults. An averaged margin combines the healthy eyes with the failing one and reports a link that looks fine while an outer eye closes.
The corrected model: real transmitters compress their outer levels, so the outer eyes are systematically smaller. That is normal. What is diagnostic is the spread against its baseline — and the three eyes must therefore be measured separately, which is why Section 12 keeps three trends rather than one.
"Both bits of a PAM4 symbol have the same error rate."
The wrong model: two bits, one symbol, one error rate.
What it costs: you cannot use the ratio as a diagnostic, so a fault that is one register read away takes an eye measurement to find. And you model the error distribution into the FEC's symbols incorrectly, because the two bits do not fail independently or equally.
The corrected model: under Gray mapping the MSB flips only on the middle threshold and the LSB flips on either outer one. Two eyes feed the LSB, one feeds the MSB, so the LSB's error rate is roughly twice the MSB's when the eyes are equal — and the departure from two is what localises a failing eye.
18. Interview Reasoning
"Why is PAM4 used at high rates and not at low ones?"
The answer that ends the topic gives both sides of the trade. It costs 9.54 dB of vertical margin, by arithmetic on level spacing, and it buys back horizontal margin by halving the symbol rate — and how much it buys back depends entirely on the channel's loss at the NRZ symbol rate. On a lossy channel the recovery exceeds the cost; on a short clean one it does not. That is why the transition happened at a particular point in the rate progression rather than as soon as PAM4 was understood.
"Why is Gray coding used for PAM4, and what does it buy?"
Because a sample error crosses one threshold, so adjacent levels should differ in one bit. Gray achieves that for all three pairs; a binary mapping flips both bits at the middle transition, roughly doubling the bit error rate. The follow-up worth anticipating is the consequence — that the MSB is fed by one eye and the LSB by two, so the LSB is about twice as error-prone, and the departure of that ratio from two is a free diagnostic.
"A PAM4 link has a higher error rate than expected. What do you read first?"
Not the aggregate error rate — that is the number you already have. The LSB-to-MSB ratio, because Gray mapping makes its expected value known and any departure localises the fault to an eye. Above two means outer eyes, which means transmitter compression; below two means the middle eye, which means threshold adaptation. Naming a single register read that partitions the problem is what distinguishes a strong answer.
19. Understanding Check
The cost is vertical and exact. Four levels across the same peak-to-peak swing V are spaced V/3 apart, so each of the three eyes is one third the height of NRZ's single eye. As an amplitude ratio:
20 · log10(1/3) = −9.54 dBThat is arithmetic on level spacing. Nothing about circuit quality appears in the derivation, which is why no amount of design effort recovers it — better circuits improve the absolute margin at both NRZ and PAM4 and leave the ratio unchanged.
The gain is horizontal and channel-dependent. At the same data rate the symbol rate halves, so the unit interval doubles and the signal's fundamental frequency halves. Chapter 3.3 §6 established that channel loss rises with frequency, so the loss at the lower rate is substantially smaller.
So the question is never "is PAM4 better". It is: on this channel, does halving the symbol rate recover more than 9.54 dB? On a lossy channel it does, decisively. On a short clean one it does not, and NRZ wins.
The follow-up to be ready for: why did the industry move when it did? Because the trade only became favourable when channel loss at NRZ symbol rates became the binding constraint. PAM4 was understood long before it was adopted.
20. What's Next
The claim this chapter defended: PAM4 does not create bandwidth. It trades one axis of Chapter 3.3's eye for the other, at an exchange rate the channel sets.
The vertical cost is arithmetic — four equally spaced levels across one swing give eyes one third as tall, which is 20·log10(1/3) or about 9.54 dB, and no circuit recovers it. The horizontal gain is real and channel-dependent: half the symbol rate means half the fundamental frequency and much less loss, which on a lossy channel recovers more than the cost and on a short one recovers less.
Two structural consequences came out of the mapping. Gray coding makes every adjacent-level error flip exactly one bit — and which bit depends on which threshold was crossed, so two eyes feed the LSB and one feeds the MSB. That asymmetry is not a curiosity; it is a free diagnostic that localises a failing eye from one register read.
And the chapter ends owing something. The 9.54 dB is paid, the horizontal gain is banked, and what is left is a link running with less margin than its predecessors — a higher raw error rate that has to be recovered somewhere.
Chapter 3.7 — Forward Error Correction is that somewhere. It explains why high-rate links need Reed-Solomon coding, where it sits relative to the PCS of Chapter 3.4, why the pre-correction error rate is the health metric and the post-correction rate is only the verdict, and what the correction costs in latency. It also has to account for Chapter 3.5's error multiplication, which arrives at the decoder having already tripled whatever this chapter's eyes delivered.
The full path is on the Ethernet curriculum index.
Continue learning
Related tutorials
- Related topic
The PHY Layer
Coding, serialisation, clock recovery and line drive all exist because a real channel attenuates, disperses and carries no clock. The PCS, PMA and PMD split follows the same logic — each owns one consequence of physics, and each changes on its own schedule.
- Related topic
Copper Ethernet
From 1000BASE-T onward every twisted pair carries both directions at once, so each receiver hears its own transmitter louder than the far end. Cancelling a known local signal is why BASE-T PHYs are adaptive signal-processing engines, and why pair count and signalling changed at every generation.
- Related topic
Fibre Ethernet
Separate strands per direction delete the echo problem that shapes copper PHYs, and introduce two others: a conversion boundary inside a pluggable module you do not own, and a link that can break in one direction while the far end still reports perfect health.
- Related topic
Differential Signalling and the Analog Channel
A PHY does not read bits off a wire — it infers symbols from a waveform the channel has attenuated, reflected and smeared into its neighbours. Differential signalling, impedance, jitter and the eye are one subject: what margin is left after the channel takes its share.
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.
