Skip to content
VLSI Mentor

Ethernet · Module 9

1 Gigabit — Four Pairs, Echo Cancellation and GMII

Four pairs, both directions at once, PAM-5 at the same 125 Mbaud — so every receiver hears its own echo louder than its partner and must subtract it. Correctness now depends on an adaptation the RTL does not control.

Chapter 9.2 got ten times the data through about three times the spectrum by choosing a better code and a better modulation, and then changed the topology underneath it.

Gigabit could not repeat that. The cable was already characterised to 100 MHz and Fast Ethernet was using 31.25 of it; another tenfold rate on the same terms would have needed spectrum that Category 5 does not have and cannot be given.

So 1000BASE-T did something different in kind: it stopped trying to keep the channel clean.

It uses all four pairs, in both directions simultaneously, which means every receiver is listening to a faint far-end signal while its own transmitter is driving the same wires — and hearing its own echo several times louder than the signal it wants. Three neighbouring pairs are coupling into it at the same time.

None of that interference is avoided. All of it is subtracted.

1. Scope — What This Chapter Owns

This chapter owns simultaneous bidirectional transmission and what it forces.

It covers the rate decomposition that makes four pairs necessary, the master/slave timing relationship that makes cancellation possible at all, the adaptive loops that remove echo and crosstalk, the pair skew that four parallel pairs introduce, and the training sequence that has to finish before any data crosses.

It also owns GMII — the MAC/PHY interface the gigabit rate produced, which reversed the direction of a clock.

It does not re-derive what other chapters own. Chapter 3.5 owns block coding as a mechanism; Chapter 3.6 owns multi-level signalling; Chapter 9.2 owns autonegotiation, whose next-page mechanism carries this chapter's master/slave preference. Chapter 9.4 owns the generation that abandoned copper's four-pair approach entirely.

The claim this chapter defends: cancellation is not a refinement of a clean channel — it is a different contract, in which the design's correctness depends on an adaptation succeeding, and the only honest response is to make non-convergence observable rather than to assume it away.

2. Why Four Pairs, and Why Both Directions

Start from the constraint and the arithmetic follows.

Category 5 is characterised to 100 MHz. Fast Ethernet's MLT-3 put its fundamental at 31.25 MHz, leaving real headroom — but not a factor of ten of it. The rate has to come from somewhere other than bandwidth.

Step 1 — spread it across four pairs.

1000 Mb/s ÷ 4 pairs = 250 Mb/s per pair

Step 2 — hold the symbol rate where Fast Ethernet already proved copper works.

125 Mbaud per pair, a symbol period of 1 ÷ 125 MHz = 8 ns

Step 3 — and the bits per symbol fall out.

250 Mb/s ÷ 125 Mbaud = 2 bits per symbol per pair

Step 4 — four pairs, two bits each, is one octet per symbol period. Which is the name of the code: 8B1Q4 — eight bits mapped to one quartet of quinary symbols, one symbol placed on each pair.

QuantityValueFrom
aggregate rate1000 Mb/sthe target
pairs4all of them, not two
per-pair rate250 Mb/s1000 ÷ 4
symbol rate125 Mbaudheld from 100BASE-TX
symbol period8 ns1 ÷ 125 MHz
bits per symbol per pair2250 ÷ 125
bits per symbol period, all pairs82 × 4 — one octet
fundamental62.5 MHz125 Mbaud ÷ 2
Cat 5 headroom1.6×100 ÷ 62.5

And PAM-5 carries more than two bits, deliberately.

log₂(5) = 2.322 bits per symbol

So each symbol has 0.322 bits of redundancy, and across four pairs the constellation has 5⁴ = 625 points where the data needs only 2⁸ = 256. 41% of the points are used; the rest are the coding gain — a four-dimensional trellis code that buys back the noise margin that five levels in the same voltage range gave away.

3. What a Gigabit Receiver Actually Hears

A gigabit receiver's input is a sum of four contributions. The wanted signal is the far end's transmission, attenuated by a hundred metres of cable. The echo is this device's own transmitter coupling back through the hybrid, which is far stronger because it has travelled no distance. Near end crosstalk is the three neighbouring local transmitters coupling into this pair, also strong and also local. Far end crosstalk is the partner's other three transmitters, attenuated like the wanted signal. The receiver subtracts the echo and the near end crosstalk using replicas built from the locally known transmit data, and equalises what remains.Wanted signalfar end, −20 dBEchoown TX, local, loudNEXT3 local TXsWhat arrivesthe sum of all fourSubtract replicasbuilt from known TX dataFEXTfar end's other pairs12
Figure 1 — the wanted signal is the faintest thing arriving at the receiver, and everything louder than it was generated locally.

Four contributions arrive on every pair, and they are not the same kind of problem.

The wanted signal is the far end's transmission after 100 m of Category 5. At 62.5 MHz that is roughly 20 dB of attenuation — an amplitude ratio of

10^(−20/20) = 0.1

a tenth of what was sent.

The echo is this device's own transmitter, coupling into its own receiver through the hybrid that shares the pair. It has travelled no distance at all, so even a hybrid with 10 dB of isolation leaves it

10^((20 − 10)/20) = 3.2× louder than the signal the receiver is trying to read.

NEXT — near-end crosstalk — is the three local transmitters coupling into this pair inside the connector and the first metres of cable. Also local, also strong.

FEXT — far-end crosstalk — is the partner's other three transmitters, which have crossed the same cable and are attenuated the same way. The mildest of the four.

And the two loud ones have a property the quiet ones do not: the device already knows exactly what it transmitted.

Which is the whole method. Echo and NEXT can be reconstructed from locally available data — the transmit symbols are right there — passed through a model of the coupling path, and subtracted. What remains is the wanted signal plus FEXT plus noise, and that is what the equaliser and the trellis decoder work on.

4. RTL 1 — Deciding Which End Owns the Clock

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE.
//
// Master/slave resolution for 1000BASE-T.
//
// WHY A LINK NEEDS ONE CLOCK:
//   Both ends transmit on all four pairs at once. Each receiver
//   subtracts a replica of its own transmission from what it hears. The
//   replica is aligned to the LOCAL transmit clock; the samples are
//   taken on the RECEIVE clock. If those two clocks differ, the
//   alignment walks and the replica stops cancelling.
//
//   The standard removes the possibility rather than tracking it:
//     MASTER -- transmits on its own reference clock,
//               125.00 MHz +/- 0.01%
//     SLAVE  -- recovers the clock from the master's signal and
//               transmits on the RECOVERED clock (loop timing)
//   One clock exists on the link. There is nothing to drift against.
//
// The preference is exchanged in autonegotiation's NEXT PAGES
// (Chapter 9.2), and it must resolve to exactly one master.
package gig_pkg;
 
  localparam int unsigned SYMBOL_RATE_MBAUD = 125;
  localparam int unsigned SYMBOL_PERIOD_NS  = 8;
  localparam int unsigned PAIRS             = 4;
  localparam int unsigned SKEW_LIMIT_NS     = 50;   // clause 40
  // 50 ns / 8 ns -> 6.25, so 7 symbol periods of range is the minimum
  // that covers the standard's worst case.
  localparam int unsigned SKEW_LIMIT_SYMS   = 7;
 
  typedef enum logic [1:0] {
    ROLE_UNRESOLVED,
    ROLE_MASTER,
    ROLE_SLAVE,
    ROLE_CONFLICT
  } role_e;
 
endpackage
 
module master_slave_timing_arbiter
  import gig_pkg::*;
#(
  parameter int unsigned CNT_W = 16
) (
  input  logic clk,
  input  logic rst_n,
 
  input  logic       page_valid,          // a next page arrived
  // This end's advertised preference.
  input  logic       local_prefers_master,
  input  logic       local_is_multiport,  // a switch: prefers master
  input  logic       local_manual_force,
 
  // The partner's, from the received next page.
  input  logic       partner_prefers_master,
  input  logic       partner_is_multiport,
  input  logic       partner_manual_force,
 
  // The tie break of last resort: a random seed each end generates.
  // Advertised, compared, higher wins.
  input  logic [10:0] local_seed,
  input  logic [10:0] partner_seed,
 
  output role_e      role,
  output logic       role_valid,
  // BOTH ends forced the same way. Not resolvable, and the link must
  // not come up -- a link with two masters has two clocks.
  output logic       conflict,
  // The resolution came from the seed, which means it is not stable
  // across restarts. Reported because a link that changes role on every
  // retrain is a link whose seeds collide.
  output logic       resolved_by_seed,
 
  output logic [CNT_W-1:0] c_resolutions,
  output logic [CNT_W-1:0] c_seed_ties
);
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      role <= ROLE_UNRESOLVED; role_valid <= 1'b0; conflict <= 1'b0;
      resolved_by_seed <= 1'b0; c_resolutions <= '0; c_seed_ties <= '0;
    end else begin
      role_valid <= 1'b0;
 
      if (page_valid) begin
        role_valid       <= 1'b1;
        resolved_by_seed <= 1'b0;
        conflict         <= 1'b0;
        if (!(&c_resolutions)) c_resolutions <= c_resolutions + 1'b1;
 
        // PRIORITY ORDER. Each rung is checked only when the one above
        // it does not separate the two ends.
        if (local_manual_force && partner_manual_force &&
            (local_prefers_master == partner_prefers_master)) begin
          // Both administratively forced the same way. There is no
          // resolution, and coming up anyway would put two clocks --
          // or none -- on a link that permits one.
          role     <= ROLE_CONFLICT;
          conflict <= 1'b1;
        end else if (local_manual_force != partner_manual_force) begin
          // A forced end wins over an unforced one.
          role <= local_manual_force
                    ? (local_prefers_master ? ROLE_MASTER : ROLE_SLAVE)
                    : (partner_prefers_master ? ROLE_SLAVE : ROLE_MASTER);
        end else if (local_is_multiport != partner_is_multiport) begin
          // A multiport device (a switch) is master over a single-port
          // one, because its clock is the one many links already share.
          role <= local_is_multiport ? ROLE_MASTER : ROLE_SLAVE;
        end else if (local_seed != partner_seed) begin
          // THE TIE BREAK. Both ends generate a seed, advertise it, and
          // the higher one takes master.
          role             <= (local_seed > partner_seed) ? ROLE_MASTER
                                                          : ROLE_SLAVE;
          resolved_by_seed <= 1'b1;
        end else begin
          // Equal seeds: no resolution this attempt. Retry with a new
          // seed rather than pick, because picking without a rule means
          // both ends pick the same.
          role <= ROLE_UNRESOLVED;
          if (!(&c_seed_ties)) c_seed_ties <= c_seed_ties + 1'b1;
        end
      end
    end
  end
 
endmodule

Classification: synthesizable.

What it teaches: that ROLE_CONFLICT must be a reachable outcome, and a design that cannot produce it will bring up a broken link. Two ends both forced to master is a configuration a human can create in thirty seconds. The correct response is to refuse the link, because a link with two independent clocks has no cancellation, and one with two slaves has no reference at all. A resolution function with no failure output resolves everything, including the cases that have no resolution.

Deliberately simplified: the seed is an input here. In a real PHY it is generated locally per attempt, and the retry-on-tie behaviour is what makes the collision case terminate.

Production implication: resolved_by_seed matters because a seed-resolved role is not stable across retrains. A link that renegotiates can swap master and slave, which restarts every adaptive loop in this chapter from scratch — so a port that reports seed resolution on every link-up is a port whose recovery time is longer than its neighbours' for a reason nothing else in the telemetry explains.

5. One Clock, Two Ends

The master end transmits using its own local reference oscillator at one hundred and twenty five megahertz. The slave end recovers a clock from the master's received signal and uses that recovered clock to drive its own transmitter, which is called loop timing. Because the slave's transmit clock and receive clock are the same clock, its echo replica stays aligned with the samples it subtracts the replica from. At the master end the same is true by construction since its transmit and receive references are also one clock. The link therefore contains a single time base and there is no frequency offset for the cancellers to track.Master reference125.00 MHz ±0.01%Master TXown clockSlave RXrecovers the clockSlave TXthe RECOVERED clockLoop timingTX clock = RX clockOne time basenothing to drift12
Figure 2 — loop timing: the slave's transmitter runs on the clock it recovered from the master, so the link has exactly one time base.

The master transmits on a local reference — 125.00 MHz with a tolerance of ±0.01%. The slave recovers a clock from what the master sends and transmits on that recovered clock, which is called loop timing.

The consequence is what matters. At the slave, the transmit clock and the receive clock are the same clock. Its echo replica is generated on the same time base as the samples it is subtracted from, so the alignment established at training stays established. At the master, the same holds by construction.

Compare it with the alternative that was rejected. Two free-running 125 MHz oscillators at ±0.01% can differ by up to 200 ppm, which is

200 × 10⁻⁶ × 125 × 10⁶ = 25 000 symbols of slip per second — an echo replica walking out of alignment 25 000 times a second, in a loop whose entire job is subtracting a signal from the right sample.

This is also why Chapter 4.4's problem does not appear on this link in the usual form. There is no frequency offset between the ends to absorb, because there is no second frequency. The elasticity moved: it is between the recovered clock and the MAC's own domain, not between the two ends of the link.

6. RTL 2 — The Adaptive Loop, and What It Reports About Itself

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// BEHAVIOURAL MODEL of the control layer around an adaptive canceller.
// The datapath is abstracted; the CONTROL is the subject.
//
// The loop's job: build a replica of the locally-known interference
// (echo from this pair's own transmitter, NEXT from the three others)
// and subtract it. It cannot be designed from a datasheet, because the
// coupling depends on THIS cable, THIS connector and THIS temperature.
// So it is MEASURED, by adapting until the residual stops falling.
//
// THREE STATES THE DESIGN MUST DISTINGUISH, and most designs carry two:
//   ADAPTING      -- residual still falling; not yet usable
//   CONVERGED     -- residual below threshold and STABLE for a dwell
//   DIVERGED      -- residual rising; the loop is making it worse
//
// The third is the one that is omitted, and its absence is why a
// mis-adapting link presents as a cable fault.
module echo_canceller_control
  import gig_pkg::*;
#(
  parameter int unsigned RESIDUAL_W    = 16,
  // Below this, the residual is small enough to decode through.
  parameter int unsigned CONV_THRESHOLD = 16'd400,
  // It must STAY there this long. A residual that dips below threshold
  // for one sample has not converged, it has been lucky.
  parameter int unsigned DWELL_CYCLES  = 16'd2048,
  // Total budget. Past this, the loop is declared failed rather than
  // left adapting forever.
  parameter int unsigned ADAPT_BUDGET  = 32'd2_000_000,
  parameter int unsigned CNT_W = 16
) (
  input  logic clk,
  input  logic rst_n,
  input  logic start,                 // begin adaptation
  input  logic freeze,                // hold coefficients
 
  // Residual error magnitude after subtraction, from the datapath.
  input  logic [RESIDUAL_W-1:0] residual,
  input  logic                  residual_valid,
 
  output logic adapting,
  output logic converged,
  // The residual is RISING. A loop that is diverging is actively making
  // the signal worse than not cancelling at all.
  output logic diverged,
  // The budget expired without convergence. Distinct from diverged:
  // this one never got worse, it just never got good enough.
  output logic budget_expired,
 
  // Sticky, surviving a retrain: this loop has failed before.
  output logic ever_failed,
 
  output logic [RESIDUAL_W-1:0] best_residual,
  output logic [CNT_W-1:0]      c_convergences,
  output logic [CNT_W-1:0]      c_failures
);
 
  logic [15:0] dwell_q;
  logic [31:0] budget_q;
  logic [RESIDUAL_W-1:0] prev_q;
  logic [3:0]  rising_q;      // consecutive rises
 
  // Four consecutive rises is a trend; one is noise. The distinction is
  // the difference between a divergence report and a false alarm on a
  // loop that is converging normally.
  localparam int unsigned RISE_TREND = 4;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      adapting <= 1'b0; converged <= 1'b0; diverged <= 1'b0;
      budget_expired <= 1'b0; ever_failed <= 1'b0;
      best_residual <= '1; dwell_q <= '0; budget_q <= '0;
      prev_q <= '1; rising_q <= '0;
      c_convergences <= '0; c_failures <= '0;
    end else if (start) begin
      adapting <= 1'b1; converged <= 1'b0; diverged <= 1'b0;
      budget_expired <= 1'b0;
      dwell_q <= '0; budget_q <= '0; best_residual <= '1;
      prev_q <= '1; rising_q <= '0;
    end else if (adapting && !freeze && residual_valid) begin
 
      budget_q <= budget_q + 1'b1;
      prev_q   <= residual;
 
      if (residual < best_residual) best_residual <= residual;
 
      // DIVERGENCE. Track a run of rises rather than a single one --
      // an adapting loop's residual is noisy and will rise sometimes.
      if (residual > prev_q) begin
        if (rising_q != RISE_TREND) rising_q <= rising_q + 1'b1;
      end else begin
        rising_q <= '0;
      end
 
      if (rising_q == RISE_TREND) begin
        adapting    <= 1'b0;
        diverged    <= 1'b1;
        ever_failed <= 1'b1;
        if (!(&c_failures)) c_failures <= c_failures + 1'b1;
 
      // CONVERGENCE requires the residual to be low AND to STAY low.
      end else if (residual <= CONV_THRESHOLD) begin
        if (dwell_q == DWELL_CYCLES) begin
          adapting  <= 1'b0;
          converged <= 1'b1;
          if (!(&c_convergences)) c_convergences <= c_convergences + 1'b1;
        end else begin
          dwell_q <= dwell_q + 1'b1;
        end
      end else begin
        // Above threshold: the dwell restarts from zero, not from where
        // it was. Partial dwell does not accumulate across excursions.
        dwell_q <= '0;
 
        if (budget_q == ADAPT_BUDGET) begin
          adapting       <= 1'b0;
          budget_expired <= 1'b1;
          ever_failed    <= 1'b1;
          if (!(&c_failures)) c_failures <= c_failures + 1'b1;
        end
      end
    end
  end
 
endmodule

Classification: behavioural model of a control layer; the datapath it wraps is not modelled.

What it teaches: that convergence has three outcomes and most designs implement one. converged is the happy path. diverged says the loop is actively making the signal worse than no cancellation at all, and budget_expired says it simply never got good enough. They demand different responses — a diverging loop should be reset and restarted; a loop that ran out of budget on a marginal cable should retrain at a lower rate or report the cable.

Deliberately simplified: the residual arrives as a magnitude and the coefficient update is not modelled. In hardware the update is a scaled correlation between the residual and the reference, and the step size is what trades convergence speed against steady-state error.

Production implication: best_residual is the number that turns a failure into a diagnosis. A loop that expired with a best residual just above threshold is on a marginal cable; one that expired near its starting value never adapted at all — a broken reference path, a dead transmitter, or a pair that is not connected. Same failure output, two entirely different faults, separated by one register.

7. Why the Dwell Exists, and Why the Budget Does

Three design decisions in Section 6 are worth stating separately, because each one closes a way of lying.

The dwell closes "converged for an instant." An adapting loop's residual is noisy, and a noisy quantity crossing a threshold will cross it early, once, by luck. A design that declares convergence on the first sample under threshold declares it before the loop is usable — and then enters data mode, where the residual promptly rises and the errors are attributed to the channel. Requiring the residual to stay below threshold for a dwell converts an instant into a state.

The trend counter closes "diverged for an instant." The symmetric error: a converging loop's residual rises sometimes. Declaring divergence on one rise aborts loops that were working. Four consecutive rises is a trend; one is noise — and the number is a tuning decision that trades detection latency against false aborts.

The budget closes "adapting forever." A loop with no time limit on a cable it cannot handle stays in adapting indefinitely, which is a state no supervisor times out and no counter records. The link never comes up, nothing reports why, and the port looks idle rather than failed. A bounded budget converts a hang into an event.

8. RTL 3 — Four Pairs Do Not Arrive Together

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE.
//
// Realigns the four pairs, which do not arrive at the same time.
//
// WHY THEY DIFFER: the four pairs in a Category 5 cable have different
// twist rates -- deliberately, because differing twists is what keeps
// them from coupling into each other. Different twist rate means
// different physical length inside the same jacket, which means
// different propagation delay.
//
// THE NUMBERS (clause 40):
//   inter-pair delay skew tolerated : 50 ns
//   symbol period at 125 Mbaud      :  8 ns
//   50 / 8                          : 6.25 symbol periods
//   so the realigner needs 7 symbols of range to cover the worst case
//
// And the skew is a property of the CABLE, so it is constant while the
// link is up. It is measured once at training and then held -- which is
// why re-measuring it during data is a bug rather than robustness.
module pair_skew_compensator
  import gig_pkg::*;
#(
  parameter int unsigned SYM_W = 3,          // one PAM-5 symbol
  parameter int unsigned DEPTH = SKEW_LIMIT_SYMS + 1,
  parameter int unsigned CNT_W = 16
) (
  input  logic clk,
  input  logic rst_n,
 
  input  logic                   train_mode,
  // Per-pair arrival of the training marker, used to measure skew.
  input  logic [PAIRS-1:0]       marker_seen,
  input  logic [PAIRS-1:0][SYM_W-1:0] sym_in,
  input  logic                   sym_valid,
 
  output logic [PAIRS-1:0][SYM_W-1:0] sym_out,
  output logic                   sym_out_valid,
 
  output logic                   skew_measured,
  output logic [PAIRS-1:0][3:0]  pair_delay_syms,
  output logic [3:0]             worst_skew_syms,
  // The measured skew needs more range than the compensator has. The
  // cable is out of specification, and the correct response is to say
  // so rather than to align three pairs and mis-align the fourth.
  output logic                   skew_out_of_range,
  output logic [CNT_W-1:0]       c_measurements
);
 
  logic [PAIRS-1:0][SYM_W-1:0] pipe_q [DEPTH];
  logic [3:0]  arrive_q [PAIRS];
  logic [3:0]  tick_q;
  logic [PAIRS-1:0] arrived_q;
  logic        measuring_q;
 
  integer i, j;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      skew_measured <= 1'b0; skew_out_of_range <= 1'b0;
      worst_skew_syms <= '0; tick_q <= '0; arrived_q <= '0;
      measuring_q <= 1'b0; c_measurements <= '0;
      for (i = 0; i < PAIRS; i = i + 1) begin
        arrive_q[i]        <= '0;
        pair_delay_syms[i] <= '0;
      end
    end else begin
 
      // MEASURE, in training only. The first pair to show its marker is
      // the reference; every other pair's delay is how many symbol
      // periods later its marker arrived.
      if (train_mode) begin
        if (!measuring_q && (|marker_seen)) begin
          measuring_q <= 1'b1;
          tick_q      <= '0;
          arrived_q   <= marker_seen;
          for (i = 0; i < PAIRS; i = i + 1)
            if (marker_seen[i]) arrive_q[i] <= '0;
        end else if (measuring_q) begin
          tick_q <= tick_q + 1'b1;
          for (i = 0; i < PAIRS; i = i + 1) begin
            if (marker_seen[i] && !arrived_q[i]) begin
              arrive_q[i]  <= tick_q + 1'b1;
              arrived_q[i] <= 1'b1;
            end
          end
 
          if (&arrived_q) begin
            measuring_q   <= 1'b0;
            skew_measured <= 1'b1;
            if (!(&c_measurements)) c_measurements <= c_measurements + 1'b1;
 
            // The pair that arrived LAST needs no delay; every earlier
            // pair is held back until it agrees.
            for (i = 0; i < PAIRS; i = i + 1)
              pair_delay_syms[i] <= tick_q - arrive_q[i];
 
            worst_skew_syms <= tick_q;
            // OUT OF RANGE. Report; do not clamp. A clamped delay
            // aligns three pairs and leaves the fourth misaligned by a
            // symbol, which decodes as a burst of errors on one pair.
            skew_out_of_range <= (tick_q > 4'(SKEW_LIMIT_SYMS));
          end
        end
      end
 
      // APPLY, in data mode. A shift pipeline per pair, tapped at the
      // measured depth. The measurement is NOT repeated here: the skew
      // is a property of the cable and does not change while the link
      // is up, so a re-measurement can only be responding to noise.
      if (sym_valid) begin
        for (j = DEPTH-1; j > 0; j = j - 1) pipe_q[j] <= pipe_q[j-1];
        pipe_q[0] <= sym_in;
      end
      sym_out_valid <= sym_valid;
    end
  end
 
  always_comb begin
    for (int p = 0; p < PAIRS; p = p + 1)
      sym_out[p] = pipe_q[pair_delay_syms[p]][p];
  end
 
endmodule

Classification: synthesizable.

What it teaches: that skew_out_of_range reports rather than clamps, and that choice is the module. A compensator asked for 9 symbols of delay when it has 7 can silently give 7 — which aligns three pairs correctly and leaves the fourth off by two symbol periods. The result is a burst of errors confined to one pair, on a link that reports training complete. Clamping converts a cable fault into a decoding mystery.

Deliberately simplified: symbol-granular delay only. A real receiver also corrects sub-symbol phase per pair, and the measurement is spread over many training frames rather than one marker.

Production implication: the measurement runs in training only, on purpose. Skew is a property of the cable — different twist rates give the four pairs different physical lengths inside the same jacket — and a property of the cable does not change while the link is up. A design that re-measures during data has built a control loop that can only respond to noise, and it will eventually respond to some.

9. RTL 4 — Nothing Crosses Until Everything Is Ready

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE.
//
// Sequences link startup, and its whole purpose is ORDERING.
//
// Gigabit bring-up has dependencies that cannot be reordered:
//
//   1. ROLE      -- master/slave must resolve first, because the slave
//                   cannot recover a clock until it knows to.
//   2. TIMING    -- the slave locks to the master's clock. Every later
//                   step assumes one time base.
//   3. SKEW      -- measured once the pairs are carrying markers.
//   4. CANCELLERS-- adapt, on all four pairs, and each must converge.
//   5. DATA      -- and only now.
//
// The sequencer's contract is that step N never begins until step N-1
// has DECLARED success, and that the whole thing is bounded.
module startup_training_sequencer
  import gig_pkg::*;
#(
  parameter int unsigned STEP_TIMEOUT = 32'd4_000_000,
  parameter int unsigned CNT_W = 16
) (
  input  logic clk,
  input  logic rst_n,
  input  logic link_signal_present,
 
  input  logic role_valid,
  input  logic role_conflict,
  input  logic timing_locked,
  input  logic skew_measured,
  input  logic skew_out_of_range,
  input  logic [PAIRS-1:0] canceller_converged,
  input  logic [PAIRS-1:0] canceller_failed,
 
  output logic       start_cancellers,
  output logic       train_mode,
  output logic       data_mode,
  output logic [2:0] stage,
  // Which stage failed, latched at the moment of failure. Held until
  // the next attempt starts, because by the time anyone reads it the
  // state machine is back at idle.
  output logic [2:0] failed_stage,
  output logic       training_failed,
  output logic [CNT_W-1:0] c_attempts,
  output logic [CNT_W-1:0] c_failures
);
 
  typedef enum logic [2:0] {
    S_IDLE, S_ROLE, S_TIMING, S_SKEW, S_CANCEL, S_DATA, S_FAILED
  } stage_e;
 
  stage_e      stage_q;
  logic [31:0] timer_q;
 
  assign stage      = stage_q;
  assign train_mode = (stage_q == S_SKEW) || (stage_q == S_CANCEL);
  assign data_mode  = (stage_q == S_DATA);
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      stage_q <= S_IDLE; timer_q <= '0; start_cancellers <= 1'b0;
      failed_stage <= 3'(S_IDLE); training_failed <= 1'b0;
      c_attempts <= '0; c_failures <= '0;
    end else begin
      start_cancellers <= 1'b0;
      training_failed  <= 1'b0;
      timer_q          <= timer_q + 1'b1;
 
      // A link that drops restarts from the beginning, always. There is
      // no partial resume: every measurement below depends on a clock
      // that is no longer known to be there.
      if (!link_signal_present && (stage_q != S_IDLE)) begin
        stage_q <= S_IDLE;
        timer_q <= '0;
      end else begin
        unique case (stage_q)
          S_IDLE: if (link_signal_present) begin
            stage_q <= S_ROLE;
            timer_q <= '0;
            if (!(&c_attempts)) c_attempts <= c_attempts + 1'b1;
          end
 
          S_ROLE: if (role_conflict) begin
            // Unresolvable. Fail immediately rather than time out --
            // waiting adds latency and cannot change the answer.
            stage_q <= S_FAILED; failed_stage <= 3'(S_ROLE);
          end else if (role_valid) begin
            stage_q <= S_TIMING; timer_q <= '0;
          end else if (timer_q == STEP_TIMEOUT) begin
            stage_q <= S_FAILED; failed_stage <= 3'(S_ROLE);
          end
 
          S_TIMING: if (timing_locked) begin
            stage_q <= S_SKEW; timer_q <= '0;
          end else if (timer_q == STEP_TIMEOUT) begin
            stage_q <= S_FAILED; failed_stage <= 3'(S_TIMING);
          end
 
          S_SKEW: if (skew_measured && skew_out_of_range) begin
            stage_q <= S_FAILED; failed_stage <= 3'(S_SKEW);
          end else if (skew_measured) begin
            stage_q          <= S_CANCEL;
            timer_q          <= '0;
            start_cancellers <= 1'b1;
          end else if (timer_q == STEP_TIMEOUT) begin
            stage_q <= S_FAILED; failed_stage <= 3'(S_SKEW);
          end
 
          S_CANCEL: if (|canceller_failed) begin
            stage_q <= S_FAILED; failed_stage <= 3'(S_CANCEL);
          end else if (&canceller_converged) begin
            // ALL FOUR. Three converged pairs and one adapting is not a
            // usable link, and admitting data now puts errors on one
            // pair that look like a cable fault on all of them.
            stage_q <= S_DATA; timer_q <= '0;
          end else if (timer_q == STEP_TIMEOUT) begin
            stage_q <= S_FAILED; failed_stage <= 3'(S_CANCEL);
          end
 
          S_DATA: ;   // steady state
 
          S_FAILED: begin
            training_failed <= 1'b1;
            if (!(&c_failures)) c_failures <= c_failures + 1'b1;
            stage_q <= S_IDLE;
            timer_q <= '0;
          end
 
          default: stage_q <= S_IDLE;
        endcase
      end
    end
  end
 
endmodule

Classification: synthesizable.

What it teaches: that &canceller_converged is a reduction over all four pairs and anything weaker is a bug. Three converged pairs and one still adapting is not 75% of a link — it is a link that will place a quarter of every octet through an uncancelled channel. And because 8B1Q4 spreads one octet across all four pairs, a single bad pair corrupts every octet, which presents as uniform, total failure rather than as one pair's problem.

Deliberately simplified: one timeout constant for every stage. Real bring-up gives each stage its own budget, because role resolution should take milliseconds and canceller adaptation may legitimately take far longer.

Production implication: failed_stage is latched at the moment of failure and held, because by the time anything reads it the state machine is back at S_IDLE. A port that reports only training_failed says a link did not come up; a port that reports failed_stage = S_ROLE says two switches were both forced to master, and failed_stage = S_SKEW says replace the cable. The same failure, three completely different work orders.

10. RTL 5 — Per-Pair Telemetry, Because the Symptom Is Not Per-Pair

An octet entering a gigabit transmitter is mapped by the eight bit to one quartet code into four quinary symbols, one placed on each of the four pairs. At the receiver all four symbols are needed to recover the octet. Therefore if a single pair is degraded, every octet that crosses the link is affected, and the frame layer reports a uniform total failure with no indication of which pair caused it. Only per pair telemetry taken below the decoder can isolate the faulty pair.One octet8 bits8B1Q4→ 4 quinary symbolsOne per pairA, B, C, DPair C degradedone of fourEvery octet wrongall four are neededPer-pair telemetrythe only thing thatisolates12
Figure 3 — 8B1Q4 spreads one octet across all four pairs, so one bad pair corrupts every octet and the symptom carries no pair information at all.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE.
//
// Per-pair health, kept because the FRAME layer's symptom cannot
// attribute a fault to a pair.
//
// 8B1Q4 places one symbol of every octet on each of the four pairs, so
// ALL FOUR are required to recover ANY octet. One degraded pair
// therefore corrupts every octet, and the frame layer sees uniform,
// total failure -- the same picture a completely dead link gives.
//
// The pair identity exists only BELOW the decoder, in the per-pair
// residual and slicer margin. If it is not captured there, it does not
// exist anywhere.
module pair_health_telemetry
  import gig_pkg::*;
#(
  parameter int unsigned MARGIN_W = 12,
  parameter int unsigned WINDOW_BITS = 20,
  parameter int unsigned CNT_W = 16,
  // A pair whose margin sits below this is degraded but decoding.
  parameter int unsigned MARGIN_FLOOR = 12'd300
) (
  input  logic clk,
  input  logic rst_n,
  input  logic clear,
 
  input  logic [PAIRS-1:0]               sample_valid,
  input  logic [PAIRS-1:0][MARGIN_W-1:0] slicer_margin,
  input  logic [PAIRS-1:0]               symbol_error,
 
  output logic [PAIRS-1:0][MARGIN_W-1:0] worst_margin,
  output logic [PAIRS-1:0][CNT_W-1:0]    c_pair_errors,
  output logic [PAIRS-1:0]               pair_degraded,
 
  // The single worst pair, and whether it stands out. A link where one
  // pair is far worse than the others is a connector or a pair fault; a
  // link where all four are equally poor is the cable or the length.
  output logic [1:0] worst_pair,
  output logic       single_pair_outlier,
  output logic       window_valid,
  // Sticky, surviving clear: this pair has been degraded before.
  output logic [PAIRS-1:0] ever_degraded
);
 
  logic [WINDOW_BITS-1:0] win_q;
  integer i;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      win_q <= '0; window_valid <= 1'b0; worst_pair <= 2'd0;
      single_pair_outlier <= 1'b0; ever_degraded <= '0;
      for (i = 0; i < PAIRS; i = i + 1) begin
        worst_margin[i]  <= '1;
        c_pair_errors[i] <= '0;
        pair_degraded[i] <= 1'b0;
      end
    end else begin
      if (clear) begin
        win_q <= '0; window_valid <= 1'b0;
        for (i = 0; i < PAIRS; i = i + 1) begin
          worst_margin[i]  <= '1;
          c_pair_errors[i] <= '0;
          pair_degraded[i] <= 1'b0;
        end
        // ever_degraded is NOT cleared. A pair that has been degraded
        // stays flagged, because the next reader is investigating an
        // intermittent fault and the history is the evidence.
      end else begin
        win_q <= win_q + 1'b1;
        if (&win_q) window_valid <= 1'b1;
 
        for (i = 0; i < PAIRS; i = i + 1) begin
          if (sample_valid[i]) begin
            if (slicer_margin[i] < worst_margin[i])
              worst_margin[i] <= slicer_margin[i];
 
            if (slicer_margin[i] < MARGIN_W'(MARGIN_FLOOR)) begin
              pair_degraded[i] <= 1'b1;
              ever_degraded[i] <= 1'b1;
            end
 
            if (symbol_error[i] && !(&c_pair_errors[i]))
              c_pair_errors[i] <= c_pair_errors[i] + 1'b1;
          end
        end
      end
    end
  end
 
  // Which pair is worst, and whether it is an OUTLIER. One pair far
  // worse than the rest is a pair or connector fault; four equally poor
  // pairs is the cable, the length, or the environment.
  always_comb begin
    logic [MARGIN_W-1:0] lo, hi;
    logic [1:0] lo_idx;
    lo = '1; hi = '0; lo_idx = 2'd0;
    for (int p = 0; p < PAIRS; p = p + 1) begin
      if (worst_margin[p] < lo) begin lo = worst_margin[p]; lo_idx = 2'(p); end
      if (worst_margin[p] > hi) hi = worst_margin[p];
    end
    worst_pair          = lo_idx;
    // A factor of two between best and worst is a difference the cable
    // alone does not produce.
    single_pair_outlier = window_valid && (hi > (lo << 1));
  end
 
endmodule

Classification: synthesizable.

What it teaches: that single_pair_outlier answers a question the error count cannot. Four pairs with equally poor margin is a cable that is too long, too old, or in a hot cabinet — a link-level fact. One pair far worse than three is a connector, a punch-down, or a damaged pair — a physical fact about one conductor. Both produce identical frame-layer symptoms, and the ratio between the best and worst margin is what separates them.

Deliberately simplified: slicer margin stands in for a real signal-quality estimate, and the outlier test is a fixed factor of two. Production PHYs compute a per-pair SNR and expose it directly.

Production implication: ever_degraded survives clear for the same reason Chapter 9.2's parallel-detection flag does — the reader is investigating an intermittent fault, and the window that contained the evidence has already been cleared by whoever looked first. A per-pair history is the difference between "this link has errors" and "pair C has failed twice this week."

11. GMII — The Interface the Rate Created

Gigabit needed a new MAC/PHY interface, and the arithmetic says why in one line.

Chapter 4.3's MII is 4 bits wide, clocked at 25 MHz for 100 Mbps and 2.5 MHz for 10:

4 bits × 25 MHz = 100 Mb/s

To reach 1000 Mb/s on 4 bits you would need 250 MHz, which is a clock nobody wanted to route across a board in 1999. So the interface got wider instead.

8 bits × 125 MHz = 1000 Mb/sGMII.

MIIGMII
data width, each direction4 bits8 bits
clock25 MHz (100), 2.5 MHz (10)125 MHz
rate100 / 10 Mb/s1000 Mb/s
transmit clock sourcethe PHY (TX_CLK)the MAC (GTX_CLK)

And the last row is the interesting one, because it is a reversal.

In MII, the PHY supplies both clocks — it drives TX_CLK to the MAC, and the MAC clocks data out with it. In GMII the MAC supplies GTX_CLK at 125 MHz and drives transmit data with it, while the PHY still supplies RX_CLK for the receive direction.

The reason is the rate. At 125 MHz, sending a clock from the PHY to the MAC and then receiving data timed to that returned clock adds a full round trip of board delay into the setup budget. Sourcing the transmit clock from the same device that sources the data removes the round trip from the timing path.

12. Properties Worth Asserting, and One Worth Refusing

Adaptive hardware changes what an assertion can honestly claim, and the split below is the point. The ordering properties are unconditional — they say the sequencer never lets a stage begin early, whatever the channel does. The honesty properties say the design declares its own state accurately. None of them asserts that the adaptation works, because that is not a property of this design.

Role resolution

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P1. Exactly one role, or an explicit conflict. There is no state in
// which the arbiter has resolved and the role is meaningless.
property p_role_is_definite;
  @(posedge clk) disable iff (!rst_n)
  role_valid |-> (role inside {ROLE_MASTER, ROLE_SLAVE,
                               ROLE_CONFLICT, ROLE_UNRESOLVED});
endproperty
a_role_is_definite: assert property (p_role_is_definite);
 
// P2. Conflict is reported exactly when it is the outcome -- so a
// supervisor can act on the flag alone.
property p_conflict_matches_role;
  @(posedge clk) disable iff (!rst_n)
  role_valid |-> (conflict == (role == ROLE_CONFLICT));
endproperty
a_conflict_matches_role: assert property (p_conflict_matches_role);
 
// P3. Both ends forced the same way NEVER produces a usable role.
// The property that keeps a two-master link from coming up.
property p_double_force_conflicts;
  @(posedge clk) disable iff (!rst_n)
  (page_valid && local_manual_force && partner_manual_force &&
   (local_prefers_master == partner_prefers_master))
    |=> (role == ROLE_CONFLICT);
endproperty
a_double_force_conflicts: assert property (p_double_force_conflicts);
 
// P4. THE HONESTY PROPERTY for this module: a seed-resolved role is
// declared as such, because it is the one that will not repeat.
property p_seed_resolution_declared;
  @(posedge clk) disable iff (!rst_n)
  (role_valid && $past(page_valid) &&
   ($past(local_manual_force) == $past(partner_manual_force)) &&
   ($past(local_is_multiport) == $past(partner_is_multiport)) &&
   ($past(local_seed) != $past(partner_seed)))
    |-> resolved_by_seed;
endproperty
a_seed_resolution_declared: assert property (p_seed_resolution_declared);

The adaptive loop's self-report

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P5. The three outcomes are mutually exclusive. A loop that is both
// converged and diverged has a control bug, not a channel problem.
property p_outcomes_exclusive;
  @(posedge clk) disable iff (!rst_n)
  $onehot0({converged, diverged, budget_expired});
endproperty
a_outcomes_exclusive: assert property (p_outcomes_exclusive);
 
// P6. Adapting and any terminal outcome are exclusive too.
property p_not_adapting_when_done;
  @(posedge clk) disable iff (!rst_n)
  (converged || diverged || budget_expired) |-> !adapting;
endproperty
a_not_adapting_when_done: assert property (p_not_adapting_when_done);
 
// P7. THE DWELL. Convergence is never declared without the residual
// having been below threshold for the full dwell -- which is what stops
// a lucky sample from being called a converged loop. A white-box
// property: dwell_q is internal, reached by bind.
property p_convergence_requires_dwell;
  @(posedge clk) disable iff (!rst_n)
  $rose(converged) |-> ($past(dwell_q) == DWELL_CYCLES) &&
                       ($past(residual) <= CONV_THRESHOLD);
endproperty
a_convergence_requires_dwell: assert property (p_convergence_requires_dwell);
 
// P8. Freeze means frozen. No outcome may change while the loop is
// held, because a frozen loop is not observing anything.
property p_freeze_holds;
  @(posedge clk) disable iff (!rst_n)
  freeze |=> $stable({converged, diverged, budget_expired});
endproperty
a_freeze_holds: assert property (p_freeze_holds);
 
// P9. Adaptation terminates. This is the property that makes "adapting"
// a bounded state rather than a place a link can disappear into.
property p_adaptation_terminates;
  @(posedge clk) disable iff (!rst_n)
  $rose(adapting) |-> ##[1:$] (converged || diverged || budget_expired);
endproperty
a_adaptation_terminates: assert property (p_adaptation_terminates);
 
// P10. Any failure is remembered. Sticky across retrains, because the
// reader of this bit is investigating something intermittent.
property p_failure_is_sticky;
  @(posedge clk) disable iff (!rst_n)
  (diverged || budget_expired) |=> ever_failed;
endproperty
a_failure_is_sticky: assert property (p_failure_is_sticky);

Skew and the training order

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P11. Skew is measured in training only. A measurement during data is
// a control loop responding to noise.
property p_skew_measured_in_training;
  @(posedge clk) disable iff (!rst_n)
  $rose(skew_measured) |-> $past(train_mode);
endproperty
a_skew_in_training: assert property (p_skew_measured_in_training);
 
// P12. Out of range is REPORTED, never clamped away. A delay request
// beyond the compensator's depth must raise the flag.
property p_skew_range_reported;
  @(posedge clk) disable iff (!rst_n)
  (skew_measured && (worst_skew_syms > SKEW_LIMIT_SYMS))
    |-> skew_out_of_range;
endproperty
a_skew_range_reported: assert property (p_skew_range_reported);
 
// P13. Delay is stable during data. The cable does not change while the
// link is up, so neither may the compensation.
property p_delays_stable_in_data;
  @(posedge clk) disable iff (!rst_n)
  data_mode |=> $stable(pair_delay_syms);
endproperty
a_delays_stable: assert property (p_delays_stable_in_data);
 
// P14. THE ORDERING PROPERTY. Data mode requires ALL FOUR cancellers
// converged -- not three, not a majority. 8B1Q4 needs every pair.
property p_data_needs_all_pairs;
  @(posedge clk) disable iff (!rst_n)
  data_mode |-> (&canceller_converged);
endproperty
a_data_needs_all_pairs: assert property (p_data_needs_all_pairs);
 
// P15. And it requires the timing lock and the skew measurement that
// every canceller's convergence was conditioned on.
property p_data_needs_prerequisites;
  @(posedge clk) disable iff (!rst_n)
  data_mode |-> (timing_locked && skew_measured && !skew_out_of_range);
endproperty
a_data_needs_prereqs: assert property (p_data_needs_prerequisites);
 
// P16. A link drop returns to idle from ANY stage. There is no partial
// resume: every measurement depended on a clock that has gone.
property p_drop_restarts;
  @(posedge clk) disable iff (!rst_n)
  (!link_signal_present && (stage != S_IDLE)) |=> (stage == S_IDLE);
endproperty
a_drop_restarts: assert property (p_drop_restarts);
 
// P17. Failure names its stage. training_failed with a stale
// failed_stage sends an engineer to the wrong subsystem.
property p_failure_names_stage;
  @(posedge clk) disable iff (!rst_n)
  training_failed |-> (failed_stage inside {3'(S_ROLE), 3'(S_TIMING),
                                            3'(S_SKEW), 3'(S_CANCEL)});
endproperty
a_failure_names_stage: assert property (p_failure_names_stage);

13. Verification Scenarios

Adaptive hardware is verified by driving the channel, not the design. The scenarios below are grouped by what the environment is doing to the loop, because that is the axis the design has no control over and therefore the axis that finds its bugs.

Role resolution

  1. Neither end forced, one multiport — the multiport end takes ROLE_MASTER, resolved_by_seed low.
  2. Neither end forced, both single-port, different seeds — higher seed takes master, resolved_by_seed high.
  3. Equal seedsROLE_UNRESOLVED, c_seed_ties increments, and the attempt is retried rather than arbitrarily decided.
  4. One end forced master, other unforced — the forced end wins regardless of multiport status. Administrative intent outranks a heuristic.
  5. Both ends forced masterROLE_CONFLICT, conflict high, and the sequencer fails at S_ROLE immediately rather than after a timeout.
  6. Both ends forced slave — also ROLE_CONFLICT. A link with no clock reference is as unusable as one with two.
  7. Forced-master versus multiport-slave-preferring — the force wins, which is the priority order's second rung being genuinely reachable.

Adaptation, driven from the channel

  1. Clean channel, fast convergence — residual falls monotonically, dwell is served, converged after exactly DWELL_CYCLES under threshold.
  2. Residual dips below threshold for one sample then risesconverged stays low. The lucky-sample case the dwell exists for.
  3. Residual reaches threshold with DWELL_CYCLES − 1 served, then rises above — the dwell restarts from zero, not from where it was.
  4. Monotonically rising residualdiverged after exactly RISE_TREND consecutive rises, ever_failed set.
  5. Three rises then a fall then three risesdiverged stays low. The trend counter resets on any fall, which is what keeps a noisy converging loop from being aborted.
  6. Residual plateaus just above threshold for the whole budgetbudget_expired, with best_residual recorded just above threshold: the marginal-cable signature.
  7. Residual never moves from its starting valuebudget_expired with best_residual at its initial value: the pair-is-not-connected signature. Same output as scenario 13, different diagnosis, and only best_residual separates them.
  8. freeze asserted mid-adaptation — no outcome changes, no counters move, and adaptation resumes on release.
  9. start re-asserted while converged — everything reinitialises, including best_residual; ever_failed survives.

Skew

  1. All four markers in the same cycle — zero skew, all delays zero, skew_out_of_range low.
  2. Skew of exactly SKEW_LIMIT_SYMS — measured, in range, compensated. The boundary that must pass.
  3. Skew of SKEW_LIMIT_SYMS + 1skew_out_of_range high and the sequencer fails at S_SKEW. The boundary that must fail, and must not be clamped.
  4. One pair's marker never arrives — the measurement does not complete, the stage times out, failed_stage = S_SKEW.
  5. Markers arrive in a different pair order on a second training — the compensation follows; nothing assumes pair A is first.
  6. Data traffic after a completed measurementpair_delay_syms never changes. P13 under load.

Sequencing and per-pair health

  1. Three cancellers converged, one still adaptingdata_mode stays low. The reduction is over all four.
  2. Three converged, one diverged — immediate failure at S_CANCEL, without waiting for the timeout.
  3. Link drops during S_CANCEL — return to S_IDLE, and the next attempt restarts at S_ROLE, not at S_CANCEL.
  4. Link drops during S_DATA — same, and c_attempts increments on the next bring-up.
  5. One pair's margin at half the others'single_pair_outlier high, worst_pair naming it, while the frame layer sees errors it cannot attribute.
  6. All four pairs' margins equally poorsingle_pair_outlier low. Same frame-layer symptom, and the flag is what distinguishes a bad connector from a bad cable run.
  7. clear asserted after a degradation — window counters clear, ever_degraded survives.

14. Debugging: Failures That All Look Like the Cable

Gigabit's diagnostic problem is that every failure below the decoder produces the same symptom above it. The table separates them by what is only visible underneath.

ObservationLikely causeThe distinguishing check
link never comes up, no errors at allrole conflict — both ends forced the same wayfailed_stage = S_ROLE; check both ends' forced settings
training reaches S_SKEW and stopscable out of delay-skew specificationworst_skew_syms against SKEW_LIMIT_SYMS; a real cable measurement
training reaches S_CANCEL and stops, best_residual just above thresholdmarginal cable — length, age, or temperaturetry a shorter run; the number is close, not absent
training stops at S_CANCEL, best_residual near its initial valuea pair is not connected, or a transmitter is deadsame failure output as the row above; only best_residual separates them
link comes up, then errors under loada canceller diverging as the channel driftsdiverged per pair, and worst_pair
one pair's margin far below the othersconnector, punch-down, or a damaged pairsingle_pair_outlier high
all four margins equally poorcable length, category, or environmentsingle_pair_outlier low — the same errors, a different work order
link renegotiates its role on every retrainseeds colliding, or a role that is not stickyresolved_by_seed high with a rising c_seed_ties
link comes up at 100 Mbps on a gigabit-capable pairgigabit training failed and autonegotiation fell backc_failures on the gigabit path; the link "works," which is why nobody looks

Three habits.

First, read failed_stage before anything else. It is one register, it is latched at the moment of failure, and it converts "the link is down" into one of four completely different investigations — two switch configurations, a cable measurement, a canceller, or a clock.

Second, read best_residual on any S_CANCEL failure. A budget expiry says the loop did not converge. best_residual says whether it was close — and close means the cable, while not-close means the pair is not there.

Third, distrust a gigabit link that came up at 100. Chapter 9.2's autonegotiation will fall back cleanly, so the link works, nothing alarms, and a third of the capacity is gone. A working link is the hardest failure to notice, and it is the one gigabit produces when its training quietly fails.

15. Common Misconceptions

"Gigabit is Fast Ethernet with a faster clock."

The wrong model: the same approach, scaled.

What it costs: you cannot explain the master/slave relationship, the training sequence, or why a gigabit link takes far longer to come up than a 100 Mbps one.

The corrected model: the symbol rate did not change at all — 125 Mbaud, exactly as 100BASE-TX. The rate came from four pairs instead of two, both directions on every pair, and two bits per symbol instead of one. And simultaneous bidirectional operation forces everything else: one clock on the link, adaptive cancellation, skew compensation, and a training sequence that must complete before any data crosses.

"Echo cancellation removes the echo."

The wrong model: a filter that eliminates the unwanted signal.

What it costs: you treat convergence as a given, and a design that cannot report failure looks complete.

The corrected model: it subtracts a replica, built from the transmit data the device already knows, passed through a model of the coupling path that was measured on this cable. What is left is a residual, not zero — and how small the residual gets depends on the cable, the noise and the step size, none of which the RTL controls. Which is why Section 12's rejected property is the one it is.

"All four pairs are equivalent, so pair-level detail does not matter."

The wrong model: four identical parallel channels; a problem on one is a quarter of a problem.

What it costs: every fault on the link gets diagnosed as "the cable," because that is all the frame layer can say.

The corrected model: 8B1Q4 makes every octet depend on all four pairs, so one degraded pair corrupts 100% of traffic and presents identically to four degraded pairs. The pair identity exists only below the decoder — and single_pair_outlier is what turns one symptom into two different work orders.

"The link is up, so training succeeded."

The wrong model: link up means the negotiated best mode was achieved.

What it costs: a third of a gigabit port's capacity, silently, for as long as nobody looks.

The corrected model: autonegotiation falls back. A gigabit link whose training fails renegotiates to 100BASE-TX, which comes up cleanly, passes traffic, and alarms nothing. The port works — at a tenth of the rate it was bought for — and the only evidence is a gigabit training failure counter that nothing is watching.

"Skew should be tracked continuously so the link stays aligned."

The wrong model: more adaptation is more robust.

What it costs: a control loop with nothing legitimate to respond to, which will eventually respond to noise and misalign a pair on a link that was working.

The corrected model: skew is a property of the cable — different twist rates give the four pairs different lengths in the same jacket — and it does not change while the link is up. It is measured once, at training, and held. The general rule: adapt to what varies, measure what does not, and a loop tracking a constant is a loop tracking noise.

16. Interview Reasoning

"How does 1000BASE-T get a gigabit over Category 5?"

The weak answer says "four pairs." The answer that ends the topic gives the decomposition and shows that the symbol rate never changed: 1000 ÷ 4 = 250 Mb/s per pair, at the same 125 Mbaud Fast Ethernet used, giving 250 ÷ 125 = 2 bits per symbol per pair — four pairs × two bits = one octet per symbol period, which is what 8B1Q4 names. PAM-5 supplies log₂(5) = 2.32 bits of capacity for two bits of data, and the 0.32 bits of redundancy funds a four-dimensional trellis code that buys back the margin five levels in the same swing gave away. The finishing point: all four pairs run in both directions at once, which is what forces everything else in the design.

"Why does 1000BASE-T need a master and a slave?"

Because both ends transmit on every pair simultaneously, so each receiver must subtract a replica of its own transmission from what it hears — and the replica is aligned to the local transmit clock while the samples come from the receive clock. If those differ, the alignment walks and the replica stops cancelling. The standard removes the possibility rather than tracking it: the master transmits on its own 125.00 MHz reference, and the slave transmits on the clock it recovered from the master — loop timing, so the link has exactly one time base. The strong answer adds the arithmetic: two free-running oscillators at ±0.01% could differ by 200 ppm, which is 25 000 symbols of slip per second in a loop whose job is subtracting from the right sample. The finishing point is the new failure mode: two ends both forced to master, and the link must refuse to come up.

"Would you assert that the echo canceller converges within its budget?"

No, and the reason is not that it is hard to observe. Convergence depends on the channel, the noise and the step size, and the design owns one of those — so the assertion's result is a statement about the testbench's channel model, not about the RTL. The stronger argument is what the property incentivises: a design graded on declaring convergence will shorten the dwell, raise the threshold and drop the divergence check, producing exactly the link that reports itself trained and then puts errors on the wire. Assert instead that the self-report is honest — outcomes exclusive, dwell genuinely served, freeze frozen, adaptation terminating, failure sticky — and make the sequencer depend on the honest report. The design's obligation is not to converge; it is to know whether it did.

"A gigabit link has FCS errors under load. Where do you look?"

Not at the cable first. The frame layer cannot attribute a fault to a pair, because 8B1Q4 puts one symbol of every octet on each pair and losing one destroys the octet as completely as losing four — so one bad pair and a bad cable run produce identical symptoms. The order is: failed_stage if training is involved, best_residual to separate a marginal cable from an absent pair, and single_pair_outlier with worst_pair to separate a connector fault from a cable-length problem. The finishing observation: check the negotiated speed, because a gigabit port that failed training falls back to 100 Mbps and works — which is the failure nobody notices.

17. Understanding Check

Four steps, and the surprise is that one quantity does not change.

Spread across four pairs: 1000 ÷ 4 = 250 Mb/s per pair. All four, not two — which means both directions share every pair.

Hold the symbol rate: 125 Mbaud, exactly what 100BASE-TX already proved copper carries. A symbol period of 1 ÷ 125 MHz = 8 ns.

Bits per symbol falls out: 250 ÷ 125 = 2 bits per symbol per pair. Across four pairs that is 8 bits per symbol period — one octet — which is what 8B1Q4 names: eight bits into one quartet of quinary symbols.

And PAM-5 carries more than two bits deliberately: log₂(5) = 2.322, so each symbol has 0.322 bits of redundancy. In four dimensions the constellation has 5⁴ = 625 points where the data needs 2⁸ = 25641% used, and the unused 59% is the four-dimensional trellis code that recovers the noise margin five levels in one voltage swing had spent.

The fundamental lands at 125 ÷ 2 = 62.5 MHz, against Category 5's 100 MHz characterisation — 1.6× headroom, where a naive tenfold scaling of Fast Ethernet would have needed 312.5 MHz on a cable that does not have it.

18. What's Next

The claim this chapter defended: cancellation is a different contract, not a refinement of a clean channel.

Every earlier generation made the medium carry the signal. 1000BASE-T made the receiver reconstruct it — four pairs, both directions at once, PAM-5 at the same 125 Mbaud Fast Ethernet used, with each receiver subtracting replicas of its own echo and its neighbours' crosstalk from what it hears. The channel got worse on purpose, because the circuitry could be made better.

And that choice propagated. One clock on the link, because a replica subtracted on a drifting clock adds an interferer instead of removing one — which is where master and slave came from, and with them a link that can fail because two ends could not agree who leads. Skew compensation, because four pairs with different twist rates are four different lengths. A training sequence, because nothing may cross until every dependency has declared success. And per-pair telemetry, because 8B1Q4 spreads every octet over all four pairs and destroys the pair identity above the decoder.

Most of all it introduced a correctness that depends on something converging — and the honest response is not to assert that it does, but to build a design that knows when it has not.

Chapter 9.4 — 10 Gigabit: 64B/66B, XGMII and the End of Shared Media takes the generation that stopped extending copper's approach and changed the terms again.

10 Gigabit replaced Chapter 3.5's 4B/5B with 64B/66B, dropping the line-code overhead from 25% to about 3% by giving up the guaranteed transition density that 4B/5B bought and paying for it with a scrambler. The MAC/PHY interface went to 32 bits, because 8 bits at 10 Gb/s would need 1.25 GHz — and XGMII carries control characters alongside data on every lane, which is a new class of thing to get wrong. And the standard did something it had never done: it removed half duplex entirely. Everything Chapter 9.1 built is not merely unreached at 10 Gigabit — it is not in the specification.

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.