Skip to content
VLSI Mentor

Ethernet · Module 3

Block Coding — 4B/5B, 8B/10B and 64B/66B

A line code buys transition density, DC balance, an invalid-pattern space and a control-symbol space. Overhead fell from 25 percent to 3 because two of those purchases moved from guarantees to probabilities — a trade that only became safe once forward error correction existed downstream.

Five chapters have deferred to this one.

Chapter 2.6 introduced line coding as the mechanism that gives a receiver transitions to recover a clock from, and used an illustrative table rather than a real code. Chapter 3.1 showed the physical reason DC balance is mandatory — a transformer cannot pass a constant. Chapter 3.3 established what the channel does to a symbol. And Chapter 3.4 built a block aligner that searches candidate offsets for one that parses, then said plainly that the search only works because a real code has patterns that cannot legally occur — and deferred the explanation here.

So the obvious question is why a code is needed at all. Data is already bits. Sending eight bits as ten looks like paying 25% of a link's capacity for nothing.

The answer is that the eight bits are not enough, and the shortfall is not one thing. A raw data stream cannot guarantee a receiver will see an edge. It cannot keep its average level near zero. It offers no way to say "this is not data". And — the one people miss — it contains no pattern that is impossible, so a receiver has no evidence for where anything begins.

What does a line code actually buy, and why did the price fall from 25 percent to 3?

1. Scope — What This Chapter Owns

This chapter owns: the four properties a line code provides and why each is a requirement rather than a preference; the complete 4B/5B code with its real table; 8B/10B's sub-block structure and running-disparity machinery; the comma symbol and how alignment actually works; 64b/66b's sync header, block-type field and scrambler; error multiplication in a self-synchronising descrambler; and the overhead arithmetic across generations with the reason for each change.

This chapter does not own: how many voltage levels carry a coded bit — Chapter 3.4 separated coding from modulation and Chapter 3.6 owns the symbol. Nor does it own forward error correction, which Chapter 3.7 owns, though Section 14 explains why 64b/66b's trade made it necessary. Alignment markers and lane deskew across a multi-lane link belong to Chapter 3.8; this chapter covers block synchronisation on one lane.

The debt it repays: Chapter 3.4 §11's block_aligner takes a block_valid input and states that its whole search depends on a code having invalid patterns. Section 10 builds the block that produces that signal, from a real code's actual structure.

2. The Four Purchases

A block coder takes data and produces coded symbols. That coding simultaneously provides four properties: guaranteed transitions for clock recovery, bounded running disparity for an AC-coupled channel, invalid patterns that make block synchronisation possible, and a control-symbol space distinct from data.Dataoctets from the layer aboveBlock coderthe one mechanismTransitionsclock recovery cannot starveDC balancean AC-coupled channel passesitInvalid patternsthe only evidence foralignmentControl spacesaying what is not data12
Figure 1 — one mechanism, four independent requirements, each from a different layer.
PropertyThe requirement it satisfiesWhich chapter established itWhat fails without it
transition densitytiming recovery needs edges in the data2.6the receiver's clock drifts and the link dies on a long run of one level
DC balancethe channel is AC-coupled and cannot pass a constant3.1 §5the signal does not arrive at all
invalid patternsa receiver must discover block boundaries3.4 §11no evidence exists; alignment is impossible
control spacethe medium always carries something, so "no data" must be sayable3.4 §9idle is indistinguishable from a data value that looks like idle

Read the fourth column. Each failure is total rather than gradual, and each belongs to a different part of the system. That is why these are four independent purchases and not one property with four names — and it is why a code that provides three of them is not three-quarters adequate.

The third row is the one that surprises people. Transition density and DC balance are widely known. That a code must contain patterns it cannot produce, purely so a receiver can rule out wrong alignments, is rarely stated — and it is the property that determines whether Chapter 3.4's aligner can work at all. Section 9 makes it precise.

3. 4B/5B — Buying Transitions, Exactly

The oldest of the three, used by FDDI and then by 100BASE-TX. It maps every 4-bit nibble onto a 5-bit code group.

The design rule is a single sentence, and everything follows from it. Of the 32 possible 5-bit patterns, discard every one that would allow three or more consecutive zeros to appear — either inside a code or across a boundary between two of them. Sixteen of the survivors carry data; the rest become control codes or stay unused.

The complete table. This is the real code, not an illustration:

NibbleCodeNibbleCode
011110810010
101001910011
210100A10110
310101B10111
401010C11010
501011D11011
601110E11100
701111F11101

And the control codes:

SymbolCodeMeaning
I11111idle
J11000start, first half
K10001start, second half
T01101end
R00111reset
S11001set
Q00000quiet — loss of signal
H00100halt
L00110start, third symbol

4. RTL 1 — The 4B/5B Codec

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. The real 4B/5B code.
//
// Design rule behind every entry: no code group permits three or more
// consecutive zeros, counting across the boundary between two adjacent
// groups. That single constraint is what guarantees the receiver's timing
// recovery always has an edge to work with.
//
// The 16 data codes below are the actual 4B/5B mappings. So are the control
// codes -- and note that NONE of the control codes is reachable by encoding
// a nibble. That separation is the control-symbol space of Section 2.
package fourb5b_pkg;
  // Control code groups. Deliberately outside the data mapping, which is
  // exactly what makes them unambiguous on the wire.
  localparam logic [4:0] C_I = 5'b11111;  // idle
  localparam logic [4:0] C_J = 5'b11000;  // start, first half
  localparam logic [4:0] C_K = 5'b10001;  // start, second half
  localparam logic [4:0] C_T = 5'b01101;  // end
  localparam logic [4:0] C_R = 5'b00111;  // reset
  localparam logic [4:0] C_S = 5'b11001;  // set
  localparam logic [4:0] C_Q = 5'b00000;  // quiet -- loss of signal
  localparam logic [4:0] C_H = 5'b00100;  // halt
  localparam logic [4:0] C_L = 5'b00110;  // start, third symbol
 
  typedef enum logic [1:0] {
    SYM_DATA    = 2'd0,
    SYM_CONTROL = 2'd1,
    SYM_INVALID = 2'd2   // parses as neither -- the alignment evidence
  } sym_class_e;
endpackage
 
module fourb5b_encoder
  import fourb5b_pkg::*;
(
  input  logic       clk,
  input  logic       rst_n,
 
  input  logic       in_valid,
  input  logic [3:0] in_nibble,
  input  logic       in_is_control,
  input  logic [3:0] in_control_sel,  // which control code, when in_is_control
 
  output logic       out_valid,
  output logic [4:0] out_code
);
 
  function automatic logic [4:0] encode_data(input logic [3:0] n);
    case (n)
      4'h0: encode_data = 5'b11110;  4'h1: encode_data = 5'b01001;
      4'h2: encode_data = 5'b10100;  4'h3: encode_data = 5'b10101;
      4'h4: encode_data = 5'b01010;  4'h5: encode_data = 5'b01011;
      4'h6: encode_data = 5'b01110;  4'h7: encode_data = 5'b01111;
      4'h8: encode_data = 5'b10010;  4'h9: encode_data = 5'b10011;
      4'hA: encode_data = 5'b10110;  4'hB: encode_data = 5'b10111;
      4'hC: encode_data = 5'b11010;  4'hD: encode_data = 5'b11011;
      4'hE: encode_data = 5'b11100;  4'hF: encode_data = 5'b11101;
    endcase
  endfunction
 
  function automatic logic [4:0] encode_control(input logic [3:0] s);
    case (s)
      4'd0:    encode_control = C_I;  4'd1:    encode_control = C_J;
      4'd2:    encode_control = C_K;  4'd3:    encode_control = C_T;
      4'd4:    encode_control = C_R;  4'd5:    encode_control = C_S;
      4'd6:    encode_control = C_Q;  4'd7:    encode_control = C_H;
      default: encode_control = C_L;
    endcase
  endfunction
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      out_valid <= 1'b0;
      out_code  <= C_I;      // idle at reset: the medium must carry something
    end else begin
      out_valid <= in_valid;
      if (in_valid) begin
        out_code <= in_is_control ? encode_control(in_control_sel)
                                  : encode_data(in_nibble);
      end else begin
        out_code <= C_I;     // gaps are idle, not silence
      end
    end
  end
 
endmodule
 
 
// SYNTHESIZABLE. The decoder, and the part that matters more than decoding.
//
// A decoder that only produces nibbles has thrown away the most valuable
// thing the code provides. THREE outcomes exist, not two: data, control, and
// a group that is neither -- and the third is the evidence Section 10's
// alignment search runs on.
module fourb5b_decoder
  import fourb5b_pkg::*;
(
  input  logic       clk,
  input  logic       rst_n,
 
  input  logic       in_valid,
  input  logic [4:0] in_code,
 
  output logic       out_valid,
  output sym_class_e out_class,
  output logic [3:0] out_nibble,     // meaningful only when out_class is DATA
  output logic [3:0] out_control,    // meaningful only when CONTROL
 
  // Saturating count of code groups that decode to neither. On an aligned,
  // healthy link this should be near zero; a rising count means either the
  // alignment is wrong or the channel is delivering errors, and Section 17
  // separates those two.
  output logic [23:0] c_invalid
);
 
  sym_class_e class_c;
  logic [3:0] nibble_c, control_c;
 
  always_comb begin
    class_c   = SYM_INVALID;
    nibble_c  = 4'h0;
    control_c = 4'h0;
 
    unique case (in_code)
      5'b11110: begin class_c = SYM_DATA; nibble_c = 4'h0; end
      5'b01001: begin class_c = SYM_DATA; nibble_c = 4'h1; end
      5'b10100: begin class_c = SYM_DATA; nibble_c = 4'h2; end
      5'b10101: begin class_c = SYM_DATA; nibble_c = 4'h3; end
      5'b01010: begin class_c = SYM_DATA; nibble_c = 4'h4; end
      5'b01011: begin class_c = SYM_DATA; nibble_c = 4'h5; end
      5'b01110: begin class_c = SYM_DATA; nibble_c = 4'h6; end
      5'b01111: begin class_c = SYM_DATA; nibble_c = 4'h7; end
      5'b10010: begin class_c = SYM_DATA; nibble_c = 4'h8; end
      5'b10011: begin class_c = SYM_DATA; nibble_c = 4'h9; end
      5'b10110: begin class_c = SYM_DATA; nibble_c = 4'hA; end
      5'b10111: begin class_c = SYM_DATA; nibble_c = 4'hB; end
      5'b11010: begin class_c = SYM_DATA; nibble_c = 4'hC; end
      5'b11011: begin class_c = SYM_DATA; nibble_c = 4'hD; end
      5'b11100: begin class_c = SYM_DATA; nibble_c = 4'hE; end
      5'b11101: begin class_c = SYM_DATA; nibble_c = 4'hF; end
 
      C_I:      begin class_c = SYM_CONTROL; control_c = 4'd0; end
      C_J:      begin class_c = SYM_CONTROL; control_c = 4'd1; end
      C_K:      begin class_c = SYM_CONTROL; control_c = 4'd2; end
      C_T:      begin class_c = SYM_CONTROL; control_c = 4'd3; end
      C_R:      begin class_c = SYM_CONTROL; control_c = 4'd4; end
      C_S:      begin class_c = SYM_CONTROL; control_c = 4'd5; end
      C_Q:      begin class_c = SYM_CONTROL; control_c = 4'd6; end
      C_H:      begin class_c = SYM_CONTROL; control_c = 4'd7; end
      C_L:      begin class_c = SYM_CONTROL; control_c = 4'd8; end
 
      // Everything else. Seven of the 32 patterns land here, and their
      // existence is the entire reason alignment is possible.
      default:  class_c = SYM_INVALID;
    endcase
  end
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      out_valid   <= 1'b0;
      out_class   <= SYM_INVALID;
      out_nibble  <= '0;
      out_control <= '0;
      c_invalid   <= '0;
    end else begin
      out_valid   <= in_valid;
      out_class   <= class_c;
      out_nibble  <= nibble_c;
      out_control <= control_c;
      if (in_valid && (class_c == SYM_INVALID) && !(&c_invalid))
        c_invalid <= c_invalid + 1'b1;
    end
  end
 
endmodule

Classification: synthesizable, with the real published code tables.

What it teaches: that decoding has three outcomes rather than two. A decoder producing only nibbles has discarded the invalid-pattern signal — which is simultaneously the alignment evidence, the error detector, and the input Chapter 3.4's aligner was written against. The SYM_INVALID branch is the most valuable line in the module.

Deliberately simplified: no 5B/4B ordering across the pair of code groups that a real 100BASE-TX PCS handles, and no NRZI or MLT-3 stage — Section 5 explains why those exist but they are modulation, and Chapter 3.4 established that coding and modulation are separate concerns.

Production implication: encoding idle rather than deasserting out_valid during a gap is not a stylistic choice. Chapter 3.4 §9 showed the failure it prevents: a PCS that goes silent between frames works perfectly under load and drops the link when traffic stops, because the far end's clock recovery starves. The reset value of out_code is C_I for the same reason.

Later ownership: MLT-3 and the modulation layer are Chapter 3.6.

5. What 4B/5B Does Not Buy

Count the ones in I = 11111. Then imagine an idle link, which sends that code group continuously.

4B/5B has no DC balance property at all. Nothing in the table bounds the running difference between ones and zeros, and the idle code is the worst case: five ones, forever. On the AC-coupled channel of Chapter 3.1 §5, that signal does not arrive.

So 100BASE-TX does not transmit 4B/5B code groups directly. It passes them through NRZI, which represents a one as a transition rather than a level, and then through MLT-3, which cycles a three-level signal through its levels on each transition. The combination converts "a run of ones" into "a run of transitions", and a run of transitions has an average near the middle level.

6. 8B/10B — Buying All Four, With Guarantees

8B/10B maps 8 bits onto 10, and it is the code that buys every one of Section 2's four properties as a hard guarantee. That completeness is what the 25% overhead pays for.

The structural trick is that it is not one 8-to-10 table. It is a 5b/6b code and a 3b/4b code, applied to the low five and high three bits respectively, and concatenated. Two small tables rather than one enormous one — 256 entries would each need two variants, and the sub-block split reduces that to 32 plus 8.

Running disparity is the machinery that makes DC balance a guarantee rather than a hope. The published rule:

  • Disparity has exactly two states, conventionally written RD−1 and RD+1, and it starts at RD−1.
  • A code group with an equal number of ones and zeros leaves the state unchanged.
  • A code group with unequal counts — a disparity of plus or minus two — comes in two variants, and the encoder picks the one that flips the state.

What that buys, stated exactly as published: "the difference between the counts of ones and zeros in a string of at least 20 bits is no more than two, and there are not more than five ones or zeros in a row."

Read both halves of that sentence, because they are the first two of Section 2's four purchases, quantified. The first half is DC balance with a numeric bound. The second half is transition density with a numeric bound. Neither is asymptotic or probabilistic; both hold at every point in every stream.

Conceptual — running disparity across eight code groups

8 cycles
A conceptual timing diagram over eight code-group periods. The symbol clock marks each code group. The running disparity alternates between its two states as unbalanced code groups are chosen. The encoded output never presents more than five identical bits in a row. Markers indicate a balanced group leaving disparity unchanged, an unbalanced group flipping it, and the bounded run length.balanced group — state holdsbalanced group — stateholdsunbalanced — variant chosen to flipunbalanced — variant chosento fliprun never exceeds fiverun never exceeds fivebound holds at every pointbound holds at every pointsym_clkbalanced10010110rd_staterun_okt0t1t2t3t4t5t6t7
Figure 2 — conceptual: the guarantee is a bound that holds at every point, not on average.

This figure is conceptual and labelled so. A digital waveform cannot show analog level accumulation; what it shows correctly is the structurerd_state is a single bit with two values, it holds on balanced groups and flips on unbalanced ones, and run_ok is asserted throughout because the bound is never violated.

7. RTL 2 — Running Disparity, Exactly

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. 8B/10B running-disparity machinery, exactly.
//
// THE CODE TABLES ARE INPUTS, NOT CONTENT. The full 5b/6b and 3b/4b tables
// are large and a partial one would mislead. What this module implements
// exactly is the part that is universal across every 8B/10B implementation
// and where integration bugs actually live:
//
//   - disparity is TWO STATES, starting at RD-1
//   - a balanced group leaves the state alone
//   - an unbalanced group has two variants; pick the one that FLIPS the state
//   - the sub-block split means disparity updates TWICE per octet, and the
//     3b/4b encoder sees the state the 5b/6b encoder left behind
//
// That last point is the one that gets implemented wrong. Updating disparity
// once per octet instead of once per sub-block produces a stream that is
// balanced on average and violates the bound locally -- which passes a naive
// test and fails on hardware.
package eightb10b_pkg;
  typedef enum logic {
    RD_MINUS = 1'b0,   // the reset state, per the code definition
    RD_PLUS  = 1'b1
  } rd_e;
 
  // Disparity of a code group: -2, 0, or +2. Never anything else.
  typedef enum logic [1:0] {
    DISP_NEG  = 2'd0,   // two more zeros than ones
    DISP_ZERO = 2'd1,
    DISP_POS  = 2'd2    // two more ones than zeros
  } disp_e;
endpackage
 
module rd_tracker
  import eightb10b_pkg::*;
(
  input  logic clk,
  input  logic rst_n,
 
  // Sub-block 1: the 5b/6b stage.
  input  logic  sb1_valid,
  input  disp_e sb1_disparity,   // disparity of the variant that will be sent
 
  // Sub-block 2: the 3b/4b stage, in the SAME octet.
  input  logic  sb2_valid,
  input  disp_e sb2_disparity,
 
  // The state each stage must encode against. sb2 sees what sb1 left.
  output rd_e  rd_for_sb1,
  output rd_e  rd_for_sb2,
 
  output rd_e  rd_state,
 
  // A code group whose disparity is inconsistent with the state it was
  // encoded against. On a correct encoder this is unreachable; on a
  // decoder it is a genuine received error, and the distinction is why
  // this output exists separately from a generic error.
  output logic disparity_violation,
  output logic [23:0] c_disparity_violation,
 
  // Longest run of identical bits observed, from an external run monitor.
  // The published bound is five; anything above it means the encoder or the
  // table is wrong, not that the channel is bad.
  input  logic [3:0]  observed_run,
  output logic [3:0]  longest_run
);
 
  rd_e rd_q;
  rd_e rd_after_sb1_c;
 
  // A balanced group leaves the state alone; an unbalanced one flips it.
  // This is the entire rule, and it is why the state is one bit.
  function automatic rd_e apply(input rd_e cur, input disp_e d);
    apply = (d == DISP_ZERO) ? cur
                             : ((cur == RD_MINUS) ? RD_PLUS : RD_MINUS);
  endfunction
 
  // The variant chosen must move the state TOWARD balance. From RD-1 an
  // unbalanced group must be the positive variant; from RD+1, the negative
  // one. A group whose disparity pushes further from zero is a violation.
  function automatic logic consistent(input rd_e cur, input disp_e d);
    consistent = (d == DISP_ZERO)
              || ((cur == RD_MINUS) && (d == DISP_POS))
              || ((cur == RD_PLUS)  && (d == DISP_NEG));
  endfunction
 
  assign rd_for_sb1     = rd_q;
  assign rd_after_sb1_c = sb1_valid ? apply(rd_q, sb1_disparity) : rd_q;
  // The ordering that gets implemented wrong: the 3b/4b stage encodes
  // against the state the 5b/6b stage LEFT, not against the octet's
  // starting state.
  assign rd_for_sb2     = rd_after_sb1_c;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      rd_q                  <= RD_MINUS;   // the defined initial state
      disparity_violation   <= 1'b0;
      c_disparity_violation <= '0;
      longest_run           <= '0;
    end else begin
      if (sb2_valid)      rd_q <= apply(rd_after_sb1_c, sb2_disparity);
      else if (sb1_valid) rd_q <= rd_after_sb1_c;
 
      disparity_violation <=
           (sb1_valid && !consistent(rd_q,           sb1_disparity))
        || (sb2_valid && !consistent(rd_after_sb1_c, sb2_disparity));
 
      if (((sb1_valid && !consistent(rd_q, sb1_disparity))
        || (sb2_valid && !consistent(rd_after_sb1_c, sb2_disparity)))
          && !(&c_disparity_violation))
        c_disparity_violation <= c_disparity_violation + 1'b1;
 
      if (observed_run > longest_run) longest_run <= observed_run;
    end
  end
 
  assign rd_state = rd_q;
 
endmodule

Classification: synthesizable, implementing the published disparity rules exactly, with the code tables as inputs.

What it teaches: that disparity updates once per sub-block, not once per octet, and that the 3b/4b stage encodes against the state the 5b/6b stage left behind. This is the bug that ships. An implementation updating once per octet produces a stream that is balanced when averaged over many octets and violates the local bound — so it passes an average-based test and fails on hardware against a receiver that depends on the bound holding at every point.

Deliberately simplified: the 5b/6b and 3b/4b tables. They are large, and a partial table is worse than an absent one because it looks complete. The disparity of each chosen variant is an input here, which is exactly the interface a real table lookup presents.

Production implication: disparity_violation means opposite things on the two sides and must be counted separately. On a transmitter it is unreachable on correct hardware and therefore a design bug. On a receiver it is a genuine channel error — a flipped bit changed a code group's disparity — and it is a sensitive error detector, because many single-bit errors change disparity even when they happen to produce another legal code group. Folding both into one counter destroys that distinction.

Later ownership: the full tables belong in a vendor or standard reference, not in a tutorial that would have to abbreviate them.

8. The Comma, and Why Alignment Works

Section 2 claimed that invalid patterns are the only evidence a receiver has for where a block begins. 8B/10B makes that concrete in a way worth studying, because it does something stronger than merely having invalid patterns.

Of the twelve control symbols the code defines, one is special: K28.5, the comma. Its bit pattern is 11000001xx or 00111110xx depending on disparity, and what matters is the run of five identical bits in the middle followed by a transition.

Why that specific shape is the whole trick: the sequence 0011111 — or its complement — cannot occur at any other alignment in a valid 8B/10B stream. Not inside a data code group, not across the boundary between two of them, not anywhere. So a receiver scanning an unaligned bit stream for that pattern has found not merely a legal symbol but a positional fingerprint: the pattern's location fixes the code-group boundary unambiguously.

9. RTL 3 — Block Synchronisation on the Invalid-Pattern Space

This is the module Chapter 3.4 §11 was written against. Its aligner takes a block_valid input and states that the search depends on the code having patterns that cannot legally occur. Here is what produces that signal.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Candidate-offset evaluation for block synchronisation.
//
// This module produces the `block_valid` signal that Chapter 3.4's
// block_aligner consumes. The pair is the complete mechanism:
//
//   this module  -- "at offset N, does the stream parse as legal code?"
//   3.4 §11      -- "gather that evidence, decide, commit, keep checking"
//
// Why the search works at all: 4B/5B defines 25 legal code groups out of 32.
// A WRONG offset slices across group boundaries and produces the other
// seven with high probability -- so wrong offsets fail fast. That asymmetry
// is exactly the one Chapter 3.4 §11 relied on, and here is its cause.
module code_group_validator
  import fourb5b_pkg::*;
#(
  parameter int unsigned GROUP_W = 5,
  parameter int unsigned OFFSETS = GROUP_W,        // 5 candidate phases
  parameter int unsigned OFF_W   = $clog2(OFFSETS)
) (
  input  logic clk,
  input  logic rst_n,
 
  input  logic             bit_valid,
  input  logic             bit_in,
 
  input  logic [OFF_W-1:0] offset,     // from the aligner: which phase to test
 
  output logic             group_seen,   // a full candidate group is ready
  output logic             group_legal,  // it decodes to data or control
  output logic [GROUP_W-1:0] group_bits,
 
  // Split evidence. A group that decodes as CONTROL is stronger evidence
  // than one that decodes as DATA -- control codes are rarer, so seeing one
  // at a candidate offset is less likely by chance. Exposed separately so a
  // smarter aligner can weight them.
  output logic             group_is_control
);
 
  logic [GROUP_W-1:0] shift_q;
  logic [OFF_W:0]     phase_q;
 
  // Legality is exactly "decodes to something", which is a lookup against
  // the same table Section 4's decoder uses. Sharing the definition is the
  // point: if the table and the validator can disagree, the aligner locks
  // onto offsets the decoder will then reject.
  function automatic sym_class_e classify(input logic [GROUP_W-1:0] g);
    case (g)
      5'b11110, 5'b01001, 5'b10100, 5'b10101,
      5'b01010, 5'b01011, 5'b01110, 5'b01111,
      5'b10010, 5'b10011, 5'b10110, 5'b10111,
      5'b11010, 5'b11011, 5'b11100, 5'b11101: classify = SYM_DATA;
 
      C_I, C_J, C_K, C_T, C_R, C_S, C_Q, C_H, C_L: classify = SYM_CONTROL;
 
      default: classify = SYM_INVALID;
    endcase
  endfunction
 
  sym_class_e class_c;
  assign class_c = classify(shift_q);
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      shift_q          <= '0;
      phase_q          <= '0;
      group_seen       <= 1'b0;
      group_legal      <= 1'b0;
      group_is_control <= 1'b0;
      group_bits       <= '0;
    end else if (bit_valid) begin
      shift_q <= {shift_q[GROUP_W-2:0], bit_in};
 
      // A candidate group completes every GROUP_W bits, phased by `offset`.
      if (phase_q == (OFF_W+1)'(GROUP_W - 1)) begin
        phase_q          <= '0;
        group_seen       <= 1'b1;
        group_bits       <= {shift_q[GROUP_W-2:0], bit_in};
        group_legal      <= (classify({shift_q[GROUP_W-2:0], bit_in}) != SYM_INVALID);
        group_is_control <= (classify({shift_q[GROUP_W-2:0], bit_in}) == SYM_CONTROL);
      end else begin
        phase_q    <= phase_q + 1'b1;
        group_seen <= 1'b0;
      end
    end else begin
      group_seen <= 1'b0;
    end
  end
 
endmodule

Classification: synthesizable.

What it teaches: where alignment evidence physically comes from. Seven of the 32 five-bit patterns are illegal, so a wrong offset — which slices across group boundaries and produces essentially arbitrary patterns — hits one of those seven with substantial probability on each group. That is exactly the "negative evidence is cheap" asymmetry Chapter 3.4 §11 built its search around, and this is its cause rather than a restatement of it.

Deliberately simplified: one lane, no descrambler in the path, and the offset is an input from the aligner rather than swept here. The separation is deliberate — this module answers "is this offset producing legal code?" and Chapter 3.4 owns "which offset should we be trying, and have we seen enough?"

Production implication: the validator and the decoder must share one definition of legality. If they can disagree — two hand-maintained tables, or a validator written from the spec and a decoder written from a vendor header — the aligner will lock onto an offset the decoder then rejects, and the link will report block lock while delivering nothing. Generate both from one source.

Later ownership: multi-lane alignment, where each lane runs this independently and the results must then be deskewed, is Chapter 3.8.

10. 64b/66b — Trading Guarantees for Probability

Now the code that changed the economics. 64b/66b prefixes a two-bit sync header to 64 payload bits.

Sync headerMeaning
01the 64 payload bits are data
10the payload holds an 8-bit type field plus 56 bits of control information or data
00invalid
11invalid

Two of the four possible headers are illegal, and that is not incidental — it is Section 2's invalid-pattern space, bought in two bits. A receiver testing a candidate offset checks the header: 00 or 11 means the offset is wrong. Section 2's third purchase, at a cost of two bits per sixty-six.

The same two bits buy the fourth purchase too. The 10 header says "this block is control", and the type field that follows distinguishes start-of-frame from idle from error. Control space, from the same two bits.

And they buy a transition. Because exactly one of the two legal headers is 01 and the other is 10, every legal header contains a transition — so there is a guaranteed edge every 66 bits regardless of payload.

11. RTL 4 — The 64b/66b Framer

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. 64b/66b block framing.
//
// The two-bit header buys THREE of Section 2's four purchases at once:
//   - invalid patterns  (00 and 11 are illegal -> alignment evidence)
//   - control space     (10 selects the type field)
//   - a guaranteed transition every 66 bits (both legal headers have one)
//
// The fourth -- adequate transition density inside the payload -- is NOT
// bought here. It is bought statistically by the scrambler of Section 12,
// and that substitution is the whole reason the overhead fell.
package sixtyfour66_pkg;
  localparam logic [1:0] SYNC_DATA    = 2'b01;
  localparam logic [1:0] SYNC_CONTROL = 2'b10;
 
  // ABBREVIATED type set. The real encoding covers frame end at each of
  // eight octet positions plus ordered sets; this is a reduced set chosen
  // to make the STRUCTURE clear without pretending to be the full table.
  typedef enum logic [7:0] {
    BT_IDLE  = 8'h1E,
    BT_START = 8'h78,
    BT_END   = 8'h87,
    BT_ERROR = 8'h1E ^ 8'hFF
  } block_type_e;
endpackage
 
module sixtyfour66_framer
  import sixtyfour66_pkg::*;
(
  input  logic clk,
  input  logic rst_n,
 
  input  logic        in_valid,
  input  logic [63:0] in_payload,
  input  logic        in_is_control,
  input  block_type_e in_type,
  output logic        in_ready,
 
  input  logic        out_ready,
  output logic        out_valid,
  output logic [1:0]  out_sync,
  output logic [63:0] out_payload
);
 
  assign in_ready = out_ready;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      out_valid   <= 1'b0;
      out_sync    <= SYNC_CONTROL;
      out_payload <= {BT_IDLE, 56'd0};
    end else if (out_ready) begin
      // Always emitting, as Chapter 3.4 §9 requires: a PCS that goes silent
      // between frames starves the far end's clock recovery.
      out_valid <= 1'b1;
      if (in_valid) begin
        out_sync    <= in_is_control ? SYNC_CONTROL : SYNC_DATA;
        // A control block spends its first octet on the type field; a data
        // block spends all 64 bits on payload. That asymmetry is why the
        // overhead figure quoted for this code is about the HEADER only.
        out_payload <= in_is_control ? {in_type, in_payload[55:0]}
                                     : in_payload;
      end else begin
        out_sync    <= SYNC_CONTROL;
        out_payload <= {BT_IDLE, 56'd0};
      end
    end
  end
 
endmodule
 
 
// SYNTHESIZABLE. Receive-side header check -- the alignment evidence.
//
// This is the 64b/66b counterpart of Section 9's code_group_validator, and
// it feeds the same Chapter 3.4 §11 aligner. Note how much WEAKER the
// evidence is: two illegal headers out of four means a wrong offset is
// rejected with probability about one half per block, against 4B/5B's
// seven-in-thirty-two per code group. Weaker evidence per block is exactly
// why 64b/66b needs a larger LOCK_THRESH and takes longer to acquire.
module sixtyfour66_sync_check
  import sixtyfour66_pkg::*;
(
  input  logic clk,
  input  logic rst_n,
 
  input  logic       hdr_valid,
  input  logic [1:0] hdr,
 
  output logic       block_seen,
  output logic       block_legal,
  output logic       block_is_control,
 
  output logic [23:0] c_invalid_header
);
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      block_seen       <= 1'b0;
      block_legal      <= 1'b0;
      block_is_control <= 1'b0;
      c_invalid_header <= '0;
    end else begin
      block_seen       <= hdr_valid;
      block_legal      <= hdr_valid && (hdr != 2'b00) && (hdr != 2'b11);
      block_is_control <= hdr_valid && (hdr == SYNC_CONTROL);
 
      if (hdr_valid && ((hdr == 2'b00) || (hdr == 2'b11))
          && !(&c_invalid_header))
        c_invalid_header <= c_invalid_header + 1'b1;
    end
  end
 
endmodule

Classification: synthesizable, with the header semantics exact and the type encoding abbreviated and labelled.

What it teaches: that the same two bits buy three of Section 2's four purchases, and that this is why the overhead collapsed. It also quantifies something Chapter 3.4 could only assert: the alignment evidence here is much weaker than 4B/5B's. Two illegal headers out of four rejects a wrong offset with probability about one half per block; seven illegal groups out of thirty-two rejects one with much higher probability per group. Weaker per-block evidence is precisely why a 64b/66b aligner needs a larger threshold and takes longer to lock — a fact Chapter 3.4 §11 parameterised without explaining.

Deliberately simplified: the block type table. The real encoding enumerates frame termination at each octet position and several ordered-set forms, and a partial table would look complete.

Production implication: c_invalid_header is the highest-value counter in a 64b/66b PCS. Rising while unlocked means the aligner is still searching, which is normal. Rising while locked means received errors, and its rate is the pre-correction error rate that Chapter 3.7 shows is the early-warning signal. Same counter, two entirely different meanings, separated only by lock state — so log both together or the number is uninterpretable.

Later ownership: the full type-field encoding belongs to the clause; the FEC that consumes this error rate is Chapter 3.7.

12. RTL 5 — The Self-Synchronising Scrambler, and What It Costs

Section 10 established that 64b/66b buys transition density and DC balance statistically, from a scrambler. This is that scrambler, and the important part of this section is not how it works but what it costs.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Self-synchronising scrambler / descrambler, 58-bit state.
//
// Self-synchronising means the DESCRAMBLER needs no seed, no handshake, and
// no synchronisation phase -- it feeds received bits into its own shift
// register and converges on its own within 58 bits. That is why this
// structure was chosen over an additive scrambler.
//
// THE PRICE, and it is the point of this module:
//
// Because each output bit is the XOR of the input bit with two DELAYED
// bits, every received error re-enters the register and reappears at each
// tap position. ONE channel error becomes THREE descrambled errors.
//
// That is not a defect to be fixed. It is inherent to self-synchronisation,
// it is the reason a scrambled code's raw error rate is multiplied before
// anything downstream sees it, and it is a direct argument for the forward
// error correction of Chapter 3.7.
module selfsync_scrambler #(
  parameter int unsigned WIDTH = 64,
  parameter int unsigned STATE_W = 58,
  // Tap positions of the polynomial. Passed as parameters rather than
  // hard-coded so the same structure serves the several polynomials
  // different standards use.
  parameter int unsigned TAP_A = 39,
  parameter int unsigned TAP_B = 58,
  parameter bit          DESCRAMBLE = 1'b0
) (
  input  logic clk,
  input  logic rst_n,
 
  input  logic               in_valid,
  input  logic [WIDTH-1:0]   in_data,
 
  output logic               out_valid,
  output logic [WIDTH-1:0]   out_data
);
 
  logic [STATE_W:1] state_q;
 
  logic [STATE_W:1]   state_c;
  logic [WIDTH-1:0]   out_c;
 
  always_comb begin
    state_c = state_q;
    out_c   = '0;
 
    // Bit-serial over the word. A production implementation unrolls this
    // into a parallel XOR network -- the recurrence is linear, so the
    // unrolled form is exact rather than approximate. The serial form is
    // written here because it makes the FEEDBACK PATH visible, and the
    // feedback path is what causes error multiplication.
    for (int unsigned i = 0; i < WIDTH; i++) begin
      automatic logic fb  = state_c[TAP_A] ^ state_c[TAP_B];
      automatic logic bit_out = in_data[i] ^ fb;
 
      out_c[i] = bit_out;
 
      // THE ONE LINE THAT DECIDES EVERYTHING.
      //
      // Scrambling shifts in what we SENT. Descrambling shifts in what we
      // RECEIVED -- which means a corrupted received bit enters the state
      // register and comes back out at each tap. That is the mechanism of
      // error multiplication, and it is visible right here.
      state_c = {state_c[STATE_W-1:1], (DESCRAMBLE ? in_data[i] : bit_out)};
    end
  end
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      state_q   <= '1;      // any non-zero seed; it converges regardless
      out_valid <= 1'b0;
      out_data  <= '0;
    end else begin
      out_valid <= in_valid;
      if (in_valid) begin
        state_q  <= state_c;
        out_data <= out_c;
      end
    end
  end
 
endmodule
 
 
// SYNTHESIZABLE INSTRUMENTATION. Measures the multiplication factor.
//
// Not decorative. The ratio between errors entering the descrambler and
// errors leaving it is the number that sizes the FEC of Chapter 3.7, and a
// design that cannot measure it is sizing FEC from theory alone.
module error_multiplication_monitor #(
  parameter int unsigned CNT_W = 24
) (
  input  logic clk,
  input  logic rst_n,
  input  logic clear,
 
  input  logic errors_in,      // a bit error arriving at the descrambler
  input  logic errors_out,     // a bit error leaving it
 
  output logic [CNT_W-1:0] c_in,
  output logic [CNT_W-1:0] c_out,
 
  // Multiplication factor scaled by 256, so a factor of 3 reads as 768.
  // Integer arithmetic keeps this synthesizable; the scale is a parameterless
  // convention documented here rather than hidden.
  output logic [15:0] factor_x256
);
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n || clear) begin
      c_in  <= '0;
      c_out <= '0;
    end else begin
      if (errors_in  && !(&c_in))  c_in  <= c_in  + 1'b1;
      if (errors_out && !(&c_out)) c_out <= c_out + 1'b1;
    end
  end
 
  always_comb begin
    factor_x256 = (c_in == 0) ? 16'd0
                              : 16'((c_out * 256) / c_in);
  end
 
endmodule

Classification: synthesizable, with the self-synchronising structure exact and the tap positions parameterised.

What it teaches: the trade in one line of code. state_c shifts in the transmitted bit when scrambling and the received bit when descrambling, and that asymmetry is the entire mechanism of error multiplication. A single channel error enters the descrambler's state register and re-emerges at each tap — one error in, three errors out for a two-tap polynomial.

Deliberately simplified: written bit-serially. Production implementations unroll the recurrence into a parallel XOR network, which is exact rather than approximate because the recurrence is linear. The serial form is used here because it makes the feedback path visible, and the feedback path is the lesson.

Production implication: measure the multiplication factor rather than assuming it. Theory says three for a two-tap polynomial, and a design that assumes three and gets four — because a tap was placed wrong, or because errors arrive in bursts that interact with the taps — has undersized its FEC by a third. This is the number Chapter 3.7 consumes.

Later ownership: what to do about the multiplied errors is Chapter 3.7.

13. The Overhead Arithmetic

Now the question the chapter opened with, answered with the three codes side by side.

CodeRatioOverheadTransition guaranteeDC balanceInvalid-pattern spaceControl space
4B/5B5 bits per 425%guaranteed: no three consecutive zerosnone — needs MLT-3 beneath it7 of 32 patterns9 defined codes
8B/10B10 bits per 825%guaranteed: at most 5 in a rowguaranteed: bounded within 2 over 20 bitslarge, plus the comma12 control symbols
64b/66b66 bits per 643.125%one transition per 66 bits, plus scramblingstatistical, from the scrambler2 of 4 headerstype field behind a 10 header

Read the last three columns across the bottom row. 64b/66b's overhead is eight times lower, and it is lower because two of its four purchases moved from the guarantee column to the statistical column. The two that stayed guaranteed — invalid patterns and control space — are exactly the two that cost only the header.

The arithmetic, since the numbers are worth having exactly:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
4B/5B :  5/4   = 1.25    -> 25%      overhead
8B/10B: 10/8   = 1.25    -> 25%      overhead
64b/66b: 66/64 = 1.03125 -> 3.125%   overhead

At 10 Gb/s of payload, 64b/66b's line rate is 10 × 66/64 = 10.3125 Gb/s — the figure Chapter 3.2 quoted for 10GBASE-R without deriving it. The derivation is this table.

14. Assertions

Two categories appear below and the distinction matters more here than in most chapters. Some of these are genuine code properties — the disparity bound and the legality of a code group are defined by the code itself, not by any implementation. Others are properties of these teaching models. Each is labelled.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─── REAL CODE PROPERTY: 4B/5B legality ────────────────────────────────────
// The set of legal code groups is defined by the code, not by this design.
// Catches: a table typo, which is otherwise invisible until one specific
// nibble value appears on the wire and decodes to garbage at the far end.
property p_encoder_emits_only_legal_groups;
  @(posedge clk) disable iff (!rst_n)
  out_valid |-> (classify(out_code) != SYM_INVALID);
endproperty
 
// ─── REAL CODE PROPERTY: round trip ────────────────────────────────────────
// Catches: encoder and decoder tables that disagree -- the classic result of
// two people transcribing the same table independently.
property p_codec_round_trip;
  @(posedge clk) disable iff (!rst_n)
  (dec_valid && (dec_class == SYM_DATA)) |-> (dec_nibble == $past(enc_nibble, 2));
endproperty
 
// ─── REAL CODE PROPERTY: the disparity bound ───────────────────────────────
// Published as: the difference between counts of ones and zeros in a string
// of at least 20 bits is no more than two. This is a property of 8B/10B, not
// of any implementation of it.
property p_disparity_state_is_two_valued;
  @(posedge clk) disable iff (!rst_n)
  (rd_state == RD_MINUS) || (rd_state == RD_PLUS);
endproperty
 
// ─── REAL CODE PROPERTY: run length ────────────────────────────────────────
// Published as: not more than five ones or zeros in a row. A run above five
// means the encoder or the table is wrong -- NOT that the channel is bad,
// which is the misdiagnosis this property prevents.
property p_run_length_bounded;
  @(posedge clk) disable iff (!rst_n)
  (longest_run <= 4'd5);
endproperty
 
// ─── Ordering, implementation model: disparity updates per sub-block ───────
// The bug that ships. Catches: a design updating disparity once per octet,
// which is balanced on average and violates the bound locally -- passing an
// average-based test and failing against real hardware.
property p_sb2_sees_sb1_result;
  @(posedge clk) disable iff (!rst_n)
  sb1_valid |-> (rd_for_sb2 == apply(rd_for_sb1, sb1_disparity));
endproperty
 
// ─── Causation, implementation model: variant selection moves toward zero ──
// Catches: an encoder selecting the variant that pushes disparity further
// from balance, which is legal-looking code that violates the DC bound.
property p_variant_moves_toward_balance;
  @(posedge clk) disable iff (!rst_n)
  (sb1_valid && (sb1_disparity != DISP_ZERO)) |-> consistent(rd_for_sb1, sb1_disparity);
endproperty
 
// ─── REAL CODE PROPERTY: 64b/66b header legality ───────────────────────────
// 00 and 11 are illegal by definition of the code. Catches: a framer that
// can emit them, which produces a stream no conformant receiver will lock to.
property p_sync_header_always_legal;
  @(posedge clk) disable iff (!rst_n)
  out_valid |-> ((out_sync == SYNC_DATA) || (out_sync == SYNC_CONTROL));
endproperty
 
// ─── Safety, implementation model: the PCS never goes silent ───────────────
// Carried forward from Chapter 3.4 §15, because the same failure exists here.
// Catches: a framer that stops emitting between frames, starving the far
// end's clock recovery -- a link that fails when traffic STOPS.
property p_framer_always_emits;
  @(posedge clk) disable iff (!rst_n)
  out_ready |=> out_valid;
endproperty
 
// ─── Conservation, implementation model: validator and decoder agree ───────
// Catches: two definitions of legality drifting apart, which makes the
// aligner lock onto offsets the decoder then rejects -- block lock reported
// while nothing is delivered.
property p_validator_matches_decoder;
  @(posedge clk) disable iff (!rst_n)
  group_seen |-> (group_legal == (classify(group_bits) != SYM_INVALID));
endproperty
 
// ─── Stability, implementation model: scrambler state converges ────────────
// ASSUMPTION: the descrambler has received at least STATE_W valid bits.
// Under that assumption its state matches the scrambler's. Stating the
// assumption is the point -- it is what "self-synchronising" means.
property p_descrambler_converges;
  @(posedge clk) disable iff (!rst_n)
  (bits_received > STATE_W) |-> (descr_state == $past(scr_state, LATENCY));
endproperty
 
// ─── Conservation, implementation model: multiplication is counted honestly ─
// Catches: an error monitor whose output count can be below its input count,
// which would report multiplication below one and is impossible for a
// self-synchronising descrambler.
property p_multiplication_at_least_unity;
  @(posedge clk) disable iff (!rst_n)
  (c_in != 0) |-> (c_out >= c_in);
endproperty
 
// ─── Safety, implementation model: idle at reset ───────────────────────────
// Catches: a reset value that is a data code, which transmits an arbitrary
// nibble before the first real word and can be mistaken for frame data.
property p_reset_emits_idle;
  @(posedge clk) disable iff (!rst_n)
  $rose(rst_n) |-> (out_code == C_I);
endproperty

15. Verification

Coding proceeds in five stages, each buying a different property. Data enters, the block code adds invalid patterns and a control space, the scrambler or code table supplies transition density, disparity control supplies DC balance, and the framed block is emitted. Each stage has a distinct verification obligation.What each stage buys, and what must be checked1Data inno properties yet2Block codeinvalid patterns, control space3Transitionsguaranteed by table, or scrambled4DC balancedisparity rule, or statistical5Framed blockall four purchases made
Figure 3 — where each purchase is made, and what has to be checked at each stage.

Scenarios

  1. Every nibble through the 4B/5B codec. All sixteen, encode then decode. Exhaustive and cheap — sixteen cases is a complete proof of the data mapping, and there is no excuse for sampling it.
  2. Every control code. All nine, and verify each decodes to SYM_CONTROL and never to SYM_DATA.
  3. All 32 five-bit patterns into the decoder. Verify exactly seven classify as SYM_INVALID, and that they are the seven the table implies. This is the alignment-evidence budget and it must be exact.
  4. Idle during a gap. Deassert in_valid and verify the encoder emits C_I, not silence and not a stale code.
  5. Reset output. Verify out_code is C_I immediately after reset — a data code here would be transmitted before the first real word.
  6. Disparity, balanced groups only. Feed a stream of disparity-zero groups and verify rd_state never changes.
  7. Disparity, alternating unbalanced groups. Verify the state flips on each and returns to its starting value after an even number.
  8. The sub-block ordering. Present a 5b/6b group with non-zero disparity and verify rd_for_sb2 reflects it. This is the bug that ships; a test that only checks the octet-boundary state passes a broken design.
  9. An inconsistent variant. Present a positive-disparity group while the state is RD_PLUS and verify disparity_violation asserts and c_disparity_violation advances.
  10. Run-length bound. Drive a long stream and verify longest_run never exceeds five. A violation means the table is wrong, not the channel.
  11. Candidate-offset validation, correct offset. Feed a properly aligned coded stream and verify group_legal is asserted on essentially every group.
  12. Candidate-offset validation, each of the four wrong offsets. Verify group_legal deasserts within a small number of groups at each. Record how many — that number is the aligner's convergence rate and it belongs in the design documentation.
  13. Validator against decoder. Cross-check both on the same stream and verify they never disagree about legality. Disagreement here produces a link that reports block lock and delivers nothing.
  14. 64b/66b headers. Drive data and control blocks and verify only 01 and 10 are ever emitted. Then inject 00 and 11 on receive and verify c_invalid_header advances and block_legal deasserts.
  15. Descrambler convergence. Start the descrambler from an arbitrary state and verify it matches within 58 valid bits and stays matched. Then verify it converges from a different arbitrary state to the same result — self-synchronisation means the initial state must not matter.
  16. Error multiplication. Inject exactly one bit error into the descrambler input and count the errors at its output. Verify the count is three for a two-tap polynomial, and that factor_x256 reads 768.
  17. Scrambler and descrambler round trip. Verify a scrambled then descrambled stream is bit-identical to the original after convergence, with no residual offset.

What the checker must own

  • An independent code table, transcribed from the specification rather than from the design. A scoreboard sharing the design's table verifies that the design agrees with itself.
  • An exhaustive decoder check over all 32 patterns. Thirty-two cases is not a sampling problem, and the seven-invalid count is the alignment-evidence budget the aligner's thresholds were derived from.
  • A run-length histogram, not a run-length bound, for the scrambled path. Section 14's rejected property explains why: the correct check is on the shape of the distribution against pseudo-random expectation, and a heavy tail is the finding.
  • An offset-fooling pattern generator built from the table graph. This is the highest-value item in a PCS verification plan and the one most often absent.
  • Coverage crosses of code-group class against alignment state. The bin (SYM_INVALID, locked, invalid count below threshold) must be well populated — that is a healthy link with a normal error rate, and a run that never reaches it has not verified the leaky-counter behaviour Chapter 3.4 §11 depends on.

16. Debugging — Which Purchase Failed

The symptom: a link that will not achieve block lock, or one that locks and delivers corrupted data.

Section 2's four purchases give the partition, because each failure has a different signature.

Step 1 — read the invalid-group or invalid-header count, and read the lock state with it. These two numbers together answer the first question, and neither answers it alone:

Invalid countLock stateWhat it meansNext measurement
highunlockedthe aligner is searching. Normalwait; if it never locks, go to step 2
highlockedreceived errors at a real ratethe channel — Chapter 3.3's margin method
near zerounlockedthe stream parses but lock is not declaredthe aligner's thresholds, not the code
near zerolockedcoding is healthythe problem is above the PCS

Row three is the counter-intuitive one and it is common. If almost every candidate group is legal and the aligner still will not commit, the code is fine and the threshold is wrong — usually a LOCK_THRESH set for one code and reused for another. Section 11 showed why that fails: 64b/66b's evidence is roughly one bit per block against 4B/5B's much stronger per-group evidence, so a threshold tuned on one code is meaningless on the other.

Step 2 — if it never locks, sweep the offsets manually and read group_legal at each. One offset showing far more legal groups than the others is the correct one, and the aligner is failing to reach it — a search bug. All offsets showing similar rates means the stream is not this code at all: wrong PHY type, wrong rate, or a descrambler mismatch.

Step 3 — if it locks and data is corrupted, read the disparity violation count. On a receiver this is a sensitive error detector, because many single-bit errors change a group's disparity even when they produce another legal group. A high disparity-violation count with a low invalid-group count means errors that are landing inside the legal space — which points at the channel rather than at alignment.

Step 4 — on a scrambled link, compare the error counts on both sides of the descrambler. factor_x256 is the measurement. A factor near three for a two-tap polynomial is expected. A factor much higher means the descrambler's state is not tracking — a stuck bit, a tap misplacement, or a convergence that never completed. A factor near one means the descrambler is not descrambling at all, which usually means it was bypassed.

Step 5 — long runs on a scrambled link. Read the run-length histogram, not a maximum. A distribution matching pseudo-random expectation means the scrambler works; a heavy tail means it does not, and the usual causes are a stuck state bit or a tap error. A single long run proves nothing, which is exactly why Section 14 rejects the assertion form.

The method stated once: the invalid-pattern count paired with lock state separates searching from erroring; the offset sweep separates a search bug from a wrong code; disparity violations find errors inside the legal space; and the multiplication factor validates the descrambler. Four readings, and each names a different owner.

17. Common Misconceptions

"Coding overhead is wasted bandwidth."

The wrong model: 8B/10B throws away 20% of the link for nothing.

What it costs: you select a code by overhead alone and get one that cannot support alignment, or you cannot explain why anyone would accept 25% when 3% exists. You treat a code as interchangeable with any other of similar ratio.

The corrected model: the overhead buys four independent things — transition density, DC balance, an invalid-pattern space, and a control-symbol space — and each has a failure mode that is total rather than gradual. The right question is never "how much overhead" but "which of the four does this code buy, and what buys the rest?"

"64b/66b is simply a better code than 8B/10B."

The wrong model: eight times less overhead with the same properties.

What it costs: you assume the same run-length and disparity guarantees hold, and design a receiver against bounds the code does not provide. You size an aligner's thresholds from 8B/10B experience and get a lock that is far too slow or far too eager.

The corrected model: 64b/66b bought two of its four properties statistically rather than structurally. It has no run-length bound and no disparity bound — only a transition every 66 bits plus a scrambler making long runs improbable. That is a different code with a different risk profile, and it only became acceptable once FEC existed downstream.

"Scrambling is encryption, or at least obfuscation."

The wrong model: the scrambler protects or hides the data.

What it costs: you assume scrambled data is opaque, and you cannot explain why a descrambler needs no key. Worse, you do not anticipate error multiplication and undersize the FEC by the multiplication factor.

The corrected model: a scrambler is a whitener. Its purpose is to make the payload's statistics pseudo-random so long runs and DC imbalance become improbable. It is self-synchronising precisely because it carries no secret — the descrambler derives its state from the received data within 58 bits. And that self-synchronisation is exactly what causes one channel error to become three.

"The comma is just another control symbol."

The wrong model: K28.5 is one of the twelve control codes, of no special significance.

What it costs: you cannot explain why 8B/10B links acquire alignment in one symbol time while 64b/66b takes many blocks. You remove periodic comma transmission as a bandwidth optimisation and destroy fast re-acquisition.

The corrected model: the comma's bit pattern cannot occur at any other alignment in a valid stream. That makes it a positional fingerprint: one occurrence identifies the boundary, where every other mechanism must accumulate evidence by elimination. Invalid patterns make alignment possible; the comma makes it fast, and it is a separate, more expensive purchase.

"A code group that decodes is a code group that is correct."

The wrong model: if the decoder produced a nibble, the symbol arrived intact.

What it costs: you rely on invalid-group counts as your only error detector and miss every error that happens to land inside the legal space. On a 4B/5B link, sixteen of the thirty-two patterns are data, so a single flipped bit lands on another legal group a substantial fraction of the time.

The corrected model: legality and correctness are different claims. Disparity violation catches much of what legality misses, because a flipped bit usually changes a group's disparity even when it produces another legal group — which is why Section 7 exposes it as a separate output, and why Section 16's step 3 reads it separately.

18. Interview Reasoning

"Why does a link need a line code at all?"

The weak answer says "for clock recovery" and stops. The answer that ends the topic gives all four purchases, notes that each failure is total rather than gradual, and names which layer requires each — timing recovery, the AC-coupled channel, the alignment search, and the need to say "not data" on a medium that always carries something. Naming the invalid-pattern space unprompted is the part that signals real familiarity, because it is the one almost nobody lists.

"Why did coding overhead fall from 25 percent to 3?"

Not "because engineers got better at coding". Because two of the four purchases moved from guaranteed to statistical: 64b/66b keeps the invalid-pattern space and control space as a hard guarantee in two bits, and buys transition density and DC balance from a scrambler as probabilities. The strong follow-up is what made that safe, and the answer is forward error correction downstream — which is why the trade happened when it did rather than earlier.

"What is error multiplication, and why does it matter?"

A self-synchronising descrambler feeds received bits into its state register, so one channel error re-emerges at each tap — one in, three out for a two-tap polynomial. It matters because it sets the error rate everything downstream must handle: FEC sized from the raw channel error rate rather than the multiplied one is undersized by the factor. Adding that the factor should be measured rather than assumed is what distinguishes someone who has debugged this from someone who has read about it.

19. Understanding Check

Four things, and each has a failure mode that is total rather than gradual.

  1. Transition density — a bound on how long the signal can go without an edge, so the receiver's timing recovery never starves. Without it the clock drifts and the link dies on a long run of one level. Chapter 2.6's requirement.
  2. DC balance — a bound on the running difference between ones and zeros. Without it the signal does not arrive at all, because the channel is AC-coupled and a transformer cannot pass a constant. Chapter 3.1's requirement.
  3. An invalid-pattern space — bit patterns the code can never produce. Without it a receiver has no evidence at all for where a block begins, and alignment is impossible. Chapter 3.4's requirement.
  4. A control-symbol space — values that cannot be confused with data, so "idle" and "frame starts here" are sayable on a medium that always carries something.

They are independent because each is required by a different part of the system — timing recovery, the physical channel, the alignment search, and the framing layer. A code providing three of them is not three-quarters adequate; it fails completely in one dimension.

The follow-up to be ready for: must one code buy all four? No. 100BASE-TX is the proof: 4B/5B buys transitions, invalid patterns and control space, and has no DC balance at all. MLT-3 beneath it buys that. What is not negotiable is that something buys each.

20. What's Next

The claim this chapter defended: a line code buys four independent things — transition density, DC balance, an invalid-pattern space, and a control-symbol space — and the overhead is the price of all four together.

That framing explains what a ratio cannot. 4B/5B buys three and leaves DC balance to MLT-3. 8B/10B buys all four as hard guarantees, with numeric bounds that hold at every point, and 25% is what guarantees cost. 64b/66b keeps two as guarantees in a two-bit header and buys the other two statistically from a scrambler — eight times cheaper, and structurally a different kind of code.

Three debts are now paid. Chapter 3.4 §11's aligner has its block_valid source and, more usefully, the reason its evidence asymmetry exists. Chapter 2.6's placeholder table has been replaced by real ones. And Chapter 3.1 §5's DC constraint has been traced from the transformer to the code table.

One debt was deliberately created. Trading guarantees for probability was only safe because something downstream catches the residue — and error multiplication in the descrambler makes that residue three times larger than the channel delivered.

Chapter 3.6 — Line Modulation comes first, because the levels a coded bit is carried on are a separate question from the code, and PAM4 makes that separation unavoidable. It asks what a second bit per symbol costs against the vertical margin Chapter 3.3 measured — and the answer is what makes Chapter 3.7's forward error correction mandatory rather than optional.

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.