Ethernet · Module 11
Link Discovery — FLP Bursts and Parallel Detection
A one is a pulse and a zero is nothing at all, which is why sixteen data bits need seventeen clock pulses to bracket them — and why a lost pulse negotiates a link downward with no error anywhere.
Every interface in Module 10 — from MII to XAUI — assumed a link that already existed at a known speed. Module 11 asks how two devices that have never met agree on one.
And this chapter asks the narrower question underneath it: what can either device observe before anything at all has been agreed?
The answer is almost nothing. No frames, because frames need a speed. No symbols, because symbols need a line code both ends have chosen. What is left is pulses on a pair of wires — and the entire discovery mechanism is built from them.
Chapter 9.2 §5 gave the shape: a burst of pulses at the same repetition rate as a 10BASE-T link pulse, carrying sixteen bits in the presence or absence of pulses at defined positions.
What that chapter did not confront is the consequence of encoding a zero as nothing at all.
A one is a pulse. It arrives, it is measured, it is evidence. A zero is an absence — and an absence has three causes: the partner sent a zero, the partner sent a one and it was lost, or the partner stopped transmitting. All three decode identically.
1. Scope — What This Chapter Owns
This chapter owns everything that happens before abilities are available: the FLP burst's timing, the decode windows, the four kinds of signalling a receiver may see, parallel detection, and the timers that bound the whole process.
It does not re-derive what other chapters own. Chapter 9.1 §9 owns the 10BASE-T link integrity pulse; Chapter 9.2 §5 owns the FLP burst's backward-compatibility argument and §4 its four-technology arbiter. Chapter 11.2 owns the link code word's fields, the priority table and the acknowledge mechanism — this chapter stops at the point where a 16-bit word has been decoded and hands it over.
The claim this chapter defends: when a zero is encoded as an absence, no property about the decoded value is assertable on its own — the assertable properties are about the evidence that makes the absence interpretable, which is why the burst carries one more clock pulse than it has data bits.
2. Four Things a Wire Can Be Doing
A receiver coming out of reset knows nothing about its partner and must decide, from electrical activity alone, which of four situations it is in.
| Observation | Partner | What happens next |
|---|---|---|
| FLP bursts — 33 positions, repeating every 8–16 ms | supports autonegotiation | abilities are exchanged |
| single link pulses — roughly every 16 ms, no bursts | 10BASE-T, predates negotiation | parallel detection → 10 Mb/s |
| continuous idle symbols | 100BASE-TX, negotiation disabled | parallel detection → 100 Mb/s |
| nothing at all | none, or in reset, or a broken cable | keep transmitting, keep waiting |
And the first two are the same signal at different scales, which is the design's central trick and its central hazard.
Chapter 9.1 §9's link integrity pulse repeats every 16 ± 8 ms. An FLP burst repeats in the range 8 to 16 ms — inside the same window — so a 10BASE-T receiver counting pulses sees pulses at the rate it expects and brings its link up, exactly as Chapter 9.2 §5 describes.
The hazard is the mirror image. A negotiating receiver must distinguish a burst from a single pulse, and the difference is entirely in the fine structure: 33 pulses spaced 125 µs apart, versus one pulse and then 16 ms of silence.
So classification is a timing measurement at two scales at once — microseconds within a burst, milliseconds between them — and getting either wrong classifies the partner incorrectly before anything else has begun.
3. The Burst, in Detail
Thirty-three pulse positions, and their arrangement is the chapter's argument.
| Quantity | Value | Note |
|---|---|---|
| positions per burst | 33 | |
| clock positions | 17 | one more than the data bits |
| data positions | 16 | one per link-code-word bit |
| clock-to-clock spacing | 125 µs, ±14 µs | window 111 to 139 µs |
| clock-to-data offset | 62.5 µs, ±7 µs | window 55.5 to 69.5 µs |
| burst duration | 16 × 125 = 2 ms | |
| burst-to-burst | 8 to 16 ms | inside 10BASE-T's 16 ± 8 ms |
| pulse width | ≈ 100 ns |
Now the ratio that matters. Sixteen data bits sit between seventeen clock pulses:
clock — data — clock — data — … — clock
Sixteen gaps require seventeen boundaries. Every single data position is bracketed by a clock pulse on each side.
Which is not framing overhead. It is what makes an absence interpretable.
A data position's meaning is decided by whether a pulse arrived in the window 62.5 ± 7 µs after the preceding clock pulse. If one did, the bit is a one. If none did, the bit is a zero — and that conclusion is drawn from having observed nothing.
The bracketing clock pulses are what license it. If the clock pulse before the data position arrived and the clock pulse after it also arrived, then during the interval between them the medium was carrying pulses and the receiver was detecting them. A missing data pulse in that interval is therefore a transmitted zero and not a lost one.
| Evidence | Conclusion |
|---|---|
| both bracketing clock pulses present, no data pulse | a transmitted zero — the receiver was demonstrably listening |
| both bracketing clock pulses present, data pulse present | a transmitted one |
| a bracketing clock pulse missing | the bit is unknown — and so is everything about this burst |
The third row is the one designs omit, and Section 15's rejected property is what happens when they do.
4. RTL 1 — Transmitting a Burst
// SYNTHESIZABLE.
//
// Transmits a fast link pulse burst.
//
// THE STRUCTURE:
// 33 positions = 17 clock + 16 data
// clock-to-clock : 125 us (+/- 14 us tolerated by the receiver)
// clock-to-data : 62.5 us (+/- 7 us)
// burst duration : 16 x 125 us = 2 ms
// burst-to-burst : 8 to 16 ms, inside 10BASE-T's 16 +/- 8 ms window
// pulse width : ~100 ns
//
// A ONE IS A PULSE AND A ZERO IS NOTHING. So this module emits between
// 17 and 33 pulses depending on the link code word, and a word of all
// zeros produces a burst that is indistinguishable, position by
// position, from 17 clock pulses -- which is exactly what it is.
package discovery_pkg;
localparam int unsigned FLP_POSITIONS = 33;
localparam int unsigned FLP_CLOCK_PULSES = 17;
localparam int unsigned FLP_DATA_BITS = 16;
// Timing, in microseconds. A receiver's windows are wider than a
// transmitter's nominal values, which is the usual asymmetry: send
// precisely, accept generously.
localparam int unsigned CLK_SPACING_US = 125;
localparam int unsigned CLK_TOL_US = 14;
localparam int unsigned DATA_OFFSET_US = 62; // 62.5, truncated
localparam int unsigned DATA_TOL_US = 7;
// A pulse later than this after a clock pulse is not a data pulse.
localparam int unsigned DATA_DETECT_MAX_US = 100;
localparam int unsigned BURST_MIN_MS = 8;
localparam int unsigned BURST_MAX_MS = 16;
// 10BASE-T link integrity: lc_max, the number of consecutive link
// pulses required to declare the link up, is between 2 and 10.
localparam int unsigned LC_MIN = 2;
localparam int unsigned LC_MAX = 10;
typedef enum logic [2:0] {
SIG_NONE, // nothing on the wire
SIG_LINK_PULSE, // single pulses at ~16 ms: 10BASE-T
SIG_FLP_BURST, // 33-position bursts: autonegotiation
SIG_IDLE_SYMBOLS, // continuous: 100BASE-TX, no negotiation
SIG_AMBIGUOUS // activity that fits none of the above
} signalling_e;
endpackage
module flp_burst_transmitter
import discovery_pkg::*;
#(
parameter int unsigned CLK_MHZ = 25,
parameter int unsigned CNT_W = 20
) (
input logic clk,
input logic rst_n,
input logic enable,
input logic [15:0] link_code_word,
// Randomised burst spacing within the legal window, so two devices
// powering up together do not lock into a repeating phase relation.
input logic [2:0] spacing_jitter_ms,
output logic pulse,
output logic burst_active,
output logic [5:0] position, // 0..32
output logic burst_complete,
output logic [CNT_W-1:0] c_bursts,
output logic [CNT_W-1:0] c_pulses,
// Pulses emitted in the last burst. Between 17 (all-zero word) and
// 33 (all-ones), and exported because it is a free check that the
// word being advertised is the word intended.
output logic [5:0] pulses_last_burst
);
localparam int unsigned TICKS_PER_US = CLK_MHZ;
// Half a clock-to-clock interval: the data position sits here.
localparam int unsigned HALF_TICKS = (CLK_SPACING_US * CLK_MHZ) / 2;
localparam int unsigned FULL_TICKS = CLK_SPACING_US * CLK_MHZ;
localparam int unsigned PULSE_TICKS = (100 * CLK_MHZ) / 1000; // ~100 ns
logic [15:0] tick_q;
logic [5:0] pos_q;
logic [5:0] pulses_q;
logic [31:0] gap_q;
logic in_burst_q;
assign position = pos_q;
assign burst_active = in_burst_q;
// A position is a CLOCK position when even, a DATA position when odd:
// 0 clk, 1 data, 2 clk, 3 data, ... 32 clk
// which gives 17 clock and 16 data, and brackets every data bit.
wire is_clock_position = !pos_q[0];
wire [3:0] data_index = pos_q[4:1];
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
tick_q <= '0; pos_q <= 6'd0; pulses_q <= 6'd0; gap_q <= '0;
in_burst_q <= 1'b0; pulse <= 1'b0; burst_complete <= 1'b0;
c_bursts <= '0; c_pulses <= '0; pulses_last_burst <= 6'd0;
end else begin
pulse <= 1'b0;
burst_complete <= 1'b0;
if (!enable) begin
in_burst_q <= 1'b0;
gap_q <= '0;
end else if (in_burst_q) begin
if (tick_q < 16'(PULSE_TICKS)) begin
// THE PULSE. A clock position always pulses; a data position
// pulses only for a one -- and emits NOTHING for a zero,
// which is the encoding this whole chapter is about.
if (is_clock_position || link_code_word[data_index]) begin
pulse <= 1'b1;
if (tick_q == 16'd0) begin
pulses_q <= pulses_q + 6'd1;
if (!(&c_pulses)) c_pulses <= c_pulses + 1'b1;
end
end
end
// Clock positions are a full interval apart; the data position
// between them sits at half.
if (tick_q == 16'(HALF_TICKS - 1)) begin
tick_q <= '0;
if (pos_q == 6'(FLP_POSITIONS - 1)) begin
in_burst_q <= 1'b0;
burst_complete <= 1'b1;
pulses_last_burst <= pulses_q;
pos_q <= 6'd0;
gap_q <= '0;
if (!(&c_bursts)) c_bursts <= c_bursts + 1'b1;
end else begin
pos_q <= pos_q + 6'd1;
end
end else begin
tick_q <= tick_q + 16'd1;
end
end else begin
// BETWEEN BURSTS. The spacing is randomised inside the legal
// window so that two devices powering up together do not settle
// into a repeating phase relationship in which one always
// transmits while the other is blind.
if (gap_q == 32'((BURST_MIN_MS + 0) * 1000 * CLK_MHZ) +
32'(spacing_jitter_ms) * 32'(1000 * CLK_MHZ)) begin
in_burst_q <= 1'b1;
pos_q <= 6'd0;
tick_q <= '0;
pulses_q <= 6'd0;
end else begin
gap_q <= gap_q + 1'b1;
end
end
end
end
endmoduleClassification: synthesizable.
What it teaches: that a burst emits between 17 and 33 pulses depending on the word, and an all-zero link code word produces a burst that is, position by position, exactly seventeen clock pulses. There is no framing, no delimiter and no length field — the burst's identity is entirely in its pulse spacing, which is why the receiver in Section 5 is a timing measurement rather than a decoder.
Deliberately simplified: the spacing jitter arrives as an input. In practice it is derived from a free-running counter or a PHY-specific pseudo-random source, and its purpose is genuine: two devices with identical fixed spacing can settle into a phase relationship where one is always mid-burst while the other is between bursts.
Production implication: pulses_last_burst is a free consistency check that almost nothing implements. The count must equal 17 + popcount(link_code_word) — so comparing it against the word being advertised catches a transmit path that is emitting a different word from the one the register holds, which is otherwise invisible until the far end reports abilities the local device does not believe it advertised.
5. RTL 2 — Receiving a Burst, and Knowing What You Did Not See
// SYNTHESIZABLE.
//
// Receives an FLP burst and reports, per position, both the decoded bit
// AND the evidence behind it.
//
// THE ASYMMETRY THIS MODULE EXISTS FOR:
// a ONE is a pulse that ARRIVED -- positive evidence
// a ZERO is a pulse that DID NOT -- and an absence has three causes:
// the partner sent a zero,
// the partner sent a one and it was lost,
// the partner stopped transmitting.
// All three decode identically.
//
// WHAT DISAMBIGUATES THEM is the CLOCK pulses. Sixteen data bits sit
// between SEVENTEEN clock pulses, so every data position is bracketed.
// If both bracketing clock pulses arrived, the medium was carrying
// pulses and this receiver was detecting them during the interval --
// so a missing data pulse was NOT SENT rather than lost.
//
// Which is why this module exports a per-bit CONFIDENCE alongside the
// decoded word, and why Section 15's rejected property is what happens
// when a design exports only the word.
module flp_burst_receiver
import discovery_pkg::*;
#(
parameter int unsigned CLK_MHZ = 25,
parameter int unsigned CNT_W = 20
) (
input logic clk,
input logic rst_n,
input logic pulse_in, // a detected pulse, one cycle wide
output logic [15:0] link_code_word,
// Bit n set = the bit at position n was bracketed by two clock pulses
// that both arrived within tolerance. Only those bits are believable.
output logic [15:0] bit_confidence,
output logic word_valid,
// TRUE only when EVERY bit was bracketed. A word with any low
// confidence bit is not a word; it is a guess with a shape.
output logic word_fully_bracketed,
output logic [5:0] clock_pulses_seen,
output logic [5:0] data_pulses_seen,
output logic [15:0] worst_clock_interval_us,
// A clock pulse outside the 111..139 us window. The burst's timing
// reference is broken from here on, because every later position is
// measured from it.
output logic clock_interval_violation,
// A pulse later than data_detect_max after its clock pulse. Not a
// data pulse -- it is the next clock pulse arriving early, and
// treating it as data shifts every subsequent bit.
output logic late_pulse_seen,
output logic [CNT_W-1:0] c_bursts_decoded,
output logic [CNT_W-1:0] c_bursts_discarded,
output logic ever_partial_burst
);
localparam int unsigned TICKS_PER_US = CLK_MHZ;
localparam int unsigned CLK_MIN_T = (CLK_SPACING_US - CLK_TOL_US) * CLK_MHZ;
localparam int unsigned CLK_MAX_T = (CLK_SPACING_US + CLK_TOL_US) * CLK_MHZ;
localparam int unsigned DAT_MIN_T = (DATA_OFFSET_US - DATA_TOL_US) * CLK_MHZ;
localparam int unsigned DAT_MAX_T = (DATA_OFFSET_US + DATA_TOL_US) * CLK_MHZ;
localparam int unsigned DET_MAX_T = DATA_DETECT_MAX_US * CLK_MHZ;
// No pulse for this long ends the burst.
localparam int unsigned BURST_END_T = 300 * CLK_MHZ;
logic [15:0] since_pulse_q;
logic [3:0] bit_index_q;
logic in_burst_q;
logic saw_data_q; // a data pulse since the last clock
logic prev_clock_ok_q; // the clock pulse before this bit was good
logic [15:0] word_q;
logic [15:0] conf_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
since_pulse_q <= '0; bit_index_q <= 4'd0; in_burst_q <= 1'b0;
saw_data_q <= 1'b0; prev_clock_ok_q <= 1'b0;
word_q <= 16'd0; conf_q <= 16'd0;
link_code_word <= 16'd0; bit_confidence <= 16'd0;
word_valid <= 1'b0; word_fully_bracketed <= 1'b0;
clock_pulses_seen <= 6'd0; data_pulses_seen <= 6'd0;
worst_clock_interval_us <= 16'd0;
clock_interval_violation <= 1'b0; late_pulse_seen <= 1'b0;
c_bursts_decoded <= '0; c_bursts_discarded <= '0;
ever_partial_burst <= 1'b0;
end else begin
word_valid <= 1'b0;
clock_interval_violation <= 1'b0;
late_pulse_seen <= 1'b0;
if (pulse_in) begin
if (!in_burst_q) begin
// FIRST PULSE of a burst. It is a clock pulse by definition,
// and it is the reference everything after is measured from.
in_burst_q <= 1'b1;
bit_index_q <= 4'd0;
word_q <= 16'd0;
conf_q <= 16'd0;
saw_data_q <= 1'b0;
prev_clock_ok_q <= 1'b1;
clock_pulses_seen <= 6'd1;
data_pulses_seen <= 6'd0;
end else if ((since_pulse_q >= 16'(DAT_MIN_T)) &&
(since_pulse_q <= 16'(DAT_MAX_T))) begin
// A DATA PULSE: inside the 55.5..69.5 us window.
saw_data_q <= 1'b1;
data_pulses_seen <= data_pulses_seen + 6'd1;
end else if ((since_pulse_q >= 16'(CLK_MIN_T)) &&
(since_pulse_q <= 16'(CLK_MAX_T))) begin
// A CLOCK PULSE. It closes the data position that preceded
// it, and its arrival is HALF the evidence for that bit.
clock_pulses_seen <= clock_pulses_seen + 6'd1;
word_q[bit_index_q] <= saw_data_q;
// CONFIDENCE. The bit is believable only if the clock pulse
// BEFORE it was good AND this one is -- which is exactly the
// bracketing the 17-for-16 ratio provides.
conf_q[bit_index_q] <= prev_clock_ok_q;
if (since_pulse_q[15:0] > worst_clock_interval_us)
worst_clock_interval_us <= since_pulse_q;
prev_clock_ok_q <= 1'b1;
saw_data_q <= 1'b0;
bit_index_q <= bit_index_q + 4'd1;
end else if (since_pulse_q > 16'(DET_MAX_T)) begin
// A pulse beyond data_detect_max but outside the clock
// window. The timing reference is broken; every bit after
// this one is measured from a position that is not where the
// transmitter put it.
late_pulse_seen <= 1'b1;
clock_interval_violation <= 1'b1;
prev_clock_ok_q <= 1'b0;
saw_data_q <= 1'b0;
bit_index_q <= bit_index_q + 4'd1;
end else begin
// Too early to be anything legal.
clock_interval_violation <= 1'b1;
prev_clock_ok_q <= 1'b0;
end
since_pulse_q <= '0;
end else if (in_burst_q) begin
if (since_pulse_q == 16'(BURST_END_T)) begin
// THE BURST ENDED. A full burst has 17 clock pulses; anything
// fewer means positions were lost, and the word is published
// WITH its confidence rather than withheld -- because a
// partially-bracketed word is still evidence, and Section 9's
// classifier needs it.
in_burst_q <= 1'b0;
link_code_word <= word_q;
bit_confidence <= conf_q;
word_valid <= 1'b1;
word_fully_bracketed <= (conf_q == 16'hFFFF) &&
(clock_pulses_seen == 6'(FLP_CLOCK_PULSES));
if ((conf_q == 16'hFFFF) &&
(clock_pulses_seen == 6'(FLP_CLOCK_PULSES))) begin
if (!(&c_bursts_decoded)) c_bursts_decoded <= c_bursts_decoded + 1'b1;
end else begin
ever_partial_burst <= 1'b1;
if (!(&c_bursts_discarded))
c_bursts_discarded <= c_bursts_discarded + 1'b1;
end
end else begin
since_pulse_q <= since_pulse_q + 16'd1;
end
end else begin
if (since_pulse_q != 16'hFFFF) since_pulse_q <= since_pulse_q + 16'd1;
end
end
end
endmoduleClassification: synthesizable.
What it teaches: that bit_confidence must be published alongside link_code_word, and a design that exports only the word has destroyed the distinction between a zero and a loss. Each confidence bit records whether both clock pulses bracketing that data position arrived within tolerance — which is precisely what makes "no pulse arrived" mean "no pulse was sent."
Deliberately simplified: the pulse detector is an idealised one-cycle input. In a real PHY it is a comparator with a threshold and a blanking interval, and its own sensitivity is part of what decides whether a marginal pulse is seen.
Production implication: late_pulse_seen catches a specific and destructive failure. A pulse arriving beyond data_detect_max is not a data pulse — it is the next clock pulse arriving early, and a receiver that accepts it as data shifts every subsequent bit by one position. The result is a perfectly-formed 16-bit word that is a rotation of the transmitted one, which decodes to a plausible and completely wrong set of abilities.
6. Why a One and a Zero Are Not Symmetric
Restate the encoding as an evidentiary problem, because that framing is what the rest of the chapter runs on.
| Bit | Signalled by | Evidence class |
|---|---|---|
| 1 | a pulse arriving in a 14 µs window | positive — something happened |
| 0 | no pulse in that window | negative — nothing happened |
Positive and negative evidence fail in opposite directions.
A one can be lost. Attenuation, a marginal detector threshold, a noise-blanking interval that happened to be open — and the receiver reads a zero. The error turns a one into a zero and is silent.
A zero cannot become a one by loss — only by a spurious detection, which is a different and rarer event.
So FLP's error behaviour is asymmetric: bits fall from one to zero far more easily than the reverse, and a link code word decoded on a marginal cable is biased toward advertising fewer abilities than the partner actually has.
Which produces a specific and quiet misbehaviour. Chapter 9.2 §4's priority resolution takes the highest common ability — so a lost pulse in the 100BASE-TX full duplex position does not break the link. It negotiates a slower one, cleanly, with no error anywhere, and both ends agree.
7. RTL 3 — Deciding What Kind of Partner This Is
// SYNTHESIZABLE.
//
// Classifies the signalling on the wire into one of four cases, which
// is the first decision in the entire link bring-up and the one every
// later step depends on.
//
// THE FOUR CASES AND THEIR SIGNATURES:
// SIG_FLP_BURST -- groups of pulses 125 us apart, groups repeating
// every 8..16 ms. The fine structure is what
// distinguishes it from the next case.
// SIG_LINK_PULSE -- SINGLE pulses roughly every 16 ms, with no
// 125 us structure inside. A 10BASE-T partner.
// SIG_IDLE_SYMBOLS -- continuous activity with no pulse structure at
// all. A 100BASE-TX partner with negotiation off.
// SIG_NONE -- nothing.
//
// THE HAZARD: the first two repeat at overlapping rates, deliberately,
// because that is what makes FLP backward compatible. So they can ONLY
// be told apart by the MICROSECOND structure inside a burst, and a
// classifier that measures only the millisecond rate cannot separate
// them at all.
module signalling_classifier
import discovery_pkg::*;
#(
parameter int unsigned CLK_MHZ = 25,
parameter int unsigned CNT_W = 20,
// Bursts or pulses observed before the classification is believed.
parameter int unsigned CONFIRM = 3
) (
input logic clk,
input logic rst_n,
input logic clear,
input logic pulse_in,
input logic activity_in, // continuous energy, not pulses
output signalling_e classification,
output logic classification_valid,
output logic classification_changed,
// The measurements the classification rests on, exported so a human
// can see WHY the design decided what it decided.
output logic [15:0] last_intra_group_us,
output logic [15:0] last_inter_group_ms,
output logic [5:0] last_group_size,
output logic [CNT_W-1:0] c_groups,
output logic [CNT_W-1:0] c_classifications,
// Activity that fits none of the four signatures. Reported rather
// than forced into the nearest one -- a partner doing something
// undefined is information, and rounding it away loses it.
output logic ambiguous_signalling,
output logic [CNT_W-1:0] c_ambiguous
);
localparam int unsigned INTRA_MIN_T = (CLK_SPACING_US - CLK_TOL_US) * CLK_MHZ;
localparam int unsigned INTRA_MAX_T = (CLK_SPACING_US + CLK_TOL_US) * CLK_MHZ;
localparam int unsigned GROUP_END_T = 300 * CLK_MHZ;
localparam int unsigned MS_TICKS = 1000 * CLK_MHZ;
logic [31:0] since_pulse_q;
logic [31:0] since_group_q;
logic [5:0] group_size_q;
logic in_group_q;
logic [3:0] confirm_q;
signalling_e candidate_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
since_pulse_q <= '0; since_group_q <= '0; group_size_q <= 6'd0;
in_group_q <= 1'b0; confirm_q <= 4'd0; candidate_q <= SIG_NONE;
classification <= SIG_NONE; classification_valid <= 1'b0;
classification_changed <= 1'b0;
last_intra_group_us <= 16'd0; last_inter_group_ms <= 16'd0;
last_group_size <= 6'd0;
c_groups <= '0; c_classifications <= '0;
ambiguous_signalling <= 1'b0; c_ambiguous <= '0;
end else if (clear) begin
c_groups <= '0; c_classifications <= '0; c_ambiguous <= '0;
classification_changed <= 1'b0;
end else begin
classification_changed <= 1'b0;
ambiguous_signalling <= 1'b0;
if (pulse_in) begin
if (!in_group_q) begin
in_group_q <= 1'b1;
group_size_q <= 6'd1;
last_inter_group_ms <= 16'(since_group_q / 32'(MS_TICKS));
since_group_q <= '0;
end else begin
group_size_q <= group_size_q + 6'd1;
// THE INTRA-GROUP MEASUREMENT. This is what separates a burst
// from a link pulse, and a classifier that omits it cannot.
last_intra_group_us <= 16'(since_pulse_q / 32'(CLK_MHZ));
end
since_pulse_q <= '0;
end else begin
if (since_pulse_q != 32'hFFFF_FFFF) since_pulse_q <= since_pulse_q + 1'b1;
if (since_group_q != 32'hFFFF_FFFF) since_group_q <= since_group_q + 1'b1;
if (in_group_q && (since_pulse_q == 32'(GROUP_END_T))) begin
// A GROUP ENDED. Classify it from its SIZE and its internal
// spacing, not from its repetition rate -- the repetition
// rates of the two cases deliberately overlap.
in_group_q <= 1'b0;
last_group_size <= group_size_q;
if (!(&c_groups)) c_groups <= c_groups + 1'b1;
if ((group_size_q >= 6'd17) &&
(last_intra_group_us >= 16'(CLK_SPACING_US - CLK_TOL_US)) &&
(last_intra_group_us <= 16'(CLK_SPACING_US + CLK_TOL_US))) begin
// 17 or more pulses at 125 us spacing: an FLP burst.
if (candidate_q == SIG_FLP_BURST) begin
if (confirm_q != 4'(CONFIRM)) confirm_q <= confirm_q + 4'd1;
end else begin
candidate_q <= SIG_FLP_BURST;
confirm_q <= 4'd1;
end
end else if (group_size_q == 6'd1) begin
// A SINGLE pulse: 10BASE-T link integrity.
if (candidate_q == SIG_LINK_PULSE) begin
if (confirm_q != 4'(CONFIRM)) confirm_q <= confirm_q + 4'd1;
end else begin
candidate_q <= SIG_LINK_PULSE;
confirm_q <= 4'd1;
end
end else begin
// Pulses, but neither one nor a well-formed burst. NOT
// rounded to the nearest case -- a partner doing something
// undefined is information.
ambiguous_signalling <= 1'b1;
candidate_q <= SIG_AMBIGUOUS;
confirm_q <= 4'd0;
if (!(&c_ambiguous)) c_ambiguous <= c_ambiguous + 1'b1;
end
end
// Continuous activity with no pulse structure at all.
if (activity_in && !in_group_q && (since_pulse_q > 32'(GROUP_END_T))) begin
if (candidate_q == SIG_IDLE_SYMBOLS) begin
if (confirm_q != 4'(CONFIRM)) confirm_q <= confirm_q + 4'd1;
end else begin
candidate_q <= SIG_IDLE_SYMBOLS;
confirm_q <= 4'd1;
end
end
end
if ((confirm_q == 4'(CONFIRM)) && (classification != candidate_q)) begin
classification <= candidate_q;
classification_valid <= 1'b1;
classification_changed <= 1'b1;
if (!(&c_classifications)) c_classifications <= c_classifications + 1'b1;
end
end
end
endmoduleClassification: synthesizable.
What it teaches: that an FLP burst and a link pulse cannot be separated by their repetition rate, because their rates deliberately overlap — that overlap is the backward-compatibility trick Chapter 9.2 §5 describes. The separation is entirely in the intra-group spacing: 17 or more pulses at 125 µs apart, versus a single pulse. A classifier that measures only the millisecond rate cannot distinguish the two cases at all, and will classify one as the other with complete confidence.
Deliberately simplified: the intra-group measurement uses the last observed spacing. A production classifier accumulates the whole group's spacings and requires them all to be in window, since one late pulse in an otherwise good burst should not disqualify it.
Production implication: ambiguous_signalling refuses to round activity into the nearest legal category, and that refusal is diagnostic. A partner emitting five pulses in a group is not a link pulse and not a burst — it is a device that is broken, mid-reset, or speaking a variant this design does not know. Forcing it to the nearest case makes the design confidently wrong, and c_ambiguous is the only counter that would ever say otherwise.
8. RTL 4 — Parallel Detection, With the Assumption Marked
// SYNTHESIZABLE.
//
// Parallel detection: determine what a NON-NEGOTIATING partner is, from
// its signalling alone -- and keep what was MEASURED strictly separate
// from what was ASSUMED.
//
// Chapter 9.2 §9 introduced this module's ancestor. What that chapter
// established, and this one implements more carefully, is that the two
// outputs have completely different epistemic status:
//
// SPEED is MEASURED. Link pulses and idle symbols are entirely
// different signals and distinguishing them takes no protocol.
// DUPLEX is ASSUMED. Nothing in either signalling says how a partner
// intends to behave WHEN IT TRANSMITS, and an idle link exhibits
// none of that behaviour.
//
// So this module carries a per-field provenance, not a single validity
// bit -- because a design that reports one result cannot distinguish a
// measurement from a default, and Chapter 9.2's duplex mismatch is
// exactly what that costs.
module parallel_detection_fsm
import discovery_pkg::*;
#(
parameter int unsigned CLK_MHZ = 25,
// How long a non-negotiating signalling must persist before parallel
// detection is used. Long enough that a slow negotiator is not
// misjudged as a device that cannot negotiate.
parameter int unsigned DETECT_MS = 500,
parameter int unsigned CNT_W = 20
) (
input logic clk,
input logic rst_n,
input signalling_e classification,
input logic classification_valid,
output logic detection_valid,
output logic [1:0] detected_speed, // 0 = 10, 1 = 100
output logic detected_duplex, // always half
// THE PROVENANCE. Two bits, one per field, and they are never equal
// on a parallel-detected link.
output logic speed_is_measured,
output logic duplex_is_assumed,
output logic partner_negotiates,
output logic [15:0] detect_elapsed_ms,
output logic [CNT_W-1:0] c_detections,
// Sticky. A port that has EVER parallel detected is a port whose
// duplex was never agreed, and that stays true after the link
// bounces and comes back looking new.
output logic ever_parallel_detected,
// The classification changed while the detection timer was running.
// Reported, because it usually means the partner was mid-reset and
// the detection would have been made about a transient.
output logic detection_aborted,
output logic [CNT_W-1:0] c_aborts
);
localparam int unsigned MS_TICKS = 1000 * CLK_MHZ;
logic [31:0] tick_q;
logic [15:0] ms_q;
logic timing_q;
signalling_e timing_class_q;
assign detect_elapsed_ms = ms_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
tick_q <= '0; ms_q <= 16'd0; timing_q <= 1'b0;
timing_class_q <= SIG_NONE;
detection_valid <= 1'b0; detected_speed <= 2'd0;
detected_duplex <= 1'b0;
speed_is_measured <= 1'b0; duplex_is_assumed <= 1'b0;
partner_negotiates <= 1'b0;
c_detections <= '0; ever_parallel_detected <= 1'b0;
detection_aborted <= 1'b0; c_aborts <= '0;
end else begin
detection_valid <= 1'b0;
detection_aborted <= 1'b0;
if (classification_valid && (classification == SIG_FLP_BURST)) begin
// The partner negotiates. Parallel detection is not used, and
// duplex will be AGREED rather than assumed.
partner_negotiates <= 1'b1;
duplex_is_assumed <= 1'b0;
speed_is_measured <= 1'b0;
timing_q <= 1'b0;
ms_q <= 16'd0;
end else if ((classification == SIG_LINK_PULSE) ||
(classification == SIG_IDLE_SYMBOLS)) begin
partner_negotiates <= 1'b0;
if (!timing_q) begin
timing_q <= 1'b1;
timing_class_q <= classification;
ms_q <= 16'd0;
tick_q <= '0;
end else if (classification != timing_class_q) begin
// THE CLASSIFICATION CHANGED mid-timer. Usually a partner
// that was mid-reset; making a detection about a transient
// is how a working device gets classified as a legacy one.
detection_aborted <= 1'b1;
timing_q <= 1'b0;
ms_q <= 16'd0;
if (!(&c_aborts)) c_aborts <= c_aborts + 1'b1;
end else if (ms_q == 16'(DETECT_MS)) begin
detection_valid <= 1'b1;
// SPEED: measured. The two signallings are entirely different
// and telling them apart requires no protocol at all.
detected_speed <= (classification == SIG_IDLE_SYMBOLS) ? 2'd1 : 2'd0;
speed_is_measured <= 1'b1;
// DUPLEX: assumed, and HALF, because duplex is unobservable
// and half is the choice that is safe against a partner that
// might collide.
detected_duplex <= 1'b0;
duplex_is_assumed <= 1'b1;
ever_parallel_detected <= 1'b1;
timing_q <= 1'b0;
ms_q <= 16'd0;
if (!(&c_detections)) c_detections <= c_detections + 1'b1;
end else begin
if (tick_q == 32'(MS_TICKS - 1)) begin
tick_q <= '0;
ms_q <= ms_q + 16'd1;
end else begin
tick_q <= tick_q + 1'b1;
end
end
end else begin
// Nothing, or ambiguous. Stop timing; do not detect about it.
if (timing_q) begin
detection_aborted <= 1'b1;
if (!(&c_aborts)) c_aborts <= c_aborts + 1'b1;
end
timing_q <= 1'b0;
ms_q <= 16'd0;
end
end
end
endmoduleClassification: synthesizable.
What it teaches: that speed_is_measured and duplex_is_assumed are two provenance bits and never one validity bit, because the two fields have genuinely different evidentiary status. Speed is a measurement — link pulses and idle symbols are entirely different signals. Duplex is a default with no evidence at all behind it, because an idle link exhibits none of the behaviour duplex describes.
Deliberately simplified: a single detection window. Real parallel detection interacts with the negotiation state machine's own timers, and the interaction is exactly where a partner that negotiates slowly gets misclassified as one that does not — which is what detection_aborted is watching for.
Production implication: detection_aborted counts an event that is otherwise invisible and frequently benign. A partner emerging from reset changes its signalling — nothing, then link pulses, then bursts — and a detector that latched the middle state would classify a negotiating gigabit PHY as a 10BASE-T device. The abort costs a retry; not aborting costs a link that runs at 10 Mb/s half duplex for as long as it stays up.
9. What Parallel Detection Can and Cannot Determine
Parallel detection produces two outputs and they are not the same kind of thing.
| Field | How it is obtained | Evidence |
|---|---|---|
| speed | 10BASE-T sends link pulses; 100BASE-TX sends continuous idle symbols | entirely different signals — measured |
| duplex | — | none whatsoever — assumed half |
Speed is easy because the two signallings are not similar. A pulse every 16 ms and a continuous symbol stream are as different as two signals on one pair can be; no protocol, no negotiation and no agreement is required to tell them apart.
Duplex is impossible because of what duplex is. It describes how a device behaves when it transmits — whether it senses carrier first and backs off on collision. An idle link exhibits none of that behaviour, because nothing is transmitting.
So the standard assumes half duplex, which is the choice that is safe against a partner that might collide: a half-duplex end deferring to a full-duplex partner merely wastes a little time, while the reverse produces Chapter 9.2's mismatch.
And a link whose duplex was assumed rather than agreed is permanently a mismatch candidate, which is why ever_parallel_detected is sticky.
10. RTL 5 — Bounding the Whole Process
// SYNTHESIZABLE.
//
// Sequences and bounds link discovery, so that every path through it
// terminates in a named outcome rather than in waiting.
//
// THE ORDERING, which cannot be rearranged:
// 1. observe -- is there anything on the wire at all?
// 2. classify -- which of the four signallings is it?
// 3a. if FLP -- decode bursts, hand the word to Chapter 11.2
// 3b. if not FLP -- run the parallel-detection timer
// 4. bounded failure -- if none of the above resolves, restart
//
// AND EVERY STEP HAS A TIMER, because every step can hang. Clause 28
// names several -- break_link_timer, link_fail_inhibit_timer,
// autoneg_wait_timer -- and their common purpose is that no state is
// entered which the design cannot leave.
module discovery_retry_sequencer
import discovery_pkg::*;
#(
parameter int unsigned CLK_MHZ = 25,
parameter int unsigned OBSERVE_MS = 100,
parameter int unsigned CLASSIFY_MS = 200,
parameter int unsigned DECODE_MS = 300,
parameter int unsigned BREAK_LINK_MS = 1500,
parameter int unsigned MAX_ATTEMPTS = 8,
parameter int unsigned CNT_W = 20
) (
input logic clk,
input logic rst_n,
input logic restart,
input logic activity_seen,
input signalling_e classification,
input logic classification_valid,
input logic word_valid,
input logic word_fully_bracketed,
input logic detection_valid,
output logic [2:0] stage,
output logic abilities_available,
output logic detection_used,
output logic discovery_failed,
output logic [2:0] failed_stage,
// Time from the first activity to a usable result. A margin
// measurement: a link that consistently takes most of its budget is
// a link one disturbance from failing.
output logic [15:0] last_discovery_ms,
output logic [15:0] worst_discovery_ms,
output logic [CNT_W-1:0] c_attempts,
output logic [CNT_W-1:0] c_successes,
output logic [CNT_W-1:0] c_failures,
output logic [3:0] attempts_this_link,
output logic ever_failed
);
typedef enum logic [2:0] {
D_IDLE, D_OBSERVE, D_CLASSIFY, D_DECODE, D_DETECT, D_DONE, D_FAILED
} dstage_e;
localparam int unsigned MS_TICKS = 1000 * CLK_MHZ;
dstage_e st_q;
logic [31:0] tick_q;
logic [15:0] ms_q;
logic [15:0] total_ms_q;
assign stage = st_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
st_q <= D_IDLE; tick_q <= '0; ms_q <= 16'd0; total_ms_q <= 16'd0;
abilities_available <= 1'b0; detection_used <= 1'b0;
discovery_failed <= 1'b0; failed_stage <= 3'(D_IDLE);
last_discovery_ms <= 16'd0; worst_discovery_ms <= 16'd0;
c_attempts <= '0; c_successes <= '0; c_failures <= '0;
attempts_this_link <= 4'd0; ever_failed <= 1'b0;
end else begin
discovery_failed <= 1'b0;
// A free-running millisecond tick shared by every stage timer.
if (tick_q == 32'(MS_TICKS - 1)) begin
tick_q <= '0;
if (ms_q != 16'hFFFF) ms_q <= ms_q + 16'd1;
if (total_ms_q != 16'hFFFF) total_ms_q <= total_ms_q + 16'd1;
end else begin
tick_q <= tick_q + 1'b1;
end
if (restart) begin
st_q <= D_OBSERVE; ms_q <= 16'd0; total_ms_q <= 16'd0;
abilities_available <= 1'b0; detection_used <= 1'b0;
attempts_this_link <= 4'd0;
if (!(&c_attempts)) c_attempts <= c_attempts + 1'b1;
end else begin
unique case (st_q)
D_IDLE: if (activity_seen) begin
st_q <= D_OBSERVE; ms_q <= 16'd0; total_ms_q <= 16'd0;
if (!(&c_attempts)) c_attempts <= c_attempts + 1'b1;
end
D_OBSERVE: if (!activity_seen && (ms_q == 16'(OBSERVE_MS))) begin
// Activity stopped before anything could be classified.
// Back to idle rather than onward -- there is nothing to
// classify and waiting longer changes nothing.
st_q <= D_IDLE;
end else if (classification_valid) begin
st_q <= D_CLASSIFY; ms_q <= 16'd0;
end else if (ms_q == 16'(OBSERVE_MS)) begin
st_q <= D_CLASSIFY; ms_q <= 16'd0;
end
D_CLASSIFY: begin
if (classification == SIG_FLP_BURST) begin
st_q <= D_DECODE; ms_q <= 16'd0;
end else if ((classification == SIG_LINK_PULSE) ||
(classification == SIG_IDLE_SYMBOLS)) begin
st_q <= D_DETECT; ms_q <= 16'd0;
end else if (ms_q == 16'(CLASSIFY_MS)) begin
st_q <= D_FAILED;
failed_stage <= 3'(D_CLASSIFY);
end
end
D_DECODE: begin
// A word is only useful if every bit was bracketed. A
// partially-bracketed word is a guess with a shape, and
// acting on it advertises abilities the partner may not
// have claimed.
if (word_valid && word_fully_bracketed) begin
st_q <= D_DONE;
abilities_available <= 1'b1;
end else if (classification != SIG_FLP_BURST) begin
// The partner stopped bursting. Re-classify rather than
// keep decoding something that is no longer there.
st_q <= D_CLASSIFY; ms_q <= 16'd0;
end else if (ms_q == 16'(DECODE_MS)) begin
st_q <= D_FAILED;
failed_stage <= 3'(D_DECODE);
end
end
D_DETECT: begin
if (detection_valid) begin
st_q <= D_DONE;
detection_used <= 1'b1;
end else if (classification == SIG_FLP_BURST) begin
// The partner started negotiating after all. Abandon the
// detection -- a negotiated result always beats a
// detected one, because it includes duplex.
st_q <= D_DECODE; ms_q <= 16'd0;
end else if (ms_q == 16'(BREAK_LINK_MS)) begin
st_q <= D_FAILED;
failed_stage <= 3'(D_DETECT);
end
end
D_DONE: begin
last_discovery_ms <= total_ms_q;
if (total_ms_q > worst_discovery_ms)
worst_discovery_ms <= total_ms_q;
if (!(&c_successes)) c_successes <= c_successes + 1'b1;
if (!activity_seen) st_q <= D_IDLE;
end
D_FAILED: begin
discovery_failed <= 1'b1;
ever_failed <= 1'b1;
if (!(&c_failures)) c_failures <= c_failures + 1'b1;
if (attempts_this_link != 4'(MAX_ATTEMPTS))
attempts_this_link <= attempts_this_link + 4'd1;
st_q <= D_IDLE;
ms_q <= 16'd0;
end
default: st_q <= D_IDLE;
endcase
end
end
end
endmoduleClassification: synthesizable.
What it teaches: that a negotiated result always supersedes a detected one, which is why D_DETECT transitions to D_DECODE if bursts appear rather than completing its timer. Parallel detection yields speed and an assumption about duplex; negotiation yields both, agreed. A partner that starts bursting halfway through a detection window was always going to negotiate — it was simply slower to start — and completing the detection would lock in an assumption that the next 200 ms would have made unnecessary.
Deliberately simplified: stage timeouts are single parameters. Clause 28 names several with specific ranges — break_link_timer, link_fail_inhibit_timer, autoneg_wait_timer — and their common property is the one this module implements: no state is entered that the design cannot leave.
Production implication: worst_discovery_ms is a margin measurement in the same family as Chapter 10.4's eye width and Chapter 10.5's convergence time. A link that discovers in 60 ms and one that takes 1400 ms both report success, and the second is one disturbance from timing out. The number is free to collect and nothing else reports it.
11. RTL 6 — The Fallback That Keeps a 1990 Device Working
// SYNTHESIZABLE.
//
// The 10BASE-T link integrity test: declare a link up after a run of
// consecutive link pulses, and drop it after a period of silence.
//
// WHY IT IS STILL HERE. Chapter 9.1 §9 built it, and it never went
// away -- because the FLP burst was deliberately designed to satisfy
// it. A negotiating partner's bursts arrive inside the same 16 +/- 8 ms
// window as link pulses, so this mechanism brings a link up against a
// partner speaking a protocol it has never heard of.
//
// THE CONSTANT WORTH KNOWING: lc_max, the number of consecutive pulses
// required, is between 2 and 10. At the 16 +/- 8 ms pulse period that
// is between:
// 2 pulses -> 32 ms nominal, 48 ms worst case
// 10 pulses -> 160 ms nominal, 240 ms worst case
// A design at the low end comes up fast and is more easily fooled by
// noise; one at the high end is robust and slow. Neither is wrong, and
// exporting which was chosen is what lets two devices' behaviour be
// compared.
module link_integrity_monitor
import discovery_pkg::*;
#(
parameter int unsigned CLK_MHZ = 25,
parameter int unsigned LC_COUNT = 4, // between LC_MIN and LC_MAX
parameter int unsigned LINK_LOSS_MS = 150,
parameter int unsigned CNT_W = 20
) (
input logic clk,
input logic rst_n,
input logic clear,
input logic pulse_in,
input logic frame_activity, // a received frame also proves life
output logic link_up,
output logic link_came_up,
output logic link_went_down,
// The consecutive-pulse run currently held, and the worst gap ever
// observed while up. The second is the margin: a link whose worst
// silence approaches the loss timer will drop on the next
// disturbance, and nothing else says so.
output logic [3:0] pulse_run,
output logic [15:0] worst_silence_ms,
output logic [15:0] loss_threshold_ms,
// The configured lc_max, exported so two devices can be compared.
output logic [3:0] lc_configured,
output logic [CNT_W-1:0] c_link_ups,
output logic [CNT_W-1:0] c_link_downs,
output logic ever_link_down,
// A pulse arriving sooner than the minimum period. Not a link pulse
// -- it is noise, or a burst this monitor is not structured to see.
output logic early_pulse
);
localparam int unsigned MS_TICKS = 1000 * CLK_MHZ;
// A link pulse cannot arrive sooner than this after the previous one.
localparam int unsigned MIN_GAP_MS = 5;
logic [31:0] tick_q;
logic [15:0] ms_q;
logic [3:0] run_q;
assign pulse_run = run_q;
assign loss_threshold_ms = 16'(LINK_LOSS_MS);
assign lc_configured = 4'(LC_COUNT);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
tick_q <= '0; ms_q <= 16'd0; run_q <= 4'd0;
link_up <= 1'b0; link_came_up <= 1'b0; link_went_down <= 1'b0;
worst_silence_ms <= 16'd0;
c_link_ups <= '0; c_link_downs <= '0; ever_link_down <= 1'b0;
early_pulse <= 1'b0;
end else begin
link_came_up <= 1'b0;
link_went_down <= 1'b0;
early_pulse <= 1'b0;
if (clear) begin
c_link_ups <= '0; c_link_downs <= '0; worst_silence_ms <= 16'd0;
// ever_link_down survives: a link that has bounced has bounced.
end
if (tick_q == 32'(MS_TICKS - 1)) begin
tick_q <= '0;
if (ms_q != 16'hFFFF) ms_q <= ms_q + 16'd1;
end else begin
tick_q <= tick_q + 1'b1;
end
if (pulse_in || frame_activity) begin
// The worst silence is recorded BEFORE the counter resets --
// it is the margin against the loss timer, and it is the only
// forward-looking number this module produces.
if (link_up && (ms_q > worst_silence_ms)) worst_silence_ms <= ms_q;
if (pulse_in && (ms_q < 16'(MIN_GAP_MS)) && (run_q != 4'd0)) begin
// Too soon to be a link pulse. This is either noise or a
// pulse from inside a burst -- and a link-integrity monitor
// watching an FLP stream sees exactly that.
early_pulse <= 1'b1;
end else begin
if (run_q != 4'(LC_COUNT)) begin
run_q <= run_q + 4'd1;
end else if (!link_up) begin
link_up <= 1'b1;
link_came_up <= 1'b1;
if (!(&c_link_ups)) c_link_ups <= c_link_ups + 1'b1;
end
end
ms_q <= 16'd0;
end else if (ms_q == 16'(LINK_LOSS_MS)) begin
run_q <= 4'd0;
if (link_up) begin
link_up <= 1'b0;
link_went_down <= 1'b1;
ever_link_down <= 1'b1;
if (!(&c_link_downs)) c_link_downs <= c_link_downs + 1'b1;
end
end
end
end
endmoduleClassification: synthesizable.
What it teaches: that worst_silence_ms is the only forward-looking number this mechanism produces, and it must be captured before the counter resets. A link whose longest observed gap is 40 ms against a 150 ms threshold has ample margin; one whose worst gap is 140 ms is up, healthy by every measure, and will drop on the next disturbance. Both report link_up.
Deliberately simplified: LC_COUNT is a parameter with a legal range of 2 to 10. Neither end of that range is wrong — a design at 2 comes up in about 32 ms and is more easily fooled by noise; one at 10 takes about 160 ms and is robust — and exporting lc_configured is what lets two devices' bring-up behaviour be compared when they disagree about how quickly a link should appear.
Production implication: early_pulse fires when this monitor is watching an FLP stream, and that is not a fault — it is the mechanism seeing pulses 125 µs apart inside a burst when it expects them 16 ms apart. A 10BASE-T device counts them all and comes up anyway, which is precisely the backward compatibility the burst was designed for. In a negotiating device the same signal means the classifier should be consulted, and early_pulse is how this module says so without needing to understand bursts itself.
12. RTL 7 — What Discovery Should Report
// SYNTHESIZABLE.
//
// Aggregates everything discovery learned into a record a later reader
// can act on -- including the things that are not errors.
//
// THE PRINCIPLE THIS MODULE IMPLEMENTS: discovery's most valuable
// outputs are not failures. They are PROVENANCE and MARGIN.
//
// provenance -- was this measured, assumed, or inherited from a
// detection? Chapter 9.2's duplex mismatch is entirely a
// provenance problem.
// margin -- how close was the process to not working? Worst
// discovery time, worst burst quality, worst silence.
//
// Neither is an error. Both are invisible unless something collects
// them, and both are what turn a link that works into a link that is
// known to keep working.
module discovery_telemetry
import discovery_pkg::*;
#(
parameter int unsigned CNT_W = 20
) (
input logic clk,
input logic rst_n,
input logic clear,
input signalling_e classification,
input logic classification_valid,
input logic word_valid,
input logic [15:0] bit_confidence,
input logic word_fully_bracketed,
input logic detection_valid,
input logic speed_is_measured,
input logic duplex_is_assumed,
input logic discovery_failed,
input logic [2:0] failed_stage,
input logic [15:0] discovery_ms,
input logic [15:0] worst_silence_ms,
input logic [15:0] loss_threshold_ms,
// Provenance.
output logic speed_was_measured,
output logic duplex_was_assumed,
output logic abilities_were_negotiated,
output signalling_e partner_kind,
// Margin.
output logic [4:0] worst_low_confidence_bits,
output logic [15:0] worst_discovery_ms,
output logic [7:0] silence_margin_percent,
// Counts.
output logic [CNT_W-1:0] c_words,
output logic [CNT_W-1:0] c_words_fully_bracketed,
output logic [CNT_W-1:0] c_detections,
output logic [CNT_W-1:0] c_failures,
output logic [2:0] first_failed_stage,
output logic first_failure_valid,
// Sticky facts a later reader needs and a counter clear destroys.
output logic ever_assumed_duplex,
output logic ever_low_confidence,
output logic ever_failed
);
function automatic logic [4:0] count_zeros (input logic [15:0] v);
count_zeros = 5'd0;
for (int i = 0; i < 16; i = i + 1)
if (!v[i]) count_zeros = count_zeros + 5'd1;
endfunction
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
speed_was_measured <= 1'b0; duplex_was_assumed <= 1'b0;
abilities_were_negotiated <= 1'b0; partner_kind <= SIG_NONE;
worst_low_confidence_bits <= 5'd0; worst_discovery_ms <= 16'd0;
silence_margin_percent <= 8'd100;
c_words <= '0; c_words_fully_bracketed <= '0;
c_detections <= '0; c_failures <= '0;
first_failed_stage <= 3'd0; first_failure_valid <= 1'b0;
ever_assumed_duplex <= 1'b0; ever_low_confidence <= 1'b0;
ever_failed <= 1'b0;
end else begin
if (clear) begin
c_words <= '0; c_words_fully_bracketed <= '0;
c_detections <= '0; c_failures <= '0;
first_failure_valid <= 1'b0;
worst_low_confidence_bits <= 5'd0; worst_discovery_ms <= 16'd0;
// The three ever_* flags survive. They are the facts a later
// reader needs and the ones a counter clear would destroy.
end
if (classification_valid) partner_kind <= classification;
if (word_valid) begin
if (!(&c_words)) c_words <= c_words + 1'b1;
abilities_were_negotiated <= 1'b1;
if (word_fully_bracketed) begin
if (!(&c_words_fully_bracketed))
c_words_fully_bracketed <= c_words_fully_bracketed + 1'b1;
end else begin
ever_low_confidence <= 1'b1;
if (count_zeros(bit_confidence) > worst_low_confidence_bits)
worst_low_confidence_bits <= count_zeros(bit_confidence);
end
end
if (detection_valid) begin
if (!(&c_detections)) c_detections <= c_detections + 1'b1;
speed_was_measured <= speed_is_measured;
duplex_was_assumed <= duplex_is_assumed;
if (duplex_is_assumed) ever_assumed_duplex <= 1'b1;
end
if (discovery_ms > worst_discovery_ms) worst_discovery_ms <= discovery_ms;
// The silence margin as a percentage of the loss threshold. A
// link at 90% is up, healthy, and will drop on the next
// disturbance -- and no other signal in the design says so.
if (loss_threshold_ms != 16'd0)
silence_margin_percent <= 8'((worst_silence_ms * 100) / loss_threshold_ms);
if (discovery_failed) begin
ever_failed <= 1'b1;
if (!(&c_failures)) c_failures <= c_failures + 1'b1;
if (!first_failure_valid) begin
first_failure_valid <= 1'b1;
first_failed_stage <= failed_stage;
end
end
end
end
endmoduleClassification: synthesizable.
What it teaches: that discovery's most valuable outputs are not errors, and a design that reports only failures has instrumented the least informative part of the process. ever_assumed_duplex marks a link as a permanent mismatch candidate. worst_low_confidence_bits says how close the bursts came to being unreadable. silence_margin_percent says how close the link came to dropping. None of the three is a fault, and every one is a prediction.
Deliberately simplified: the margin is expressed as a percentage computed in RTL. Production designs export the two raw numbers and let software divide, keeping a divider out of the design entirely — the same argument Chapter 9.4 §10 makes about the deficit-idle average.
Production implication: the three ever_* flags survive clear specifically because a counter reset is what happens just before somebody investigates. An operator clears the statistics, runs a test, and reads a clean set of counters — losing the one fact that mattered, which is that this link's duplex was never agreed. The sticky flags are the design's memory of things a fresh window cannot show.
13. The Timers, and What Each One Bounds
Clause 28 names several timers, and their common purpose is that no state can be entered which the design cannot leave.
| Timer | Bounds | What happens without it |
|---|---|---|
break_link_timer | how long link is broken before renegotiating | a restart that never completes |
link_fail_inhibit_timer | how long a chosen technology is given to establish | a device stuck trying a technology that will not work |
autoneg_wait_timer | how long to wait after a technology reports ready | two ends settling on different technologies |
data_detect_max_timer | ≈ 100 µs — a pulse later than this is not data | every subsequent bit shifted by one position |
interval_timer | the clock-to-clock spacing within a burst | no timing reference at all |
| the link-loss timer | silence before a link is declared down | a link that is gone and reported up |
The fourth row is the one with a number and the one that produces the most interesting failure.
A pulse arriving more than about 100 µs after its clock pulse is not a data pulse — it is the next clock pulse arriving early, at the low end of the 111 to 139 µs window with some jitter.
A receiver that accepts it as data has consumed a clock pulse as a data bit, and every subsequent position is now measured from the wrong reference. The result is a well-formed 16-bit word that is a rotation of the transmitted one — decoding to a plausible, entirely wrong set of abilities, with no error indication anywhere.
And the discovery time budget, computed:
| Path | Working | Time |
|---|---|---|
| one burst | 16 × 125 µs | 2 ms |
| three matching words at 16 ms spacing | 3 × 16 | 48 ms |
link integrity, lc_max = 4 at 16 ms | 4 × 16 | 64 ms |
link integrity, lc_max = 10 worst case | 10 × 24 | 240 ms |
| parallel detection window | — | hundreds of ms |
So a negotiated link comes up in tens of milliseconds and a parallel-detected one in hundreds — which is itself a diagnostic: a link that consistently takes half a second to appear is probably not negotiating.
14. Properties Worth Asserting, and One Worth Refusing
Discovery's properties divide by evidence class, which is a division no earlier chapter needed. Some are about what was observed — pulses, intervals, counts — and hold unconditionally. Some are about what was concluded from an absence, and those can only be asserted together with the evidence that licenses the conclusion.
Burst transmission
// P1. A burst has exactly 33 positions.
property p_burst_has_33_positions;
@(posedge clk) disable iff (!rst_n)
burst_complete |-> ($past(position) == 6'(FLP_POSITIONS - 1));
endproperty
a_burst_33_positions: assert property (p_burst_has_33_positions);
// P2. Even positions are clock positions and always pulse; odd
// positions are data and pulse only for a one.
property p_clock_positions_always_pulse;
@(posedge clk) disable iff (!rst_n)
(burst_active && !position[0] && (tick_q == 16'd0)) |-> pulse;
endproperty
a_clock_always_pulses: assert property (p_clock_positions_always_pulse);
// P3. THE ENCODING. A data position pulses if and only if its bit is
// a one -- so a zero emits nothing at all.
property p_data_pulse_iff_one;
@(posedge clk) disable iff (!rst_n)
(burst_active && position[0] && (tick_q == 16'd0))
|-> (pulse == link_code_word[position[4:1]]);
endproperty
a_data_pulse_iff_one: assert property (p_data_pulse_iff_one);
// P4. The pulse count equals 17 plus the number of ones. A free
// consistency check on the word actually being advertised.
property p_pulse_count_matches_word;
@(posedge clk) disable iff (!rst_n)
burst_complete |-> (pulses_last_burst ==
6'(FLP_CLOCK_PULSES) + 6'($countones(link_code_word)));
endproperty
a_pulse_count_matches: assert property (p_pulse_count_matches_word);
// P5. Bursts repeat inside the legal window, which is what keeps a
// 10BASE-T partner's link up.
property p_burst_spacing_in_window;
@(posedge clk) disable iff (!rst_n)
$rose(burst_active) |-> ($past(gap_q) >= 32'(BURST_MIN_MS * 1000 * CLK_MHZ));
endproperty
a_burst_spacing: assert property (p_burst_spacing_in_window);Burst reception — decoded value AND evidence
// P6. THE PROPERTY THIS CHAPTER IS ABOUT. A bit is marked confident
// only when BOTH bracketing clock pulses arrived. This is what makes
// an absence interpretable.
property p_confidence_requires_bracketing;
@(posedge clk) disable iff (!rst_n)
(word_valid && bit_confidence[0]) |-> $past(prev_clock_ok_q, 1);
endproperty
a_confidence_requires_bracketing: assert property (p_confidence_requires_bracketing);
// P7. A fully bracketed word requires all 17 clock pulses. Sixteen
// gaps need seventeen boundaries, and a missing one leaves a bit
// unlicensed.
property p_full_bracket_needs_17_clocks;
@(posedge clk) disable iff (!rst_n)
word_fully_bracketed |-> (clock_pulses_seen == 6'(FLP_CLOCK_PULSES));
endproperty
a_full_bracket_needs_17: assert property (p_full_bracket_needs_17_clocks);
// P8. And it requires every confidence bit set.
property p_full_bracket_needs_all_confidence;
@(posedge clk) disable iff (!rst_n)
word_fully_bracketed |-> (bit_confidence == 16'hFFFF);
endproperty
a_full_bracket_all_conf: assert property (p_full_bracket_needs_all_confidence);
// P9. A clock interval outside 111..139 us is REPORTED, and it
// invalidates the bits that depend on it rather than being absorbed.
property p_clock_interval_violation_reported;
@(posedge clk) disable iff (!rst_n)
clock_interval_violation |=> !prev_clock_ok_q;
endproperty
a_clock_violation_reported: assert property (p_clock_interval_violation_reported);
// P10. A pulse beyond data_detect_max is NOT accepted as data. It is
// the next clock pulse arriving early, and accepting it shifts every
// subsequent bit.
property p_late_pulse_not_data;
@(posedge clk) disable iff (!rst_n)
late_pulse_seen |-> !$past(saw_data_q);
endproperty
a_late_pulse_not_data: assert property (p_late_pulse_not_data);
// P11. Every burst produces a word, valid or not -- the word is
// published WITH its confidence rather than withheld, because a
// partially bracketed word is still evidence.
property p_every_burst_produces_a_word;
@(posedge clk) disable iff (!rst_n)
($fell(in_burst_q) && $past(in_burst_q)) |-> word_valid;
endproperty
a_every_burst_a_word: assert property (p_every_burst_produces_a_word);Classification
// P12. A classification requires CONFIRM consistent observations.
// One burst is not a partner type.
property p_classification_needs_confirmation;
@(posedge clk) disable iff (!rst_n)
classification_changed |-> ($past(confirm_q) == 4'(CONFIRM));
endproperty
a_classification_confirmed: assert property (p_classification_needs_confirmation);
// P13. THE INTRA-GROUP PROPERTY. An FLP classification requires 17 or
// more pulses at 125 us spacing -- the repetition RATE cannot
// distinguish a burst from a link pulse, because they deliberately
// overlap.
property p_flp_needs_intra_group_structure;
@(posedge clk) disable iff (!rst_n)
(classification_changed && (classification == SIG_FLP_BURST))
|-> ((last_group_size >= 6'd17) &&
(last_intra_group_us >= 16'(CLK_SPACING_US - CLK_TOL_US)) &&
(last_intra_group_us <= 16'(CLK_SPACING_US + CLK_TOL_US)));
endproperty
a_flp_needs_structure: assert property (p_flp_needs_intra_group_structure);
// P14. Activity fitting no signature is REPORTED, never rounded to the
// nearest legal case.
property p_ambiguous_not_rounded;
@(posedge clk) disable iff (!rst_n)
ambiguous_signalling |-> (classification != SIG_FLP_BURST);
endproperty
a_ambiguous_not_rounded: assert property (p_ambiguous_not_rounded);Parallel detection — provenance
// P15. Parallel detection NEVER yields full duplex. Duplex is not
// observable on an idle link, so a detected full-duplex result has
// inferred something from nothing.
property p_detection_never_full_duplex;
@(posedge clk) disable iff (!rst_n)
detection_valid |-> !detected_duplex;
endproperty
a_detection_never_full: assert property (p_detection_never_full_duplex);
// P16. And it ALWAYS marks duplex as assumed. The provenance bit is
// not optional.
property p_detection_marks_assumption;
@(posedge clk) disable iff (!rst_n)
detection_valid |-> duplex_is_assumed;
endproperty
a_detection_marks_assumption: assert property (p_detection_marks_assumption);
// P17. While speed is marked measured -- two provenance bits, never
// one validity bit.
property p_speed_marked_measured;
@(posedge clk) disable iff (!rst_n)
detection_valid |-> speed_is_measured;
endproperty
a_speed_marked_measured: assert property (p_speed_marked_measured);
// P18. An FLP burst suppresses detection entirely. A partner that
// negotiates is never guessed about.
property p_flp_suppresses_detection;
@(posedge clk) disable iff (!rst_n)
(classification == SIG_FLP_BURST) |=> !detection_valid;
endproperty
a_flp_suppresses: assert property (p_flp_suppresses_detection);
// P19. The sticky flag survives -- a port whose duplex was never
// agreed stays flagged across link bounces.
property p_ever_detected_sticky;
@(posedge clk) disable iff (!rst_n)
ever_parallel_detected |=> ever_parallel_detected;
endproperty
a_ever_detected_sticky: assert property (p_ever_detected_sticky);Sequencing
// P20. Abilities are declared available only from a FULLY BRACKETED
// word. A partial word is a guess with a shape.
property p_abilities_need_full_bracket;
@(posedge clk) disable iff (!rst_n)
$rose(abilities_available) |-> $past(word_fully_bracketed);
endproperty
a_abilities_need_bracket: assert property (p_abilities_need_full_bracket);
// P21. A negotiated result supersedes a detection in progress.
property p_negotiation_supersedes_detection;
@(posedge clk) disable iff (!rst_n)
((stage == 3'(D_DETECT)) && (classification == SIG_FLP_BURST))
|=> (stage == 3'(D_DECODE));
endproperty
a_negotiation_supersedes: assert property (p_negotiation_supersedes_detection);
// P22. Every attempt terminates: done or failed, never neither.
property p_discovery_terminates;
@(posedge clk) disable iff (!rst_n)
(stage == 3'(D_OBSERVE)) |-> ##[1:$] ((stage == 3'(D_DONE)) ||
(stage == 3'(D_FAILED)) ||
(stage == 3'(D_IDLE)));
endproperty
a_discovery_terminates: assert property (p_discovery_terminates);
// P23. A failure names its stage, latched at the first one.
property p_failure_names_stage;
@(posedge clk) disable iff (!rst_n)
discovery_failed |-> (failed_stage inside {3'(D_CLASSIFY), 3'(D_DECODE),
3'(D_DETECT)});
endproperty
a_failure_names_stage: assert property (p_failure_names_stage);15. Verification Scenarios
Burst transmission
- A word of all zeros — the burst emits exactly 17 pulses, all at clock positions, and is bit-for-bit the same shape as seventeen clock pulses. The case that shows what the encoding actually is.
- A word of all ones — 33 pulses, the maximum.
0x00A1—pulses_last_burst = 17 + 3 = 20. P4 with a specific value.- Clock-to-clock spacing — 125 µs at every one of the sixteen intervals.
- Clock-to-data spacing — 62.5 µs for every position carrying a one.
- Burst-to-burst spacing across many bursts — always inside 8 to 16 ms.
- Spacing jitter varied — the interval changes between bursts, so two devices cannot lock into a fixed phase relationship.
enabledeasserted mid-burst — the burst is abandoned cleanly; no partial burst is emitted on the next enable.
Burst reception
- A perfect burst — 17 clock pulses in window, correct word,
bit_confidence = 0xFFFF,word_fully_bracketed. - One data pulse deleted — that bit reads zero,
bit_confidencestays 0xFFFF, and the word is confidently wrong. The scenario the whole chapter is about. - One clock pulse deleted — the two bits bracketed by it lose confidence;
word_fully_bracketedlow;c_bursts_discardedincrements. - A clock pulse at 105 µs — inside
data_detect_maxbut outside the data window and outside the clock window:clock_interval_violation. - A clock pulse at 145 µs — outside the
111 to 139 µswindow: violation, and the bits depending on it lose confidence. - A data pulse at 80 µs — beyond the
55.5 to 69.5 µswindow: not counted as data, and not counted as a clock pulse either. - A pulse at 110 µs treated as data (a deliberate mutation) — every subsequent bit shifts by one; the decoded word is a rotation of the transmitted one. P10.
- A burst truncated at position 20 — a word is still published, with
bit_confidencelow on the missing bits andword_fully_bracketedlow. - Two bursts back to back with no gap — the burst-end timer separates them; neither is merged into the other.
- A spurious pulse in a data window on a zero bit — that bit reads one. The opposite error, and it is rarer because noise must create a pulse rather than remove one.
Classification
- Well-formed FLP bursts — classified
SIG_FLP_BURSTafterCONFIRMgroups. - Single pulses every 16 ms —
SIG_LINK_PULSE. - Single pulses every 10 ms — still
SIG_LINK_PULSE; the rates overlap and the group size is what decides. - Groups of five pulses at 125 µs —
ambiguous_signalling, not rounded to either legal case. - Groups of 17 pulses at 200 µs spacing — ambiguous; the size fits but the intra-group spacing does not.
- Continuous activity with no pulses —
SIG_IDLE_SYMBOLS. - A classifier that measures only the repetition rate (deliberate mutation) — classifies bursts as link pulses with full confidence. P13, demonstrated.
- Signalling changing from none to link pulses to bursts — the classification follows, and each change requires
CONFIRMobservations.
Parallel detection
- Link pulses for the full detection window —
detection_valid, speed 10,speed_is_measured,duplex_is_assumedhigh,detected_duplexlow. - Idle symbols for the full window — speed 100, same provenance.
- An FLP burst at window − 1 ms — detection aborted;
D_DETECTmoves toD_DECODE. The slow negotiator that would otherwise be misclassified. - Signalling changing mid-window —
detection_aborted,c_abortsincrements, no detection made. - Detection followed by a link bounce and a clean renegotiation —
ever_parallel_detectedsurvives. - A detector that reports full duplex (deliberate mutation) — P15 fires.
Link integrity and sequencing
LC_COUNTconsecutive pulses —link_up,link_came_uppulses once.LC_COUNT − 1pulses then silence — link stays down.- A gap of
LINK_LOSS_MS—link_went_down,ever_link_downsticky. - A gap of
LINK_LOSS_MS − 10— link stays up, andworst_silence_msrecords the near miss. The margin, captured on a working link. - FLP bursts observed by the link-integrity monitor —
early_pulsefires on the intra-burst pulses, and the link still comes up. Backward compatibility, tested. - Discovery with no activity at all — stays in
D_IDLE; no attempt counted. - Activity that never classifies —
D_FAILEDwithfailed_stage = D_CLASSIFY. - Bursts that never fully bracket —
D_FAILEDwithfailed_stage = D_DECODE.
16. Debugging: Nothing Here Reports an Error
| Observation | Likely cause | The distinguishing check |
|---|---|---|
| link comes up one speed below what both ends support | a lost data pulse | compare pulses_last_burst at one end with data_pulses_seen at the other |
| link comes up half duplex on a full-duplex-capable pair | the same, in the duplex bit | the same comparison; also ever_low_confidence |
| link comes up at 10 Mb/s against a gigabit PHY | parallel detection fired on a slow negotiator | ever_parallel_detected, c_aborts |
| duplex mismatch symptoms with clean negotiation | duplex was assumed, not agreed | ever_assumed_duplex — one register read, before any traffic |
| discovery takes ~500 ms every time | parallel detection is being used | c_detections against c_words; a negotiated link takes tens of ms |
c_bursts_discarded rising | clock pulses being lost | the burst's timing reference is breaking; check the cable |
| decoded abilities are a rotation of the truth | a pulse accepted beyond data_detect_max | late_pulse_seen; every bit after it is shifted |
| the classifier reports link pulses against a negotiating PHY | classifying by repetition rate only | last_group_size and last_intra_group_us |
ambiguous_signalling rising | a partner mid-reset, broken, or non-standard | c_ambiguous; not a link fault |
link up, silence_margin_percent near 100 | about to drop | nothing else reports it |
Four habits.
First, read ever_assumed_duplex before investigating any duplex symptom. It is one register, available before any traffic has run, and it distinguishes the partner is half duplex from nobody ever asked. Chapter 9.2 spent a whole chapter on the consequences; this is the bit that predicts them.
Second, treat "negotiated one step below expectations" as a lost pulse until proved otherwise. A missing data pulse turns a one into a zero, a zero removes an ability, and the priority resolver cleanly selects the next option down. No error is reported by anything, at either end, because from the protocol's point of view nothing went wrong.
Third, compare pulse counts across the two ends. pulses_last_burst at the transmitter must equal 17 + popcount(word); data_pulses_seen at the receiver must equal popcount(word). A discrepancy is the only direct evidence a data pulse was lost, and it needs both ends — exactly as Chapter 9.2's duplex mismatch does.
Fourth, distinguish discarded bursts from wrong bursts. c_bursts_discarded rising means clock pulses are being lost, which the design detects. A quiet counter with a wrong result means data pulses are being lost, which it cannot.
17. Common Misconceptions
"Autonegotiation exchanges bits, so a lost bit breaks it."
The wrong model: corruption produces a failure.
What it costs: you cannot explain a link that comes up perfectly at the wrong speed.
The corrected model: a one is a pulse and a zero is nothing, so a lost pulse turns a one into a zero — and a zero in a technology-ability bit means I cannot do this. The priority resolver takes the highest common ability, so a lost bit removes an option and the link comes up at the next one down, cleanly, with both ends agreeing and no error reported anywhere. The failure mode of a marginal cable is not a broken negotiation; it is a slower one.
"The 17 clock pulses are framing overhead."
The wrong model: structure you pay for to delimit the data.
What it costs: you cannot see why a design must publish bit_confidence, and you build a receiver that cannot distinguish a zero from a loss.
The corrected model: sixteen data bits sit between seventeen clock pulses, so every data position is bracketed on both sides. That bracketing is what makes an absence interpretable: if both neighbouring clock pulses arrived, the medium was carrying pulses and the receiver was detecting them during that interval — so a missing data pulse was not sent. The ratio is the evidentiary structure, not the framing.
"An FLP burst and a link pulse are easy to tell apart — they repeat at different rates."
The wrong model: the repetition rate is the discriminator.
What it costs: a classifier that confidently identifies bursts as link pulses.
The corrected model: their rates deliberately overlap. A link pulse repeats every 16 ± 8 ms; an FLP burst repeats every 8 to 16 ms — inside the same window, on purpose, because that overlap is what makes a 1990 10BASE-T device bring its link up against a negotiating partner. The only discriminator is the intra-group structure: 17 or more pulses 125 µs apart versus a single pulse.
"Parallel detection determines the partner's configuration."
The wrong model: it works out what the partner is.
What it costs: Chapter 9.2's duplex mismatch, with no way to predict it.
The corrected model: it determines speed and assumes duplex, and the two have completely different evidentiary status. Speed is measured — link pulses and continuous idle symbols are entirely different signals. Duplex is unobservable, because duplex describes how a device behaves when it transmits and an idle link exhibits none of that. The standard assumes half, which is safe against a partner that might collide — and a link whose duplex was assumed is permanently a mismatch candidate, which is why the flag is sticky.
"If the link came up, discovery succeeded."
The wrong model: link up means the right answer was found.
What it costs: the three quiet failures above, indefinitely.
The corrected model: discovery can produce a working link and a wrong answer in at least three ways — a lost data pulse negotiating downward, a slow negotiator parallel-detected as a legacy device, and an assumed duplex that has not yet met load. All three report success. The distinguishing signals are provenance (ever_assumed_duplex, speed_is_measured) and margin (worst_low_confidence_bits, worst_discovery_ms, silence_margin_percent) — none of which is an error, and all of which are invisible unless collected.
18. Interview Reasoning
"Why does an FLP burst have 33 positions rather than 32?"
Because sixteen data bits need seventeen boundaries. The burst alternates clock and data positions — clock, data, clock, data, …, clock — so every data position is bracketed by a clock pulse on each side. The strong answer says why that matters: a one is a pulse and a zero is an absence, and an absence has three causes — the partner sent a zero, the partner sent a one that was lost, or the partner stopped transmitting. The bracketing clock pulses are what disambiguate them. If both arrived within the 111 to 139 µs window, the medium was carrying pulses and the receiver was detecting them during that interval, so a missing data pulse was not sent rather than lost. The finishing point: the extra clock pulse is not framing overhead — it is the evidence that makes half the encoding's symbols interpretable at all, and a receiver that does not publish a per-bit confidence has thrown it away.
"What happens to autonegotiation on a marginal cable?"
It negotiates downward, silently, with no error reported by anything. A lost pulse turns a one into a zero; a zero in a technology-ability bit means I cannot do this; and Chapter 9.2's priority resolver takes the highest common ability, so removing an option cleanly selects the next one down. Both ends agree, the link comes up, and every counter is clean. The strong answer names the asymmetry behind it: errors are one-directional. A one can be lost and become a zero; a zero becomes a one only through a spurious detection, which is much rarer. So a marginal link is biased toward advertising fewer abilities than the partner actually has. The finishing observation: the only direct evidence is a cross-end comparison — the transmitter's pulse count is 17 + popcount(word) and the receiver's data-pulse count should be popcount(word).
"How do you distinguish an FLP burst from a 10BASE-T link pulse?"
Not by the repetition rate, because they deliberately overlap. A link pulse repeats every 16 ± 8 ms and an FLP burst every 8 to 16 ms — inside the same window on purpose, because that overlap is exactly what makes a 1990 device count pulses at the rate it expects and bring its link up against a protocol that did not exist when it was built. The discriminator is the fine structure: a burst is 17 or more pulses spaced 125 µs apart; a link pulse is one pulse followed by milliseconds of silence. So the classifier must measure at two scales simultaneously — microseconds within a group, milliseconds between groups — and one that measures only the millisecond rate will classify bursts as link pulses with complete confidence. The finishing point: the cost of backward compatibility lands entirely on the new device, which is the general shape of a compatible extension.
"Would you assert that a data position with no pulse decodes to zero?"
No — it is a tautology, and its cost is what it displaces. The decoder assigns the bit from whether a pulse was seen, so the property checks that one line of RTL agrees with itself; both sides of the comparison are mine. And the thing it appears to be about — that an absence on the wire means a transmitted zero — is not checkable inside the receiver at all, because an absence has three causes and the receiver's entire input is the absence. The real damage is that a design graded on it has no reason to compute a per-bit confidence, so it ships unable to distinguish a partner that advertised fewer abilities from a cable that lost the pulses saying otherwise. Assert the evidence instead: a bit is confident only when both bracketing clock pulses arrived in window; a fully-bracketed word requires all seventeen; a clock interval outside tolerance invalidates the bits depending on it; a pulse beyond data_detect_max is not data; and every burst publishes its word together with its confidence. The test: what else, besides the thing I am concluding, would produce this observation?
19. Understanding Check
Because sixteen gaps need seventeen boundaries, and every data bit must be bracketed on both sides.
The burst alternates: clock, data, clock, data, …, clock — 17 clock positions and 16 data positions, in 33 slots.
And the reason it matters is the encoding. A one is a pulse arriving in the window 62.5 ± 7 µs after the preceding clock pulse. A zero is nothing at all — which is a conclusion drawn from an absence.
| Evidence | Conclusion |
|---|---|
| both bracketing clock pulses present, no data pulse | a transmitted zero |
| both bracketing clock pulses present, data pulse present | a transmitted one |
| a bracketing clock pulse missing | unknown — not zero |
If both neighbouring clock pulses arrived within the 111 to 139 µs window, then during the interval between them the medium was demonstrably carrying pulses and this receiver was demonstrably detecting them. So a missing data pulse in that interval was not sent, rather than lost.
The extra clock pulse is not framing overhead — it is what makes half the encoding's symbols interpretable, and a receiver that publishes only the decoded word has discarded it.
20. What's Next
The claim this chapter defended: when a zero is encoded as an absence, no property about the decoded value is assertable on its own.
Before anything is agreed, two devices can observe only pulses — and the FLP burst puts sixteen bits into presence and absence at defined positions, 125 µs apart, with data windows at 62.5 ± 7 µs. A one is measured. A zero is inferred. And an absence has three causes: a transmitted zero, a lost pulse, or a partner that stopped.
Which is why the burst carries seventeen clock pulses for sixteen data bits. Sixteen gaps need seventeen boundaries, every data position is bracketed, and two arriving clock pulses are what license reading the silence between them as a transmitted zero. The ratio is the evidentiary structure, and a receiver that publishes only the word has thrown it away.
The same shape appears again at link scale. Duplex is signalled by nothing at all, so parallel detection reports speed as a measurement and duplex as an assumption — two provenance bits, never one validity bit, and ever_parallel_detected sticky because a link whose duplex was never agreed stays a mismatch candidate forever.
And the failures do not fail. A lost data pulse negotiates one step down, cleanly, with both ends agreeing and no counter moving. A slow negotiator gets parallel-detected as a legacy device. An assumed duplex works perfectly until load arrives. All three report success, which is why this chapter's telemetry is provenance and margin rather than error counts.
Chapter 11.2 — Ability Advertisement and Priority Resolution takes the sixteen bits this chapter delivered and asks what they mean.
Chapter 9.2 §4 gave a four-technology arbiter; 11.2 gives the full link code word field by field — the selector, the eight technology-ability bits, remote fault, acknowledge and next page — the complete priority table, and the acknowledge mechanism, which requires three identical copies received before it is set and six to eight sent before the exchange completes. And its hard claim is one this chapter has already brushed against: the protocol exchanges abilities and never conclusions. Each end resolves its own table, independently, and nothing anywhere confirms that the two resolutions agree — which is the gap Chapter 9.2's duplex mismatch lives in, and the one 11.2's rejected property is about.
The full path is on the Ethernet curriculum index.
Continue learning
Related tutorials
- Related topic
Link Establishment
A link climbs a ladder of six gates, each attemptable only once the one below it has succeeded. Every gate is a search whose duration depends on the evidence available to it, which is why bring-up time is a distribution and why the only useful question is which gate it stalled at.
- Related topic
Ability Advertisement and Priority Resolution
The protocol exchanges abilities and never conclusions. Each end resolves against its own copy of a priority table that is never transmitted, and acknowledge confirms receipt of an advertisement and nothing more.
- Related topic
Full Link Bring-Up Sequence
Eight stages from power-on to a legally transmitting MAC, and the ordering is a data dependency. Each stage measures what the next assumes, and a stage run early does not fail — it succeeds against garbage.
- Related topic
Negotiation Failures and Duplex Mismatch
Seven ways a link comes up wrong and six of them report no error, because every device behaved correctly. Diagnosis is set narrowing over evidence, and three causes cannot be seen from one end at all.
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.
