Ethernet · Module 9
10 Gigabit — 64B/66B, XGMII and the End of Shared Media
64B/66B cut line-code overhead from 25% to 3.125% by replacing a run-length guarantee with a probability, XGMII went 32 bits wide with per-lane control, and half duplex was removed from the standard outright.
Chapter 9.3 made copper carry a gigabit by giving up on a clean channel and cancelling what could not be avoided. 10 Gigabit did not extend that approach. It changed the terms in three places at once.
The line code stopped guaranteeing anything. Chapter 3.5's 4B/5B spends 25% of the line to guarantee a transition density. 64B/66B spends 3.125% and guarantees nothing — it makes long runs improbable with a scrambler, and takes the residual risk.
The MAC/PHY interface became four parallel lanes with control characters on each. A byte on XGMII is data or control depending on a bit beside it, and where a control character may legally sit is now part of the protocol.
And the standard removed half duplex. Not deprecated, not optional — removed. Everything Chapter 9.1 built is, at 10 Gigabit, not merely unreachable but absent from the specification.
1. Scope — What This Chapter Owns
This chapter owns the 10 Gigabit interface and the placement rules that come with lanes.
It covers the 64B/66B trade and what the scrambler is actually buying, the XGMII lane structure, why a frame must start on lane 0, the deficit idle mechanism that keeps the interframe gap honest when it cannot be exact, control-character legality, and lane alignment.
It does not re-derive what other chapters own. Chapter 3.5 owns block coding as a mechanism and Chapter 4.4 owns clock compensation; Chapter 5.9 owns the interframe gap's purpose, which this chapter's deficit mechanism preserves under a constraint the gap was never designed for. Chapter 9.5 owns lane aggregation across multiple physical lanes, which is a different problem from XGMII's four byte lanes on one board.
The claim this chapter defends: every one of 10 Gigabit's changes trades a guarantee for a cheaper mechanism plus an obligation to detect the failure — and a design that asserts the failure away instead of detecting it has deleted the functionality the standard required.
2. From 25% to 3.125%
Compare the line codes on the one axis that matters, and the choice is stark.
| Code | Expansion | Overhead | Fraction of line rate spent |
|---|---|---|---|
| Manchester | 2.00000 | 100% | 50% |
| 4B/5B | 1.25000 | 25% | 20% |
| 8B/10B | 1.25000 | 25% | 20% |
| 64B/66B | 1.03125 | 3.125% | 3.03% |
Which sets the serial rate for 10GBASE-R:
10 Gb/s × 66/64 = 10.3125 Gbaud
against 10 × 1.25 = 12.5 Gbaud if 8B/10B had been carried forward — a 2.1875 Gbaud difference, at a rate where every gigabaud is optics, board loss and power.
And the saving is not cleverness. It is a guarantee that was given up.
4B/5B works by exclusion. Sixteen of the thirty-two 5-bit codes are chosen for data, selected so that no legal sequence of them contains a long run without a transition. The bound is structural: it holds for every possible data pattern, including one an adversary chose.
64B/66B works by scrambling. The 64 payload bits are passed through a self-synchronising scrambler — x⁵⁸ + x³⁹ + 1 — whose output has no run bound at all. What it has is a distribution: a run of k identical bits has probability of roughly 2⁻ᵏ.
P(run of 32) ≈ 2⁻³² ≈ 2.3 × 10⁻¹⁰
P(run of 64) ≈ 2⁻⁶⁴ ≈ 5.4 × 10⁻²⁰
So a receiver's clock recovery is designed against a probability rather than a bound, and the two extra bits per block — the sync header — are what remains structural.
3. The Two Bits That Stayed Structural
A 66-bit block is two bits of sync header and 64 bits of scrambled payload, and the header takes only two of its four possible values.
01 marks a data block. 10 marks a control block. 00 and 11 are illegal — which means every legal header contains a transition, and therefore the line has a guaranteed transition once every 66 bits no matter what the scrambler produces.
And the header is not scrambled, which is the part that makes block lock possible at all.
Follow the bootstrap. A receiver arriving mid-stream has a bit stream and no idea where blocks begin. It cannot descramble, because a self-synchronising descrambler needs to be fed the right bits in the right order. What it can do is hypothesise an offset — one of 66 — and check whether the two bits at that offset are 01 or 10, block after block. A wrong offset lands on scrambled payload, which is 00 or 11 about half the time, so a wrong hypothesis is eliminated in a handful of blocks while the right one survives indefinitely.
Which is why the header is exempt from scrambling. Scrambling it would make it indistinguishable from payload, and there would be nothing in the stream to search for.
4. RTL 1 — Placing a Frame on Four Lanes
// SYNTHESIZABLE.
//
// Places frames onto XGMII, which is four byte lanes wide.
//
// XGMII, by the numbers:
// 32 data bits = 4 lanes x 8 bits, one CONTROL bit per lane
// 10 Gb/s / 32 = 312.5 MT/s
// 312.5 / 2 = 156.25 MHz DDR clock
// (8 bits at 10 Gb/s would have needed 1.25 GHz -- which is why
// GMII's width could not simply be carried forward.)
//
// A byte's meaning depends on its control bit:
// ctrl = 0 -- the byte is DATA
// ctrl = 1 -- the byte is a CONTROL character:
// /I/ 0x07 idle /S/ 0xFB start
// /T/ 0xFD terminate /E/ 0xFE error
// /Q/ 0x9C sequence (ordered set, lane 0 only)
//
// THE PLACEMENT RULE:
// /S/ -- lane 0 ONLY
// /T/ -- any lane
// /I/ -- any lane
package xgmii_pkg;
localparam int unsigned LANES = 4;
localparam logic [7:0] XC_IDLE = 8'h07;
localparam logic [7:0] XC_START = 8'hFB;
localparam logic [7:0] XC_TERMINATE = 8'hFD;
localparam logic [7:0] XC_ERROR = 8'hFE;
localparam logic [7:0] XC_SEQUENCE = 8'h9C;
// Minimum interframe gap, in bytes (Chapter 5.9). One XGMII transfer
// carries 4 bytes, so 12 bytes is exactly 3 columns -- but only when
// the frame's length is a multiple of 4.
localparam int unsigned IPG_BYTES = 12;
localparam int unsigned BYTES_PER_COLUMN = 4;
endpackage
module xgmii_lane_encoder
import xgmii_pkg::*;
#(
parameter int unsigned CNT_W = 16
) (
input logic clk,
input logic rst_n,
input logic frame_start, // a frame's first byte is offered
input logic frame_active,
input logic frame_end,
input logic [1:0] last_lane, // which lane holds the last byte
input logic [LANES-1:0][7:0] data_in,
output logic [LANES-1:0][7:0] xgmii_d,
output logic [LANES-1:0] xgmii_c,
// The MAC offered a frame start when the encoder was not on a lane-0
// boundary. It is deferred, never placed off lane 0 -- and the count
// is what makes the deferral visible.
output logic start_deferred,
output logic [CNT_W-1:0] c_frames,
output logic [CNT_W-1:0] c_start_deferrals
);
logic sending_q;
integer i;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
sending_q <= 1'b0; start_deferred <= 1'b0;
c_frames <= '0; c_start_deferrals <= '0;
for (i = 0; i < LANES; i = i + 1) begin
xgmii_d[i] <= XC_IDLE;
xgmii_c[i] <= 1'b1;
end
end else begin
start_deferred <= 1'b0;
// Default: all four lanes idle. Everything below overwrites.
for (i = 0; i < LANES; i = i + 1) begin
xgmii_d[i] <= XC_IDLE;
xgmii_c[i] <= 1'b1;
end
if (frame_start && !sending_q) begin
// START. Lane 0 carries /S/; lanes 1..3 carry the frame's first
// three bytes. There is no other legal arrangement -- a start
// on lane 2 is not a late start, it is an illegal one.
xgmii_d[0] <= XC_START;
xgmii_c[0] <= 1'b1;
for (i = 1; i < LANES; i = i + 1) begin
xgmii_d[i] <= data_in[i];
xgmii_c[i] <= 1'b0;
end
sending_q <= 1'b1;
if (!(&c_frames)) c_frames <= c_frames + 1'b1;
end else if (frame_end && sending_q) begin
// TERMINATE, on any lane. Lanes before it carry the frame's
// last bytes; lanes after it are idle.
for (i = 0; i < LANES; i = i + 1) begin
if (i < int'(last_lane)) begin
xgmii_d[i] <= data_in[i];
xgmii_c[i] <= 1'b0;
end else if (i == int'(last_lane)) begin
xgmii_d[i] <= XC_TERMINATE;
xgmii_c[i] <= 1'b1;
end
end
sending_q <= 1'b0;
end else if (sending_q && frame_active) begin
for (i = 0; i < LANES; i = i + 1) begin
xgmii_d[i] <= data_in[i];
xgmii_c[i] <= 1'b0;
end
end else if (frame_start && sending_q) begin
// A start offered while a frame is in flight. Defer and report;
// placing it would emit a start inside a frame.
start_deferred <= 1'b1;
if (!(&c_start_deferrals))
c_start_deferrals <= c_start_deferrals + 1'b1;
end
end
end
endmoduleClassification: synthesizable.
What it teaches: that /S/ on lane 0 is a placement rule, not a preference, and the encoder must defer rather than compromise. There is no legal representation of a frame that starts on lane 2 — a receiver seeing a start character there has no defined behaviour to fall back on, because the standard did not define one. Deferring costs a transfer; placing it wrongly costs the frame and, depending on the receiver, the ones after it.
Deliberately simplified: the preamble is not shown; in the real encoding, /S/ replaces the first preamble octet and the remaining preamble bytes follow on lanes 1 to 3.
Production implication: c_start_deferrals is the counter that connects this module to Section 6. Every deferral is idle time the encoder spent waiting for a lane-0 boundary — and because a frame's length determines which lane its terminate lands on, frame lengths that are not multiples of four systematically produce deferrals. The gap that results is longer than the standard's minimum, which is legal but wasteful, and the deficit idle mechanism exists to give some of it back.
5. Why Lane 0, and What It Costs
The rule exists because a receiver has to know where a frame begins without searching.
If a start character could appear on any lane, every receiver would have to examine all four control bits every transfer and handle four different byte alignments for the frame that follows. Fixing the start to lane 0 makes the frame's byte alignment a constant, and the receive path becomes a fixed datapath rather than a four-way barrel shift.
The terminate character is not fixed, because it cannot be. A frame's length is whatever the MAC produced, and forcing it to a multiple of four would change the frame format — so terminate is allowed on any lane, and the misalignment is pushed into the gap.
Which is where the cost lands. Work through the four cases.
| Frame length mod 4 | Lanes used in the last column | Idle bytes to reach the next lane-0 boundary |
|---|---|---|
| 0 | all 4 | 0 |
| 1 | 1 | 3 |
| 2 | 2 | 2 |
| 3 | 3 | 1 |
The minimum interframe gap is 12 bytes (Chapter 5.9), and 12 ÷ 4 = 3 columns exactly. So when a frame's length is a multiple of four, the gap is three columns and the next frame starts on lane 0 naturally — the mechanism costs nothing.
When it is not, the last column is part-filled with idle, and those idle bytes are on top of the 12. The realised gap becomes 13, 14 or 15 bytes.
And that is not an error — it is a legal gap, because the minimum is a floor. But it is capacity given away, systematically, on every frame whose length is not a multiple of four, which is three lengths out of every four.
6. RTL 2 — Paying the Deficit Back
// SYNTHESIZABLE.
//
// Deficit idle count: keep the AVERAGE interframe gap at the standard's
// minimum when the gap can only be adjusted four bytes at a time.
//
// THE PROBLEM (Section 5):
// The gap must be at least 12 bytes. A frame start must be on lane 0.
// A frame whose length is not a multiple of 4 leaves 1 to 3 lanes of
// its final column unusable, so the realised gap is 13, 14 or 15.
//
// THE MECHANISM:
// Allow the transmitter to DELETE up to 3 idle bytes -- taking a
// single gap below 12 -- provided it has previously ACCUMULATED that
// many extra idle bytes. The deficit is bounded (0..3), so no gap can
// be short unless an earlier one was long by at least as much.
//
// The result: every individual gap is between 9 and 15 bytes, and the
// long-run average is 12. Which preserves what Chapter 5.9's gap is
// FOR -- receiver recovery time, on average -- while giving back the
// capacity the lane rule was taking.
module xgmii_deficit_idle_handler
import xgmii_pkg::*;
#(
parameter int unsigned CNT_W = 16
) (
input logic clk,
input logic rst_n,
input logic frame_ended,
input logic [1:0] end_remainder, // frame_length mod 4
input logic next_frame_waiting,
output logic [3:0] gap_bytes, // gap to emit for this frame
output logic [1:0] deficit, // 0..3, the running credit
output logic gap_shortened,
output logic gap_lengthened,
// The average, tracked so the mechanism can be checked rather than
// trusted: total idle bytes emitted against frames sent.
output logic [CNT_W+4:0] total_gap_bytes,
output logic [CNT_W-1:0] c_gaps
);
// Idle bytes forced by the lane rule, given the frame's remainder.
wire [1:0] forced_pad = (end_remainder == 2'd0) ? 2'd0
: 2'd(4) - end_remainder;
logic [1:0] deficit_q;
assign deficit = deficit_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
deficit_q <= 2'd0; gap_bytes <= 4'd12;
gap_shortened <= 1'b0; gap_lengthened <= 1'b0;
total_gap_bytes <= '0; c_gaps <= '0;
end else begin
gap_shortened <= 1'b0;
gap_lengthened <= 1'b0;
if (frame_ended && next_frame_waiting) begin
if (!(&c_gaps)) c_gaps <= c_gaps + 1'b1;
if (forced_pad == 2'd0) begin
// The frame ended on a column boundary. If credit has been
// accumulated, SPEND it now -- this is the only kind of gap
// that can be shortened, because it is the only one whose
// length is not already forced.
if (deficit_q != 2'd0) begin
gap_bytes <= 4'(IPG_BYTES) - 4'(deficit_q);
deficit_q <= 2'd0;
gap_shortened <= 1'b1;
total_gap_bytes <= total_gap_bytes +
(CNT_W+5)'(IPG_BYTES - int'(deficit_q));
end else begin
gap_bytes <= 4'(IPG_BYTES);
total_gap_bytes <= total_gap_bytes + (CNT_W+5)'(IPG_BYTES);
end
end else begin
// The lane rule forces extra idle. ACCUMULATE the excess as
// credit, saturating -- an unbounded deficit would let a long
// run of awkward lengths buy an arbitrarily short gap later,
// which is not what the averaging is for.
gap_bytes <= 4'(IPG_BYTES) + 4'(forced_pad);
gap_lengthened <= 1'b1;
deficit_q <= (deficit_q + forced_pad > 2'd3)
? 2'd3 : deficit_q + forced_pad;
total_gap_bytes <= total_gap_bytes +
(CNT_W+5)'(IPG_BYTES + int'(forced_pad));
end
end
end
end
endmoduleClassification: synthesizable.
What it teaches: that the deficit is bounded and saturating, and that bound is what makes it a credit rather than a licence. An unbounded deficit accumulated over a long run of awkward frame lengths would eventually authorise an arbitrarily short gap — and a receiver that gets a 3-byte gap after a hundred long ones has not been treated fairly on average, it has been given less than it needs at the one moment it mattered. Bounding the credit at 3 keeps every individual gap within a narrow band around the minimum.
Deliberately simplified: the deficit is spent entirely at the first opportunity. Real implementations spend at most one or two bytes per gap, which smooths the distribution further.
Production implication: total_gap_bytes against c_gaps is the mechanism checking itself. The averaging claim is falsifiable — divide one by the other and the result must sit at the minimum, not above it and never below it. A design that implements deficit idle without measuring the average has implemented a mechanism whose entire justification is a statistical claim, and left the claim untested.
7. What the Deficit Preserves, and What It Does Not
It is worth being precise about what has been traded, because "the gap is sometimes shorter than the minimum" sounds like a violation.
Chapter 5.9 established what the gap is for: recovery time at the receiver — a window in which a receiver finishes with one frame before the next arrives. That requirement is about the receiver's ability to keep up, which is a rate question, and a rate question is answered by an average.
So the deficit mechanism is conformant in the dimension that matters and non-conformant in a dimension nobody specified. Any single gap may be as short as 9 bytes; the average is exactly 12; and the receiver's per-second workload is identical to a link that always sent 12.
What it does not preserve is the worst case. A receiver whose recovery genuinely needs 12 bytes every time — not on average — would be broken by this mechanism, and the standard's answer is that no such receiver is conformant, because the requirement was always a rate.
8. RTL 3 — Which Control Characters Are Legal Where
// SYNTHESIZABLE.
//
// Checks the legality of the control characters on a received XGMII
// stream, and REPORTS violations rather than assuming they cannot
// happen.
//
// This is a RECEIVE-side module, and the distinction matters more here
// than anywhere else in the chapter. On the transmit side, the
// placement rules are obligations this design must meet. On the receive
// side they are claims about a PEER -- and a peer can be wrong, can be
// mid-reset, or can be a link that is bit-slipped.
//
// So every rule below is DETECTED. None is assumed.
//
// /S/ start -- lane 0 only, and only when no frame is open
// /T/ terminate -- any lane, and only when a frame is open
// /I/ idle -- any lane, and only between frames
// /E/ error -- any lane, always legal, always meaningful
// /Q/ sequence -- lane 0 only
// any other control code -- reserved, and receiving one is an event
module xgmii_control_validator
import xgmii_pkg::*;
#(
parameter int unsigned CNT_W = 16
) (
input logic clk,
input logic rst_n,
input logic clear,
input logic [LANES-1:0][7:0] xgmii_d,
input logic [LANES-1:0] xgmii_c,
input logic valid,
output logic violation,
output logic [2:0] violation_kind,
output logic [1:0] violation_lane,
// The FIRST violation, latched and held. A stream that has gone wrong
// produces many; only the first one has a cause.
output logic first_violation_valid,
output logic [2:0] first_violation_kind,
output logic [1:0] first_violation_lane,
output logic [CNT_W-1:0] c_violations,
output logic frame_open
);
typedef enum logic [2:0] {
V_NONE,
V_START_OFF_LANE0, // /S/ somewhere other than lane 0
V_START_INSIDE_FRAME, // /S/ while a frame is already open
V_TERMINATE_NO_FRAME, // /T/ with no frame open
V_DATA_NO_FRAME, // a data byte outside any frame
V_RESERVED_CODE, // a control byte that is not a defined code
V_SEQUENCE_OFF_LANE0 // /Q/ somewhere other than lane 0
} viol_e;
logic open_q;
assign frame_open = open_q;
logic [2:0] kind_c;
logic [1:0] lane_c;
logic viol_c;
logic open_n;
always_comb begin
viol_c = 1'b0;
kind_c = 3'(V_NONE);
lane_c = 2'd0;
open_n = open_q;
if (valid) begin
for (int l = 0; l < LANES; l = l + 1) begin
if (xgmii_c[l]) begin
unique case (xgmii_d[l])
XC_START: begin
if (l != 0) begin
// NOT clamped, NOT ignored. The stream is telling us
// something the standard says cannot happen, and the
// only useful response is to name it.
viol_c = 1'b1; kind_c = 3'(V_START_OFF_LANE0); lane_c = 2'(l);
end else if (open_q) begin
viol_c = 1'b1; kind_c = 3'(V_START_INSIDE_FRAME); lane_c = 2'(l);
end else begin
open_n = 1'b1;
end
end
XC_TERMINATE: begin
if (!open_q && !open_n) begin
viol_c = 1'b1; kind_c = 3'(V_TERMINATE_NO_FRAME); lane_c = 2'(l);
end else begin
open_n = 1'b0;
end
end
XC_SEQUENCE: begin
if (l != 0) begin
viol_c = 1'b1; kind_c = 3'(V_SEQUENCE_OFF_LANE0); lane_c = 2'(l);
end
end
XC_IDLE, XC_ERROR: ; // legal on any lane, at any time
default: begin
viol_c = 1'b1; kind_c = 3'(V_RESERVED_CODE); lane_c = 2'(l);
end
endcase
end else if (!open_q && !open_n) begin
// A data byte with no frame open. Usually a bit slip, and it
// is the violation that most often precedes all the others.
viol_c = 1'b1; kind_c = 3'(V_DATA_NO_FRAME); lane_c = 2'(l);
end
end
end
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
open_q <= 1'b0; violation <= 1'b0; violation_kind <= 3'(V_NONE);
violation_lane <= 2'd0; first_violation_valid <= 1'b0;
first_violation_kind <= 3'(V_NONE); first_violation_lane <= 2'd0;
c_violations <= '0;
end else begin
open_q <= open_n;
violation <= viol_c;
violation_kind <= kind_c;
violation_lane <= lane_c;
if (clear) begin
c_violations <= '0;
first_violation_valid <= 1'b0;
end else if (viol_c) begin
if (!(&c_violations)) c_violations <= c_violations + 1'b1;
// FIRST CAUSE. A bit-slipped stream produces a violation every
// transfer; the hundredth one describes the aftermath, and only
// the first one describes what happened.
if (!first_violation_valid) begin
first_violation_valid <= 1'b1;
first_violation_kind <= kind_c;
first_violation_lane <= lane_c;
end
end
end
end
endmoduleClassification: synthesizable.
What it teaches: that a receive-side placement rule is a thing to detect, not a thing to assume — and that the two are opposite design decisions with opposite consequences. A transmitter must meet the lane-0 rule. A receiver must notice when its peer did not, because the peer may be resetting, mis-implemented, or on a link that has slipped a bit. The rule is identical; the obligation is inverted.
Deliberately simplified: violations are detected per transfer without recovery. A production receiver also has to decide what to do with the partial frame it was assembling, which is a separate policy question.
Production implication: first_violation_kind exists because a stream that has gone wrong produces a violation every single transfer, and the counter saturates in microseconds. V_DATA_NO_FRAME as a first violation says the link slipped and everything after is aftermath; V_START_OFF_LANE0 as a first violation says the peer's encoder is wrong and the link is otherwise fine. Same counter value, opposite investigations — and only the first cause distinguishes them.
9. RTL 4 — Are the Four Lanes Still the Same Four Lanes?
// SYNTHESIZABLE.
//
// Watches XGMII for the failures that come specifically from being
// PARALLEL, which a serial interface cannot have.
//
// Four byte lanes on a board are four sets of traces with four sets of
// setup and hold margins. What goes wrong is not the protocol:
//
// A LANE STUCK -- one lane frozen at a value while the others
// move. Idle is 0x07, so a stuck-at-idle lane on
// an idle link looks perfectly healthy and is
// invisible until traffic starts.
// A LANE INVERTED / SWAPPED -- lanes crossed on the board. The
// control bits still line up; only the data is
// rearranged, so the stream stays structurally
// legal and every frame fails its CRC.
// CONTROL/DATA SKEW -- the control bit arriving a transfer away from
// the data byte it describes.
//
// The common property: NONE of these makes the stream illegal. They
// make it wrong. So they are found by watching for activity and
// agreement, not by checking rules.
module xgmii_lane_alignment_checker
import xgmii_pkg::*;
#(
parameter int unsigned WINDOW_BITS = 20,
parameter int unsigned CNT_W = 16
) (
input logic clk,
input logic rst_n,
input logic clear,
input logic [LANES-1:0][7:0] xgmii_d,
input logic [LANES-1:0] xgmii_c,
input logic valid,
input logic frame_open,
output logic [LANES-1:0] lane_stuck,
output logic [LANES-1:0] lane_never_carried_data,
output logic window_valid,
// All four lanes must leave idle together at a frame's start. If some
// do and some do not, the lanes are not aligned with each other.
output logic partial_column,
output logic [CNT_W-1:0] c_partial_columns,
output logic [LANES-1:0] ever_stuck
);
logic [WINDOW_BITS-1:0] win_q;
logic [LANES-1:0][7:0] prev_d;
logic [LANES-1:0] changed_q;
logic [LANES-1:0] carried_data_q;
integer i;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
win_q <= '0; window_valid <= 1'b0; changed_q <= '0;
carried_data_q <= '0; lane_stuck <= '0; ever_stuck <= '0;
lane_never_carried_data <= '0; partial_column <= 1'b0;
c_partial_columns <= '0;
for (i = 0; i < LANES; i = i + 1) prev_d[i] <= 8'h00;
end else begin
partial_column <= 1'b0;
if (clear) begin
win_q <= '0; window_valid <= 1'b0;
changed_q <= '0; carried_data_q <= '0;
lane_stuck <= '0; lane_never_carried_data <= '0;
c_partial_columns <= '0;
// ever_stuck is NOT cleared -- a lane that has been stuck is
// evidence about the board, and the board did not change.
end else if (valid) begin
win_q <= win_q + 1'b1;
prev_d <= xgmii_d;
for (i = 0; i < LANES; i = i + 1) begin
if (xgmii_d[i] != prev_d[i]) changed_q[i] <= 1'b1;
if (!xgmii_c[i]) carried_data_q[i] <= 1'b1;
end
// A start column must take ALL FOUR lanes out of idle together:
// lane 0 carries /S/ and lanes 1..3 carry data. Anything else
// is lanes that are not moving as one.
if (xgmii_c[0] && (xgmii_d[0] == XC_START)) begin
if (xgmii_c[1] || xgmii_c[2] || xgmii_c[3]) begin
partial_column <= 1'b1;
if (!(&c_partial_columns))
c_partial_columns <= c_partial_columns + 1'b1;
end
end
if (&win_q) begin
window_valid <= 1'b1;
for (i = 0; i < LANES; i = i + 1) begin
// Stuck: a whole window with no change at all.
lane_stuck[i] <= !changed_q[i];
if (!changed_q[i]) ever_stuck[i] <= 1'b1;
// Never carried data: a lane that has only ever been
// control. On an idle link this is normal; with frames
// flowing it means the lane is not connected.
lane_never_carried_data[i] <= !carried_data_q[i];
end
changed_q <= '0;
carried_data_q <= '0;
end
end
end
end
endmoduleClassification: synthesizable.
What it teaches: that the failures unique to a parallel interface are the ones that leave the protocol legal. A lane stuck at 0x07 on an idle link is indistinguishable from a healthy idle lane — the value is correct, the control bit is correct, every rule is satisfied. The fault becomes visible only when traffic starts, which is why the check is activity over a window rather than a rule over a transfer.
Deliberately simplified: stuck detection uses a change-over-window heuristic. Production interfaces train the lanes against a known pattern at bring-up, which detects the same faults deterministically and only once.
Production implication: lane_never_carried_data is the check that catches a fault at commissioning rather than in production. A board with lane 2 unconnected passes every link-up test — idle flows, the link comes up, the LEDs are green — and fails on the first frame. A checker that reports which lanes have never carried a data byte turns a fault found by the first user into one found by the first self-test.
10. RTL 5 — Measuring the Statistic the Mechanism Rests On
// SYNTHESIZABLE.
//
// Measures the three statistical claims 10 Gigabit rests on, because a
// statistic nobody samples is an assumption.
//
// 1. RUN LENGTH -- the scrambler makes a long run improbable, not
// impossible. The design's clock recovery is specified against a
// run bound it was never given. So MEASURE the longest run seen.
// 2. AVERAGE GAP -- deficit idle claims the mean interframe gap is
// the standard's minimum. So COMPUTE the mean and compare.
// 3. BLOCK LOCK -- established by eliminating wrong offsets. A link
// that relocks repeatedly is one whose elimination is marginal.
// So COUNT relocks, and remember that it ever happened.
module xgmii_conformance_monitor
import xgmii_pkg::*;
#(
parameter int unsigned RUN_W = 8,
parameter int unsigned CNT_W = 24,
// The run length beyond which clock recovery has no margin left.
parameter int unsigned RUN_ALARM = 8'd64
) (
input logic clk,
input logic rst_n,
input logic clear,
input logic serial_bit,
input logic serial_valid,
input logic gap_emitted,
input logic [3:0] gap_bytes,
input logic block_lock,
output logic [RUN_W-1:0] longest_run,
output logic run_alarm,
output logic [CNT_W-1:0] total_gap_bytes,
output logic [CNT_W-1:0] c_gaps,
output logic [CNT_W-1:0] c_relocks,
// Sticky across clear: this link has lost block lock at least once.
output logic ever_lost_lock,
output logic ever_run_alarm
);
logic prev_bit;
logic [RUN_W-1:0] run_q;
logic lock_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
prev_bit <= 1'b0; run_q <= '0; longest_run <= '0;
run_alarm <= 1'b0; total_gap_bytes <= '0; c_gaps <= '0;
c_relocks <= '0; lock_q <= 1'b0;
ever_lost_lock <= 1'b0; ever_run_alarm <= 1'b0;
end else begin
run_alarm <= 1'b0;
if (clear) begin
longest_run <= '0; total_gap_bytes <= '0;
c_gaps <= '0; c_relocks <= '0;
// ever_lost_lock and ever_run_alarm survive. They are the two
// facts a later reader needs and the two a clear destroys.
end else begin
// 1. RUN LENGTH, on the serial stream.
if (serial_valid) begin
prev_bit <= serial_bit;
if (serial_bit == prev_bit) begin
if (!(&run_q)) run_q <= run_q + 1'b1;
if ((run_q + 1'b1) > longest_run) longest_run <= run_q + 1'b1;
if ((run_q + 1'b1) == RUN_W'(RUN_ALARM)) begin
run_alarm <= 1'b1;
ever_run_alarm <= 1'b1;
end
end else begin
run_q <= '0;
end
end
// 2. AVERAGE GAP. Two counters, and the division is done by
// whoever reads them -- which keeps a divider out of a path
// that runs at line rate.
if (gap_emitted) begin
total_gap_bytes <= total_gap_bytes + CNT_W'(gap_bytes);
if (!(&c_gaps)) c_gaps <= c_gaps + 1'b1;
end
// 3. BLOCK LOCK. Count re-acquisitions, not the state.
lock_q <= block_lock;
if (block_lock && !lock_q) begin
if (!(&c_relocks)) c_relocks <= c_relocks + 1'b1;
end
if (!block_lock && lock_q) ever_lost_lock <= 1'b1;
end
end
end
endmoduleClassification: synthesizable.
What it teaches: that each of 10 Gigabit's three cost reductions replaced a guarantee with a statistic, and a statistic nobody samples is indistinguishable from an assumption. longest_run is the scrambler's claim, measured. total_gap_bytes ÷ c_gaps is the deficit mechanism's claim, computed. c_relocks is block lock's claim, counted. Without them the design has three properties it believes and cannot demonstrate.
Deliberately simplified: the run-length counter is shown on the serial stream, which in a real device is inside the SerDes and not visible at this level. The measurement is usually available as a PHY register instead.
Production implication: the division is deliberately not in the RTL. total_gap_bytes and c_gaps are exported separately and divided by whoever reads them — which keeps a divider out of a datapath running at line rate, and has the side benefit that a reader can compute the average over any interval by differencing two samples, rather than being given a number averaged over a window somebody else chose.
11. The Mode That Was Removed
10 Gigabit Ethernet is full duplex only. Not by convention, not by default — half duplex is not in the specification.
Which makes it the first time Ethernet deleted rather than deprecated.
Follow what Chapter 9.2 established. On a full-duplex switch port, carrier sense, collision detection, jam, backoff and the slot time are all still specified, still implemented, and never reached. They cost area and they are never exercised. By 10 Gigabit there was no plausible deployment in which any of them would be entered: there are no 10 Gigabit hubs, no shared 10 Gigabit media, and no half-duplex 10 Gigabit partner to negotiate with.
So the committee removed the mode, and with it the last reason for the mechanisms to exist.
And the frame did not change at all.
| Mechanism | Origin | At 10 Gigabit |
|---|---|---|
| CSMA/CD, jam, backoff | shared coax | removed from the standard |
| slot time | collision detectability | removed |
| the 64-octet minimum frame | the slot time | still enforced |
| the 96-bit interframe gap | receiver recovery | still enforced (on average) |
| the 7-octet preamble and SFD | receiver synchronisation | still present |
| CRC-32 | error detection | unchanged |
Read the two halves of that table together, because it is this module's whole argument. Everything in the top half is a property of a medium, and when the medium went, so did the mechanism. Everything in the bottom half is a property of a frame — and frames cross links that do not share the property that produced them, so each one is inherited by a generation that has no use for it.
12. Properties Worth Asserting, and One Worth Refusing
The split that organises this section is transmit versus receive, and it is the sharpest version of a distinction every earlier chapter only implied. On the transmit side the placement rules are obligations this design must meet, and asserting them is exactly right. On the receive side the identical rules are claims about a peer — and asserting them there deletes the functionality the standard requires.
Transmit-side placement — obligations to assert
// P1. A start character is ONLY ever emitted on lane 0. On the TX side
// this is a design obligation, and it is unconditionally assertable.
property p_tx_start_on_lane0;
@(posedge clk) disable iff (!rst_n)
!((xgmii_c[1] && (xgmii_d[1] == XC_START)) ||
(xgmii_c[2] && (xgmii_d[2] == XC_START)) ||
(xgmii_c[3] && (xgmii_d[3] == XC_START)));
endproperty
a_tx_start_on_lane0: assert property (p_tx_start_on_lane0);
// P2. A start column takes all four lanes out of idle together: /S/ on
// lane 0 and DATA on lanes 1 to 3. There is no partial start.
property p_tx_start_column_complete;
@(posedge clk) disable iff (!rst_n)
(xgmii_c[0] && (xgmii_d[0] == XC_START))
|-> (!xgmii_c[1] && !xgmii_c[2] && !xgmii_c[3]);
endproperty
a_tx_start_column_complete: assert property (p_tx_start_column_complete);
// P3. A start is never emitted inside an open frame. The encoder defers
// instead, which is what start_deferred reports.
property p_tx_no_start_inside_frame;
@(posedge clk) disable iff (!rst_n)
(sending_q && frame_start) |-> start_deferred;
endproperty
a_tx_no_start_inside_frame: assert property (p_tx_no_start_inside_frame);
// P4. Every lane after the terminate character is idle. A data byte
// after /T/ is a byte the receiver has no frame to put in. Written per
// lane, because terminate is legal on all four.
generate
for (genvar L = 0; L < LANES-1; L++) begin : g_after_terminate
property p_tx_idle_after_terminate;
@(posedge clk) disable iff (!rst_n)
(xgmii_c[L] && (xgmii_d[L] == XC_TERMINATE))
|-> (xgmii_c[L+1] && (xgmii_d[L+1] == XC_IDLE));
endproperty
a_tx_idle_after_terminate: assert property (p_tx_idle_after_terminate);
end
endgenerate
// P5. A frame is opened before it is closed. Terminate with no open
// frame is a transmit-path bug, not a peer's problem.
property p_tx_terminate_needs_frame;
@(posedge clk) disable iff (!rst_n)
frame_end |-> sending_q;
endproperty
a_tx_terminate_needs_frame: assert property (p_tx_terminate_needs_frame);The deficit — bounds, not outcomes
// P6. The deficit is bounded. An unbounded credit would eventually
// authorise an arbitrarily short gap.
property p_deficit_bounded;
@(posedge clk) disable iff (!rst_n)
deficit <= 2'd3;
endproperty
a_deficit_bounded: assert property (p_deficit_bounded);
// P7. Every emitted gap sits in the band the mechanism permits: never
// shorter than the minimum less the maximum credit, never longer than
// the minimum plus the maximum forced padding.
property p_gap_within_band;
@(posedge clk) disable iff (!rst_n)
gap_emitted |-> ((gap_bytes >= 4'(IPG_BYTES - 3)) &&
(gap_bytes <= 4'(IPG_BYTES + 3)));
endproperty
a_gap_within_band: assert property (p_gap_within_band);
// P8. THE CREDIT PROPERTY. A gap is only shortened when credit exists,
// which is what makes this an average rather than a shortcut.
property p_shorten_needs_credit;
@(posedge clk) disable iff (!rst_n)
gap_shortened |-> ($past(deficit) != 2'd0);
endproperty
a_shorten_needs_credit: assert property (p_shorten_needs_credit);
// P9. Spending the credit empties it. Credit is not reusable, which is
// the difference between averaging and drifting.
property p_credit_spent_once;
@(posedge clk) disable iff (!rst_n)
gap_shortened |=> (deficit == 2'd0);
endproperty
a_credit_spent_once: assert property (p_credit_spent_once);
// P10. Credit only ever accrues from a gap that was FORCED long. It
// cannot be created by an idle link or by a frame that fitted exactly.
property p_credit_from_forced_padding;
@(posedge clk) disable iff (!rst_n)
(deficit > $past(deficit)) |-> $past(gap_lengthened);
endproperty
a_credit_from_padding: assert property (p_credit_from_forced_padding);Receive-side detection — the honesty properties
// P11. Every violation is REPORTED with a kind. A violation flag with
// no kind is a design that noticed and cannot say what.
property p_violation_has_kind;
@(posedge clk) disable iff (!rst_n)
violation |-> (violation_kind != 3'(V_NONE));
endproperty
a_violation_has_kind: assert property (p_violation_has_kind);
// P12. FIRST CAUSE. The first violation is latched and never
// overwritten -- because the hundredth describes the aftermath.
property p_first_violation_stable;
@(posedge clk) disable iff (!rst_n)
(first_violation_valid && !clear)
|=> $stable({first_violation_kind, first_violation_lane});
endproperty
a_first_violation_stable: assert property (p_first_violation_stable);
// P13. A start off lane 0 on the RECEIVED stream is detected -- the
// receive-side counterpart of P1, and it is a DETECTION property
// rather than an assumption about the peer.
property p_rx_detects_start_off_lane0;
@(posedge clk) disable iff (!rst_n)
(valid && ((xgmii_c[1] && (xgmii_d[1] == XC_START)) ||
(xgmii_c[2] && (xgmii_d[2] == XC_START)) ||
(xgmii_c[3] && (xgmii_d[3] == XC_START))))
|=> (violation && (violation_kind == 3'(V_START_OFF_LANE0)));
endproperty
a_rx_detects_start_off_lane0: assert property (p_rx_detects_start_off_lane0);
// P14. A reserved control code is detected rather than silently
// treated as one of the defined ones.
property p_rx_detects_reserved;
@(posedge clk) disable iff (!rst_n)
(valid && xgmii_c[0] &&
!(xgmii_d[0] inside {XC_IDLE, XC_START, XC_TERMINATE,
XC_ERROR, XC_SEQUENCE}))
|=> (violation && (violation_kind == 3'(V_RESERVED_CODE)));
endproperty
a_rx_detects_reserved: assert property (p_rx_detects_reserved);
// P15. frame_open tracks start and terminate and nothing else. A frame
// that opens without a start is a state machine that drifted.
property p_frame_open_needs_start;
@(posedge clk) disable iff (!rst_n)
$rose(frame_open) |-> $past(valid && xgmii_c[0] &&
(xgmii_d[0] == XC_START));
endproperty
a_frame_open_needs_start: assert property (p_frame_open_needs_start);
// P16. Sticky evidence survives clear. A lane that has been stuck is a
// fact about the board, and clearing a counter does not fix a board.
property p_ever_stuck_survives;
@(posedge clk) disable iff (!rst_n)
(|ever_stuck) |=> (|ever_stuck);
endproperty
a_ever_stuck_survives: assert property (p_ever_stuck_survives);
// P17. And so does the lock history, for the same reason.
property p_lock_history_survives;
@(posedge clk) disable iff (!rst_n)
ever_lost_lock |=> ever_lost_lock;
endproperty
a_lock_history_survives: assert property (p_lock_history_survives);13. Verification Scenarios
Group the scenarios by which failure they are hunting, because 10 Gigabit's faults sort into three families that look nothing alike from the frame layer.
Lane placement, transmit side
- Frame length a multiple of four — terminate on lane 3, no forced padding, gap exactly the minimum.
- Frame length mod 4 = 1 — terminate on lane 0 of the final column, three idle bytes forced,
gap_lengthenedhigh. - Frame length mod 4 = 2 — two forced idle bytes.
- Frame length mod 4 = 3 — one forced idle byte. All four remainders, because each produces a different column shape.
- Back-to-back minimum frames — a start every four columns,
/S/on lane 0 every time. - A start offered while a frame is open —
start_deferredpulses,c_start_deferralsincrements, no start character is emitted anywhere. - Terminate on each of the four lanes in turn — lanes after it are idle in every case, which is P4 across its whole range.
- A 64-octet minimum frame —
64 mod 4 = 0, so the floor happens to be lane-friendly, and no padding is forced. The case that hides the mechanism.
Deficit idle
- A long run of length-multiple-of-4 frames — deficit stays at zero, every gap is the minimum,
gap_shortenednever fires. - One awkward frame, then a lane-friendly one — credit accrues then is spent; the second gap is short by exactly the credit.
- A run of awkward frames — the deficit saturates at 3 and does not keep growing.
- Credit spent, immediately followed by another lane-friendly frame — the second gap is the full minimum, because credit is not reusable.
- Idle link between frames — no credit accrues from an idle link, only from a gap that was forced long.
- Ten thousand random frame lengths —
total_gap_bytes ÷ c_gapsconverges to the minimum. The mechanism's entire justification, measured. - Every emitted gap — within the permitted band, none shorter than the minimum less the maximum credit. P7 over the whole run.
Receive-side violations
- A start character on lane 2 —
V_START_OFF_LANE0, latched as first cause, and the receiver does not open a frame. - A start inside an open frame —
V_START_INSIDE_FRAME, distinct from the previous kind. - A terminate with no frame open —
V_TERMINATE_NO_FRAME. - A data byte between frames —
V_DATA_NO_FRAME, which is the bit-slip signature. - An undefined control code —
V_RESERVED_CODE, not silently mapped to idle. - A sequence character on lane 3 —
V_SEQUENCE_OFF_LANE0. - A stream that goes wrong and stays wrong — a violation every transfer, the counter saturating, and
first_violation_kindunchanged from the first one. clearduring a violation storm — the counter clears,first_violation_validclears, and the next violation latches a fresh first cause.
Lane-level faults, which leave the protocol legal
- Lane 2 stuck at
0x07on an idle link — no violation at all, because idle on lane 2 is exactly what should be there. The fault is invisible. - The same lane stuck once frames start —
lane_never_carried_data[2]high,lane_stuck[2]high after a full window. - Lanes 1 and 2 swapped on the board — every control bit still lines up, every rule still passes, every frame fails its CRC. The checker's activity tests are the only thing with anything to say.
- A start column with lane 1 still carrying idle —
partial_column, which is lanes not moving as one. clearafter a stuck lane recovers —ever_stucksurvives, because the board did not change.
14. Debugging: Three Families of Fault
Sort by what the control stream says, because that is what separates the families.
| Observation | Family | The distinguishing check |
|---|---|---|
XGMII violations every transfer, first cause V_DATA_NO_FRAME | the link slipped | first_violation_kind; everything after is aftermath |
violations every transfer, first cause V_START_OFF_LANE0 | the peer's encoder is wrong | the link is otherwise fine; the fault is in the other device |
| 100% CRC failures, zero XGMII violations | a lane fault — stuck, swapped, or unconnected | lane_never_carried_data, lane_stuck, partial_column |
| CRC failures at a low rate, no violations | bit errors on the serial link | longest_run against the alarm, and the FEC or BER counters |
| block lock lost and regained repeatedly | marginal receiver or a run-length excursion | c_relocks with longest_run; ever_run_alarm |
| throughput below expectation, no errors anywhere | the gap — deficit idle absent or mis-implemented | total_gap_bytes ÷ c_gaps against the minimum |
| link comes up, idles cleanly, fails on first traffic | a lane stuck at idle | invisible while idle; lane_never_carried_data at first frame |
Three habits.
First, read first_violation_kind and ignore the count. A broken XGMII stream produces a violation every transfer and saturates its counter in microseconds. The count tells you the stream is broken, which you knew. The first cause tells you what broke it.
Second, treat "CRC failures with no XGMII violations" as a lane fault until proved otherwise. The combination is diagnostic: something is corrupting bytes while leaving the control structure intact, and a rearranged or frozen lane does exactly that while a protocol error does not.
Third, compute the average gap before believing the throughput number. Deficit idle's whole claim is statistical, and a design that implements the mechanism without measuring it has an untested justification. total_gap_bytes ÷ c_gaps is two register reads and a division, and it either sits at the minimum or it does not.
15. Common Misconceptions
"64B/66B is just a more efficient block code."
The wrong model: the same idea as 4B/5B, better engineered.
What it costs: you cannot say what was given up, and you treat a probabilistic guarantee as a structural one.
The corrected model: 4B/5B bounds the run length structurally — it excludes the codes that would produce long runs, so the bound holds for every possible data pattern including an adversarial one. 64B/66B has no bound at all. It scrambles, which makes a run of k bits have probability around 2⁻ᵏ, and the receiver's clock recovery is designed against that distribution. The 21.875 percentage points of overhead that were saved bought a guarantee, and it was sold.
"The sync header is overhead."
The wrong model: two wasted bits per block.
What it costs: you miss that it is the only structural guarantee left in the line code.
The corrected model: the header does three jobs at once. Its two legal values, 01 and 10, both contain a transition — so there is a guaranteed transition once every 66 bits whatever the scrambler does. It is unscrambled, so a receiver can search 66 offsets for it and establish block lock before it can descramble anything. And it distinguishes data from control blocks. Two bits, three guarantees, 3.03% of the line.
"A gap shorter than the minimum is a violation."
The wrong model: the interframe gap is a per-frame floor and going under it is non-conformant.
What it costs: you read deficit idle as a cheat and cannot explain why the standard defines it.
The corrected model: the gap exists to give the receiver recovery time, which is a rate requirement, and a rate requirement is satisfied on average. Deficit idle allows a gap to be short only when an earlier one was long by at least as much, with the credit bounded at 3 bytes — so every individual gap sits in a narrow band and the mean is exactly the minimum. The receiver's per-second workload is identical to a link that always sent 12.
"A start on lane 2 is just a late start."
The wrong model: the lane rule is a convention, and a receiver can cope.
What it costs: Section 12's rejected property, and a receive path with no handling for an event the standard requires it to handle.
The corrected model: there is no defined receiver behaviour for a start character off lane 0, because the standard did not define one — so a receiver that meets it does whatever its state machine happens to do, which is typically a frame assembled from the wrong bytes. The transmitter must never emit it; the receiver must detect and report it. Same rule, opposite obligations, and asserting the receive side away deletes the handling.
"All four lanes are the same, so a lane fault is a quarter of a problem."
The wrong model: four parallel byte lanes degrade gracefully.
What it costs: every lane fault gets diagnosed as "the PHY."
The corrected model: a column is four bytes of one frame, so one bad lane corrupts a quarter of every column and therefore every frame. And the fault leaves the control stream perfectly legal — a lane stuck at idle on an idle link is bit-for-bit correct — so it produces a 100% CRC failure rate with zero protocol violations. The only thing that isolates it is per-lane activity over time, which is the same lesson Chapter 9.3 reached from 8B1Q4.
16. Interview Reasoning
"Why did 10 Gigabit move from 8B/10B to 64B/66B?"
The weak answer says efficiency. The answer that ends the topic gives both the number and the thing that was sold to get it: 25% overhead becomes 3.125%, so the serial rate drops from 10 × 1.25 = 12.5 to 10 × 66/64 = 10.3125 Gbaud — over 2 Gbaud, at a rate where every gigabaud is optics, loss and power. And what paid for it was the run-length guarantee. 8B/10B and 4B/5B bound the run structurally, for every possible pattern; 64B/66B scrambles, giving a run of k a probability near 2⁻ᵏ and no bound at all. The finishing point: the two sync-header bits are what stayed structural — 01 and 10 both contain a transition, so there is a guaranteed transition every 66 bits, and because the header is unscrambled a receiver can search for it to establish block lock before it can descramble.
"Why must an XGMII frame start on lane 0, and what does that cost?"
Because fixing the start makes the frame's byte alignment a constant, so the receive datapath is fixed rather than a four-way barrel shift. Terminate cannot be fixed, because a frame's length is whatever the MAC produced — so the misalignment is pushed into the gap. The cost: a frame whose length is not a multiple of four ends mid-column, forcing 1 to 3 extra idle bytes on top of the 12-byte minimum, on three lengths out of every four. The strong answer names the remedy: deficit idle, which lets a gap be short by up to 3 bytes when an earlier one was long by as much, keeping every gap in a narrow band and the average at the minimum.
"Would you assert that a received start character is always on lane 0?"
No — and the interesting part is that the identical property is correct on the transmit side. On transmit it is an obligation this design must meet, and if it fires the encoder is wrong. On receive it asserts that a peer obeys the standard, and a peer can be mid-reset, mis-implemented, or on a slipped link. The damage is not that it might fire; it is what it does while it passes: a receive path that asserts the violation away has no reason to implement handling for it, so the handling is absent, the stimulus is never written, and in silicon the event produces whatever the state machine happens to do. Assert instead that the violation is detected and named. The test: is this design the producer of the thing I am asserting, or its consumer?
"A 10 Gigabit link has a 100% CRC failure rate and no protocol errors. What is it?"
A lane fault — stuck, unconnected, or swapped — and the combination is diagnostic. A column carries four bytes of one frame, so one bad lane corrupts every frame; and a lane fault leaves the control structure legal, because it rearranges or freezes data without breaking any placement rule. The strong answer adds why it survived commissioning: a lane stuck at the idle character is bit-for-bit identical to a healthy lane on an idle link — the link comes up, idles cleanly, and fails at the first frame. The checks are lane_never_carried_data and lane_stuck over a window, plus a known-pattern test at bring-up for the swap case, because a swap passes every rule-based check there is.
17. Understanding Check
It bought 21.875 percentage points of line-code overhead and sold a structural guarantee.
The number:
| Code | Expansion | Overhead |
|---|---|---|
| 4B/5B, 8B/10B | 1.25000 | 25% |
| 64B/66B | 1.03125 | 3.125% |
Which sets the serial rate: 10 × 66/64 = 10.3125 Gbaud, against 10 × 1.25 = 12.5 — over 2 Gbaud saved, at a rate where every gigabaud costs optics, board loss and power.
And what paid for it was the run-length bound. 4B/5B selects 16 of 32 five-bit codes so that no legal sequence contains a long run — a bound that holds for every possible data pattern, including one chosen adversarially. 64B/66B has no bound. The scrambler x⁵⁸ + x³⁹ + 1 makes a run of k bits have probability near 2⁻ᵏ:
P(run of 32) ≈ 2.3 × 10⁻¹⁰ · P(run of 64) ≈ 5.4 × 10⁻²⁰
So clock recovery is now designed against a distribution rather than a guarantee — which is safe because clock recovery is itself a rate mechanism, and which creates an obligation to measure the longest run actually seen.
18. What's Next
The claim this chapter defended: every one of 10 Gigabit's changes traded a guarantee for a cheaper mechanism plus an obligation to detect the failure.
64B/66B traded 4B/5B's structural run bound for a scrambler's distribution, cutting line-code overhead from 25% to 3.125% and the serial rate from 12.5 to 10.3125 Gbaud — and left two unscrambled sync-header bits doing three jobs, because a receiver that cannot descramble yet still needs something to find. Deficit idle traded a per-frame interframe gap for an average one, which is conformant precisely because the gap was always a rate requirement. And the lane-0 start rule traded a receiver's barrel shift for forced idle bytes on three frame lengths out of four.
And the standard did something it had never done before: it removed half duplex. Everything Chapter 9.1 built is not merely unreached at 10 Gigabit but absent — while the 64-octet floor, the interframe gap, the preamble and CRC-32 all survived intact, because each is a property of a frame and frames cross links that do not share the property that produced them.
The obligation that runs through all of it is measurement. A run bound that is a probability, a gap that is an average, and a block lock established by elimination are three statistical claims — and a statistic nobody samples is indistinguishable from an assumption.
Chapter 9.5 — 25G to 100G: Per-Lane Rate and Lane Aggregation takes the step this chapter's XGMII only hints at.
XGMII's four byte lanes are four sets of traces on one board, arriving together by construction. A 100 Gigabit link is built from multiple physical lanes that traverse different fibres or different traces and arrive at genuinely different times — so the data has to be striped across them, marked so the far end can tell which lane is which, and deskewed before it can be reassembled. Chapter 9.5 covers how a link is built from lanes, what sets the per-lane rate, and why alignment markers are the mechanism that makes a multi-lane link a single link.
The full path is on the Ethernet curriculum index.
Continue learning
Related tutorials
- Related topic
Block Coding — 4B/5B, 8B/10B and 64B/66B
A line code buys transition density, DC balance, an invalid-pattern space and a control-symbol space. Overhead fell from 25 percent to 3 because two of those purchases moved from guarantees to probabilities — a trade that only became safe once forward error correction existed downstream.
- Related topic
XGMII — Seventy-Four Signals, and the Interface Built to Avoid Them
32 data and 4 control bits each way at 156.25 MHz DDR is 74 signals nobody routes. XAUI carries the same 10 Gb/s on sixteen wires and pays in per-lane deskew — plus a transparency that holds for frames and not for the stream.
- Related topic
The Shared-Medium Problem
Why several independent transmitters on one medium is a distributed timing problem, not a formatting problem. Propagation delay makes every station's view of the medium stale, so two locally correct decisions can still collide — and that is the constraint the Ethernet MAC was built around.
- Related topic
CSMA/CD, Collision Domains and Slot Time
Slot time is the parameter the whole half-duplex MAC hangs on: it bounds medium acquisition, bounds a collision fragment, and is the retransmission quantum. Deriving it from round-trip propagation plus jam is what fixes Ethernet's minimum frame size — a timing constant wearing a frame-format costume.
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.
