Skip to content
VLSI Mentor

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

A layered stack. The MAC sits at the top handling octets. Below it the reconciliation sublayer maps MAC signals onto a media-independent interface. Below that the PCS handles code blocks, the PMA handles bits on lanes, and the PMD handles symbols on the medium. At the bottom is the medium-dependent interface and the medium itself.From octets to a physical mediumMACthe unit here is an octet — framing, addressing, the check valuethe unit here is an octet — framing, addressing, the check valueReconciliation sublayermaps MAC service signals onto the interface below itmaps MAC service signals onto the interface below itMedia-independent interfacea parallel word interface — the boundary you usually integrate againsta parallel word interface — the boundary you usually integrate againstPCSthe unit becomes a code block — a boundary that did not exist abovethe unit becomes a code block — a boundary that did not exist abovePMAthe unit becomes a bit on a lane — serialisation and timingthe unit becomes a bit on a lane — serialisation and timingPMDthe unit becomes a symbol on a medium — drivers, detectors, levelsthe unit becomes a symbol on a medium — drivers, detectors, levelsMDI and the mediumthe connector and the copper or fibre beyond itthe connector and the copper or fibre beyond it
Figure 1 — four units of information, and the boundaries between them.

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.

BoundaryUnit aboveUnit belowWhat has to happen at the crossing
MAC to reconciliationframeoctet stream plus controlframe structure becomes a signalled stream
xMII to PCSparallel octets plus controlcode blocka boundary is created that did not exist above
PCS to PMAcode blockbit on a laneblocks are flattened and distributed across lanes
PMA to PMDbit on a lanesymbol on a mediumbits 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.

Several PCS choices on the left, each representing a coding, feed a single PMA in the centre. The PMA feeds several PMD choices on the right, each representing a medium. Because every PCS reaches every PMD through the shared PMA, the number of designs is the sum of the two sets rather than their product.PCS — coding Aone line codePCS — coding Banother line codePCS — coding Ca thirdPMAbits on lanes, either wayPMD — copperone mediumPMD — short fibreanother mediumPMD — long fibrea third12
Figure 2 — one PMA in the middle turns a product into a sum.

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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
 
endmodule

Classification: 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
 
endmodule

Classification: 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
 
endmodule

Classification: 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.

A four-state alignment machine. SEARCH proposes a candidate block offset and moves to QUALIFY. QUALIFY returns to SEARCH on an invalid block, advancing to the next candidate offset, or moves to LOCKED after enough consecutive valid blocks. LOCKED moves to LOSS when invalid blocks accumulate past a threshold. LOSS returns to SEARCH to begin again.SEARCHQUALIFYLOCKEDLOSStry an offsettry an offsetinvalid blockinvalid blockenough valid blocksenough valid blockstoo many invalidtoo many invalidstart overstart over
Figure 3 — a hypothesis, evidence for it, a commitment, and a way back.

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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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;
 
endmodule

Classification: 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
 
endmodule

Classification: 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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;
 
endmodule

Classification: 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.

SublayerWhat you can prove, and howWhat you cannot
Reconciliation and xMIIRTL simulation, formal for the control encodingnothing relevant lives outside RTL here
PCSRTL simulation, formal for coding and alignment properties; exhaustive on small codesnothing — this sublayer is fully digital
PMARTL for ordering, lane assignment and elastic-buffer behaviourjitter transfer, clock-recovery loop dynamics, real serialiser timing
PMDalmost nothing in RTLeverything 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:

  1. "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.
  2. 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.
  3. 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─── 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));
endproperty

16. Verification

Scenarios

  1. 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.
  2. Idle between frames. Verify the PCS emits idle blocks continuously in the gap and that blk_valid never deasserts. This is p_pcs_always_emits, and a design that fails it works perfectly under load and drops the link when traffic stops.
  3. A frame abandoned mid-transmission. Verify xmii_error propagates, the PCS emits BT_ERROR, and c_frames_aborted rather than a general error counter advances.
  4. Back-pressure from the PMA. Stall blk_ready mid-frame and verify no word is dropped, held data does not change, and longest_stall records the peak rather than only the total.
  5. 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.
  6. 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 LANES and repeat — a parameterisation change is what breaks the contract.
  7. 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_THRESH consecutive valid ones.
  8. Alignment onto the last candidate offset. Verify the offset counter wraps correctly and the search does not stall at the end of its range.
  9. A single invalid block while locked. Verify the aligner stays locked and bad_q increments by one. This is the scenario the rejected property would have broken.
  10. Sustained invalid blocks while locked. Verify lock is released after LOSS_THRESH, lock_lost_pulse fires once, c_lock_lost advances, and the machine passes through S_LOSS rather than jumping to S_SEARCH.
  11. 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.
  12. Status vector monotonicity. Deassert signal_detect alone and verify every higher bit falls, lowest_failing reads zero, and link_up drops.
  13. Each sublayer failing individually. Four runs, one per bit. Verify lowest_failing names the right sublayer each time and that no higher bit stays set.
  14. A remote fault with all four vector bits set. Verify link_up is low while up_vector is all ones — the case that proves faults are correctly kept out of the vector.
  15. Link flapping. Drop and restore the link repeatedly and verify uptime_cycles restarts each time while link_up reads high at almost every sample point.
  16. Down-vector capture. Drop the link, let the lower layers recover, then read down_vector and verify it holds the state at the moment of failure rather than the current one.
  17. 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_valid against bad_q band. 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_failingThe lowest layer that is unsatisfiedWhere to go
0PMD — no signal at allthe medium, connectors, the far-end transmitter: Chapter 3.1 or Chapter 3.2
1PMA — signal present, no clock recoveredsignal quality, or a rate mismatch between the ends
2PCS — clock recovered, blocks do not parsea coding mismatch — the two ends are different PHY types
3PCS — blocks parse, lanes not alignedlane order, lane polarity, or excessive skew
4everything satisfiedabove 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

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.