Ethernet · Module 3
Fibre Ethernet
Separate strands per direction delete the echo problem that shapes copper PHYs, and introduce two others: a conversion boundary inside a pluggable module you do not own, and a link that can break in one direction while the far end still reports perfect health.
Chapter 3.1 built its whole architecture around one problem: a twisted pair carries both directions at once, so a receiver hears its own transmitter louder than the far end, and every gigabit copper PHY is organised around cancelling that echo.
Fibre deletes that problem outright. An optical link uses two strands, one per direction. Transmit and receive never share a path, so there is no echo, no hybrid, and no cancellation. A fibre PHY is architecturally simpler than a BASE-T PHY of the same rate, and measurably lower latency for exactly that reason.
So the interesting question is not what fibre solves. It is what fibre introduces in exchange — and there are two things, both of which shape the RTL you will write.
First, the signal has to change domain. Electrons drive a light source; light crosses glass; a detector turns it back into current. That conversion happens in hardware which, in most systems, sits in a pluggable module on the other side of a connector — not on your board and not in your silicon.
Second, because the two directions are physically separate, they can fail separately. A copper pair breaks and both ends generally notice. A fibre strand breaks and only one end notices, while the other keeps transmitting into a link it believes is healthy.
What does an optical link cost in exchange for removing the echo, and what must the digital design do about it?
1. Scope — What This Chapter Owns
This chapter owns: why fibre has no echo problem, the electrical-to-optical conversion in both directions, the module boundary and what it does to a design's interface, multimode against single-mode and why reach differs, the loss budget, the one-sided failure and remote-fault signalling, and per-lane optical status as an architecture.
This chapter does not own: the physics of light propagation, laser design, or detector physics beyond what a digital designer needs to reason about the boundary. It does not own the line codes carried over fibre — Chapter 3.5 owns those — nor the sublayer split, which Chapter 3.4 develops properly. Lane alignment and deskew across a multi-lane optical link belong to Chapter 3.8.
The test applied throughout: does an RTL or verification engineer need this to reason about the interface they integrate against? Laser rate equations do not pass. Knowing that a transmit fault must disable the transmitter, and why, does.
2. The Transmit Side — Electrons to Light
The digital side hands the optical front end a serial electrical stream, exactly as Chapter 2.6's PMA produced. What happens next is a conversion.
A driver turns that stream into a modulated current, and the current drives a light source — in short-reach parts typically a vertical-cavity surface-emitting laser, in longer-reach parts an edge-emitting laser, and in the oldest and slowest optical Ethernet variants a light-emitting diode. The source's optical power follows the drive current, so a serial bit stream becomes a serial intensity stream: more light for one symbol value, less for another.
Three consequences for the digital designer follow immediately:
- The light source is not simply on or off. It is biased so that even a "zero" carries some light, because a source driven fully off takes time to turn back on, and that delay would distort every edge. The ratio between the two levels — the extinction ratio — is a real specified parameter, and it is why an optical zero is not darkness.
- The source needs continuous control. Its output for a given current drifts with temperature and with age. Optical modules therefore contain a closed loop that monitors output and adjusts bias, which is one of the things behind a module's status reporting.
- A failed source must be turned off, quickly and by hardware. A laser that has failed in a way that produces excess optical power is an eye-safety hazard. This is why every optical module exposes a transmit-fault indication and accepts a transmit-disable input, and why Section 8's controller treats that path as the highest-priority behaviour in the design rather than as one status bit among several.
3. The Receive Side — Light Back to Electrons
At the far end, a photodetector converts incident light into a current. That current is tiny — this is the end of a link that may have crossed kilometres — so it is immediately amplified by a transimpedance amplifier, then a limiting amplifier, and only then is it a signal a clock and data recovery circuit can work with.
Read the two rows as two independent links. They share a module and a connector and nothing else. That separation is the whole reason there is no echo — and, in Section 12, the whole reason a link can fail in one direction only.
What the receive side gives the digital design is one signal that has no copper equivalent: loss of signal. The detector can report that the incident optical power has fallen below a usable threshold. That is a genuinely different claim from "I cannot recover a clock" or "I cannot achieve block lock", and keeping the three apart is what makes an optical link diagnosable.
| Indication | What it claims | What it points at |
|---|---|---|
| loss of signal | not enough light is arriving | the fibre, the connectors, or the far-end transmitter |
| no clock lock | light is arriving but timing cannot be recovered | signal quality, or a rate mismatch |
| no block lock | timing recovered but the coding does not parse | wrong PHY type, or a coding fault |
A design that reports only "link down" has thrown away a three-way partition that would otherwise have named the responsible subsystem before anyone walked to the rack.
4. Multimode, Single-Mode, and Where the Reach Difference Comes From
Two fibre types dominate, and the difference is the diameter of the core the light travels down.
Multimode fibre has a relatively large core. Light can take several distinct paths — modes — down it, and because those paths have different lengths, energy launched at one instant arrives spread over a span of time. That spreading is modal dispersion, and it grows with distance. Past some length the spreading from one symbol overlaps the next, and the receiver can no longer separate them. Multimode's reach limit is dispersion, not power.
Single-mode fibre has a core small enough that essentially one path exists. Modal dispersion largely vanishes, and reach extends to kilometres.
The published figures show the pattern clearly, and they show something else worth noticing:
| Variant | Wavelength | Fibre | Reach |
|---|---|---|---|
| 1000BASE-SX | 770 to 860 nm | multimode | 275 to 550 m, by fibre grade |
| 1000BASE-LX | 1270 to 1355 nm | single-mode | up to 5 km |
| 10GBASE-SR | 850 nm | multimode | 33 m (OM1), 82 m (OM2), 300 m (OM3), 400 m (OM4) |
| 10GBASE-LR | 1310 nm | single-mode | 10 km |
| 100BASE-FX | 1300 nm | multimode | — |
The consequence for a system designer is that the choice of fibre type is a commitment made in the building, and the choice of module must match it. A short-reach module on single-mode fibre and a long-reach module on multimode fibre both fail, in different ways, and neither failure is visible to any RTL you will write. Which is exactly why Section 10's module identity matters.
5. Reach as a Loss Budget
Copper's reach was a budget in Chapter 3.1, balanced against loss, reflections and crosstalk. Optical reach is a budget too, and a simpler one to state because crosstalk and echo are absent.
The inequality: the optical power launched by the transmitter, minus everything the path takes away, must exceed the minimum power the receiver needs to decide reliably.
| What consumes the budget | Depends on |
|---|---|
| fibre attenuation | length, and the fibre's loss per kilometre at this wavelength |
| connector loss | how many connections the path contains, and how clean they are |
| splice loss | how many splices, and their quality |
| dispersion penalty | length and fibre type — this is what limits multimode |
| ageing and temperature margin | how much headroom the design reserves |
Three things follow that a digital designer should carry:
- A link that works today and fails in a year has usually not broken. It has run out of margin. Sources age and lose output; connectors accumulate contamination. If the design left no headroom, ordinary ageing crosses the threshold.
- Connectors dominate short links. On a 30-metre run inside a building, the fibre's own attenuation is nearly nothing and the patch panels are nearly everything. A dirty connector is the single most common optical fault, and it is invisible to every register in the system.
- Too much power is also a failure. A receiver has a maximum as well as a minimum. Putting a long-reach module on a short link can saturate the far-end receiver, and the symptom — errors on a short link that a longer one carries fine — is thoroughly counter-intuitive and wastes hours.
6. The Module Boundary — Where Your Design Stops
In most systems the optical front end is not on the board. It is a pluggable transceiver: the host provides a cage and a connector, and the module provides everything from the electrical interface outward.
That connector is your design's real boundary, and it changes what "the PHY" means for verification.
| What is inside the module | What your design owns |
|---|---|
| laser driver, light source, bias control | the serial electrical stream to and from it |
| photodetector, amplifiers | interpreting its status indications |
| in many modules, the clock and data recovery | presence detection and safe defaults |
| the identity of the module and its reach class | the decision to enable transmit at all |
| its own temperature, power and optical monitors | reading, timestamping and reporting all of the above |
Four things the module boundary makes possible that no copper PHY has to handle:
- Absence. A cage can be empty. The design must behave sanely with no module present, and "sanely" specifically means not enabling transmit into nothing and not reporting a link fault when the correct report is "no module".
- Hot insertion and removal. A module can appear or vanish during operation. Presence is a live signal, not a boot-time constant.
- Mismatch. A module can be present, healthy, and wrong — the wrong reach class, the wrong wavelength, the wrong rate. Every status bit reads healthy and the link does not work.
- Independent failure. The module can fail while the host silicon is perfect, and the reverse. Attributing a fault to the correct side of the connector is the single most valuable thing an optical status design does.
7. RTL 1 — The Lane Supervisory Controller
This is the block that sits against the module boundary. Its job is to decide when it is safe and sensible to transmit, when a lane may be declared usable, and — most importantly — to never conflate those two decisions.
Two features of the topology are load-bearing. FAULT returns to PRESENT, not to ENABLED — a cleared transmit fault must not silently re-enable a laser; the enable decision is made again, deliberately. And READY falls back to ENABLED rather than to PRESENT on signal loss, because losing the far end's light says nothing about our own transmitter, which should keep running so the far end can still hear us. That asymmetry is Section 12's whole subject.
// SYNTHESIZABLE. Supervises one optical lane across a module boundary.
//
// The rule this module exists to enforce: TRANSMIT SAFETY AND RECEIVE
// READINESS ARE INDEPENDENT AXES. Losing the far end's light must not stop
// our transmitter -- if it did, one broken strand would take down both
// directions and the far end could never tell us what it sees.
package optical_pkg;
typedef enum logic [2:0] {
LANE_ABSENT = 3'd0, // no module in the cage
LANE_PRESENT = 3'd1, // module present, transmit not enabled
LANE_ENABLED = 3'd2, // transmitting; not yet receiving usefully
LANE_READY = 3'd3, // transmitting and receiving
LANE_FAULT = 3'd4 // transmit fault; source forced off
} lane_state_e;
endpackage
module optical_lane_supervisor
import optical_pkg::*;
(
input logic clk,
input logic rst_n,
// From the module, across the connector.
input logic module_present, // presence detect
input logic tx_fault, // the module reports its source has failed
input logic rx_los, // loss of signal: not enough light arriving
input logic cdr_lock, // timing recovered from the received signal
// From the host: policy, not status.
input logic admin_enable, // operator or software permits this lane up
output lane_state_e state,
// Drives the module's transmit-disable pin, active high to DISABLE.
// Combinational in the fault term on purpose -- see the comment below.
output logic tx_disable,
output logic lane_ready, // safe for the PCS to use
output logic fault_latched, // sticky: a fault occurred
output logic removed_while_up // sticky: module pulled during service
);
lane_state_e state_q, state_d;
always_comb begin
state_d = state_q;
unique case (state_q)
LANE_ABSENT: if (module_present) state_d = LANE_PRESENT;
LANE_PRESENT: begin
if (!module_present) state_d = LANE_ABSENT;
else if (tx_fault) state_d = LANE_FAULT;
else if (admin_enable) state_d = LANE_ENABLED;
end
LANE_ENABLED: begin
if (!module_present) state_d = LANE_ABSENT;
else if (tx_fault) state_d = LANE_FAULT;
else if (!admin_enable) state_d = LANE_PRESENT;
else if (!rx_los && cdr_lock) state_d = LANE_READY;
end
LANE_READY: begin
if (!module_present) state_d = LANE_ABSENT;
else if (tx_fault) state_d = LANE_FAULT;
else if (!admin_enable) state_d = LANE_PRESENT;
// Fall back to ENABLED, NOT to PRESENT. Our transmitter keeps
// running: the far end losing us and us losing the far end are
// different events, and only one of them is happening.
else if (rx_los || !cdr_lock) state_d = LANE_ENABLED;
end
LANE_FAULT: begin
if (!module_present) state_d = LANE_ABSENT;
// Return to PRESENT, not ENABLED. A cleared fault must not silently
// re-enable an optical source; the enable decision is made again.
else if (!tx_fault) state_d = LANE_PRESENT;
end
default: state_d = LANE_ABSENT;
endcase
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state_q <= LANE_ABSENT;
fault_latched <= 1'b0;
removed_while_up <= 1'b0;
end else begin
state_q <= state_d;
if (tx_fault) fault_latched <= 1'b1;
// A module pulled while the lane was carrying traffic is an
// operational event worth distinguishing from one that was never in.
if ((state_q == LANE_READY) && !module_present) removed_while_up <= 1'b1;
end
end
assign state = state_q;
assign lane_ready = (state_q == LANE_READY);
// Transmit is disabled unless EVERY condition permits it. The tx_fault
// term is combinational rather than state-derived so that a fault turns
// the source off in the same cycle it is reported, without waiting for a
// state transition. This is the one place in the module where a
// registered response would be wrong.
assign tx_disable = tx_fault
|| !module_present
|| !admin_enable
|| (state_q == LANE_ABSENT)
|| (state_q == LANE_FAULT);
endmoduleClassification: synthesizable.
What it teaches: that transmit and receive are independent axes and must not be collapsed. The single most damaging simplification available here is to disable transmit on rx_los — it looks tidy, it "cleans up" a link that is not working, and it makes the unidirectional failure of Section 12 undiagnosable by converting a one-sided fault into a two-sided one.
Deliberately simplified: the module interface. Real transceivers present presence, fault and loss-of-signal on specific pins with defined electrical behaviour, plus a management bus whose register layout is defined by the form factor's own specification rather than by IEEE 802.3. This model takes clean synchronous inputs and deliberately invents no register map.
Production implication: tx_disable is asserted by default and released only when everything permits. Optical sources are an eye-safety matter, and a design whose default is "on" has the polarity of that argument backwards. Assert the safe state at reset and require positive evidence to leave it.
Later ownership: how a lane's readiness combines with block lock and alignment to declare a link up belongs to Chapter 3.8.
8. RTL 2 — Multi-Lane Fault Aggregation
Higher-rate optical Ethernet distributes one data stream across several optical lanes. That makes lane aggregation a first-class problem, and it is a different problem from Chapter 3.1's pair aggregation in one important respect: the failing lane's identity is a physical claim about one fibre or one optical channel, and preserving it is what makes a repair possible.
// SYNTHESIZABLE. Combines per-lane optical state into a link verdict, and
// -- the part that matters -- attributes failure to a SIDE of the boundary.
//
// Three causes, three owners, three different next actions:
// PLANT : fibre, connectors, or the far-end transmitter
// MODULE : the transceiver in our own cage
// HOST : our silicon, our enable policy, our electrical interface
module optical_link_aggregator
import optical_pkg::*;
#(
parameter int unsigned LANES = 4
) (
input logic clk,
input logic rst_n,
input lane_state_e lane_state [LANES],
input logic [LANES-1:0] lane_rx_los,
input logic [LANES-1:0] lane_tx_fault,
input logic [LANES-1:0] lane_present,
input logic admin_enable,
output logic link_candidate, // every lane usable
// Preserved per-lane visibility. Never reduced to a single bit.
output logic [LANES-1:0] ready_mask,
output logic [LANES-1:0] failing_mask,
// Attribution. At most one is asserted; all low means no failure.
output logic cause_plant,
output logic cause_module,
output logic cause_host,
// Captured on the down edge, held until the link recovers.
output logic [LANES-1:0] down_lanes,
output logic down_valid
);
logic [LANES-1:0] ready_c;
logic all_ready_c;
logic candidate_q;
always_comb begin
for (int unsigned l = 0; l < LANES; l++) ready_c[l] = (lane_state[l] == LANE_READY);
all_ready_c = &ready_c;
end
assign failing_mask = ~ready_c;
// Attribution priority is deliberate and is the module's core claim.
//
// A missing module explains everything downstream of it, so it is checked
// first: reporting "no light" when the cage is empty sends an engineer to
// the fibre plant for a fault that is in their hand. A transmit fault is
// likewise a definite statement about OUR module. Only when the module is
// present and healthy does loss of signal become evidence about the plant.
always_comb begin
cause_host = 1'b0;
cause_module = 1'b0;
cause_plant = 1'b0;
if (!all_ready_c) begin
if (!(&lane_present)) cause_module = 1'b1; // absent or partial
else if (|lane_tx_fault) cause_module = 1'b1; // our source failed
else if (!admin_enable) cause_host = 1'b1; // we chose not to
else if (|lane_rx_los) cause_plant = 1'b1; // no light arriving
else cause_host = 1'b1; // lock or datapath
end
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
candidate_q <= 1'b0;
down_lanes <= '0;
down_valid <= 1'b0;
end else begin
candidate_q <= all_ready_c;
// Down edge only. Sampling continuously would record recovery.
if (candidate_q && !all_ready_c) begin
down_lanes <= ~ready_c;
down_valid <= 1'b1;
end
end
end
assign link_candidate = candidate_q;
assign ready_mask = ready_c;
endmoduleClassification: synthesizable.
What it teaches: that fault attribution is a design output, not something an engineer derives afterwards. The priority ordering encodes real reasoning: an absent module explains every downstream symptom, so it must be reported first, or the design will send someone to inspect fibre for a fault they are holding.
Deliberately simplified: no per-lane attribution — the module reports one cause for the link. Production designs often attribute per lane, which matters when lanes are carried on physically separate fibres and only one is damaged.
Production implication: cause_plant is the only one of the three that requires physical access to investigate, and it is the most expensive. Getting the priority wrong so that a host-side or module-side fault is reported as cause_plant dispatches a technician for a fault that a register read would have found. That is a real operational cost, produced by four lines of priority ordering.
Later ownership: lane deskew and alignment markers belong to Chapter 3.8.
9. The One-Sided Failure, and Why Fibre Needs Remote Fault
This is the failure mode that has no clean copper analogue, and it follows directly from Section 3's two independent rows.
A fibre pair is two strands. Suppose the strand carrying light from the far end to us is broken — a bad connector, a bend, a damaged patch cord — while the strand carrying our light to them is perfect.
What each end sees:
| Our end | The far end | |
|---|---|---|
| receiving light? | no — loss of signal | yes, perfectly |
| transmitting? | yes, and it is arriving | yes, and it is going nowhere |
| what our own status says | receive fault | everything healthy |
| what it does next | stops using the link | keeps sending traffic into a hole |
The far end has no local evidence of any problem at all. Its transmitter works, its receiver works, its lock is solid. Nothing in its data path can detect this, because from its side nothing is wrong.
The only way it can learn is for us to tell it — and we can, because our transmit direction still works. That is remote fault signalling: the end that can still transmit reports what it is failing to receive, and the far end learns from the report rather than from observation.
Three design consequences, and Section 7's RTL already encodes the first:
- Never disable transmit because receive failed. If the end that loses signal also turns its transmitter off, the fault becomes symmetric, the report never gets sent, and both ends are left guessing. Section 7's
LANE_READYtoLANE_ENABLEDfallback exists precisely to keep the transmitter running. This is the single most important rule in the chapter. - Local fault and remote fault are different indications. "I cannot hear you" and "you told me you cannot hear me" point at opposite ends of the link, and a design that merges them into one fault bit has destroyed the direction information — which is the only useful thing either of them carries.
- The far end's fault clears on a different schedule. When the broken strand is repaired, our end recovers on its own evidence. The far end recovers when we stop reporting the fault. The two do not happen in the same cycle, and a design that assumes symmetry will oscillate.
10. RTL 3 — Local and Remote Fault
// SYNTHESIZABLE. Local and remote fault state.
//
// LOCAL fault: we cannot receive. Something between the far-end
// transmitter and our detector is broken.
// REMOTE fault: the far end told us IT cannot receive. Something between
// our transmitter and its detector is broken.
//
// These name OPPOSITE halves of the link. Merging them into one bit throws
// away the direction, which is the only useful thing either one carries.
module fault_signalling #(
// Consecutive cycles a condition must hold before it is signalled, and
// before it is withdrawn. Both directions are debounced: a fault that
// chatters is worse than one that is reported a little late.
parameter int unsigned ASSERT_HOLD = 64,
parameter int unsigned CLEAR_HOLD = 256,
parameter int unsigned CNT_W = 16
) (
input logic clk,
input logic rst_n,
input logic rx_usable, // we are receiving and locked
input logic remote_fault_seen, // the far end is reporting a fault to us
input logic lane_enabled, // our transmitter is running
output logic local_fault, // we cannot receive
output logic remote_fault, // the far end cannot receive us
output logic signal_remote, // transmit a fault report to the far end
output logic link_usable, // neither direction is faulted
// Sticky history. During an outage both may eventually be set; which one
// came FIRST identifies which strand broke.
output logic first_was_local,
output logic first_fault_valid
);
logic [CNT_W-1:0] lf_assert_q, lf_clear_q;
logic [CNT_W-1:0] rf_assert_q, rf_clear_q;
logic local_q, remote_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
lf_assert_q <= '0; lf_clear_q <= '0;
rf_assert_q <= '0; rf_clear_q <= '0;
local_q <= 1'b0;
remote_q <= 1'b0;
first_was_local <= 1'b0;
first_fault_valid <= 1'b0;
end else begin
// ── local: debounce in both directions ──────────────────────────────
if (!rx_usable) begin
lf_clear_q <= '0;
lf_assert_q <= (lf_assert_q < CNT_W'(ASSERT_HOLD)) ? lf_assert_q + 1'b1
: lf_assert_q;
if (lf_assert_q >= CNT_W'(ASSERT_HOLD)) local_q <= 1'b1;
end else begin
lf_assert_q <= '0;
lf_clear_q <= (lf_clear_q < CNT_W'(CLEAR_HOLD)) ? lf_clear_q + 1'b1
: lf_clear_q;
if (lf_clear_q >= CNT_W'(CLEAR_HOLD)) local_q <= 1'b0;
end
// ── remote: same shape, independent counters ────────────────────────
if (remote_fault_seen) begin
rf_clear_q <= '0;
rf_assert_q <= (rf_assert_q < CNT_W'(ASSERT_HOLD)) ? rf_assert_q + 1'b1
: rf_assert_q;
if (rf_assert_q >= CNT_W'(ASSERT_HOLD)) remote_q <= 1'b1;
end else begin
rf_assert_q <= '0;
rf_clear_q <= (rf_clear_q < CNT_W'(CLEAR_HOLD)) ? rf_clear_q + 1'b1
: rf_clear_q;
if (rf_clear_q >= CNT_W'(CLEAR_HOLD)) remote_q <= 1'b0;
end
// Which came first identifies the broken strand, and by the time an
// operator looks both are usually set.
if (!first_fault_valid && (local_q || remote_q)) begin
first_fault_valid <= 1'b1;
first_was_local <= local_q;
end
end
end
assign local_fault = local_q;
assign remote_fault = remote_q;
// We report our LOCAL fault outward, and we can only do so while our own
// transmitter is running -- which is exactly why Section 7 never disables
// transmit on a receive failure.
assign signal_remote = local_q && lane_enabled;
assign link_usable = !local_q && !remote_q;
endmoduleClassification: synthesizable.
What it teaches: that the two fault directions are separate state with separate debouncing, and that reporting a local fault depends on the transmitter still running. signal_remote is gated on lane_enabled, which makes the dependency explicit in the code rather than leaving it as a convention someone can break later.
Deliberately simplified: no encoding. What a local fault looks like on the wire is coding-specific — ordered sets on some PHY families, coded control blocks on others — and Chapter 3.5 owns the representation.
Production implication: the clear hold is deliberately four times the assert hold. A link recovering from a marginal fault will hover near the threshold, and asymmetric debouncing means it reports the fault quickly and withdraws it only once recovery is convincing. Symmetric debouncing produces a link that flaps, and every layer above pays for each flap.
Later ownership: the on-the-wire representation belongs to Chapter 3.5; how fault state gates link-up belongs to Chapter 3.8.
11. RTL 4 — Module Status, Without Inventing a Register Map
A pluggable module reports temperature, supply voltage, transmit optical power, receive optical power and its own identity over a management interface. The layout of that interface is defined by the module form factor's own specification — not by IEEE 802.3 — and it differs between form factors.
So this model deliberately does not encode one. What it models is the part that is common to all of them and that designs get wrong regardless of which one they use: management data is sampled, slow, and can be stale, and treating it as if it were live status causes real misdiagnosis.
// SYNTHESIZABLE. A shadow of a slow management channel, with staleness
// tracked explicitly.
//
// The failure this prevents: a management read takes milliseconds, but the
// fast status pins change in microseconds. Software that reads a shadow
// register and believes it is live will diagnose a link from a snapshot
// taken before the event it is investigating.
module module_status_shadow #(
parameter int unsigned FIELDS = 4, // temp, vcc, tx power, rx power
parameter int unsigned FIELD_W = 16,
// Cycles after which a sampled value is considered stale.
parameter int unsigned STALE_AFTER = 100_000,
parameter int unsigned AGE_W = $clog2(STALE_AFTER + 1)
) (
input logic clk,
input logic rst_n,
input logic module_present,
// From the (unmodelled) management-bus controller.
input logic sample_valid,
input logic [FIELD_W-1:0] sample_data [FIELDS],
input logic sample_error, // the read did not complete
output logic [FIELD_W-1:0] shadow [FIELDS],
output logic shadow_valid, // has ever been populated
output logic shadow_stale, // too old to reason from
output logic [AGE_W-1:0] shadow_age, // cycles since last good sample
// Read failures are their own signal: a module that stops answering its
// management bus is failing, even while its fast status pins look fine.
output logic [15:0] mgmt_error_count
);
logic [AGE_W-1:0] age_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
for (int unsigned f = 0; f < FIELDS; f++) shadow[f] <= '0;
shadow_valid <= 1'b0;
age_q <= '0;
mgmt_error_count <= '0;
end else if (!module_present) begin
// The module left. Its readings describe a component that is no longer
// in the system, and holding them invites reasoning from them.
shadow_valid <= 1'b0;
age_q <= '0;
end else begin
if (sample_valid && !sample_error) begin
for (int unsigned f = 0; f < FIELDS; f++) shadow[f] <= sample_data[f];
shadow_valid <= 1'b1;
age_q <= '0;
end else begin
if (age_q < AGE_W'(STALE_AFTER)) age_q <= age_q + 1'b1;
end
if (sample_error && !(&mgmt_error_count)) begin
mgmt_error_count <= mgmt_error_count + 1'b1;
end
end
end
assign shadow_age = age_q;
assign shadow_stale = shadow_valid && (age_q >= AGE_W'(STALE_AFTER));
endmoduleClassification: synthesizable.
What it teaches: the staleness discipline. Every value from a slow management channel is a sample with an age, and exposing the age alongside the value is what lets software know whether the reading describes the event it is investigating or a moment several milliseconds before it.
Deliberately simplified: no register map, no bus protocol, no field semantics. Those belong to the form factor's specification and inventing them here would teach something false.
Production implication: clearing shadow_valid when the module leaves is not fussiness. A held reading from a removed module is worse than no reading — it describes a component that is no longer in the system, and it looks exactly like a live one. mgmt_error_count is the second half of the same idea: a module that stops answering its management bus is failing, and that failure is invisible on the fast status pins.
12. RTL 5 — Optical Telemetry
// SYNTHESIZABLE INSTRUMENTATION.
//
// Each counter answers a specific operational question:
// c_los -> is light arriving intermittently? (plant, connectors)
// c_lock_loss -> is light arriving but marginal? (signal quality)
// c_tx_fault -> is our own source failing? (module)
// c_insert/remove-> is the module seated properly? (mechanical)
// c_local/remote -> which STRAND is broken? (direction)
module optical_telemetry #(
parameter int unsigned CNT_W = 24
) (
input logic clk,
input logic rst_n,
input logic clear,
input logic ev_los, // rising edge of loss of signal
input logic ev_lock_loss,
input logic ev_tx_fault,
input logic ev_module_insert,
input logic ev_module_remove,
input logic ev_local_fault,
input logic ev_remote_fault,
input logic ev_mgmt_error,
output logic [CNT_W-1:0] c_los,
output logic [CNT_W-1:0] c_lock_loss,
output logic [CNT_W-1:0] c_tx_fault,
output logic [CNT_W-1:0] c_insert,
output logic [CNT_W-1:0] c_remove,
output logic [CNT_W-1:0] c_local_fault,
output logic [CNT_W-1:0] c_remote_fault,
output logic [CNT_W-1:0] c_mgmt_error,
// Survives `clear`: these are properties of the installation, not of the
// measurement window an operator happened to choose.
output logic ever_ready,
output logic ever_tx_faulted
);
function automatic logic [CNT_W-1:0] bump(input logic [CNT_W-1:0] v,
input logic en);
bump = (en && !(&v)) ? (v + 1'b1) : v; // saturating
endfunction
input logic lane_ready_now; // placeholder removed below
endmoduleThat last module has a deliberate defect, and it is the one this section is really about.
// SYNTHESIZABLE INSTRUMENTATION. Corrected: the stray port declaration is
// gone, and lane_ready_now is where it belongs -- in the port list.
module optical_telemetry #(
parameter int unsigned CNT_W = 24
) (
input logic clk,
input logic rst_n,
input logic clear,
input logic ev_los,
input logic ev_lock_loss,
input logic ev_tx_fault,
input logic ev_module_insert,
input logic ev_module_remove,
input logic ev_local_fault,
input logic ev_remote_fault,
input logic ev_mgmt_error,
input logic lane_ready_now,
output logic [CNT_W-1:0] c_los,
output logic [CNT_W-1:0] c_lock_loss,
output logic [CNT_W-1:0] c_tx_fault,
output logic [CNT_W-1:0] c_insert,
output logic [CNT_W-1:0] c_remove,
output logic [CNT_W-1:0] c_local_fault,
output logic [CNT_W-1:0] c_remote_fault,
output logic [CNT_W-1:0] c_mgmt_error,
output logic ever_ready,
output logic ever_tx_faulted
);
function automatic logic [CNT_W-1:0] bump(input logic [CNT_W-1:0] v,
input logic en);
bump = (en && !(&v)) ? (v + 1'b1) : v;
endfunction
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
c_los <= '0; c_lock_loss <= '0; c_tx_fault <= '0;
c_insert <= '0; c_remove <= '0;
c_local_fault <= '0; c_remote_fault <= '0; c_mgmt_error <= '0;
ever_ready <= 1'b0;
ever_tx_faulted <= 1'b0;
end else begin
if (clear) begin
c_los <= '0; c_lock_loss <= '0; c_tx_fault <= '0;
c_insert <= '0; c_remove <= '0;
c_local_fault <= '0; c_remote_fault <= '0; c_mgmt_error <= '0;
end else begin
c_los <= bump(c_los, ev_los);
c_lock_loss <= bump(c_lock_loss, ev_lock_loss);
c_tx_fault <= bump(c_tx_fault, ev_tx_fault);
c_insert <= bump(c_insert, ev_module_insert);
c_remove <= bump(c_remove, ev_module_remove);
c_local_fault <= bump(c_local_fault, ev_local_fault);
c_remote_fault <= bump(c_remote_fault, ev_remote_fault);
c_mgmt_error <= bump(c_mgmt_error, ev_mgmt_error);
end
// Deliberately outside the `clear` branch.
if (lane_ready_now) ever_ready <= 1'b1;
if (ev_tx_fault) ever_tx_faulted <= 1'b1;
end
end
endmoduleClassification: synthesizable instrumentation.
What it teaches: that the counters worth having are the ones that partition causes across the module boundary. c_local_fault against c_remote_fault answers which strand — the single most valuable question on an optical link, and one no copper counter has an equivalent for.
Deliberately simplified: no timestamps. Correlating an optical event with something elsewhere in the system needs them, and production designs have them.
Production implication: ever_ready sits outside the clear branch on purpose. An operator clearing counters to get a clean window would otherwise destroy the distinction between "this link has never worked" — wrong module, wrong fibre, wrong far end — and "this link works and is degrading". Those are different faults with different fixes, and the first is far more common on a newly installed link than anyone expects.
13. Assertions
Every property below is a property of these teaching models. IEEE 802.3 defines fault signalling and PMD characteristics; how a particular design supervises a pluggable module and attributes faults is an implementation choice, and module management interfaces are defined by form-factor specifications rather than by 802.3.
// ─── Safety: the transmitter is off unless everything permits it ───────────
// The most important property in the chapter. Optical sources are an
// eye-safety matter, and the safe state must be the default rather than a
// state the design happens to reach. Catches: any refactor that makes
// tx_disable state-derived and therefore one cycle late.
property p_tx_disabled_on_fault;
@(posedge clk) disable iff (!rst_n)
tx_fault |-> tx_disable;
endproperty
property p_tx_disabled_when_absent;
@(posedge clk) disable iff (!rst_n)
!module_present |-> tx_disable;
endproperty
// ─── Safety: receive failure must NOT stop transmit ────────────────────────
// The property that keeps a one-sided failure diagnosable. If losing the far
// end's light also silenced us, the fault would become symmetric and the
// remote-fault report could never be sent. Catches the tidy-looking
// simplification that breaks Section 9 entirely.
property p_rx_loss_does_not_disable_tx;
@(posedge clk) disable iff (!rst_n)
(rx_los && module_present && admin_enable && !tx_fault) |-> !tx_disable;
endproperty
// ─── Ordering: a cleared fault does not silently re-enable a source ────────
// Catches: FAULT routed back to ENABLED, which re-fires a laser that just
// reported failing, with no fresh decision in between.
property p_fault_exits_via_present;
@(posedge clk) disable iff (!rst_n)
((state == LANE_FAULT) && module_present && !tx_fault)
|=> (state == LANE_PRESENT);
endproperty
// ─── Safety: readiness requires every precondition ─────────────────────────
// Catches: a lane declared usable on lock alone, while loss of signal is
// still asserted -- a CDR can report lock on noise.
property p_ready_requires_all;
@(posedge clk) disable iff (!rst_n)
lane_ready |-> (module_present && admin_enable && !tx_fault && !rx_los && cdr_lock);
endproperty
// ─── Mutual exclusion on attribution ───────────────────────────────────────
// Catches: two causes asserted at once, which sends an engineer to two
// places and makes the attribution useless.
property p_cause_at_most_one;
@(posedge clk) disable iff (!rst_n)
$onehot0({cause_plant, cause_module, cause_host});
endproperty
// ─── Conservation: a failure always has a cause ────────────────────────────
// Catches: a failure path that reports "not ready" with no attribution,
// which is the same as no instrumentation at all.
property p_failure_has_a_cause;
@(posedge clk) disable iff (!rst_n)
!link_candidate |-> $onehot({cause_plant, cause_module, cause_host});
endproperty
// ─── Causation: an absent module is never blamed on the plant ──────────────
// Catches the priority inversion that dispatches a technician to inspect
// fibre for an empty cage.
property p_absent_module_not_plant;
@(posedge clk) disable iff (!rst_n)
!(&lane_present) |-> !cause_plant;
endproperty
// ─── Causation: we only report a fault we can actually send ────────────────
// Catches: signal_remote asserted with the transmitter disabled, which is a
// report nobody receives and a status bit that lies.
property p_signal_remote_requires_tx;
@(posedge clk) disable iff (!rst_n)
signal_remote |-> lane_enabled;
endproperty
// ─── Stability: faults are debounced in both directions ────────────────────
// Catches: an undebounced clear, so a marginal link flaps and every layer
// above pays for each transition.
property p_local_fault_debounced;
@(posedge clk) disable iff (!rst_n)
$fell(local_fault) |-> $past(rx_usable, CLEAR_HOLD);
endproperty
// ─── Safety: stale management data is marked, not silently trusted ─────────
// Catches: a shadow that reports valid forever after the module is pulled.
property p_shadow_invalid_when_absent;
@(posedge clk) disable iff (!rst_n)
!module_present |=> !shadow_valid;
endproperty
// ─── Causation: the down cause is captured on the edge ─────────────────────
// Catches: continuous sampling, which records recovery instead of failure.
property p_down_lanes_captured_on_edge;
@(posedge clk) disable iff (!rst_n)
$fell(link_candidate) |=> $stable(down_lanes);
endproperty14. Verification
Scenarios
- Cold start with no module. Verify
LANE_ABSENT,tx_disableasserted, and that the reported cause iscause_modulerather thancause_plant. This is the most common first-power-on state and the easiest to get wrong. - Module inserted, administratively disabled. Verify
LANE_PRESENT, transmit still disabled, andcause_host— not a module or plant fault. The design chose this. - Module inserted and enabled, no far end. Verify
LANE_ENABLED, transmit running,rx_losasserted,local_faultafter the debounce, andcause_plant. - Full bring-up. Far end appears; verify
LANE_READY,link_candidate, and that no fault is reported. - Transmit fault while ready. Verify
tx_disableasserts in the same cycle astx_fault, not after a state transition. Sample combinationally — a test that only checks the next clock edge passes on a broken design. - Transmit fault clears. Verify the lane returns to
PRESENT, not toENABLED, and that transmit stays disabled until the enable decision is made again. - Loss of signal while ready. Verify the lane falls back to
ENABLEDand — the critical check —tx_disablestays low. This is the scenario the rejected property would break. - Module removed while carrying traffic. Verify
LANE_ABSENT,removed_while_upsticky set,shadow_validcleared, and transmit disabled. - Module reinserted after removal. Verify the shadow does not report stale values from the previous module, and that
c_insertandc_removeboth advanced. - Management bus stops responding while the module is present. Verify
mgmt_error_countadvances,shadow_staleasserts afterSTALE_AFTER, and that the fast status pins are unaffected — a module can fail its management interface while its data path works. - Marginal receive, chattering
rx_los. Verify the assert and clear debouncing both hold, and thatlocal_faultdoes not toggle once per input transition. - Remote fault received while our receive is healthy. Verify
remote_faultasserts,local_faultstays low,link_usabledeasserts, andfirst_was_localis low. This is the far side of Section 9's scenario. - Both faults, local first. Verify
first_was_localrecords the local one and does not update when the remote one follows. - Both faults, remote first. The mirror. Together these two prove the sticky ordering is real and not an artefact of the assertion order.
- Multi-lane: one lane fails, three healthy. Verify
link_candidatedrops,failing_masknames exactly that lane, anddown_lanescaptures it on the down edge. - CDR reports lock while
rx_losis asserted. A real and nasty case — recovery circuits can lock onto noise. Verifylane_readystays low, because readiness requires both. - Reset during any state. Verify the machine returns to
ABSENTwith transmit disabled, and thatever_readyandever_tx_faultedare the only things cleared by reset but not byclear.
What the checker must own
- A two-instance testbench. One instance cannot test Section 9 at all, and Section 9 is the chapter's central claim. Building it is the single highest-value item in an optical verification plan.
- Combinational sampling of
tx_disable. The safety path is deliberately combinational, and a checker that samples only on clock edges cannot distinguish a same-cycle response from a next-cycle one — which is the whole property. - Coverage crosses of module presence against administrative enable against transmit fault against receive status. The bin
(present, enabled, no tx fault, rx_los, tx_disable asserted)must be unreachable; if it is ever hit, the rejected property has crept into the design.
15. Debugging — Which Side of the Connector
The symptom: a fibre link that will not come up. No traffic, both ends report a problem, and there are three organisations who might own it.
Step 1 — read presence. If the cage reports empty, stop. Nothing else matters, and no amount of fibre inspection will help. This sounds too obvious to state and is the most common wasted first hour on a new installation.
Step 2 — read tx_fault. A transmit fault is a definite statement about our own module, and it is the only one of the three causes that can be resolved without touching the fibre or contacting the far end. Replace the module.
Step 3 — read rx_los at each end, separately. This is the measurement that partitions the whole problem, and it needs both ends:
Our rx_los | Their rx_los | What broke |
|---|---|---|
| asserted | asserted | both strands, or the far end is not transmitting at all |
| asserted | clear | the strand carrying their light to us |
| clear | asserted | the strand carrying our light to them |
| clear | clear | light is arriving both ways — the problem is above the PMD |
Rows two and three are the one-sided failure, and they are why the reading must be taken at both ends. From one end alone, "we have loss of signal" is consistent with three of the four rows.
Step 4 — light arriving both ways and still no link. Now descend the way Chapter 2.6 taught: is there clock lock, and then is there block lock? Light with no lock points at signal quality or a rate mismatch. Lock with no block lock points at a coding mismatch — very often the wrong module type at one end, which is Section 6's mismatch case and reads healthy on every status bit.
Step 5 — the link comes up and errors. Now the loss budget is the frame. Check received optical power against the module's specified range, remembering Section 5's third point: too much power fails too, so a long-reach module on a short link is a real cause of exactly this symptom.
Step 6 — intermittent, with no errors logged. Read c_los and c_lock_loss over an interval. Loss-of-signal events point at connectors and mechanical disturbance; lock-loss events without loss of signal point at marginal power or dispersion. And clean a connector before doing anything expensive — it is the most common optical fault by a wide margin and it is invisible to every register in the system.
The method stated once: each optical status bit is a claim about a specific side of a specific boundary, and reading presence, then transmit fault, then receive signal at both ends narrows three organisations to one in about a minute. Everything after that is measurement, and it is measurement of a known thing.
16. Common Misconceptions
"Fibre removes the need for a PHY."
The wrong model: light travels perfectly, so the complexity of Chapter 3.1 disappears and what remains is a serialiser and a laser.
What it costs: you budget no time for link bring-up and no logic for module supervision. You have no design response to an absent module, a transmit fault, or a hot removal — all of which are routine. You discover during integration that most of the states your system can be in were never modelled.
The corrected model: fibre removes the echo problem, which is real and significant. It replaces it with a boundary — a conversion that usually lives in a pluggable module you do not own — and with an asymmetry, because two strands can fail independently. The PHY is architecturally simpler and its supervision is considerably more complex.
"Loss of signal means the fibre is broken."
The wrong model: no light arriving means the glass is damaged.
What it costs: you dispatch someone to inspect fibre for faults that are a register read away — an empty cage, a disabled transmitter, a far end that has not been powered on. Every one of those produces loss of signal at your end and none of them is in the plant.
The corrected model: loss of signal means not enough light is arriving, which is consistent with a break, a dirty connector, a far-end module that is absent or faulted, or a far end that is administratively disabled. Section 8's attribution priority exists to eliminate the cheap causes before blaming the expensive one.
"If we cannot receive, we should stop transmitting."
The wrong model: transmitting into a link that is not working wastes light and confuses things.
What it costs: this is the worst mistake in the chapter. It converts a one-sided failure into a symmetric silent deadlock, destroys the remote-fault report that is the far end's only source of information, and produces a link that will not recover after the fault is repaired because neither end will transmit first.
The corrected model: transmit is gated on transmit-side conditions only — presence, transmit fault, administrative policy. Losing the far end's light says nothing about our own transmitter, and keeping it running is what makes the failure diagnosable.
"A module that reports healthy is a module that will work."
The wrong model: presence, no transmit fault and no loss of signal means the optical path is correct.
What it costs: you cannot explain a link where both ends report perfect health and no traffic passes. You look at the fibre, the far end, and eventually the MAC, without ever considering that the modules are simply incompatible.
The corrected model: a module can be present, healthy and wrong — the wrong reach class, the wrong wavelength, the wrong rate. Every status bit reads correct because every bit is about the module's own health, not about whether it matches the link. Module identity is a separate check, and Section 11's shadow is where it lives.
"More optical power is safer."
The wrong model: if the link is marginal, a longer-reach module will fix it.
What it costs: a long-reach module on a short link can saturate the far-end receiver. The symptom — errors on a short link that a longer one carries fine — is so counter-intuitive that it routinely survives several rounds of investigation.
The corrected model: a receiver has a maximum as well as a minimum. Reach is a budget with two bounds, and overshooting the upper one is a real and regularly encountered fault.
17. Interview Reasoning
"Why is a 10GBASE-LR PHY lower latency than a 10GBASE-T PHY at the same rate?"
The answer that ends the topic names the absence of work: no echo cancellation, no crosstalk cancellation, no equaliser convergence over an unknown channel, and no LDPC decoder. Fibre gives each direction its own strand, so the entire signal-processing burden that defines Chapter 3.1 is simply not present. Naming the published latency figures makes it concrete.
"A fibre link is down. What is your first question?"
Not "is the fibre broken". The strong answer establishes an ordering — presence, then transmit fault, then loss of signal at both ends — and explains what each outcome would mean. The two-ended reading is the part that distinguishes real experience: from one end alone, "we have loss of signal" cannot separate a break in our receive strand from a far end that is simply not transmitting.
"Why does optical Ethernet have remote-fault signalling when copper largely does not?"
Because two strands can fail independently, so an end can be entirely healthy locally while the link is broken. It has no local evidence and no way to acquire any — the only path is a report from the end that can still hear. On a twisted pair carrying both directions, a break generally affects both directions and both ends notice. The follow-up worth anticipating is what happens if the reporting end disables its transmitter, and the answer is Section 13's rejected property.
18. Understanding Check
Because the two directions travel on separate strands. A transmitter and a receiver at the same end never share a physical path, so no local signal leaks into the local receiver. There is no hybrid and nothing to cancel.
That absence is architecturally significant: the echo canceller, crosstalk cancellers and channel equaliser that dominate a gigabit BASE-T PHY are simply not present, which is why an optical PHY is smaller, cooler and measurably lower latency at the same rate.
What replaces it, and both are design problems rather than physics problems:
- A boundary. The electrical-to-optical conversion normally lives in a pluggable module across a connector. Your design's real interface is presence pins, fault indications, and a slow management channel to a component that can be absent, wrong, or failing independently of your silicon.
- An asymmetry. Two strands means two independent failure paths, so a link can be broken in one direction while the other end sees perfect health.
The follow-up to be ready for: is fibre therefore simpler? The PHY is. The supervision is not — an optical design must handle absence, hot removal, module mismatch and one-sided failure, and a copper design handles none of those.
19. What's Next
Fibre's defining trade is stated in one line: it removes the echo problem and adds a boundary problem and an asymmetry problem.
The echo removal is real and structural — separate strands per direction mean no hybrid, no cancellation, and an architecturally simpler, lower-latency PHY. What comes back is a conversion that usually lives across a connector in a module you do not own, and a link whose two directions can fail independently. The first makes fault attribution a design output; the second makes remote-fault signalling necessary and makes "never disable transmit on a receive failure" the most important rule in the chapter.
Chapter 3.3 — Differential Signalling and the Analog Channel now takes the question both media have been deferring. Chapter 3.1 named attenuation, reflections and inter-symbol interference and pointed forward; this chapter named dispersion and did the same. All of them are the same underlying subject: what a real channel does to a transmitted symbol, and what is left for the receiver to infer.
That chapter is where the eye diagram, timing margin and voltage margin get built properly — and where the reason a PHY infers symbols rather than reading them finally becomes precise.
The full path is on the Ethernet curriculum index.
Continue learning
Related tutorials
- Related topic
The PHY Layer
Coding, serialisation, clock recovery and line drive all exist because a real channel attenuates, disperses and carries no clock. The PCS, PMA and PMD split follows the same logic — each owns one consequence of physics, and each changes on its own schedule.
- 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
Differential Signalling and the Analog Channel
A PHY does not read bits off a wire — it infers symbols from a waveform the channel has attenuated, reflected and smeared into its neighbours. Differential signalling, impedance, jitter and the eye are one subject: what margin is left after the channel takes its share.
- Related topic
Why the PHY Is Internally Layered — PCS, PMA, PMD
Each PHY sublayer boundary sits where the unit of information changes: octets become blocks, blocks become bits on lanes, bits become symbols on a medium. That placement turns N codings times M media into N plus M designs, and it marks where simulation stops being sufficient evidence.
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.
