Ethernet · Module 4
The Reconciliation Sublayer and the xMII Contract
The xMII generations are a record of what each had to give up — width, pins, timing margin, even parallelism — to keep carrying the same vocabulary as rates rose. That one vocabulary survived six unrelated physical forms is what media-independence actually means.
Chapter 4.1 established who owns what — a responsibility belongs to the side that can detect its own failure — and stated the assumption contract in both directions. Chapter 3.4 approached the same boundary from below and named the media-independent interface as the PHY's top edge.
Neither asked the question this chapter exists for.
A boundary and an interface are not the same thing. A boundary is an agreement about responsibility. An interface is a concrete set of wires, widths, clock rates and encodings — and there have been at least six of them for Ethernet, with completely different pin counts, completely different clocking, and in one case no parallel data path at all.
If the interface changed six times, in what sense is there one contract?
The usual answer is that the standard defines a service and the interfaces implement it. True, and it explains nothing — it just moves the question to what makes a service definition durable when every implementation of it differs.
What makes an interface media-independent, and what did each generation have to give up to stay that way?
1. Scope — What This Chapter Owns
This chapter owns: what the reconciliation sublayer maps between and why that mapping is thin; the vocabulary test for media-independence and how to apply it to a proposed signal; the xMII generations as a record of trades rather than a list of speeds; what a generation change actually costs in RTL; and the conformance checking that belongs to the interface as distinct from the contract.
This chapter does not own: the ownership rule or the assumption contract — Chapter 4.1 owns both and this chapter cites them. Nor the sublayer-split criterion, which is Chapter 3.4's. Chapter 4.3 owns the cycle-by-cycle sequence across the interface; this chapter gives the interface's shape and vocabulary, not its timing. Clock-domain conversion is Chapter 4.4 and Chapter 4.6.
The distinction from its two neighbours, stated once: Chapter 4.1 asked who is answerable. Chapter 3.4 asked where does the unit of information change. This chapter asks what makes a concrete interface durable across implementations that share nothing physically — and the answer is a property of its vocabulary, not of its wires.
2. What the Reconciliation Sublayer Reconciles
The name is unhelpful, and the block is small enough that its smallness is the point.
Above it, the MAC speaks in a service: a request to transmit a frame, an indication that one has arrived, an indication about whether the medium is available. Abstract, timeless, and expressed in terms of frames.
Below it, the interface speaks in signals: a data bus, an enable, an error, a valid, a clock. Concrete, cycle-by-cycle, and expressed in terms of nibbles or octets.
The reconciliation sublayer maps one onto the other, and does nothing else. It adds no function. It buffers nothing of consequence. It makes no decision the MAC did not already make.
Why the thinness matters more than the function. A thick reconciliation sublayer — one that buffered, reordered, retimed, or made decisions — would put behaviour between the MAC and the interface. Behaviour has to be re-verified when either side changes, and the whole point of the boundary is that it does not.
So the reconciliation sublayer is where the interface generation is absorbed, and nowhere else. When MII becomes GMII, this block changes. The MAC above it does not. That is the mechanism by which the vocabulary outlives the wires, and it only works because the block is thin enough to rewrite.
3. The Vocabulary Test
Section 2's table is the evidence for the claim in the opening callout, and the claim is worth stating as a test you can apply.
A signal belongs on a media-independent interface only if it can be named without reference to a medium or a coding.
Apply it to the existing signals:
| Signal | Names what | Passes? |
|---|---|---|
TXD / RXD | data | yes — data is data on any medium |
TX_EN | "a frame is being transmitted" | yes |
TX_ER | "this frame is bad" | yes |
RX_DV | "a frame is arriving" | yes |
RX_ER | "these octets are suspect" | yes |
CRS / COL | medium busy, collision | yes, but see below |
Now apply it to signals a designer might propose adding:
| Proposed | Names what | Passes? | Why |
|---|---|---|---|
fec_corrected_count | FEC corrections | no | presumes FEC exists — Chapter 3.7 showed some links have none |
eye_margin | vertical margin | no | presumes an eye, so an analog electrical channel |
lane_skew | inter-lane delay | no | presumes multiple lanes |
block_lock | PCS block alignment | no | presumes a block code — Chapter 3.5 showed which families have one |
link_usable | can this carry frames | yes | a claim about capability, not mechanism |
rate | how fast | yes | a number, not a mechanism |
Every "no" is a medium or a coding leaking upward, and Chapter 4.1 §5 named the cost precisely: a MAC that reads FEC counts cannot be paired with a PHY that has no FEC.
The CRS/COL row deserves its caveat. They name a medium condition — busy, collided — which is arguably medium-specific in spirit even though the words are generic. That is why later generations dropped them: the conditions they name stopped existing. The test is about vocabulary, and vocabulary can pass the test and still become obsolete.
4. Six Generations, One Vocabulary
Now the evidence for the chapter's central claim. The physical form changed completely; the vocabulary barely moved.
| Generation | Data width | Clock | Signalling | What it gave up |
|---|---|---|---|---|
| MII | 4 bits each way | 25 MHz for 100 Mb/s | SDR | nothing — the baseline |
| RMII | 2 bits each way | 50 MHz reference | SDR | width, to halve the pins |
| GMII | 8 bits each way | 125 MHz | SDR | pin count, to reach gigabit at a tolerable clock |
| RGMII | 4 bits each way | 125 MHz | DDR | timing margin — data on both clock edges |
| SGMII | — | 625 MHz | DDR | parallelism itself — serialised |
| XGMII | 32 bits each way | 156.25 MHz | DDR | pin count and timing margin together |
Read the last column downward. That is the chapter's thesis in one column.
Each generation faced the same problem — carry more bits per second — and each solved it by giving up something different:
- RMII gave up width to save pins, and paid by doubling the clock.
- GMII gave up pins to keep the clock manageable at gigabit.
- RGMII gave up timing margin: DDR halves the pins again, and Chapter 3.3's two-axis margin argument applies directly — a DDR interface has half the setup-and-hold window of an SDR one at the same clock.
- SGMII gave up the parallel bus entirely, which is the largest departure of all and the one that most tests the claim of a persistent vocabulary.
- XGMII gave up both pins and margin, taking 32 bits and DDR.
Meanwhile the vocabulary did not change. Every one of these still carries data, an enable or its equivalent, an error indication, and a validity indication. A reconciliation sublayer rewritten for a new generation maps the same service onto different wires, and the MAC above it is untouched.
5. RTL 1 — The Reconciliation Sublayer, Both Directions
// SYNTHESIZABLE. The reconciliation sublayer, both directions.
//
// It adds NO function. It buffers nothing of consequence. It makes no
// decision the MAC did not already make. That is deliberate: behaviour
// placed here has to be re-verified whenever either side changes, and the
// entire value of the boundary is that it does not.
//
// This is the ONLY block that changes when the interface generation changes.
// Everything above it is untouched, which is the mechanism by which the
// vocabulary of Section 3 outlives the wires of Section 4.
package rs_pkg;
// The MAC's service, as signals. Frame-oriented and generation-free.
typedef enum logic [1:0] {
SVC_IDLE = 2'd0,
SVC_TRANSMIT = 2'd1, // a frame is in progress
SVC_ABORT = 2'd2 // abandon the frame in progress
} tx_service_e;
typedef enum logic [1:0] {
IND_IDLE = 2'd0,
IND_RECEIVE = 2'd1, // a frame is arriving
IND_SUSPECT = 2'd2 // arriving, and the PHY does not trust it
} rx_indication_e;
endpackage
module reconciliation_sublayer
import rs_pkg::*;
#(
// Interface data width. 4 for MII, 8 for GMII, 32 for XGMII. The
// generation is a PARAMETER, which is the concrete form of Section 4's
// claim -- one block, several forms, one vocabulary.
parameter int unsigned IF_W = 8
) (
input logic clk,
input logic rst_n,
// ── MAC service, transmit ───────────────────────────────────────────────
input tx_service_e mac_tx_service,
input logic [IF_W-1:0] mac_tx_data,
output logic mac_tx_accept,
// ── Interface, transmit ─────────────────────────────────────────────────
output logic [IF_W-1:0] txd,
output logic tx_en,
output logic tx_er,
input logic tx_accept, // not present on every generation
// ── Interface, receive ──────────────────────────────────────────────────
input logic [IF_W-1:0] rxd,
input logic rx_dv,
input logic rx_er,
// ── MAC indication, receive ─────────────────────────────────────────────
output rx_indication_e mac_rx_indication,
output logic [IF_W-1:0] mac_rx_data,
// ── Medium conditions, passed through unchanged ─────────────────────────
// These are MII-era vocabulary (Chapter 1.2). On a full-duplex link they
// are unreachable, exactly as Chapter 1.5 showed for the access logic.
// They are passed rather than interpreted -- interpreting them here would
// be behaviour, and behaviour is what this block must not contain.
input logic crs,
input logic col,
output logic mac_medium_busy,
output logic mac_collision
);
// ── Transmit mapping ────────────────────────────────────────────────────
// A frame boundary becomes an enable's rising and falling edge. An abort
// becomes a marked cycle. That is the entire transmit direction.
always_comb begin
txd = mac_tx_data;
tx_en = (mac_tx_service != SVC_IDLE);
// TX_ER asserted WITH TX_EN means "this frame is bad". Asserted without
// it means something else entirely on some generations, which is why
// the two are driven together and never independently.
tx_er = (mac_tx_service == SVC_ABORT);
mac_tx_accept = tx_accept;
end
// ── Receive mapping ─────────────────────────────────────────────────────
always_comb begin
mac_rx_data = rxd;
if (!rx_dv) mac_rx_indication = IND_IDLE;
else if (rx_er) mac_rx_indication = IND_SUSPECT;
else mac_rx_indication = IND_RECEIVE;
end
// ── Medium conditions ───────────────────────────────────────────────────
assign mac_medium_busy = crs;
assign mac_collision = col;
endmoduleClassification: synthesizable.
What it teaches: that the mapping is combinational and stateless, and that this is a deliberate architectural property rather than a simplification. The moment this block holds state, it holds behaviour, and behaviour must be re-verified whenever either neighbour changes — which defeats the boundary.
Deliberately simplified: no generation-specific timing. Real reconciliation sublayers for DDR generations must handle the two half-cycles, and tx_accept does not exist on every generation — MII has no flow control at this level at all, which Section 9 develops.
Production implication: tx_en and tx_er are driven together, from one service enum, never independently. On several generations TX_ER asserted without TX_EN means something entirely different from an in-frame error — it is a separate signalling mechanism. A design that drives them from two unrelated sources will eventually assert that combination by accident and transmit a signal it did not intend.
Later ownership: the cycle-by-cycle timing of these signals is Chapter 4.3; clock-domain handling is Chapter 4.4 and Chapter 4.6.
6. RTL 2 — A Generation Adapter
Section 4 claimed one vocabulary survives across generations. This is that claim made executable: a block that speaks GMII on one side and MII on the other, with the MAC above it unaware.
// SYNTHESIZABLE. An 8-bit to 4-bit interface adapter.
//
// Section 4's claim, executable: the same vocabulary at two different
// widths. The MAC above sees octets; the interface below sees nibbles; and
// the words data, enable, error and valid mean the same on both sides.
//
// THE ADAPTER HAS STATE AND THE RECONCILIATION SUBLAYER DOES NOT, and the
// reason is worth being precise about. The reconciliation sublayer maps a
// vocabulary; this changes a RATE. One MAC octet becomes two interface
// nibbles, so something must remember which half is being sent -- and that
// is exactly the fact Section 13's rejected property assumes away.
module width_adapter_8_to_4 (
input logic clk, // the NIBBLE clock -- twice the octet rate
input logic rst_n,
// Wide side: one octet per octet-clock, offered continuously.
input logic wide_valid,
input logic [7:0] wide_data,
input logic wide_en,
input logic wide_er,
output logic wide_accept,
// Narrow side: two nibbles per octet.
output logic narrow_valid,
output logic [3:0] narrow_data,
output logic narrow_en,
output logic narrow_er,
// Which half is on the wire. Exposed because a receiver that guesses this
// wrong reassembles every octet with its halves swapped -- and that
// failure looks like data corruption on a link reporting perfect health,
// which is the same class of contract fault Chapter 3.4 §10 named for
// lane ordering.
output logic second_half
);
logic phase_q; // 0 = first nibble, 1 = second
logic [7:0] hold_q;
logic en_q, er_q, valid_q;
// NIBBLE ORDER IS A CONTRACT. Least-significant nibble first is the
// convention here and it must match the far end. Nothing detects a
// mismatch; every bit arrives and every octet is wrong.
always_comb begin
narrow_data = phase_q ? hold_q[7:4] : hold_q[3:0];
narrow_valid = valid_q;
narrow_en = en_q;
narrow_er = er_q;
second_half = phase_q;
end
// Accept a new octet only when the second nibble is going out, so the
// wide side runs at exactly half the nibble rate.
assign wide_accept = phase_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
phase_q <= 1'b0;
hold_q <= '0;
en_q <= 1'b0;
er_q <= 1'b0;
valid_q <= 1'b0;
end else begin
if (!phase_q) begin
// First nibble going out; the octet was captured last cycle.
phase_q <= 1'b1;
end else begin
// Second nibble going out; take the next octet now.
phase_q <= 1'b0;
hold_q <= wide_data;
en_q <= wide_en;
er_q <= wide_er;
valid_q <= wide_valid;
end
end
end
endmoduleClassification: synthesizable.
What it teaches: that a generation change is a rate change, not a rewiring. One MAC octet becomes two interface cycles, so the adapter holds state and the two sides run at different rates. Any reasoning that assumes a one-to-one correspondence between MAC cycles and interface cycles is generation-specific — and Section 13 shows the assertion that encodes that assumption.
Deliberately simplified: no clock-domain crossing. Both sides here run from the nibble clock with the wide side accepting every other cycle. A real adapter between genuinely asynchronous domains needs the structures of Chapter 4.4 and Chapter 4.6.
Production implication: nibble order is a contract with no self-check. Least-significant-first is the convention used here, and if the far end reassembles most-significant-first, every bit arrives and every octet is wrong. The symptom is data corruption on a link whose every status indicator is healthy — structurally identical to Chapter 3.4 §10's lane-ordering fault and Chapter 3.1 §6's pair-striping fault. Exposing second_half costs one flip-flop of visibility and makes the contract observable.
Later ownership: asynchronous width conversion belongs to Chapter 4.6.
7. RTL 3 — The Reassembler, Where the Contract Bites
Section 6 split octets into nibbles. This is the other direction, and it is where the nibble-order contract actually produces a failure — because splitting cannot be wrong on its own, only disagreeing about the convention can.
// SYNTHESIZABLE. Narrow to wide, the receive direction.
//
// SPLITTING CANNOT BE WRONG BY ITSELF. Any consistent convention produces a
// stream some receiver can reassemble. The fault is always a DISAGREEMENT,
// and disagreements only become visible here -- which is why this module,
// not Section 6's, carries the alignment recovery.
//
// The subtlety: a receiver joining mid-stream does not know whether the
// first nibble it sees is a low half or a high half. It must be TOLD, by
// the frame boundary, and a receiver that guesses produces octets that are
// each built from halves of two adjacent octets -- every bit present, every
// octet wrong.
module width_reassembler_4_to_8 (
input logic clk,
input logic rst_n,
input logic narrow_valid,
input logic [3:0] narrow_data,
input logic narrow_en, // frame in progress
input logic narrow_er,
output logic wide_valid,
output logic [7:0] wide_data,
output logic wide_en,
output logic wide_er,
// Phase is derived from the FRAME BOUNDARY, never guessed. The rising
// edge of narrow_en defines the first nibble of the first octet, and
// everything after it follows by counting.
output logic phase,
// A frame whose nibble count was ODD. The frame carried half an octet,
// which means either the transmitter truncated mid-octet or a nibble was
// lost. Either way the frame is unusable and must be marked -- silently
// dropping the stray nibble produces a frame one octet short whose check
// value then fails, and the loss is attributed to the link.
output logic odd_nibble_count,
output logic [19:0] c_odd_nibble_frames
);
logic phase_q;
logic [3:0] low_q;
logic er_q;
logic in_frame_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
phase_q <= 1'b0;
low_q <= '0;
er_q <= 1'b0;
in_frame_q <= 1'b0;
wide_valid <= 1'b0;
wide_data <= '0;
wide_en <= 1'b0;
wide_er <= 1'b0;
odd_nibble_count <= 1'b0;
c_odd_nibble_frames <= '0;
end else begin
wide_valid <= 1'b0;
odd_nibble_count <= 1'b0;
if (narrow_valid && narrow_en) begin
if (!in_frame_q) begin
// Frame start: this nibble is a LOW half, by definition. The
// boundary supplies the phase so it never has to be guessed.
in_frame_q <= 1'b1;
phase_q <= 1'b1;
low_q <= narrow_data;
er_q <= narrow_er;
end else if (!phase_q) begin
phase_q <= 1'b1;
low_q <= narrow_data;
er_q <= narrow_er;
end else begin
// High half arrived: emit the octet.
phase_q <= 1'b0;
wide_valid <= 1'b1;
wide_data <= {narrow_data, low_q}; // low-first convention
wide_en <= 1'b1;
wide_er <= er_q || narrow_er;
end
end else if (in_frame_q) begin
// Frame ended. If a low half is held, the frame had an odd number
// of nibbles and is unusable.
in_frame_q <= 1'b0;
phase_q <= 1'b0;
wide_en <= 1'b0;
if (phase_q) begin
odd_nibble_count <= 1'b1;
if (!(&c_odd_nibble_frames)) c_odd_nibble_frames <= c_odd_nibble_frames + 1'b1;
end
end
end
end
assign phase = phase_q;
endmoduleClassification: synthesizable.
What it teaches: that phase must be derived from the frame boundary, never guessed. A receiver joining a stream mid-frame cannot know whether the nibble it sees is a low half or a high half — and if it assumes, every octet it builds is assembled from halves of two adjacent octets. Every bit is present, every octet is wrong, and the link reports perfect health. The rising edge of the enable is what supplies the answer.
Deliberately simplified: no clock-domain crossing, and no recovery from a lost nibble mid-frame beyond marking the frame. A production design may resynchronise at the next frame boundary, which this does implicitly by re-deriving phase.
Production implication: odd_nibble_count must be its own indication. A frame with an odd nibble count carried half an octet, and silently dropping the stray nibble produces a frame one octet short — whose check value then fails, and whose loss is attributed to the link. That misattribution is exactly the class Chapter 4.1 §14 described: the reporting side is not the causing side.
And keeping this separate from Section 6's splitter is deliberate. A single module holding both directions invites someone to share the nibble-order constant, after which a convention change applies to one direction only — producing a link that transmits correctly and receives garbage, with both halves individually reviewable and correct.
8. RTL 4 — The Control-Encoding Translator
Width is the easy half of a generation change. Control encoding is the hard half, because generations disagree about how "this is not data" is expressed.
// SYNTHESIZABLE. Control-encoding translation between two conventions.
//
// Generations disagree about HOW to say "this is not data":
//
// SIDEBAND : separate wires. TX_EN says a frame is in progress; TX_ER
// says it is bad. Costs pins. Data code space is untouched.
//
// IN-BAND : reserved values inside the data path itself. Costs code
// space -- some data values become unusable -- and pins are
// saved.
//
// Translating is not relabelling. Going sideband-to-in-band can FAIL: if
// the payload contains the reserved value, it must be escaped or the
// receiver will read data as control. That failure has no counterpart in
// the other direction, and it is the asymmetry this module exists to show.
package ctrl_enc_pkg;
typedef enum logic [1:0] {
ENC_SIDEBAND = 2'd0,
ENC_INBAND = 2'd1
} encoding_e;
endpackage
module control_encoding_translator
import ctrl_enc_pkg::*;
#(
parameter int unsigned W = 8,
// The in-band reserved values. Any data octet equal to one of these must
// be escaped, because a receiver cannot distinguish them otherwise.
parameter logic [7:0] IB_START = 8'hFB,
parameter logic [7:0] IB_END = 8'hFD,
parameter logic [7:0] IB_ERROR = 8'hFE,
parameter logic [7:0] IB_ESCAPE = 8'hFC
) (
input logic clk,
input logic rst_n,
// ── Sideband side ───────────────────────────────────────────────────────
input logic sb_valid,
input logic [W-1:0] sb_data,
input logic sb_en,
input logic sb_er,
output logic sb_accept,
// ── In-band side ────────────────────────────────────────────────────────
output logic ib_valid,
output logic [W-1:0] ib_data,
input logic ib_accept,
// A data octet collided with a reserved value and had to be escaped,
// costing an extra cycle. Counted because a payload that happens to be
// rich in reserved values silently costs THROUGHPUT -- a real effect that
// depends on the data and is invisible without this counter.
output logic [23:0] c_escapes_inserted,
// The worst run of consecutive escapes seen. Sizing upstream buffering
// from the average rather than this is how a design overflows under a
// pathological payload.
output logic [15:0] longest_escape_run
);
logic escaping_q; // an escape was emitted; the octet follows
logic [W-1:0] held_q;
logic [15:0] run_q;
logic needs_escape_c;
assign needs_escape_c = sb_valid && sb_en && !sb_er
&& ((sb_data == IB_START) || (sb_data == IB_END)
|| (sb_data == IB_ERROR) || (sb_data == IB_ESCAPE));
always_comb begin
ib_valid = sb_valid || escaping_q;
if (escaping_q) ib_data = held_q; // the escaped octet
else if (sb_er) ib_data = IB_ERROR;
else if (!sb_en) ib_data = IB_END;
else if (needs_escape_c) ib_data = IB_ESCAPE;
else ib_data = sb_data;
// The upstream is held for one cycle while an escape goes out. This is
// the throughput cost, and it is data-dependent.
sb_accept = ib_accept && !needs_escape_c;
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
escaping_q <= 1'b0;
held_q <= '0;
c_escapes_inserted <= '0;
run_q <= '0;
longest_escape_run <= '0;
end else if (ib_accept) begin
if (needs_escape_c && !escaping_q) begin
escaping_q <= 1'b1;
held_q <= sb_data;
if (!(&c_escapes_inserted)) c_escapes_inserted <= c_escapes_inserted + 1'b1;
run_q <= run_q + 1'b1;
if (run_q + 1'b1 > longest_escape_run) longest_escape_run <= run_q + 1'b1;
end else begin
escaping_q <= 1'b0;
if (!needs_escape_c) run_q <= '0;
end
end
end
endmoduleClassification: synthesizable.
What it teaches: that in-band and sideband control are not equivalent encodings of the same thing. Sideband costs pins and leaves the data space intact. In-band costs code space, and the moment payload can collide with a reserved value, escaping becomes necessary — which makes throughput data-dependent. Sideband has no equivalent failure.
Deliberately simplified: the reserved values are parameters chosen for the model, not any generation's actual encoding. Real in-band control encodings are defined per generation and interact with the block coding of Chapter 3.5 — which is where the real solution lives, because a block code's invalid-pattern space provides control symbols that cannot collide with data at all.
Production implication: longest_escape_run is the number that matters for sizing, not c_escapes_inserted. A payload rich in reserved values — a test pattern, a compressed stream, a memory dump — produces a burst of escapes, and upstream buffering sized from the average escape rate overflows on the burst. This is the same average-versus-worst-case trap Chapter 3.4 §12 named for stall depth.
And it explains a real design decision. Chapter 3.5 §2 listed a control-symbol space as one of the four things a line code buys. This module is what you have to build when you do not have one — and comparing them is the clearest argument for why a proper code's invalid-pattern space is worth its overhead.
9. RTL 5 — What a Generation Change Actually Costs
// SYNTHESIZABLE INSTRUMENTATION.
//
// Section 4 said each generation gave up something. This measures what,
// at runtime, so the trade is a number rather than an adjective.
//
// Three costs, and they are not interchangeable:
// PINS -- fixed at design time, but worth computing for comparison
// TIMING -- the setup-and-hold window, which DDR halves
// THROUGHPUT -- cycles spent on control and escapes rather than data
module generation_cost_monitor #(
parameter int unsigned IF_W = 8,
parameter bit IS_DDR = 1'b0,
parameter int unsigned CLK_MHZ = 125,
parameter int unsigned CNT_W = 32
) (
input logic clk,
input logic rst_n,
input logic clear,
input logic if_valid,
input logic if_is_data, // this cycle carried payload
input logic if_is_control, // this cycle carried control or an escape
// Pin count for this configuration. Data lines both ways, plus control
// and clock. Computed rather than documented so a parameter change
// updates it.
output logic [7:0] pin_estimate,
// The available setup-and-hold window in picoseconds. DDR halves it,
// which is Section 4's "gave up timing margin" as an actual quantity.
output logic [15:0] window_ps,
// Throughput efficiency: data cycles as a fraction of valid cycles,
// scaled by 256. Below 256 means cycles are being spent on control.
output logic [15:0] efficiency_x256,
output logic [CNT_W-1:0] c_data_cycles,
output logic [CNT_W-1:0] c_control_cycles
);
// Data both directions, plus enable, error, valid and clock each way.
localparam int unsigned CTRL_PINS = 6;
assign pin_estimate = 8'((IF_W * 2) + CTRL_PINS + (IS_DDR ? 2 : 2));
// One full clock period in ps, halved for DDR. This is the entire
// arithmetic behind "DDR gave up timing margin" -- half the window at the
// same clock, which is why RGMII needs controlled trace delay where GMII
// does not.
localparam int unsigned PERIOD_PS = 1_000_000 / CLK_MHZ;
assign window_ps = 16'(IS_DDR ? (PERIOD_PS / 2) : PERIOD_PS);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n || clear) begin
c_data_cycles <= '0;
c_control_cycles <= '0;
end else if (if_valid) begin
if (if_is_data && !(&c_data_cycles)) c_data_cycles <= c_data_cycles + 1'b1;
if (if_is_control && !(&c_control_cycles)) c_control_cycles <= c_control_cycles + 1'b1;
end
end
always_comb begin
automatic logic [CNT_W:0] total = c_data_cycles + c_control_cycles;
efficiency_x256 = (total == 0) ? 16'd0
: 16'((c_data_cycles * 256) / total);
end
endmoduleClassification: synthesizable instrumentation.
What it teaches: that Section 4's trades are quantities, and the timing one especially. window_ps is the whole argument for why DDR is a real cost: at 125 MHz, an SDR interface has an 8 ns window and a DDR one has 4 ns for the same clock. That is why RGMII requires controlled trace delay — often a deliberate delay on the clock — where GMII does not, and it is a board-design consequence of an interface choice.
Deliberately simplified: pin_estimate counts signal pins only and ignores power, ground and the return-path pins that a real count includes.
Production implication: efficiency_x256 is the one that surprises people. An interface whose control encoding is in-band spends cycles on control and escapes, so its effective throughput is below its nominal rate — and Section 8 showed that shortfall is data-dependent. A design sized from the nominal figure will be short by the escape rate under exactly the payloads that produce the most escapes.
Later ownership: the board-level timing consequences of DDR belong to a physical-design discipline rather than to this track.
10. RTL 6 — Interface Conformance, Distinct From Contract Conformance
Chapter 4.1 built two conformance checkers for the contract — the assumptions each side makes about the other. This checks something different and both are needed.
// SYNTHESIZABLE INSTRUMENTATION. Interface-level, not contract-level.
//
// Chapter 4.1's checkers ask "is each side honouring what the other
// assumed?" -- a semantic question about the contract.
//
// This asks "are these signals in a legal combination for THIS interface
// generation?" -- a syntactic question about the wires. The two are
// independent: a design can honour every assumption and still drive an
// illegal signal combination, and the far end's behaviour is then
// undefined rather than wrong.
//
// Generation-specific rules are PARAMETERS, because the same wires mean
// different things on different generations -- which is Section 4's claim
// seen from the checking side.
module interface_conformance #(
parameter int unsigned IF_W = 8,
// Does this generation define TX_ER asserted WITHOUT TX_EN as meaningful?
// On some it signals something specific; on others it is illegal. The
// checker cannot know without being told.
parameter bit ER_WITHOUT_EN_LEGAL = 1'b0,
// Minimum idle cycles this generation requires between frames.
parameter int unsigned MIN_IDLE = 12,
parameter int unsigned CNT_W = 20
) (
input logic clk,
input logic rst_n,
input logic clear,
input logic [IF_W-1:0] txd,
input logic tx_en,
input logic tx_er,
input logic [IF_W-1:0] rxd,
input logic rx_dv,
input logic rx_er,
// ── Illegal combinations ────────────────────────────────────────────────
// TX_ER without TX_EN. Legal on some generations, illegal on others, and
// a design ported between them without checking will drive it.
output logic v_er_without_en,
output logic [CNT_W-1:0] c_er_without_en,
// Data driven while the enable is low. Not an error on every generation,
// but it wastes power and on some it is genuinely illegal.
output logic v_data_while_disabled,
output logic [CNT_W-1:0] c_data_while_disabled,
// ── Timing conformance ──────────────────────────────────────────────────
output logic v_idle_too_short,
output logic [CNT_W-1:0] c_idle_too_short,
// ── Receive-side conformance ────────────────────────────────────────────
// RX_ER asserted outside a frame. On generations where this is defined it
// carries a specific meaning; where it is not, it is noise the MAC may
// act on.
output logic v_rx_er_outside_frame,
output logic [CNT_W-1:0] c_rx_er_outside_frame,
// Sticky: this design drove a combination that is legal on SOME
// generation but not the one configured. The most useful single bit here,
// because it is the signature of a design ported without re-checking.
output logic generation_mismatch_suspected
);
logic [$clog2(MIN_IDLE+2)-1:0] idle_q;
logic in_rx_frame_q;
function automatic logic [CNT_W-1:0] bump(input logic [CNT_W-1:0] v,
input logic en);
bump = (en && !(&v)) ? (v + 1'b1) : v;
endfunction
always_comb begin
v_er_without_en = tx_er && !tx_en && !ER_WITHOUT_EN_LEGAL;
v_data_while_disabled = !tx_en && (txd != '0);
v_idle_too_short = tx_en && (idle_q < ($clog2(MIN_IDLE+2))'(MIN_IDLE));
v_rx_er_outside_frame = rx_er && !rx_dv;
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
idle_q <= '1;
in_rx_frame_q <= 1'b0;
c_er_without_en <= '0;
c_data_while_disabled <= '0;
c_idle_too_short <= '0;
c_rx_er_outside_frame <= '0;
generation_mismatch_suspected <= 1'b0;
end else begin
if (clear) begin
c_er_without_en <= '0;
c_data_while_disabled <= '0;
c_idle_too_short <= '0;
c_rx_er_outside_frame <= '0;
// The sticky bit deliberately survives a clear -- it is a property
// of the design's configuration, not of a measurement window.
end else begin
c_er_without_en <= bump(c_er_without_en, v_er_without_en);
c_data_while_disabled <= bump(c_data_while_disabled, v_data_while_disabled);
c_idle_too_short <= bump(c_idle_too_short, v_idle_too_short);
c_rx_er_outside_frame <= bump(c_rx_er_outside_frame, v_rx_er_outside_frame);
end
// Idle tracking on the transmit side.
if (tx_en) idle_q <= '0;
else if (!(&idle_q)) idle_q <= idle_q + 1'b1;
if (rx_dv) in_rx_frame_q <= 1'b1;
else in_rx_frame_q <= 1'b0;
// A combination that is legal on another generation but not this one.
if (v_er_without_en || v_rx_er_outside_frame)
generation_mismatch_suspected <= 1'b1;
end
end
endmoduleClassification: synthesizable instrumentation.
What it teaches: that syntactic and semantic conformance are independent, and that checking one does not check the other. A MAC can honour every one of Chapter 4.1 §4's assumptions and still drive a signal combination that this generation does not define — and the far end's response is then undefined, which is worse than wrong because it is not reproducible.
Deliberately simplified: the generation-specific rules are parameters rather than a table of every generation's legality matrix. A production checker for a design supporting several generations carries the matrix.
Production implication: generation_mismatch_suspected is the highest-value bit in the module and it survives clear deliberately. It is the signature of a design ported between generations without re-checking its signal usage — a very common integration fault, because the ported design works for the overwhelming majority of traffic and drives the illegal combination only in a corner case. By the time anyone investigates, the corner case has passed; the sticky bit is what remains.
Later ownership: the full per-generation legality matrix belongs in a design's own interface specification.
11. Assertions
Every property below is a property of these teaching models and of the interface conventions they parameterise. IEEE 802.3 specifies each interface generation's signals and timing per clause; which combinations a particular design may drive is set by the generation it targets, and the checker of Section 10 takes that as a parameter for exactly that reason.
// ─── Causation: the reconciliation mapping is stateless ────────────────────
// The architectural property of Section 5. Catches state creeping into the
// mapping, which puts behaviour between the MAC and the interface -- after
// which both neighbours must be re-verified whenever either changes.
property p_rs_mapping_is_combinational;
@(posedge clk) disable iff (!rst_n)
(tx_en == (mac_tx_service != SVC_IDLE))
&& (tx_er == (mac_tx_service == SVC_ABORT));
endproperty
// ─── Safety: TX_EN and TX_ER are driven together ───────────────────────────
// Catches two independent drivers, which will eventually produce TX_ER
// without TX_EN by accident -- a combination that means something entirely
// different on several generations.
property p_er_implies_en;
@(posedge clk) disable iff (!rst_n)
tx_er |-> tx_en;
endproperty
// ─── Conservation: the receive indication is exhaustive ────────────────────
// Catches a fourth state appearing in the mapping, which would leave a
// received cycle unclassified.
property p_rx_indication_exhaustive;
@(posedge clk) disable iff (!rst_n)
(mac_rx_indication == IND_IDLE)
|| (mac_rx_indication == IND_RECEIVE)
|| (mac_rx_indication == IND_SUSPECT);
endproperty
// ─── Ordering: the adapter alternates halves ───────────────────────────────
// Catches a phase counter that can stall or skip, after which every
// subsequent octet is assembled from halves of two different octets.
property p_adapter_alternates;
@(posedge clk) disable iff (!rst_n)
1'b1 |=> (second_half != $past(second_half));
endproperty
// ─── Conservation: the wide side runs at half the narrow rate ──────────────
// Catches an accept that asserts every cycle, which drops every other
// octet -- and drops them silently, because nothing downstream can tell an
// octet was never offered.
property p_wide_accepts_every_other;
@(posedge clk) disable iff (!rst_n)
wide_accept |=> !wide_accept;
endproperty
// ─── Safety: the nibble contract holds ─────────────────────────────────────
// Least-significant nibble first. Catches a reordering that delivers every
// bit into the wrong half, which presents as data corruption on a link
// reporting perfect health.
property p_nibble_order;
@(posedge clk) disable iff (!rst_n)
(narrow_valid && !second_half) |-> (narrow_data == hold_q[3:0]);
endproperty
// ─── Safety: reserved values are always escaped ────────────────────────────
// Catches a payload octet equal to a reserved value reaching the in-band
// side unescaped, after which the receiver reads data as control and the
// frame structure collapses from that point on.
property p_reserved_always_escaped;
@(posedge clk) disable iff (!rst_n)
(ib_valid && !escaping_q && (ib_data == IB_START)) |-> !$past(sb_en);
endproperty
// ─── Conservation: an escape is followed by its octet ──────────────────────
// Catches an escape emitted without the octet it introduces, which shifts
// every subsequent byte by one.
property p_escape_followed_by_data;
@(posedge clk) disable iff (!rst_n)
(ib_valid && ib_accept && (ib_data == IB_ESCAPE)) |=> escaping_q;
endproperty
// ─── Bounded response: escaping terminates ─────────────────────────────────
// Catches an escape state that can persist, stalling the interface
// indefinitely on one octet.
property p_escape_is_one_cycle;
@(posedge clk) disable iff (!rst_n)
(escaping_q && ib_accept) |=> !escaping_q;
endproperty
// ─── Safety: the timing window follows the signalling mode ─────────────────
// Section 9's arithmetic, asserted. Catches a DDR configuration reporting an
// SDR window, which makes every downstream timing budget optimistic by a
// factor of two.
property p_window_halves_for_ddr;
@(posedge clk) disable iff (!rst_n)
IS_DDR |-> (window_ps == (1_000_000 / CLK_MHZ) / 2);
endproperty
// ─── Conservation: efficiency accounting closes ────────────────────────────
// Catches a cycle counted as neither data nor control, which biases the
// efficiency figure in a way no downstream analysis can detect.
property p_cycles_classified;
@(posedge clk) disable iff (!rst_n)
if_valid |-> (if_is_data ^ if_is_control);
endproperty
// ─── Stability: the generation-mismatch flag never clears ──────────────────
// Catches the sticky bit folded into the clear branch, destroying the
// signature of a design ported without re-checking its signal usage.
property p_mismatch_flag_sticky;
@(posedge clk) disable iff (!rst_n)
generation_mismatch_suspected |=> generation_mismatch_suspected;
endproperty
// ─── Causation: idle enforcement uses the configured minimum ───────────────
// Catches a checker hard-coded to one generation's gap, which then passes
// a design wired to a generation with a different requirement.
property p_idle_check_uses_parameter;
@(posedge clk) disable iff (!rst_n)
v_idle_too_short |-> (idle_q < MIN_IDLE);
endproperty
// ─── Causation: reassembly phase comes from the frame boundary ─────────────
// Catches a receiver that guesses phase, which builds every octet from
// halves of two adjacent octets -- every bit present, every octet wrong,
// and the link reporting perfect health.
property p_phase_from_frame_start;
@(posedge clk) disable iff (!rst_n)
($rose(narrow_en) && narrow_valid) |=> phase;
endproperty
// ─── Conservation: two nibbles make exactly one octet ──────────────────────
// Catches an emit on the wrong phase, which halves or doubles the octet
// count without any other symptom.
property p_octet_per_two_nibbles;
@(posedge clk) disable iff (!rst_n)
wide_valid |-> $past(phase);
endproperty
// ─── Safety: an odd nibble count is reported, never dropped ────────────────
// Catches silent truncation of a stray half-octet, which produces a frame
// one octet short whose check value then fails -- and the loss is
// attributed to the link rather than to the reassembler.
property p_odd_nibble_reported;
@(posedge clk) disable iff (!rst_n)
($fell(narrow_en) && $past(phase)) |=> odd_nibble_count;
endproperty12. Verification
Conceptual — an octet becoming two nibbles
8 cyclesThis figure is conceptual and labelled so. It shows the ratio and the ordering correctly — which is what Section 11's rejected property gets wrong — and does not attempt to show DDR half-cycles, which no digital waveform can represent.
Scenarios
- A frame through the reconciliation sublayer, transmit. Verify
tx_enrises with the first octet and falls after the last, and thattxdtracksmac_tx_datacombinationally with no cycle of delay. - A frame through the reconciliation sublayer, receive. Verify
mac_rx_indicationisIND_RECEIVEwhilerx_dvis asserted andIND_IDLEoutside it. - A received frame the PHY does not trust. Assert
rx_ermid-frame and verify the indication becomesIND_SUSPECT— notIND_IDLE, which would drop the frame silently, and notIND_RECEIVE, which would deliver it as good. - An aborted transmit. Drive
SVC_ABORTand verifytx_erasserts withtx_en, never without it. - Medium conditions. Toggle
crsandcoland verify they pass through unchanged and are not interpreted. Interpretation here would be behaviour, which Section 5 argued must not live in this block. - The mapping is stateless. Hold the inputs constant across a reset and verify the outputs are unchanged by the reset — a combinational mapping has nothing to reset.
- Adapter, a single octet. Verify exactly two narrow cycles, low nibble first, and
wide_acceptasserted on exactly one of them. - Adapter, a long frame. Verify the phase alternates for the whole frame with no slip, and that the octet count out equals the octet count in.
- Adapter, reset mid-octet. Verify no half-octet escapes and the phase returns to its reset value.
- Adapter, nibble order. Drive a distinct octet and verify by hand which half appears first. Then reverse the convention in the model and verify the check catches it — a scoreboard sharing the design's ordering verifies nothing.
- Translator, a payload with no reserved values. Verify no escapes and full throughput.
- Translator, a payload octet equal to each reserved value. Four runs, one per value. Verify an escape precedes each and the octet arrives intact.
- Translator, consecutive reserved values. Verify
longest_escape_runrecords the run and that throughput halves for its duration. - Translator, a reserved value in an abort. Verify the abort's control encoding wins and the data is not escaped into it.
- Cost monitor, SDR against DDR. Two parameterisations. Verify
window_pshalves for DDR at the same clock — Section 9's arithmetic, checked. - Cost monitor, efficiency. Drive a known mix of data and control cycles and verify
efficiency_x256matches the computed fraction exactly. - Conformance,
TX_ERwithoutTX_EN. Verify the violation fires when the parameter says it is illegal and does not fire when the parameter says it is legal. Both runs are needed; a checker verified only in the illegal configuration would pass while hard-coded. - Conformance, sticky mismatch across a clear. Trigger the flag, clear the counters, and verify the flag survives.
- Reassembly, phase from the frame boundary. Start a frame and verify the first nibble is treated as a low half without the receiver having to be told separately.
- Reassembly, a joiner mid-frame. Start the reassembler mid-stream and verify it produces nothing until a frame boundary supplies the phase — a receiver that guesses builds every octet from halves of two adjacent ones.
- A frame with an odd nibble count. Verify
odd_nibble_countpulses and the frame is marked rather than silently one octet short.
What the checker must own
- A reference model of the mapping, written from the service definition rather than from the RTL. Section 5's block is small enough that a scoreboard copied from it verifies only self-consistency.
- A parameterised octet reassembler that reads the declared ratio and the exposed phase rather than assuming either. Scenario 19 part 4 exists to test the scoreboard, not the design.
- At least two elaborations in regression. One width proves nothing about parameterisation, and the whole architectural claim of this chapter is about surviving a change of form.
- Coverage crosses of interface generation against control encoding against escape occurrence. The bin
(in-band encoding, reserved value in payload, escape emitted)must be well populated — it is data-dependent, so a run on clean payloads never reaches it.
13. Debugging — Vocabulary, Ratio, or Encoding
The symptom: a MAC and a PHY that were each verified independently, wired together, and delivering corrupted frames.
Step 1 — read generation_mismatch_suspected. One sticky bit, and it answers the first question: has this design driven a combination that is illegal for the generation it is wired to? A design ported between generations without re-checking its signal usage sets it, usually long before anyone investigates, and the corner case that set it has passed.
Step 2 — if the frames are corrupted but structurally intact, suspect the ratio. The signature is specific and worth recognising:
| Corruption pattern | Likely cause | Where to look |
|---|---|---|
| every octet has its halves swapped | nibble order — the contract of Section 6 | the adapter's convention at both ends |
| every octet after the first is shifted by one | an escape emitted without its data | Section 8's translator |
| exactly half the octets are missing | wide_accept asserting every cycle | the adapter's ratio |
| octets fine, frame boundaries wrong | control encoding disagreement | Section 8, and the generation's legality rules |
Row one is the one that wastes the most time, because every bit arrives, every status indicator is healthy, and the corruption looks random until someone notices it is a consistent nibble swap. Exposing second_half is what turns that into a two-minute check.
Step 3 — if throughput is below nominal with no errors, read the escape counters. Section 8 showed in-band control makes throughput data-dependent. A link that is fast on random traffic and slow on a specific payload is not degrading — it is escaping, and longest_escape_run says how badly.
Step 4 — if the interface is DDR and the failures are intermittent and temperature-dependent, read window_ps and compare it against the board's actual timing. Section 9's arithmetic is the check: a DDR interface has half the window of an SDR one at the same clock, and a design ported from SDR to DDR without re-doing timing closure will work at room temperature and fail at the extremes.
Step 5 — only now suspect the contract. Chapter 4.1 §13's attribution is the right tool, and it is deliberately last: syntactic faults are cheaper to rule out than semantic ones, and a signal-level mismatch will make the contract checkers fire spuriously and name the wrong thing.
The method stated once: the sticky mismatch bit rules out a bad port, the corruption pattern names the ratio or the encoding, the escape counters explain throughput, and the timing window explains intermittency — and all four are cheaper than reasoning about the contract, which is why they come first.
14. Common Misconceptions
"The xMII is just a bus."
The wrong model: a set of wires between two blocks, no different from any other parallel connection.
What it costs: you add signals to it freely — a FEC count here, a lane-skew reading there — and each one silently narrows which PHYs your MAC can work with. You cannot explain why the interface is defined by a standard at all, since a bus is a local design decision.
The corrected model: it is a vocabulary with a durability property. Its signals name data, frame boundaries, errors and validity, and nothing about a medium or a coding — which is why a MAC written against it works over media that did not exist when it was written. Every signal you add must pass Section 3's test or it is a medium leaking upward.
"Media-independent means the interface never changes."
The wrong model: one interface, fixed forever, is what independence means.
What it costs: you are surprised by the generations, and you treat a generation change as a failure of the abstraction rather than as the abstraction working. You budget a port as a rewiring.
The corrected model: the form changes constantly — 4 bits at 25 MHz, 8 at 125, 4 double-pumped, no parallel bus at all, 32 double-pumped — and the vocabulary persists. SGMII is the strongest evidence, not the exception: it abandons the parallel bus entirely and the MAC above is unaffected, because what crosses the boundary was never wires.
"A generation change is a rewiring."
The wrong model: different widths, same logic, adjust the connections.
What it costs: you assume one MAC cycle equals one interface cycle, which is Section 11's rejected property in design form. You size buffers from the wrong ratio, you write assertions that are green on one platform and fail everywhere else, and a port throttles throughput to satisfy them.
The corrected model: a generation change is a rate change. An 8-bit MAC feeding a 4-bit interface produces two interface cycles per octet, so the adapter holds state and the two sides run at different rates. Nibble order becomes a contract with no self-check, exactly as lane order is in Chapter 3.4 §10.
"In-band and sideband control are the same thing, differently packaged."
The wrong model: both say "this is not data", so the choice is cosmetic.
What it costs: you assume throughput is independent of payload, and it is not. A payload rich in reserved values produces a burst of escapes, and buffering sized from the average overflows on the burst. You also cannot explain why block codes bother with a control-symbol space.
The corrected model: sideband costs pins and leaves the data space intact. In-band costs code space, and the moment payload can collide with a reserved value, escaping makes throughput data-dependent. Sideband has no equivalent failure. Chapter 3.5 §2 listed a control-symbol space as one of the four things a line code buys — Section 8's translator is what you build when you do not have one.
"The reconciliation sublayer is a small MAC."
The wrong model: it is a bit of logic that helps the MAC talk to the PHY, so putting useful things in it is natural.
What it costs: you add buffering, retiming, or a decision to it — and now behaviour sits between the MAC and the interface. That behaviour must be re-verified whenever either neighbour changes, which defeats the entire purpose of the boundary and makes every generation port a verification project.
The corrected model: it is a stateless mapping and its thinness is the design. It maps a frame-oriented service onto cycle-oriented signals and adds nothing. That is precisely what makes it cheap enough to rewrite per generation, which is the mechanism by which the vocabulary outlives the wires.
15. Interview Reasoning
"What does media-independent actually mean?"
The weak answer is "it works with any PHY". The answer that ends the topic gives the vocabulary test — the interface names data, frame boundaries, errors and validity, and nothing about a medium or a coding — and then applies it to a signal that fails, such as a FEC correction count, naming the cost: a MAC that cannot be paired with a PHY that has no FEC. Citing SGMII as evidence rather than as an exception is what signals real understanding, because it shows the contract surviving the largest possible change of form.
"What does a generation change cost?"
Not "a bit of rewiring". Name the three costs and which generation paid which: RMII gave up width to save pins, GMII gave up pins to keep the clock manageable, RGMII gave up timing margin by going DDR — half the setup-and-hold window at the same clock, which is why it needs controlled trace delay where GMII does not. The strong follow-up is what it costs in RTL: a rate change, state in the adapter, and a nibble-order contract with no self-check.
"You port a verified MAC to a new interface generation and the assertion suite lights up. What happened?"
The likely answer is not that the design broke. It is that the suite contained ratio-dependent properties written as general invariants — true at the old width, false at the new one — and the danger is the fix: satisfying them by forcing a one-to-one ratio the new interface cannot provide, which throttles throughput permanently. The correct properties are conservation properties with the ratio as a parameter, and the way to find out before the port is to run the suite against two elaborations.
16. Understanding Check
An interface is media-independent to the degree that its vocabulary contains nothing from the medium.
Look at what the interface names: data, a transmit enable, a transmit error, a receive data valid, a receive error. Not one of those words is optical, electrical, or about coding. No wavelength, no impedance, no disparity, no lane. That absence is the design, and it is why a MAC written against this vocabulary works over media that did not exist when it was written.
The test: could this signal be named without referring to a medium or a coding?
| Proposed | Passes? | Why |
|---|---|---|
link_usable | yes | a claim about capability, not mechanism |
rate | yes | a number |
fec_corrected_count | no | presumes FEC exists |
block_lock | no | presumes a block code |
lane_skew | no | presumes multiple lanes |
Every "no" is a medium leaking upward, and Chapter 4.1 §5 named the cost: a MAC that reads FEC counts cannot be paired with a PHY that has no FEC.
The follow-up to be ready for: what about the link status vector? It half-passes — block_locked and lanes_aligned name mechanisms not every family has. Chapter 4.1 carries it anyway as a deliberate trade: a little vocabulary leakage for a MAC that can say where a link failed. The test is a reasoning tool, not a rule that decides every case.
17. What's Next
The claim this chapter defended: the xMII generations are not a progression of speeds. They are a record of what had to be given up to keep carrying the same vocabulary as rates rose — and the survival of that vocabulary across six completely different physical forms is what media-independence means.
The vocabulary test gives the working rule: a signal belongs on this interface only if it can be named without reference to a medium or a coding. The reconciliation sublayer is stateless because state is behaviour, and behaviour at a boundary must be re-verified whenever either neighbour changes. And a generation change is a rate change, which is why adapters hold state, why nibble order becomes an unchecked contract, and why an assertion written at one ratio is a coincidence rather than an invariant.
What this chapter deliberately did not give is time. Every signal here was described by what it means, not by when it moves. tx_en rises at the start of a frame — but on which cycle relative to the first data? Where is the preamble consumed? What happens to a frame already in flight when back-pressure arrives?
Chapter 4.3 — Data Flow Across the Boundary answers those, tracing a frame across the interface cycle by cycle in both directions. Chapter 4.1 gave a static contract and this chapter gave a static vocabulary; 4.3 puts both in time, and the temporal view reveals obligations that neither static view can express.
The full path is on the Ethernet curriculum index.
Continue learning
Related tutorials
- Related topic
Where the MAC Ends and the PHY Begins
The MAC/PHY boundary is generated by one rule: a responsibility belongs to the side that can detect its own failure. That rule decides every case — and it explains why each side is blind to the other's failures, which is what makes a contract violation invisible from both sides.
- Related topic
Data Flow Across the Boundary
Transmit is scheduled and receive is not. That one temporal fact is why the transmit path can be back-pressured and the receive path cannot — so one needs a handshake and the other a buffer, and being unready costs latency in one direction and a whole frame in the other.
- Related topic
Elastic Buffering and Clock Compensation
Two independent oscillators differ by a bounded amount forever, and a bounded rate difference still accumulates without limit unless something discharges it. The interframe gap is that opportunity — which is why it is not negotiable and why the buffer is far smaller than intuition suggests.
- Related topic
The MAC/PHY Boundary in RTL
What five chapters described as one boundary is three in silicon: a data boundary at the port list, a clock boundary inside the elastic buffer, and a reset boundary that is an order rather than a place. Confusing any two produces a specific, recognisable integration failure.
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.
