Ethernet · Module 3
Why the PHY Is Internally Layered — PCS, PMA, PMD
Each PHY sublayer boundary sits where the unit of information changes: octets become blocks, blocks become bits on lanes, bits become symbols on a medium. That placement turns N codings times M media into N plus M designs, and it marks where simulation stops being sufficient evidence.
Chapter 2.6 named the three sublayers and gave a first answer for why they exist: they change on different schedules. That answer is true and it is not enough to design against, because it does not tell you where a boundary belongs — only that boundaries are useful.
Chapter 3.3 supplied what was missing. It established that a receiver does not read bits; it infers symbols from a distorted waveform, and everything it does protects one of two margins. With that in place, the sublayer question becomes answerable rather than merely assertable.
Here is the answer this chapter defends.
Every sublayer boundary in the PHY sits exactly where the unit of information changes. Above the PHY the unit is an octet. The PCS turns a stream of octets into blocks — a unit that did not exist before it. The PMA turns blocks into bits on lanes. The PMD turns bits into symbols on a medium. Each boundary is a place where the thing being counted is different on the two sides, and that is not a coincidence — it is the criterion.
Where does each sublayer boundary belong, what does each side own, and why does the split make a PHY buildable?
1. Scope — And What This Adds to Chapter 2.6
Chapter 2.6 introduced the sublayers as part of describing what a PHY owes the MAC. This chapter takes them as the subject.
This chapter owns: the full stack from the MAC down to the medium including the reconciliation sublayer and the media-independent interface; the criterion that places each boundary; the combinatorial reuse argument; what each sublayer owns, stated with the qualifications that make the statements true across PHY families; the digital interfaces an RTL engineer integrates against; and the mapping from sublayer boundary to kind of evidence.
This chapter does not own the codes. 4B/5B, 8B/10B, 64B/66B and their properties belong to Chapter 3.5. PAM4 and line modulation belong to Chapter 3.6. Forward error correction and where it sits belong to Chapter 3.7. Alignment markers, deskew and the full link bring-up sequence belong to Chapter 3.8.
That deferral is deliberate and it is load-bearing. The point of this chapter is the boundary, not the contents. A chapter that also taught 64B/66B would teach the reader that the PCS is 64B/66B, which is exactly the overgeneralisation Section 4 spends its length dismantling.
2. The Stack, Named Precisely
Two of these need naming properly because they are the ones most often skipped.
The reconciliation sublayer exists to map the MAC's abstract service — a request to transmit a frame, an indication that one arrived, an indication that the medium is busy — onto the concrete signals of the interface below it. It is thin and it is not decorative: it is the piece that lets the same MAC sit above a series of different interface generations, because when the interface changes, the reconciliation sublayer changes and the MAC does not.
The media-independent interface is the parallel word interface between the reconciliation sublayer and the PCS. Its name is the entire architectural claim: media-independent. Everything above it is the same regardless of whether the link is copper or fibre; everything below it is not. Several generations exist with different widths and clocking, scaled to their rates.
For most RTL engineers this interface, not the medium, is where the PHY begins. You integrate a MAC against it, or a PHY against it. What lies below is either purchased IP or another team's problem, and the interface is the contract.
3. The Criterion — Boundaries Where the Unit Changes
The stack above could be presented as a list to memorise. The reason it has the shape it does is more useful.
| Boundary | Unit above | Unit below | What has to happen at the crossing |
|---|---|---|---|
| MAC to reconciliation | frame | octet stream plus control | frame structure becomes a signalled stream |
| xMII to PCS | parallel octets plus control | code block | a boundary is created that did not exist above |
| PCS to PMA | code block | bit on a lane | blocks are flattened and distributed across lanes |
| PMA to PMD | bit on a lane | symbol on a medium | bits become physical levels, and levels are not bits |
Read the third column downward. Each entry names something that does not exist on the other side. A code block is not an octet grouping — it is a unit with its own boundary, its own control encoding, and its own synchronisation requirement, and nothing above the PCS knows it exists. A symbol is not a bit — one symbol may carry one bit or several, and the mapping between them is the PMD's business.
That is the criterion, and it is testable. Ask of any proposed boundary: what unit exists on one side and not the other? If the answer is "the same unit, just handed along", the boundary carries no information and is arbitrary. If the answer names a new unit, the boundary is real, and every function that creates or consumes that unit belongs on the same side of it.
Applying the criterion resolves the questions people actually argue about:
- Where does block synchronisation belong? It is the recovery of the block boundary. Blocks exist only below the PCS boundary, so block synchronisation is PCS work. Not MAC — the MAC has never heard of a block.
- Where does serialisation belong? It converts a parallel unit to a per-lane bit stream. That is the PCS-to-PMA crossing by definition, so it is PMA work.
- Where does clock recovery belong? Symbol timing is a property of the received symbol stream, which is the PMD-to-PMA crossing. It is PMA work, informed by what the PMD delivers.
- Where does equalisation belong? It operates on the analog waveform before any symbol decision exists — below every unit boundary in the table. It is PMD-side work, which is why Chapter 3.3 had almost no RTL for it.
4. Why the Split Exists — The Combinatorial Argument
The reuse argument is usually stated qualitatively: "the split allows reuse". Made concrete it is much stronger.
Suppose N codings and M media. A monolithic PHY design, with no internal boundary, requires N × M distinct PHYs — one per combination. Adding one new medium requires N new designs.
With the boundary placed at the unit change, a coding is a PCS and a medium is a PMD, and they compose. The design set is N + M. Adding a new medium requires one new PMD, and every existing coding immediately works over it.
The figure makes the arithmetic visible. Three codings and three media: nine combinations, six blocks. The saving grows with the square of the catalogue.
The published clause structure shows this is real, not theoretical. 10GBASE-SR and 10GBASE-LR are different media — 850 nm multimode and 1310 nm single-mode — and both use the 64b/66b PCS of Clause 49, differing only in the PMD, Clause 52. One coding, two media, one PCS. That is the argument working exactly as described.
5. What the PCS Owns
Stated with the qualification that makes it true across families: the PCS owns everything that is true of the bit stream regardless of the medium.
Concretely, and depending on the PHY family:
- Coding and decoding — mapping data and control to code blocks and back. Chapter 3.5 owns the codes themselves.
- Scrambling and descrambling — where the family uses it, to avoid pathological patterns.
- Block synchronisation — recovering where blocks begin from an unaligned bit stream. This is the function that most clearly defines the PCS, because the block is the unit the PCS created.
- Control and idle representation — how "no data right now" and "a frame starts here" are expressed as code, given that the medium is always carrying something.
- Alignment markers, lane distribution and reassembly — in multi-lane families, striping blocks across lanes and putting them back in order.
- Rate difference compensation — inserting and deleting idles to absorb the difference between two independent clocks, the elastic-buffer function Chapter 2.6 introduced.
- Declaring that a functional link exists from the coding's own point of view.
6. What the PMA Owns
The PMA owns the conversion between a parallel, block-structured world and a serial, per-lane one, and the timing that conversion requires.
- Serialisation and deserialisation — the parallel-to-serial boundary in both directions.
- Clock recovery — extracting symbol timing from the received stream, because no clock was transmitted alongside it.
- Per-lane bit transport between the PCS and the PMD.
- Lane multiplexing, where the lane counts on the two sides differ.
- Framing and octet synchronisation in the families that place those functions here rather than in the PCS.
The PMA is the sublayer whose boundary is least crisp, and it is worth saying so rather than pretending otherwise. Clock recovery is arguably an analog function; serialisation at high rates is inseparable from the circuit that drives the medium; and different families place framing and scrambling on different sides of the PCS-to-PMA line.
What is stable across families is the PMA's role: it is the sublayer where "a block" stops being a meaningful unit and "a bit at a time on this lane" starts. Everything above it can reason in blocks. Nothing below it can.
7. What the PMD Owns
The PMD owns everything that is true of the medium regardless of the coding.
- The transceiver for the physical medium — drivers and detectors.
- Electrical or optical characteristics — levels, extinction ratio, launch power, return loss, the specified envelopes.
- The medium-dependent interface: the connector.
- Signal detection, where the medium supports it — Chapter 3.2's loss-of-signal is a PMD function.
The PMD is where Chapter 3.3 lives. Insertion loss, reflections, jitter, the eye — every one of those is a PMD-side property, and none of them is expressible in RTL. That is not a limitation of the sublayer model; it is the model correctly identifying where the digital description of the system stops.
Which is exactly why the PMD is the sublayer most often supplied as purchased IP or as a pluggable module. Its specification is a set of measured electrical or optical characteristics, and designing to those characteristics is a different discipline from the one that produces the PCS.
8. RTL 1 — The Reconciliation Boundary
The media-independent interface is where most RTL engineers meet the PHY, so it is where the RTL starts. The reconciliation sublayer's job is narrow and easy to get subtly wrong.
// SYNTHESIZABLE. The reconciliation boundary: frames in, words plus control
// out.
//
// The subtlety worth the module: a frame is a bounded object and a word
// interface is a continuous stream. Something must mark where a frame
// begins and ends, and that marking is CONTROL, not data -- which means the
// interface must be able to express values that are not data at all.
//
// This does not implement any specific xMII generation. It implements the
// shape they all share.
package xmii_pkg;
typedef enum logic [1:0] {
XC_DATA = 2'd0, // this word is frame data
XC_START = 2'd1, // this word begins a frame
XC_END = 2'd2, // this word ends a frame
XC_IDLE = 2'd3 // no frame in progress
} xmii_ctrl_e;
endpackage
module reconciliation_tx
import xmii_pkg::*;
#(
parameter int unsigned WORD_W = 32,
parameter int unsigned OCT_N = WORD_W / 8
) (
input logic clk,
input logic rst_n,
// MAC-side service: a frame presented as a stream of words.
input logic mac_valid,
input logic [WORD_W-1:0] mac_data,
input logic mac_start,
input logic mac_end,
input logic [$clog2(OCT_N+1)-1:0] mac_end_octets, // valid octets in the last word
input logic mac_error, // the MAC aborted this frame
output logic mac_ready,
// Interface side.
output logic xmii_valid,
output logic [WORD_W-1:0] xmii_data,
output xmii_ctrl_e xmii_ctrl,
output logic [$clog2(OCT_N+1)-1:0] xmii_end_octets,
// A frame the MAC abandoned must be marked so the PCS emits an
// unambiguously bad frame rather than a short valid one. Silently
// truncating is the failure this output exists to prevent.
output logic xmii_error,
input logic xmii_ready,
// Observability: frames started, completed, and abandoned. The third
// number is the interesting one and it is usually not counted.
output logic [31:0] c_frames_started,
output logic [31:0] c_frames_completed,
output logic [31:0] c_frames_aborted
);
logic in_frame_q;
assign mac_ready = xmii_ready;
always_comb begin
xmii_valid = mac_valid;
xmii_data = mac_data;
xmii_end_octets = mac_end_octets;
xmii_error = mac_valid && mac_error;
if (!mac_valid) xmii_ctrl = XC_IDLE;
else if (mac_start) xmii_ctrl = XC_START;
else if (mac_end) xmii_ctrl = XC_END;
else xmii_ctrl = XC_DATA;
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
in_frame_q <= 1'b0;
c_frames_started <= '0;
c_frames_completed <= '0;
c_frames_aborted <= '0;
end else if (mac_valid && xmii_ready) begin
if (mac_start) begin
in_frame_q <= 1'b1;
c_frames_started <= c_frames_started + 1'b1;
end
if (mac_end) begin
in_frame_q <= 1'b0;
if (mac_error) c_frames_aborted <= c_frames_aborted + 1'b1;
else c_frames_completed <= c_frames_completed + 1'b1;
end
end
end
endmoduleClassification: synthesizable.
What it teaches: that the interface must express control, not only data — and that the control encoding is what makes a frame boundary visible in a continuous stream. It also names the abort path, which is the piece most often missing. A MAC can abandon a frame mid-transmission, and the receiver at the far end must be able to tell an abandoned frame from a short valid one.
Deliberately simplified: no specific interface generation, no width or clocking rules, and no back-pressure subtleties. Real interfaces differ in all of these.
Production implication: c_frames_aborted is counted separately for a reason. An abort is not an error in the link — it is the local MAC giving up, usually because of an underrun. If it is folded into a general error count, an engineer investigating packet loss will look at the link when the fault is in the host's memory system.
Later ownership: how the PCS turns xmii_error into a code the far end recognises is Chapter 3.5.
9. RTL 2 — The PCS Transmit Pipeline
The PCS creates the block. This model shows the three stages that creation takes, without committing to any particular code.
// SYNTHESIZABLE. PCS transmit: octets and control in, code blocks out.
//
// Three stages, and the separation between them is the lesson:
//
// 1. CLASSIFY -- decide what kind of block this is. Data? Control? A
// block that begins a frame? This is a decision about MEANING and it
// is made once.
// 2. ENCODE -- map the payload and its type onto the code. This stage
// consumes the classification and never re-derives it.
// 3. EMIT -- present the block with its own valid, on the PCS's own
// flow-control terms.
//
// Merging classify into encode is the usual shortcut, and it is why adding
// a new control type later touches the code table in several places.
package pcs_pkg;
typedef enum logic [2:0] {
BT_IDLE = 3'd0,
BT_DATA = 3'd1,
BT_START = 3'd2,
BT_END = 3'd3,
BT_ERROR = 3'd4 // deliberately corrupt: an abandoned frame
} block_type_e;
endpackage
module pcs_tx_pipeline
import xmii_pkg::*;
import pcs_pkg::*;
#(
parameter int unsigned WORD_W = 32,
// Block payload plus a type field. NOT any standard's block format --
// Chapter 3.5 owns those.
parameter int unsigned BLOCK_W = WORD_W + 4
) (
input logic clk,
input logic rst_n,
input logic in_valid,
input logic [WORD_W-1:0] in_data,
input xmii_ctrl_e in_ctrl,
input logic in_error,
output logic in_ready,
output logic blk_valid,
output logic [BLOCK_W-1:0] blk_data,
output block_type_e blk_type,
input logic blk_ready,
// Observability, per block type. A PCS that cannot report its own block
// mix is very hard to debug from the far end, because the far end can
// only report what it received and not what was meant.
output logic [31:0] c_blocks [5]
);
// ── stage 1: classify ─────────────────────────────────────────────────
block_type_e type_c;
always_comb begin
if (in_error) type_c = BT_ERROR;
else if (in_ctrl == XC_START) type_c = BT_START;
else if (in_ctrl == XC_END) type_c = BT_END;
else if (in_ctrl == XC_DATA) type_c = BT_DATA;
else type_c = BT_IDLE;
end
// ── stage 2: encode ───────────────────────────────────────────────────
// PLACEHOLDER. A real code maps payload and type onto a block whose
// properties -- DC balance, transition density, an invalid-pattern space
// for synchronisation -- are the whole subject of Chapter 3.5. This
// concatenation has NONE of those properties and is not a code.
function automatic logic [BLOCK_W-1:0] encode_block(
input logic [WORD_W-1:0] payload,
input block_type_e t);
encode_block = {1'b0, t, payload};
endfunction
// ── stage 3: emit ─────────────────────────────────────────────────────
assign in_ready = blk_ready;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
blk_valid <= 1'b0;
blk_data <= '0;
blk_type <= BT_IDLE;
for (int unsigned i = 0; i < 5; i++) c_blocks[i] <= '0;
end else if (blk_ready) begin
// Idle is emitted when there is nothing to send. The medium is always
// carrying something -- that is what makes clock recovery possible --
// so "nothing to send" is itself a block that must be encoded.
blk_valid <= 1'b1;
blk_type <= in_valid ? type_c : BT_IDLE;
blk_data <= in_valid ? encode_block(in_data, type_c)
: encode_block('0, BT_IDLE);
c_blocks[in_valid ? type_c : BT_IDLE]
<= c_blocks[in_valid ? type_c : BT_IDLE] + 1'b1;
end
end
endmoduleClassification: synthesizable, with the encoding stage explicitly a placeholder.
What it teaches: the three-stage shape, and one fact that surprises people — blk_valid is asserted continuously. The PCS emits idle blocks when there is no data, because the medium must always carry something. Chapter 2.6 established why: a receiver recovers its clock from transitions in the data, so a silent medium is a medium the far end loses timing on. Idle is not the absence of transmission; it is a thing that is transmitted.
Deliberately simplified: the encoding. encode_block has no DC balance, no transition-density guarantee and no invalid-pattern space, and it therefore could not support block synchronisation at all. Every one of those properties is what makes a real code a code, and Chapter 3.5 owns them.
Production implication: the classify-then-encode separation is what makes a new control type cheap. Merging them means every code-table entry embeds a classification decision, and adding a type later touches the table in several places with no compiler help. The separation costs one enum.
Later ownership: Chapter 3.5 owns the codes; Chapter 3.7 owns where forward error correction inserts into this pipeline.
10. RTL 3 — The PMA Serialiser
The PCS-to-PMA crossing is where a block stops being a unit. This model shows the crossing and, more importantly, states what it is not.
// SYNTHESIZABLE (functional model). Block-to-lane serialisation.
//
// NOT A SERDES. A real high-rate serialiser is a mixed-signal circuit; this
// is a functional description of what the PCS-to-PMA crossing MEANS, so
// that bit ordering and lane distribution are expressible and assertable.
//
// The property worth the module: BIT ORDER AND LANE ASSIGNMENT ARE A
// CONTRACT. Both ends must agree, and disagreement produces a link that
// carries every bit and delivers none of them correctly -- a failure that
// looks like data corruption, not like a link fault.
module pma_serialiser #(
parameter int unsigned BLOCK_W = 36,
parameter int unsigned LANES = 4,
parameter int unsigned PER_LANE = BLOCK_W / LANES
) (
input logic clk,
input logic rst_n,
input logic blk_valid,
input logic [BLOCK_W-1:0] blk_data,
output logic blk_ready,
output logic [LANES-1:0] lane_bit,
output logic lane_valid,
// Where in the block we currently are. Exposed because a receiver that
// has lost alignment needs to be told what the transmitter believes, and
// because it makes the ordering contract observable rather than implicit.
output logic [$clog2(PER_LANE)-1:0] bit_index,
output logic block_boundary
);
logic [BLOCK_W-1:0] shift_q;
logic [$clog2(PER_LANE)-1:0] idx_q;
logic busy_q;
assign blk_ready = !busy_q || (idx_q == ($clog2(PER_LANE))'(PER_LANE - 1));
assign bit_index = idx_q;
assign block_boundary = busy_q && (idx_q == '0);
assign lane_valid = busy_q;
// THE CONTRACT, in one expression: lane L carries the contiguous slice
// [L*PER_LANE, (L+1)*PER_LANE) of the block, most significant bit first.
//
// Every alternative -- round-robin across lanes, least significant first,
// a different slice order -- is equally implementable and completely
// incompatible. Both ends must agree, and nothing detects disagreement
// except the data being wrong.
always_comb begin
for (int unsigned l = 0; l < LANES; l++) begin
lane_bit[l] = shift_q[l*PER_LANE + (PER_LANE - 1) - idx_q];
end
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
shift_q <= '0;
idx_q <= '0;
busy_q <= 1'b0;
end else begin
if (blk_valid && blk_ready) begin
shift_q <= blk_data;
idx_q <= '0;
busy_q <= 1'b1;
end else if (busy_q) begin
if (idx_q == ($clog2(PER_LANE))'(PER_LANE - 1)) busy_q <= 1'b0;
else idx_q <= idx_q + 1'b1;
end
end
end
endmoduleClassification: synthesizable functional model, explicitly not an implementation model.
What it teaches: that bit ordering and lane assignment are a contract, not an implementation detail — and that the contract has no self-checking property. A receiver that reassembles in a different order gets every bit and no correct data, and the symptom is data corruption on a link whose every status indicator reads healthy. Chapter 3.1 hit the identical failure mode with pair striping, which is not a coincidence: it is the same class of contract at a different boundary.
Deliberately simplified: everything about the physical implementation. There is no phase-locked loop, no driver, no clock distribution, and the timing here has no relationship to a real serialiser's.
Production implication: exposing bit_index and block_boundary costs almost nothing and makes the ordering contract observable. In an integration where two teams own the two ends, a visible index is the difference between finding an ordering mismatch in an afternoon and finding it in a week — because with it you can compare what the transmitter believes against what the receiver believes, directly.
Later ownership: how lanes are aligned and deskewed at the far end is Chapter 3.8.
11. RTL 4 — Receive Alignment, as a Search With Evidence
The transmitter knows where every block begins. The receiver is handed an undifferentiated bit stream and must discover it. That asymmetry is the same one Chapter 2.2 named for frames, appearing again one layer down: transmit decides, receive discovers.
Why LOSS is a state rather than a transition straight back to SEARCH. Losing lock is an event worth observing, timestamping and counting. Collapsing it into an arc makes it invisible — the machine simply reappears in SEARCH with nothing recording that it was ever locked. Section 13's status vector and Section 17's debugging method both depend on the distinction being expressible.
// SYNTHESIZABLE. Block alignment as a hypothesis test.
//
// THE CONTRACT THIS DEPENDS ON: the code has invalid patterns, so a wrong
// offset produces detectable garbage. Without that property no evidence
// exists and alignment is impossible. Chapter 3.5 owns why real codes have
// it; this module owns what to do with it.
//
// The structure is: propose an offset, gather evidence, commit, and keep
// checking. That last part is why LOCKED can be left.
module block_aligner
import pcs_pkg::*;
#(
parameter int unsigned OFFSETS = 66, // candidate offsets to try
parameter int unsigned OFF_W = $clog2(OFFSETS),
parameter int unsigned LOCK_THRESH = 64, // consecutive valid to commit
parameter int unsigned LOSS_THRESH = 16, // invalid, in a window, to give up
parameter int unsigned CNT_W = 8
) (
input logic clk,
input logic rst_n,
input logic blk_seen, // a candidate block was presented
input logic block_valid, // it parsed as a legal block at this offset
output logic [OFF_W-1:0] offset, // the offset currently hypothesised
output logic locked,
output logic searching,
// Lock loss as an EVENT, not merely a state change. Sticky and counted,
// because by the time an operator looks the machine has usually
// re-locked and current state says nothing happened.
output logic lock_lost_pulse,
output logic [15:0] c_lock_lost,
output logic [15:0] c_offsets_tried,
// How much evidence backs the current lock. Exposed because a lock held
// on thin evidence and a lock held on strong evidence look identical from
// outside, and only one of them is about to fail.
output logic [CNT_W-1:0] good_run
);
typedef enum logic [1:0] {
S_SEARCH = 2'd0,
S_QUAL = 2'd1,
S_LOCKED = 2'd2,
S_LOSS = 2'd3
} align_state_e;
align_state_e state_q, state_d;
logic [OFF_W-1:0] off_q, off_d;
logic [CNT_W-1:0] good_q, good_d;
logic [CNT_W-1:0] bad_q, bad_d;
always_comb begin
state_d = state_q;
off_d = off_q;
good_d = good_q;
bad_d = bad_q;
unique case (state_q)
S_SEARCH: begin
good_d = '0;
bad_d = '0;
if (blk_seen) state_d = S_QUAL;
end
S_QUAL: begin
if (blk_seen) begin
if (block_valid) begin
good_d = good_q + 1'b1;
if (good_q + 1'b1 >= CNT_W'(LOCK_THRESH)) state_d = S_LOCKED;
end else begin
// One invalid block is enough to reject an offset. Evidence for
// a WRONG offset is cheap: a wrong offset produces garbage
// almost immediately. Evidence for a RIGHT one is expensive,
// which is why LOCK_THRESH is large and this test is a single
// failure. The asymmetry is deliberate and it is what makes the
// search fast.
good_d = '0;
off_d = (off_q == OFF_W'(OFFSETS - 1)) ? '0 : off_q + 1'b1;
state_d = S_SEARCH;
end
end
end
S_LOCKED: begin
if (blk_seen) begin
if (block_valid) begin
bad_d = (bad_q != '0) ? bad_q - 1'b1 : bad_q; // leaky, not sticky
if (good_q != '1) good_d = good_q + 1'b1;
end else begin
bad_d = bad_q + 1'b1;
if (bad_q + 1'b1 >= CNT_W'(LOSS_THRESH)) state_d = S_LOSS;
end
end
end
S_LOSS: begin
good_d = '0;
bad_d = '0;
state_d = S_SEARCH;
end
default: state_d = S_SEARCH;
endcase
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state_q <= S_SEARCH;
off_q <= '0;
good_q <= '0;
bad_q <= '0;
c_lock_lost <= '0;
c_offsets_tried <= '0;
lock_lost_pulse <= 1'b0;
end else begin
state_q <= state_d;
off_q <= off_d;
good_q <= good_d;
bad_q <= bad_d;
lock_lost_pulse <= (state_q == S_LOCKED) && (state_d == S_LOSS);
if ((state_q == S_LOCKED) && (state_d == S_LOSS) && !(&c_lock_lost))
c_lock_lost <= c_lock_lost + 1'b1;
if ((off_d != off_q) && !(&c_offsets_tried))
c_offsets_tried <= c_offsets_tried + 1'b1;
end
end
assign offset = off_q;
assign locked = (state_q == S_LOCKED);
assign searching = (state_q == S_SEARCH) || (state_q == S_QUAL);
assign good_run = good_q;
endmoduleClassification: synthesizable.
What it teaches: the asymmetry of evidence, which is the module's real subject. Rejecting an offset takes one invalid block; accepting one takes many valid ones. That is not arbitrary — a wrong offset produces garbage almost immediately, so negative evidence is cheap, while a right offset only proves itself over time. Getting the asymmetry backwards produces a search that is either glacially slow or locks confidently onto noise.
Deliberately simplified: no descrambler interaction, no alignment markers, no multi-lane deskew, and the offset space is a parameter rather than a code property.
Production implication: bad_q leaks downward on a good block rather than being reset outright. Reset-on-good means a link with a steady low error rate never accumulates enough to declare loss, and holds a lock it should have abandoned. Never decrementing means the counter is cumulative and a link that has been up for a week eventually declares loss from ordinary background errors. A leaky bucket is the only one of the three that measures a rate, and rate is what matters.
Later ownership: alignment markers and lane deskew are Chapter 3.8; the invalid-pattern property this module depends on is Chapter 3.5.
12. RTL 5 — Ready-Valid at the Sublayer Boundary
Two sublayers exchange data across a boundary where the clock domains, the widths and sometimes the rates differ. That boundary needs a discipline, and the discipline has one rule people break.
// SYNTHESIZABLE. A registered ready-valid boundary between sublayers.
//
// Why this exists at a sublayer boundary rather than a plain wire: if the
// PCS's ready comes combinationally from the PMA's ready, the two blocks
// share a timing path and can no longer be closed independently. The whole
// reuse argument of Section 4 assumes they CAN be, so the boundary has to
// be registered in both directions.
//
// The rule people break: ONCE VALID IS ASSERTED, DATA MUST NOT CHANGE
// UNTIL READY IS SEEN. A producer that withdraws or mutates a held word
// corrupts data in a way that only appears under back-pressure -- which is
// exactly the condition light testing never creates.
module sublayer_skid #(
parameter int unsigned W = 36
) (
input logic clk,
input logic rst_n,
input logic up_valid,
input logic [W-1:0] up_data,
output logic up_ready,
output logic dn_valid,
output logic [W-1:0] dn_data,
input logic dn_ready,
// Back-pressure visibility. Cycles the upstream was stalled, and the
// longest single stall. Both matter: total stall says how much throughput
// was lost, longest stall says whether an upstream buffer can absorb it.
output logic [31:0] c_stall_cycles,
output logic [15:0] longest_stall
);
logic [W-1:0] skid_q;
logic skid_full_q;
logic [15:0] run_q;
assign up_ready = !skid_full_q;
assign dn_valid = skid_full_q;
assign dn_data = skid_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
skid_q <= '0;
skid_full_q <= 1'b0;
c_stall_cycles <= '0;
longest_stall <= '0;
run_q <= '0;
end else begin
if (skid_full_q && dn_ready) begin
// Hand off, and accept a new word in the same cycle if one is there.
skid_full_q <= up_valid;
if (up_valid) skid_q <= up_data;
end else if (!skid_full_q && up_valid) begin
skid_q <= up_data;
skid_full_q <= 1'b1;
end
// A stall is upstream having data and being unable to hand it over.
if (up_valid && !up_ready) begin
c_stall_cycles <= c_stall_cycles + 1'b1;
run_q <= run_q + 1'b1;
if (run_q + 1'b1 > longest_stall) longest_stall <= run_q + 1'b1;
end else begin
run_q <= '0;
end
end
end
endmoduleClassification: synthesizable.
What it teaches: why the boundary must be registered in both directions. If ready propagates combinationally through the boundary, the two sublayers share a timing path and cannot be closed, verified or replaced independently — which contradicts the entire architectural argument for having a boundary there.
Deliberately simplified: one entry, single clock domain. A real PCS-to-PMA boundary frequently crosses clock domains and needs an asynchronous structure — the elastic buffer Chapter 2.6 described.
Production implication: longest_stall matters more than c_stall_cycles for sizing. Total stall tells you throughput lost; the longest single stall tells you how deep an upstream buffer must be to ride it out. Designs are sized from the total and then overflow on the peak, which is a bug that only appears under sustained load.
13. RTL 6 — The Status Vector, Not the Status Bit
Every sublayer produces evidence about the link. The temptation is to reduce all of it to link_up, and it is the single most consequential mistake available in a PHY integration.
// SYNTHESIZABLE. Sublayer status, preserved and ordered.
//
// The vector is ordered from the MEDIUM UPWARD, and the ordering is the
// diagnostic. The lowest deasserted bit names the sublayer that owns the
// problem, because every bit above it depends on the ones below.
//
// A single link_up bit answers "is it working". The vector answers "where
// is it broken", which is the question that has an owner.
module phy_status_vector (
input logic clk,
input logic rst_n,
// ── PMD evidence: is there a signal at all? ───────────────────────────
input logic signal_detect,
// ── PMA evidence: can we recover timing from it? ──────────────────────
input logic clock_locked,
// ── PCS evidence: does the coding parse? ──────────────────────────────
input logic block_locked,
input logic lanes_aligned, // trivially true for single-lane families
// ── Link-level evidence ───────────────────────────────────────────────
input logic local_fault,
input logic remote_fault,
// Ordered from the medium upward.
output logic [3:0] up_vector,
output logic link_up,
// The lowest layer that is NOT satisfied. 4 means everything is.
output logic [2:0] lowest_failing,
// Captured at the moment the link last dropped. By the time anyone reads
// it, the lower layers have usually recovered and live status is useless.
output logic [3:0] down_vector,
output logic down_valid,
// How long the link has been continuously up. A link that reports up but
// whose uptime keeps restarting is flapping, and no instantaneous read of
// link_up can ever show that.
output logic [31:0] uptime_cycles
);
logic link_up_q;
always_comb begin
up_vector[0] = signal_detect; // PMD
up_vector[1] = signal_detect && clock_locked; // PMA
up_vector[2] = up_vector[1] && block_locked; // PCS
up_vector[3] = up_vector[2] && lanes_aligned; // PCS, multi-lane
lowest_failing = 3'd4;
for (int i = 3; i >= 0; i--) begin
if (!up_vector[i]) lowest_failing = 3'(i);
end
end
// Faults gate the link but deliberately do NOT appear in the vector. The
// vector describes what the receiver can do; a fault describes what one
// end has been TOLD. Merging them would make the vector's ordering claim
// false, because a remote fault says nothing about our own lower layers.
wire link_up_c = (&up_vector) && !local_fault && !remote_fault;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
link_up_q <= 1'b0;
down_vector <= '0;
down_valid <= 1'b0;
uptime_cycles <= '0;
end else begin
link_up_q <= link_up_c;
if (link_up_q && !link_up_c) begin
down_vector <= up_vector; // the state AT the moment of failure
down_valid <= 1'b1;
end
if (link_up_c) uptime_cycles <= uptime_cycles + 1'b1;
else uptime_cycles <= '0;
end
end
assign link_up = link_up_q;
endmoduleClassification: synthesizable.
What it teaches: that a status vector ordered from the medium upward is self-diagnosing. Each bit depends on the ones below it, so the lowest deasserted bit names the sublayer that owns the problem — no interpretation required, and no reasoning about which layer to check first.
Deliberately simplified: four bits. Production PHYs expose more, including per-lane variants of the upper two and additional PMD-side detail.
Production implication: keeping faults out of the vector is a real design decision and not an oversight. The vector's ordering claim — each bit depends on the ones below — is what makes it diagnostic, and a remote fault says nothing about our own lower layers. Folding it in makes the ordering false, and a vector whose ordering is false is worse than no vector, because it will confidently point at the wrong sublayer.
And uptime_cycles catches what link_up cannot. A link that flaps every few minutes reports up at essentially every instant anyone checks. Only a monotonically increasing uptime that keeps restarting reveals it — the same argument Chapter 3.1 made about retrain counts, at a different layer.
14. Where the Evidence Changes
The third claim from the opening callout, made concrete. The sublayer boundaries are also boundaries between kinds of evidence, and that mapping is what a verification plan is actually built from.
| Sublayer | What you can prove, and how | What you cannot |
|---|---|---|
| Reconciliation and xMII | RTL simulation, formal for the control encoding | nothing relevant lives outside RTL here |
| PCS | RTL simulation, formal for coding and alignment properties; exhaustive on small codes | nothing — this sublayer is fully digital |
| PMA | RTL for ordering, lane assignment and elastic-buffer behaviour | jitter transfer, clock-recovery loop dynamics, real serialiser timing |
| PMD | almost nothing in RTL | everything that matters: eye, jitter, return loss, launch power, sensitivity — all measured |
Read the bottom two rows against a project plan. A PCS is verified the way any digital block is verified, and the plan is a normal RTL plan. A PMD is characterised on a bench, against specified masks, with instruments — and no amount of simulation substitutes. The PMA sits across the line: its function is provable in RTL and its timing behaviour is not.
Three consequences that decide how a project goes:
- "The PHY is verified" is never a meaningful statement. Ask which sublayer. A team reporting a fully verified PHY on the strength of RTL coverage has verified the top two rows and characterised nothing.
- The PMD's schedule is set by silicon and lab time, not by simulation. It cannot start before there is hardware, which means it is on the critical path in a way the PCS is not, and it must be planned as such from the beginning.
- The PMA is where verification arguments happen, because both kinds of evidence apply to it and neither is sufficient alone. Deciding early which properties are RTL's responsibility and which are measurement's is the single most useful hour a PHY verification lead can spend.
15. Assertions
Every property below is a property of these teaching models. IEEE 802.3 specifies sublayer service interfaces and coding behaviour per clause; how a particular design implements alignment thresholds, status aggregation and boundary flow control is an implementation choice.
// ─── Safety: an abandoned frame is marked, never silently truncated ────────
// Catches: an abort path that drops the error indication, so the far end
// receives a short but structurally valid frame. It then fails the check
// value and is counted as a link error, sending the investigation to the
// PHY for a fault in the host's memory system.
property p_abort_is_marked;
@(posedge clk) disable iff (!rst_n)
(mac_valid && mac_error) |-> xmii_error;
endproperty
// ─── Conservation: every accepted word produces exactly one block ──────────
// Catches: a pipeline that drops a word under back-pressure, which removes
// octets from the middle of a frame -- undetectable until the check value
// fails at the far end.
property p_word_produces_block;
@(posedge clk) disable iff (!rst_n)
(in_valid && in_ready) |=> blk_valid;
endproperty
// ─── Safety: the PCS never goes silent ─────────────────────────────────────
// Idle is a transmitted thing, not an absence. Catches: a design that stops
// emitting when the MAC has nothing to send, which starves the far end's
// clock recovery and drops the link during quiet periods -- a failure that
// only appears at low traffic and never under load.
property p_pcs_always_emits;
@(posedge clk) disable iff (!rst_n)
blk_ready |=> blk_valid;
endproperty
// ─── Causation: classification determines block type, once ─────────────────
// Catches: a type re-derived at the encode stage, which can disagree with
// the classification stage on a control word.
property p_type_matches_control;
@(posedge clk) disable iff (!rst_n)
(blk_valid && (blk_type == BT_START)) |-> $past(in_ctrl == XC_START);
endproperty
// ─── Safety: the lane assignment contract holds ────────────────────────────
// Catches: a reordering that still delivers every bit, to the wrong place.
// Presents as data corruption on a link whose every status bit is healthy.
property p_lane_slice_contract;
@(posedge clk) disable iff (!rst_n)
lane_valid |-> (lane_bit[0] == shift_q[PER_LANE - 1 - bit_index]);
endproperty
// ─── Conservation: a block is serialised exactly once ──────────────────────
// Catches: a ready that reasserts mid-block, letting a new block overwrite
// one still in flight.
property p_block_not_overwritten;
@(posedge clk) disable iff (!rst_n)
(busy_q && (idx_q != PER_LANE-1)) |-> !blk_ready;
endproperty
// ─── Ordering: lock requires sustained evidence ────────────────────────────
// Catches: a threshold comparison that lets a single valid block declare
// lock, which locks confidently onto noise.
property p_lock_needs_evidence;
@(posedge clk) disable iff (!rst_n)
$rose(locked) |-> ($past(good_run) >= LOCK_THRESH - 1);
endproperty
// ─── Ordering: one invalid block rejects an offset ─────────────────────────
// The evidence asymmetry, asserted. Catches: symmetric thresholds, which
// make the search take orders of magnitude longer than it should.
property p_one_bad_rejects_offset;
@(posedge clk) disable iff (!rst_n)
((state_q == S_QUAL) && blk_seen && !block_valid) |=> (state_q == S_SEARCH);
endproperty
// ─── Liveness with an explicit assumption ──────────────────────────────────
// ASSUMPTION: the transmitter is sending, and one offset is correct. Under
// that assumption the search terminates in lock. Without the assumption the
// property is unprovable, and stating the assumption is the point.
property p_search_terminates_under_assumption;
@(posedge clk) disable iff (!rst_n)
searching |-> ##[1:$] locked;
endproperty
// ─── Stability: the bad counter leaks rather than resetting ────────────────
// Catches: reset-on-good, which lets a steadily erroring link hold a lock
// it should have abandoned. Rate, not absence, is the criterion.
property p_bad_counter_leaks;
@(posedge clk) disable iff (!rst_n)
((state_q == S_LOCKED) && blk_seen && block_valid && ($past(bad_q) != 0))
|=> (bad_q == $past(bad_q) - 1);
endproperty
// ─── Safety: the status vector is monotone from the medium upward ──────────
// The property that makes the vector diagnostic. Each bit depends on the
// ones below, so a set bit implies every lower bit is set. Catches: a bit
// wired directly from its own sublayer without the dependency chain, which
// silently destroys the "lowest failing bit names the owner" rule.
property p_status_vector_monotone;
@(posedge clk) disable iff (!rst_n)
(up_vector[3] |-> up_vector[2]) and
(up_vector[2] |-> up_vector[1]) and
(up_vector[1] |-> up_vector[0]);
endproperty
// ─── Causation: the down vector is captured on the edge ────────────────────
// Catches: continuous sampling, which records recovery rather than failure.
property p_down_vector_on_edge;
@(posedge clk) disable iff (!rst_n)
$fell(link_up) |=> $stable(down_vector);
endproperty
// ─── Safety: uptime resets on every drop ───────────────────────────────────
// Catches: an uptime that survives a drop, which hides flapping completely.
property p_uptime_resets;
@(posedge clk) disable iff (!rst_n)
!link_up |-> (uptime_cycles == 0);
endproperty
// ─── Safety: held data does not change before it is accepted ───────────────
// The ready-valid rule people break. Catches: a producer mutating a word it
// has already offered -- corruption that appears only under back-pressure,
// which light testing never creates.
property p_held_data_stable;
@(posedge clk) disable iff (!rst_n)
(up_valid && !up_ready) |=> (up_valid && $stable(up_data));
endproperty16. Verification
Scenarios
- A frame across the whole stack. Drive one frame at the reconciliation boundary and verify it emerges as blocks with the right types, serialises with the right lane assignment, and re-aligns at the far end. The nominal end-to-end case.
- Idle between frames. Verify the PCS emits idle blocks continuously in the gap and that
blk_validnever deasserts. This isp_pcs_always_emits, and a design that fails it works perfectly under load and drops the link when traffic stops. - A frame abandoned mid-transmission. Verify
xmii_errorpropagates, the PCS emitsBT_ERROR, andc_frames_abortedrather than a general error counter advances. - Back-pressure from the PMA. Stall
blk_readymid-frame and verify no word is dropped, held data does not change, andlongest_stallrecords the peak rather than only the total. - Back-pressure of exactly one cycle, repeatedly. The skid buffer's hardest case. Verify full throughput is maintained across the boundary and that no word is duplicated at the hand-off.
- Lane assignment. Drive a block with a distinct value per lane slice and verify each lane carries its own contiguous slice, most significant bit first. Then reparameterise
LANESand repeat — a parameterisation change is what breaks the contract. - Cold alignment. Start the aligner unaligned and verify it walks candidate offsets, rejects each wrong one on the first invalid block, and locks only after
LOCK_THRESHconsecutive valid ones. - Alignment onto the last candidate offset. Verify the offset counter wraps correctly and the search does not stall at the end of its range.
- A single invalid block while locked. Verify the aligner stays locked and
bad_qincrements by one. This is the scenario the rejected property would have broken. - Sustained invalid blocks while locked. Verify lock is released after
LOSS_THRESH,lock_lost_pulsefires once,c_lock_lostadvances, and the machine passes throughS_LOSSrather than jumping toS_SEARCH. - The leaky counter. Alternate invalid and valid blocks at a rate below the threshold and verify lock is never lost. Then raise the rate above it and verify lock is lost. Together these prove the counter measures a rate rather than a total.
- Status vector monotonicity. Deassert
signal_detectalone and verify every higher bit falls,lowest_failingreads zero, andlink_updrops. - Each sublayer failing individually. Four runs, one per bit. Verify
lowest_failingnames the right sublayer each time and that no higher bit stays set. - A remote fault with all four vector bits set. Verify
link_upis low whileup_vectoris all ones — the case that proves faults are correctly kept out of the vector. - Link flapping. Drop and restore the link repeatedly and verify
uptime_cyclesrestarts each time whilelink_upreads high at almost every sample point. - Down-vector capture. Drop the link, let the lower layers recover, then read
down_vectorand verify it holds the state at the moment of failure rather than the current one. - Reset at every stage. Verify no partial block escapes the PCS, no partially serialised block escapes the PMA, and the aligner returns to
S_SEARCH.
What the checker must own
- A block-level reference model that independently computes the expected block type from the interface control. It must implement the classification rule from the specification, not from the design, or it replicates the design's bugs.
- A serialisation scoreboard that reassembles blocks from the lane streams using an independently written ordering rule. This is the only check that catches a contract mismatch, and it is worthless if it shares code with the design.
- A pattern generator for scenario 18 capable of constructing offset-fooling sequences from the code's own structure. This is the highest-value item in a PCS verification plan and the one most often absent.
- Coverage crosses of aligner state against
block_validagainstbad_qband. The bin(LOCKED, invalid block, bad_q below threshold)must be well populated — that is the healthy-link-with-errors state, and a run that never reaches it has not verified the leaky counter at all.
17. Debugging — Descending by the Vector
The symptom: a link that will not come up, with a PHY somebody else designed and a MAC somebody else designed.
Step 1 — read lowest_failing. One register, and it names the sublayer that owns the problem. Every bit above the lowest failing one is a consequence, not a cause, and investigating any of them wastes the time it takes to find that out.
lowest_failing | The lowest layer that is unsatisfied | Where to go |
|---|---|---|
| 0 | PMD — no signal at all | the medium, connectors, the far-end transmitter: Chapter 3.1 or Chapter 3.2 |
| 1 | PMA — signal present, no clock recovered | signal quality, or a rate mismatch between the ends |
| 2 | PCS — clock recovered, blocks do not parse | a coding mismatch — the two ends are different PHY types |
| 3 | PCS — blocks parse, lanes not aligned | lane order, lane polarity, or excessive skew |
| 4 | everything satisfied | above the PHY, or a fault indication |
Step 2 — the two mismatch rows are the ones worth naming. Rows 1 and 2 are the classic integration failures, and both present as "the link does not come up" with hardware that is entirely healthy.
- Signal but no clock lock very often means the two ends are configured for different rates. Both PHYs are fine; they disagree about what they are doing.
- Clock lock but no block lock very often means different codings — one end expecting one block format and the other sending another. Again both are fine individually.
Neither is a hardware fault, and no amount of measuring will reveal either. They are found by comparing configuration, and the vector is what tells you to go do that.
Step 3 — everything satisfied and the link is still down. Then it is a fault indication, and the local-versus-remote distinction of Chapter 3.2 applies: a local fault means we cannot receive, a remote fault means the far end told us it cannot. Opposite ends of the link, and the vector deliberately excludes both so that this question stays separate from the sublayer question.
Step 4 — the link comes up and then drops. Read down_vector, not up_vector. By the time anyone looks, the lower layers have almost always recovered, and live status shows a healthy link that is not working. The captured vector is the state at the moment of failure and is usually the only evidence remaining.
Step 5 — the link reports up and users report problems. Read uptime_cycles. A link that flaps every few minutes reports up at essentially every instant it is sampled, and only a restarting uptime reveals it. Then read c_lock_lost over an interval: lock losses without loss of signal point at marginal margin, and Chapter 3.3's method takes over from there.
The method stated once: the vector is ordered so that the lowest deasserted bit names an owner. One register read replaces a layer-by-layer investigation, and the two configuration mismatches — which no measurement finds — are exactly the two the vector isolates.
18. Common Misconceptions
"The PHY is one monolithic block below the MAC."
The wrong model: everything below the media-independent interface is an opaque component that either works or does not.
What it costs: you cannot localise a failure, so "the link is down" has no owner and every investigation starts from scratch. You plan one kind of verification for the whole thing and discover late that the PMD needs silicon and lab time you never scheduled. And you cannot explain why one vendor's PCS works with another's PMD, because your model has no place for that to be possible.
The corrected model: three sublayers with boundaries placed where the unit of information changes — octet, block, bit-on-a-lane, symbol-on-a-medium. Each boundary is a place where a new unit exists on one side and not the other, and that placement is what makes the parts composable.
"The PCS is 64B/66B."
The wrong model: one family's coding is what a PCS is.
What it costs: you integrate the next PHY family against assumptions that do not hold — expecting alignment markers on a single-lane family, expecting scrambling where there is none, expecting a block size that is not the one in use. The failures appear at a boundary you believed was settled, which is the worst place for them.
The corrected model: the PCS owns whatever is true of the bit stream regardless of the medium, and which specific functions that includes is family-dependent. 1000BASE-X uses 8B/10B on one lane with no alignment markers. 10GBASE-R uses 64b/66b with scrambling. A multi-lane PCS adds markers and deskew. Claims about the PCS must be conditional on the family or they are wrong somewhere.
"The split means you can always mix any PCS with any PMD."
The wrong model: the reuse argument is universal.
What it costs: you plan a product line assuming any coding composes with any medium, and discover that BASE-T does not work that way. You plan two independent verification workstreams for a family whose sublayers are entangled, and the schedule fails.
The corrected model: the split yields independent reuse where the physical channel does not dictate the coding. It does for 1000BASE-X and 10GBASE-R — both 10GBASE-SR and 10GBASE-LR share the Clause 49 PCS and differ only in PMD. It does not for 1000BASE-T, whose trellis coding and PAM-5 mapping were chosen for that channel's impairments — which is why 802.3 specifies the whole PHY in Clause 40. The clause structure tells you which case you are in.
"Idle means nothing is being transmitted."
The wrong model: between frames the PHY is quiet.
What it costs: you design a PCS that stops emitting when the MAC has nothing to send. It works perfectly under load and drops the link during quiet periods, because the far end's clock recovery starves. The symptom — a link that fails when traffic stops — is counter-intuitive enough to survive several rounds of investigation.
The corrected model: idle is a thing that is transmitted. The medium always carries something, because the receiver recovers its clock from transitions in the data. That is why Section 9's blk_valid is asserted continuously and why the code needs a representation for "no data".
"Once the receiver is locked, it stays locked unless something breaks."
The wrong model: lock is a state you reach and hold.
What it costs: this is Section 15's rejected property. Asserting it fires immediately on real hardware, and "fixing" the design removes the invalid-block branch from S_LOCKED — producing a receiver that holds lock through arbitrary degradation, decodes garbage indefinitely, and reports itself healthy the entire time.
The corrected model: a physical channel delivers a finite error rate, so invalid blocks are expected on a perfectly healthy link. Lock is maintained against a rate, which is what the leaky counter measures and what LOSS_THRESH declares. Never is the wrong word; rarely is the right one.
19. Interview Reasoning
"Why is the PHY split into three sublayers?"
The weak answer recites the three names. The answer that ends the topic gives a criterion: boundaries sit where the unit of information changes — octet, block, bit-on-a-lane, symbol — and then gives the payoff, which is combinatorial. N codings and M media need N times M monolithic PHYs and N plus M composable ones. Citing 10GBASE-SR and 10GBASE-LR sharing the Clause 49 PCS and differing only in Clause 52 PMD makes it concrete rather than theoretical.
"Where does block synchronisation belong, and why?"
Apply the criterion out loud. A block is a unit that exists only below the PCS boundary — nothing above the PCS knows blocks exist — so recovering a block boundary is necessarily PCS work. The strong follow-up is what makes it possible at all: the code has invalid patterns, so a wrong offset produces detectable garbage. Without an invalid-pattern space there is no evidence and no search.
"How would you plan verification for a PHY?"
The answer that signals real experience partitions by evidence, not by block. The PCS is fully digital and provable in simulation. The PMD is characterised by measurement, cannot start before silicon, and is therefore on the critical path in a way the PCS is not. The PMA straddles, and deciding which of its properties belong to RTL and which to measurement is the decision worth making early. Adding that the clause structure predicts whether these are two independent workstreams or one coupled one is the part that separates a good answer from a complete one.
20. Understanding Check
Boundaries sit where the unit of information changes. Four units, three boundaries:
- Above the PHY the unit is an octet.
- The PCS creates the block — a unit with its own boundary, its own control encoding, and its own synchronisation requirement, which nothing above it knows exists.
- The PMA turns blocks into bits on lanes.
- The PMD turns bits into symbols on a medium — and one symbol may carry one bit or several.
The criterion is testable. Ask of any proposed boundary: what unit exists on one side and not the other? "The same unit, handed along" means the boundary carries no information and is arbitrary. A new unit means the boundary is real, and every function that creates or consumes that unit belongs on the same side of it.
What it predicts, immediately:
- Block synchronisation recovers a block boundary, blocks exist only below the PCS boundary, so it is PCS work.
- Serialisation converts a parallel unit into a per-lane bit stream — that crossing is the PMA by definition.
- Equalisation operates on the waveform before any symbol decision exists, below every unit boundary, so it is PMD-side. Which is exactly why Chapter 3.3 had almost no RTL for it.
The follow-up to be ready for: why does the placement matter beyond tidiness? Because it is what makes the parts compose — the next question.
21. What's Next
The claim this chapter defended: the PHY's sublayer boundaries sit exactly where the unit of information changes — octet, block, bit-on-a-lane, symbol-on-a-medium — and that placement is not a convention but a criterion you can apply to settle where a function belongs.
The placement pays off combinatorially: N codings and M media need N plus M composable pieces rather than N times M monolithic ones, which is why 10GBASE-SR and 10GBASE-LR share a PCS and differ only in PMD. And it pays off in reverse where the channel dictates the coding — 1000BASE-T's single clause is the standard telling you that its sublayers do not separate that way.
The boundaries are also where the evidence changes, and that is the part a project plan lives or dies on: the PCS is provable in simulation, the PMD is characterised by measurement, and the PMA is where the two arguments meet.
Module 3 now has its frame, and the remaining chapters fill it in.
Chapter 3.5 — Block Coding takes the codes this chapter deliberately deferred: 4B/5B, 8B/10B and 64B/66B, why DC balance and transition density are requirements rather than preferences, where the invalid-pattern space that makes Section 11's alignment search possible comes from, and why coding overhead fell so sharply across generations.
Then Chapter 3.6 separates line modulation from block coding and asks what PAM4 trades away for its extra bit — against the vertical margin Chapter 3.3 measured. Chapter 3.7 explains why high-rate links need forward error correction and what latency it costs. Chapter 3.8 puts the whole bring-up sequence together.
The full path is on the Ethernet curriculum index.
Continue learning
Related tutorials
- Related topic
The PHY Layer
Coding, serialisation, clock recovery and line drive all exist because a real channel attenuates, disperses and carries no clock. The PCS, PMA and PMD split follows the same logic — each owns one consequence of physics, and each changes on its own schedule.
- Related topic
Ethernet System Architecture
Client, MAC, reconciliation sublayer, PCS, PMA, PMD, medium — six blocks whose port lists are the real content. Each contract has two halves: what a layer delivers, and what it is forbidden to know about its neighbours, which is why one MAC outlived every physical layer.
- Related topic
Copper Ethernet
From 1000BASE-T onward every twisted pair carries both directions at once, so each receiver hears its own transmitter louder than the far end. Cancelling a known local signal is why BASE-T PHYs are adaptive signal-processing engines, and why pair count and signalling changed at every generation.
- Related topic
Fibre Ethernet
Separate strands per direction delete the echo problem that shapes copper PHYs, and introduce two others: a conversion boundary inside a pluggable module you do not own, and a link that can break in one direction while the far end still reports perfect health.
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.
