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.
| Quantity | Value | From |
|---|---|---|
| aggregate rate | 1000 Mb/s | the target |
| pairs | 4 | all of them, not two |
| per-pair rate | 250 Mb/s | 1000 ÷ 4 |
| symbol rate | 125 Mbaud | held from 100BASE-TX |
| symbol period | 8 ns | 1 ÷ 125 MHz |
| bits per symbol per pair | 2 | 250 ÷ 125 |
| bits per symbol period, all pairs | 8 | 2 × 4 — one octet |
| fundamental | 62.5 MHz | 125 Mbaud ÷ 2 |
| Cat 5 headroom | 1.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
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
// 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
endmoduleClassification: 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 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
// 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
endmoduleClassification: 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
// 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
endmoduleClassification: 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
// 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
endmoduleClassification: 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
// 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
endmoduleClassification: 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/s — GMII.
| MII | GMII | |
|---|---|---|
| data width, each direction | 4 bits | 8 bits |
| clock | 25 MHz (100), 2.5 MHz (10) | 125 MHz |
| rate | 100 / 10 Mb/s | 1000 Mb/s |
| transmit clock source | the 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
// 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
// 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
// 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
- Neither end forced, one multiport — the multiport end takes
ROLE_MASTER,resolved_by_seedlow. - Neither end forced, both single-port, different seeds — higher seed takes master,
resolved_by_seedhigh. - Equal seeds —
ROLE_UNRESOLVED,c_seed_tiesincrements, and the attempt is retried rather than arbitrarily decided. - One end forced master, other unforced — the forced end wins regardless of multiport status. Administrative intent outranks a heuristic.
- Both ends forced master —
ROLE_CONFLICT,conflicthigh, and the sequencer fails atS_ROLEimmediately rather than after a timeout. - Both ends forced slave — also
ROLE_CONFLICT. A link with no clock reference is as unusable as one with two. - 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
- Clean channel, fast convergence — residual falls monotonically, dwell is served,
convergedafter exactlyDWELL_CYCLESunder threshold. - Residual dips below threshold for one sample then rises —
convergedstays low. The lucky-sample case the dwell exists for. - Residual reaches threshold with
DWELL_CYCLES − 1served, then rises above — the dwell restarts from zero, not from where it was. - Monotonically rising residual —
divergedafter exactlyRISE_TRENDconsecutive rises,ever_failedset. - Three rises then a fall then three rises —
divergedstays low. The trend counter resets on any fall, which is what keeps a noisy converging loop from being aborted. - Residual plateaus just above threshold for the whole budget —
budget_expired, withbest_residualrecorded just above threshold: the marginal-cable signature. - Residual never moves from its starting value —
budget_expiredwithbest_residualat its initial value: the pair-is-not-connected signature. Same output as scenario 13, different diagnosis, and onlybest_residualseparates them. freezeasserted mid-adaptation — no outcome changes, no counters move, and adaptation resumes on release.startre-asserted while converged — everything reinitialises, includingbest_residual;ever_failedsurvives.
Skew
- All four markers in the same cycle — zero skew, all delays zero,
skew_out_of_rangelow. - Skew of exactly
SKEW_LIMIT_SYMS— measured, in range, compensated. The boundary that must pass. - Skew of
SKEW_LIMIT_SYMS + 1—skew_out_of_rangehigh and the sequencer fails atS_SKEW. The boundary that must fail, and must not be clamped. - One pair's marker never arrives — the measurement does not complete, the stage times out,
failed_stage = S_SKEW. - Markers arrive in a different pair order on a second training — the compensation follows; nothing assumes pair A is first.
- Data traffic after a completed measurement —
pair_delay_symsnever changes. P13 under load.
Sequencing and per-pair health
- Three cancellers converged, one still adapting —
data_modestays low. The reduction is over all four. - Three converged, one
diverged— immediate failure atS_CANCEL, without waiting for the timeout. - Link drops during
S_CANCEL— return toS_IDLE, and the next attempt restarts atS_ROLE, not atS_CANCEL. - Link drops during
S_DATA— same, andc_attemptsincrements on the next bring-up. - One pair's margin at half the others' —
single_pair_outlierhigh,worst_pairnaming it, while the frame layer sees errors it cannot attribute. - All four pairs' margins equally poor —
single_pair_outlierlow. Same frame-layer symptom, and the flag is what distinguishes a bad connector from a bad cable run. clearasserted after a degradation — window counters clear,ever_degradedsurvives.
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.
| Observation | Likely cause | The distinguishing check |
|---|---|---|
| link never comes up, no errors at all | role conflict — both ends forced the same way | failed_stage = S_ROLE; check both ends' forced settings |
training reaches S_SKEW and stops | cable out of delay-skew specification | worst_skew_syms against SKEW_LIMIT_SYMS; a real cable measurement |
training reaches S_CANCEL and stops, best_residual just above threshold | marginal cable — length, age, or temperature | try a shorter run; the number is close, not absent |
training stops at S_CANCEL, best_residual near its initial value | a pair is not connected, or a transmitter is dead | same failure output as the row above; only best_residual separates them |
| link comes up, then errors under load | a canceller diverging as the channel drifts | diverged per pair, and worst_pair |
| one pair's margin far below the others | connector, punch-down, or a damaged pair | single_pair_outlier high |
| all four margins equally poor | cable length, category, or environment | single_pair_outlier low — the same errors, a different work order |
| link renegotiates its role on every retrain | seeds colliding, or a role that is not sticky | resolved_by_seed high with a rising c_seed_ties |
| link comes up at 100 Mbps on a gigabit-capable pair | gigabit training failed and autonegotiation fell back | c_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⁸ = 256 — 41% 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
Related tutorials
- Related topic
Copper Ethernet
From 1000BASE-T onward every twisted pair carries both directions at once, so each receiver hears its own transmitter louder than the far end. Cancelling a known local signal is why BASE-T PHYs are adaptive signal-processing engines, and why pair count and signalling changed at every generation.
- Related topic
The Reconciliation Sublayer and the xMII Contract
The xMII generations are a record of what each had to give up — width, pins, timing margin, even parallelism — to keep carrying the same vocabulary as rates rose. That one vocabulary survived six unrelated physical forms is what media-independence actually means.
- Related topic
GMII — Eight Bits, and a Clock That Changed Direction
The MAC sources GTX_CLK because MII's round-trip timing path consumed half an 8 ns period. What that bought was speed; what it cost was an abstraction, since setup and hold at the pins are invisible to RTL.
- Related topic
The Shared-Medium Problem
Why several independent transmitters on one medium is a distributed timing problem, not a formatting problem. Propagation delay makes every station's view of the medium stale, so two locally correct decisions can still collide — and that is the constraint the Ethernet MAC was built around.
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.
