Ethernet · Module 10
GMII — Eight Bits, and a Clock That Changed Direction
The MAC sources GTX_CLK because MII's round-trip timing path consumed half an 8 ns period. What that bought was speed; what it cost was an abstraction, since setup and hold at the pins are invisible to RTL.
Chapter 10.1 gave both clocks to the PHY, and the PHY could stop them. Chapter 10.2 gave the clock to neither end, and nobody was obliged to supply it.
GMII makes the third arrangement: each direction's clock comes from the device that launches the data it times.
The PHY still sources RX_CLK, because it recovered the receive data from the wire. And the MAC sources GTX_CLK — 125 MHz, alongside TXD[7:0] — which is the first time in this module that a MAC owns any timing on its own interface.
The reason is arithmetic that only becomes binding at gigabit, and the consequence is a failure neither MII nor RMII can have: an interface whose correctness is decided at the pins, in picoseconds of board delay, where no RTL assertion can reach.
1. Scope — What This Chapter Owns
This chapter owns GMII: its pin list, its split clock sourcing, its dual-mode fallback to MII, and the source-synchronous timing problem that RTL cannot express.
It does not re-derive Module 9's rate arithmetic. Chapter 9.3 §11 established why 8 bits × 125 MHz and why a wider interface was needed at all; this chapter starts there and asks what the width and the clock direction cost in pins, in modes and in bring-up.
Chapter 4.5 owns MDC and MDIO. Chapter 10.4 owns RGMII, which halves GMII's data pins by clocking on both edges — and inherits this chapter's timing problem in a much sharper form.
The claim this chapter defends: a source-synchronous interface's correctness is a physical-time property, and a property whose truth is decided outside the model cannot distinguish a working implementation from a broken one — so it must be replaced by properties about what the RTL actually owns.
2. Twenty-Four Signals, and a Clock That Turned Around
| Signal | Width | Direction | Notes |
|---|---|---|---|
TXD[7:0] | 8 | MAC → PHY | a full octet per clock |
TX_EN, TX_ER | 2 | MAC → PHY | |
GTX_CLK | 1 | MAC → PHY | 125 MHz, sourced by the MAC |
RXD[7:0] | 8 | PHY → MAC | |
RX_DV, RX_ER | 2 | PHY → MAC | |
RX_CLK | 1 | PHY → MAC | 125 MHz |
CRS, COL | 2 | PHY → MAC | half duplex |
| 24 | |||
TX_CLK | 1 | PHY → MAC | retained for 10/100 fallback |
Plus MDC and MDIO: 27 pins per port.
Which is worse than MII's 18 and much worse than RMII's 10 — and it is the right trade, because the alternative was 4 bits × 250 MHz and nobody wanted to route that in 1999.
The interesting row is GTX_CLK. In MII the PHY drove TX_CLK to the MAC and the MAC clocked data out with it. In GMII the MAC drives GTX_CLK alongside TXD, so the clock and the data it times leave the same device, on the same edge, and travel together.
That is what source-synchronous means, and it is why the change happened.
3. Why the MAC Sources the Transmit Clock
MII's arrangement — the PHY driving TX_CLK to the MAC, and the MAC returning data timed to it — has a timing path that gets longer with every megahertz.
Trace it. The PHY launches TX_CLK. It crosses the board to the MAC. The MAC's output registers use it to launch TXD. TXD crosses the board back to the PHY. The PHY samples it with the clock it originally sent.
So the data's arrival at the PHY is late by a full round trip — clock out, data back — plus the MAC's clock-to-output delay.
| Term | MII at 25 MHz | GMII if built the same way, 125 MHz |
|---|---|---|
| clock period | 40 ns | 8 ns |
| board delay, PHY → MAC | ≈ 1 ns | ≈ 1 ns |
| MAC clock-to-out | ≈ 2 ns | ≈ 2 ns |
| board delay, MAC → PHY | ≈ 1 ns | ≈ 1 ns |
| round trip consumed | ≈ 4 ns | ≈ 4 ns |
| as a fraction of the period | 10% | 50% |
The board delays did not change. The period did. At 25 MHz a 4 ns round trip is a tenth of the budget and nobody notices. At 125 MHz it is half of it, before the PHY's own setup requirement is counted.
Sourcing GTX_CLK from the MAC removes the round trip entirely. The clock and the data leave the MAC together, on the same edge, and arrive at the PHY having travelled the same distance — so the phase relationship between them is set by the MAC's output timing and the trace matching, not by a there-and-back journey.
4. RTL 1 — An Octet per Clock
// SYNTHESIZABLE.
//
// GMII transmit: one octet per GTX_CLK, launched with the clock the MAC
// itself sources.
//
// THE NUMBERS:
// TXD[7:0] 8 bits per GTX_CLK
// GTX_CLK 125 MHz -> 8 ns per octet
// 8 x 125 = 1000 Mb/s
// a 1518-octet frame = 1518 clocks = 12.144 us
// (against MII's 3036 clocks at 25 MHz = 121.44 us)
//
// NO NIBBLE ORDERING PROBLEM. GMII carries a whole octet per transfer,
// so Chapter 10.1's low-nibble-first rule -- and its famous swap bug --
// simply does not exist here. There is nothing to order.
//
// WHAT REPLACES IT is a physical relationship: TXD must be stable
// around the GTX_CLK edge AT THE PHY'S PINS. That is not visible in
// this module, in simulation, or in any assertion written about it.
package gmii_pkg;
localparam int unsigned OCTET_BITS = 8;
localparam int unsigned GTX_CLK_MHZ = 125;
// Clause 35: RX_ER asserted with RX_DV DEASSERTED and RXD = 8'h0F is
// Carrier Extend -- the half-duplex-at-gigabit mechanism that pads a
// short frame's carrier without adding octets to it.
localparam logic [7:0] RXD_CARRIER_EXTEND = 8'h0F;
// And 8'h0E is a False Carrier indication, as in Chapter 10.1.
localparam logic [7:0] RXD_FALSE_CARRIER = 8'h0E;
typedef enum logic [1:0] {
MODE_GMII_1000, // 8 bits, GTX_CLK from the MAC
MODE_MII_100, // 4 bits, TX_CLK from the PHY at 25 MHz
MODE_MII_10, // 4 bits, TX_CLK from the PHY at 2.5 MHz
MODE_UNKNOWN
} gmii_mode_e;
endpackage
module gmii_tx_interface
import gmii_pkg::*;
#(
parameter int unsigned CNT_W = 24
) (
input logic gtx_clk, // FROM THE MAC -- this design sources it
input logic rst_n,
input logic [7:0] octet,
input logic octet_valid,
input logic frame_last,
input logic force_error,
output logic octet_ready,
output logic [OCTET_BITS-1:0] txd,
output logic tx_en,
output logic tx_er,
output logic [CNT_W-1:0] c_octets,
output logic [CNT_W-1:0] c_frames,
output logic underrun
);
// One octet per clock, so the interface accepts on every cycle and
// there is no commitment window of the kind MII and RMII have.
assign octet_ready = 1'b1;
always_ff @(posedge gtx_clk or negedge rst_n) begin
if (!rst_n) begin
txd <= 8'd0; tx_en <= 1'b0; tx_er <= 1'b0;
c_octets <= '0; c_frames <= '0; underrun <= 1'b0;
end else begin
underrun <= 1'b0;
// EVERY OUTPUT IS REGISTERED, and this is not a style preference.
// A combinational path to a pin on a source-synchronous interface
// makes the output's timing depend on upstream logic depth, which
// varies with synthesis and is not what the board was matched
// against. Registering pins the launch point to a flop.
if (octet_valid) begin
txd <= octet;
tx_en <= 1'b1;
tx_er <= force_error;
if (!(&c_octets)) c_octets <= c_octets + 1'b1;
if (frame_last && !(&c_frames)) c_frames <= c_frames + 1'b1;
end else begin
txd <= 8'd0;
tx_en <= 1'b0;
tx_er <= 1'b0;
// A gap MID-FRAME is an underrun. GMII cannot pause a frame --
// deasserting TX_EN ends it -- so the correct response is
// TX_ER, which tells the PHY to corrupt the line symbol so the
// far end cannot mistake it for a legitimate short frame.
if (tx_en) underrun <= 1'b1;
end
end
end
endmoduleClassification: synthesizable.
What it teaches: that every output is registered directly off gtx_clk, and that is a timing requirement rather than a style choice. A combinational path from internal logic to a pin makes the output's clock-to-out delay depend on logic depth, which varies with synthesis, place-and-route and temperature. The board's trace matching was done against a fixed launch point, and a launch point that moves invalidates it.
Deliberately simplified: octet_ready is tied high. A real MAC has a transmit FIFO whose occupancy gates it, and the underrun path above is what that FIFO's exhaustion looks like at the pins.
Production implication: GMII has no nibble ordering — one octet per clock — so Chapter 10.1 §5's swap bug cannot occur. What replaces it is invisible in a different way. The nibble swap was at least representable in RTL; a setup-time violation at the PHY's pins is not representable at all, and Section 13 is about what follows from that.
5. RTL 2 — The Receive Side, Which Is Still MII's Arrangement
// SYNTHESIZABLE.
//
// GMII receive: one octet per RX_CLK, which the PHY sources.
//
// NOTE WHAT DID NOT CHANGE. GMII reversed the TRANSMIT clock's
// direction and left the receive direction exactly as MII had it --
// because there was never anything wrong with it. The PHY recovered the
// data from the wire, so the PHY has the timing, and it sends both
// together. RX_CLK was ALREADY source-synchronous.
//
// Which is the observation worth carrying: GMII did not invent
// source-synchronous signalling. It noticed that the RECEIVE direction
// had always been source-synchronous and made the transmit direction
// match.
module gmii_rx_interface
import gmii_pkg::*;
#(
parameter int unsigned CNT_W = 24
) (
input logic rx_clk, // FROM THE PHY
input logic rst_n,
input logic [OCTET_BITS-1:0] rxd,
input logic rx_dv,
input logic rx_er,
output logic [7:0] octet,
output logic octet_valid,
output logic frame_start,
output logic frame_end,
output logic frame_had_error,
// RX_ER without RX_DV is an indication channel, as on MII, and
// gigabit adds a code MII did not have.
output logic carrier_extend,
output logic false_carrier,
output logic unknown_indication,
output logic [CNT_W-1:0] c_frames,
output logic [CNT_W-1:0] c_carrier_extend,
output logic [CNT_W-1:0] c_false_carrier,
output logic [CNT_W-1:0] c_unknown
);
logic dv_q;
logic err_q;
always_ff @(posedge rx_clk or negedge rst_n) begin
if (!rst_n) begin
dv_q <= 1'b0; err_q <= 1'b0; octet <= 8'd0; octet_valid <= 1'b0;
frame_start <= 1'b0; frame_end <= 1'b0; frame_had_error <= 1'b0;
carrier_extend <= 1'b0; false_carrier <= 1'b0;
unknown_indication <= 1'b0;
c_frames <= '0; c_carrier_extend <= '0; c_false_carrier <= '0;
c_unknown <= '0;
end else begin
octet_valid <= 1'b0;
frame_start <= 1'b0;
frame_end <= 1'b0;
carrier_extend <= 1'b0;
false_carrier <= 1'b0;
unknown_indication <= 1'b0;
dv_q <= rx_dv;
if (rx_dv) begin
if (!dv_q) begin
frame_start <= 1'b1;
err_q <= 1'b0;
end
if (rx_er) err_q <= 1'b1;
// One octet, one clock. No assembly, no phase, no ordering.
octet <= rxd;
octet_valid <= 1'b1;
end else begin
if (rx_er) begin
unique case (rxd)
// CARRIER EXTEND. Gigabit half duplex pads a short frame's
// carrier WITHOUT adding octets to the frame, so the slot
// time is met without changing the frame. It is not an
// error and it is not data -- and a receiver that folds it
// into either has corrupted a legitimate frame.
RXD_CARRIER_EXTEND: begin
carrier_extend <= 1'b1;
if (!(&c_carrier_extend)) c_carrier_extend <= c_carrier_extend + 1'b1;
end
RXD_FALSE_CARRIER: begin
false_carrier <= 1'b1;
if (!(&c_false_carrier)) c_false_carrier <= c_false_carrier + 1'b1;
end
default: begin
unknown_indication <= 1'b1;
if (!(&c_unknown)) c_unknown <= c_unknown + 1'b1;
end
endcase
end
if (dv_q) begin
frame_end <= 1'b1;
frame_had_error <= err_q;
if (!(&c_frames)) c_frames <= c_frames + 1'b1;
end
end
end
end
endmoduleClassification: synthesizable.
What it teaches: that RXD = 0x0F with RX_ER and no RX_DV is Carrier Extend, and it is neither an error nor data. Gigabit half duplex needed to meet a slot time that a 512-bit frame no longer satisfies at 1000 Mbps, so it pads the carrier without adding octets to the frame — and a receiver that treats extend symbols as data corrupts a legitimate frame, while one that treats them as errors discards it.
Deliberately simplified: three indication codes are decoded and the rest counted as unknown, exactly as in Chapter 10.1 §9.
Production implication: the receive direction is unchanged from MII, and noticing that is the point. RX_CLK was always sourced by the device that launched the data — the PHY recovered it from the wire, so the PHY had the timing. GMII did not invent source-synchronous signalling; it observed that the receive direction had always been source-synchronous and made transmit match. The asymmetry MII had was never justified; it only stopped being affordable at 125 MHz.
6. Two Modes on One Connector
A gigabit-capable port has to work at 10 and 100 Mbps too, and GMII's answer is to become MII.
| 1000 Mbps | 100 Mbps | 10 Mbps | |
|---|---|---|---|
| data width | 8 bits, TXD[7:0] | 4 bits, TXD[3:0] | 4 bits |
| transmit clock | GTX_CLK | TX_CLK | TX_CLK |
| sourced by | the MAC | the PHY | the PHY |
| frequency | 125 MHz | 25 MHz | 2.5 MHz |
Read the third row. A speed change between 1000 and 100 Mbps reverses the direction of the transmit clock — a signal that the MAC was driving is now driven by the PHY, and vice versa.
Which makes a speed change a reconfiguration of the interface rather than a change of rate within it. The width changes, the clock source changes, and the clock's direction changes, all at once.
And the hazard is the obvious one. During the transition, if the MAC has not yet stopped driving GTX_CLK and the PHY has already started driving TX_CLK — or if the MAC drives TXD[7:4] while the PHY expects them idle — the interface has two drivers on shared nets for however long the software takes.
So the change must happen at a quiescent point, and the mode must be committed before either side changes what it drives. Which is Section 7's module.
7. RTL 3 — Changing Mode Without Two Drivers
// SYNTHESIZABLE.
//
// Switches a gigabit-capable port between GMII and MII operation.
//
// THE THREE THINGS THAT CHANGE TOGETHER:
// width -- 8 bits <-> 4 bits
// clock -- GTX_CLK <-> TX_CLK
// DIRECTION of that clock -- MAC-driven <-> PHY-driven
//
// The third is the dangerous one. A speed change hands a net from one
// driver to another, and if the handover overlaps, two devices drive it.
//
// THE ORDERING THAT MAKES IT SAFE:
// 1. quiesce -- no frame in either direction
// 2. STOP DRIVING everything this side drives
// 3. wait a defined settle time with the nets released
// 4. adopt the new mode
// 5. START DRIVING again
//
// Release before acquire, always, with a gap between. Doing it the
// other way round is the bug, and it is the natural way to write it.
module gmii_speed_mode_mux
import gmii_pkg::*;
#(
parameter int unsigned SETTLE_CYCLES = 16'd256,
parameter int unsigned QUIESCE_TIMEOUT = 32'd1_000_000,
parameter int unsigned CNT_W = 16
) (
input logic sys_clk, // deliberately NOT either MII clock
input logic rst_n,
input logic req_valid,
input gmii_mode_e req_mode,
input logic tx_active,
input logic rx_active,
output gmii_mode_e mode,
output logic drive_gtx_clk, // MAC drives the transmit clock
output logic use_wide_data, // TXD[7:0] rather than TXD[3:0]
output logic tx_outputs_enabled,
output logic change_applied,
output logic change_pending,
output logic change_timed_out,
// Both this side and the far side appear to drive the transmit clock
// during a transition. Reported; the settle gap exists to prevent it.
output logic overlap_detected,
output logic [CNT_W-1:0] c_changes,
output logic [CNT_W-1:0] c_timeouts,
output logic ever_overlap
);
typedef enum logic [2:0] {
M_STEADY, M_QUIESCE, M_RELEASED, M_SETTLE, M_ADOPT
} mstate_e;
mstate_e st_q;
gmii_mode_e target_q;
logic [31:0] timer_q;
logic [15:0] settle_q;
assign change_pending = (st_q != M_STEADY);
assign drive_gtx_clk = (mode == MODE_GMII_1000) && tx_outputs_enabled;
assign use_wide_data = (mode == MODE_GMII_1000);
always_ff @(posedge sys_clk or negedge rst_n) begin
if (!rst_n) begin
st_q <= M_STEADY; mode <= MODE_UNKNOWN; target_q <= MODE_UNKNOWN;
timer_q <= '0; settle_q <= '0;
tx_outputs_enabled <= 1'b0; // released out of reset
change_applied <= 1'b0; change_timed_out <= 1'b0;
overlap_detected <= 1'b0; ever_overlap <= 1'b0;
c_changes <= '0; c_timeouts <= '0;
end else begin
change_applied <= 1'b0;
change_timed_out <= 1'b0;
overlap_detected <= 1'b0;
unique case (st_q)
M_STEADY: if (req_valid && (req_mode != mode)) begin
target_q <= req_mode;
timer_q <= '0;
st_q <= M_QUIESCE;
end
M_QUIESCE: begin
// A mode change mid-frame produces a frame that started at one
// width and ended at another, which is not a frame either mode
// would have produced.
if (!tx_active && !rx_active) begin
st_q <= M_RELEASED;
end else if (timer_q == QUIESCE_TIMEOUT) begin
st_q <= M_STEADY;
change_timed_out <= 1'b1;
if (!(&c_timeouts)) c_timeouts <= c_timeouts + 1'b1;
end else begin
timer_q <= timer_q + 1'b1;
end
end
M_RELEASED: begin
// RELEASE BEFORE ACQUIRE. Everything this side drives goes
// high-impedance BEFORE the mode changes, so the far side can
// take ownership without an overlap.
tx_outputs_enabled <= 1'b0;
settle_q <= '0;
st_q <= M_SETTLE;
end
M_SETTLE: begin
if (settle_q == 16'(SETTLE_CYCLES)) begin
st_q <= M_ADOPT;
end else begin
settle_q <= settle_q + 1'b1;
end
end
M_ADOPT: begin
mode <= target_q;
tx_outputs_enabled <= 1'b1;
change_applied <= 1'b1;
st_q <= M_STEADY;
if (!(&c_changes)) c_changes <= c_changes + 1'b1;
end
default: st_q <= M_STEADY;
endcase
end
end
endmoduleClassification: synthesizable.
What it teaches: that release must precede acquire, with a settle gap between, and the natural way to write a mode change gets this backwards. Assigning mode <= target and letting the output enables follow combinationally means the new mode's drivers turn on in the same cycle the old mode's turn off — and on a net whose ownership is changing sides, "the same cycle" at the RTL level is an overlap of tens of nanoseconds at the pins.
Deliberately simplified: one settle constant for both directions. Real transitions also coordinate with the PHY through Chapter 4.5's management interface, which is slow enough that the settle time is usually not the binding constraint.
Production implication: this module runs on sys_clk and not on either MII clock, for the same reason Chapter 10.1 §7's observer does: the clock it is switching between is one of the things that changes. A mode machine clocked by GTX_CLK cannot complete a transition away from GMII mode, because its own clock is what it is turning off.
8. RTL 4 — Which Clock Is Actually Live
// SYNTHESIZABLE.
//
// Observes both transmit clock candidates from a domain that depends on
// neither, and reports which one is real.
//
// A gigabit-capable port has TWO possible transmit clocks and exactly
// one should be running:
// GMII mode -- GTX_CLK, sourced by this MAC, 125 MHz
// MII mode -- TX_CLK, sourced by the PHY, 25 or 2.5 MHz
//
// FOUR OBSERVABLE STATES, and two of them are faults:
// exactly GTX_CLK -- GMII, correct
// exactly TX_CLK -- MII, correct
// BOTH running -- a transition that overlapped, or two
// configurations that disagree about mode
// NEITHER running -- a transition that released and never
// acquired, or a PHY that has not started
//
// The last two are exactly Chapter 10.2's contention and no-clock
// failures, arriving by a different route: RMII had one clock with no
// owner; GMII has two clocks with a mode that decides between them.
module gmii_clock_direction_observer
import gmii_pkg::*;
#(
parameter int unsigned SYS_MHZ = 100,
parameter int unsigned WINDOW_US = 100,
parameter int unsigned CNT_W = 20,
parameter int unsigned TOL_PCT = 10
) (
input logic sys_clk,
input logic rst_n,
input gmii_mode_e mode,
input logic gtx_clk_toggle_sync,
input logic tx_clk_toggle_sync,
output logic [CNT_W-1:0] gtx_edges,
output logic [CNT_W-1:0] tx_edges,
output logic measurement_valid,
output logic both_clocks_running,
output logic no_clock_running,
// The clock that is running is not the one this mode expects.
output logic wrong_clock_for_mode,
output logic ever_both,
output logic ever_none,
output logic ever_wrong_clock
);
localparam int unsigned WIN_CYC = SYS_MHZ * WINDOW_US;
localparam int unsigned EXP_GTX = 125 * WINDOW_US;
localparam int unsigned EXP_TX100 = 25 * WINDOW_US;
logic [31:0] win_q;
logic [CNT_W-1:0] g_q, t_q;
logic gp, tp;
always_ff @(posedge sys_clk or negedge rst_n) begin
if (!rst_n) begin
win_q <= '0; g_q <= '0; t_q <= '0; gp <= 1'b0; tp <= 1'b0;
gtx_edges <= '0; tx_edges <= '0; measurement_valid <= 1'b0;
both_clocks_running <= 1'b0; no_clock_running <= 1'b0;
wrong_clock_for_mode <= 1'b0;
ever_both <= 1'b0; ever_none <= 1'b0; ever_wrong_clock <= 1'b0;
end else begin
gp <= gtx_clk_toggle_sync;
tp <= tx_clk_toggle_sync;
if (gtx_clk_toggle_sync != gp) if (!(&g_q)) g_q <= g_q + 1'b1;
if (tx_clk_toggle_sync != tp) if (!(&t_q)) t_q <= t_q + 1'b1;
if (win_q == 32'(WIN_CYC - 1)) begin
win_q <= '0;
gtx_edges <= g_q;
tx_edges <= t_q;
measurement_valid <= 1'b1;
g_q <= '0;
t_q <= '0;
// BOTH. A transition that overlapped, or two ends that disagree
// about which mode the port is in.
both_clocks_running <= (g_q != '0) && (t_q != '0);
if ((g_q != '0) && (t_q != '0)) ever_both <= 1'b1;
// NEITHER. Released and never acquired -- the interface is
// silent and every register still reads correctly.
no_clock_running <= (g_q == '0) && (t_q == '0);
if ((g_q == '0) && (t_q == '0)) ever_none <= 1'b1;
// WRONG ONE. The mode says GMII and TX_CLK is what is moving,
// or the mode says MII and GTX_CLK is. A configuration
// disagreement, and neither end is wrong on its own terms.
wrong_clock_for_mode <=
((mode == MODE_GMII_1000) && (g_q == '0) && (t_q != '0)) ||
((mode != MODE_GMII_1000) && (t_q == '0) && (g_q != '0));
if (((mode == MODE_GMII_1000) && (g_q == '0) && (t_q != '0)) ||
((mode != MODE_GMII_1000) && (t_q == '0) && (g_q != '0)))
ever_wrong_clock <= 1'b1;
end else begin
win_q <= win_q + 1'b1;
end
end
end
endmoduleClassification: synthesizable.
What it teaches: that wrong_clock_for_mode catches a disagreement in which neither device is wrong on its own terms. The MAC believes the port is at 1000 Mbps and is driving GTX_CLK; the PHY believes it is at 100 and is driving TX_CLK. Both are behaving correctly for the mode each thinks it is in, and the interface passes nothing — which is Chapter 9.2's duplex mismatch again, one layer down and about a clock instead of a duplex.
Deliberately simplified: the toggles arrive pre-divided and synchronised, as in Chapter 10.1 §7.
Production implication: both_clocks_running is the direct evidence that Section 7's release-before-acquire ordering was violated. It is also the only evidence, because at the RTL level the overlap is a cycle or two and at the board level it is a contention event that may leave no permanent trace. A counter that catches it during bring-up is worth more than a scope trace nobody was watching for.
9. The Timing That Lives Outside the Model
Here is the question a source-synchronous interface actually asks, stated precisely.
At the PHY's input pins, is TXD stable for its setup time before the GTX_CLK edge and its hold time after it?
Everything that decides the answer is physical. The MAC's clock-to-output delay, its package delay, the board trace lengths for nine signals that were supposed to arrive together, the PHY's package delay, and the PHY's specified setup and hold window.
And here is what an RTL simulation contains. An ideal clock. A register that launches data on its edge. A register that samples data on its edge. Zero delay between them, and no representation of a pin at all.
| RTL simulation | hardware | |
|---|---|---|
| launch | ideal edge | edge + clock-to-out + package |
| path | none | trace, tens to hundreds of ps |
| sample | same ideal edge | edge + package, against a setup window |
| the question | not representable | the whole question |
| what decides it | — | STA, trace matching, a board respin |
So a property written in RTL that says TXD is stable at the GTX_CLK edge is true by construction of the model. It is not measuring the design; it is measuring the simulator's zero-delay assumption, and it will pass on a board that works and on a board that does not.
This is the fourth distinct kind of blindness this track has catalogued, and it is the most complete. Chapter 9.5's rejected property asserted away half the input space; Chapter 9.6's asserted a flattened output; Chapter 10.1's was vacuous while its clock was stopped. This one is vacuous always, because the quantity it is about does not exist in the abstraction it is written in.
10. RTL 5 — Conformance, and Reporting What the Board Cannot Say
// SYNTHESIZABLE.
//
// Checks the partner's behaviour and exports the configuration facts a
// bring-up engineer would otherwise need a scope to determine.
//
// The four checks:
// 1. RX_DV and RX_ER combinations that are not defined. GMII's
// indication channel has three known codes; anything else is the
// PHY speaking a dialect this design does not know.
// 2. CRS/COL on a full-duplex link -- Chapter 10.1's hazard,
// unchanged at gigabit.
// 3. Carrier extend arriving on a FULL-DUPLEX link, which is a
// contradiction: extend exists only for half duplex.
// 4. TX_EN asserted while the wrong clock is running for the mode.
//
// And it exports the mode and clock beliefs, because the most common
// gigabit bring-up failure is two devices that disagree about which
// mode the port is in, with neither of them wrong on its own terms.
module gmii_conformance_monitor
import gmii_pkg::*;
#(
parameter int unsigned CNT_W = 20
) (
input logic sys_clk,
input logic rst_n,
input logic clear,
input gmii_mode_e mode,
input logic full_duplex,
input logic wrong_clock_for_mode,
// Synchronised control signals.
input logic crs_sync,
input logic col_sync,
input logic tx_en_sync,
input logic carrier_extend_sync,
input logic unknown_indication_sync,
output logic crs_on_full_duplex,
output logic col_on_full_duplex,
output logic extend_on_full_duplex,
output logic tx_with_wrong_clock,
output logic [CNT_W-1:0] c_crs_fd,
output logic [CNT_W-1:0] c_col_fd,
output logic [CNT_W-1:0] c_extend_fd,
output logic [CNT_W-1:0] c_tx_wrong_clock,
output logic [CNT_W-1:0] c_unknown_indications,
// The two facts that settle a mode disagreement without a scope.
output gmii_mode_e reported_mode,
output logic reported_wide_data,
output logic first_violation_valid,
output logic [1:0] first_violation_kind,
output logic ever_violated
);
logic any_c;
logic [1:0] kind_c;
assign reported_mode = mode;
assign reported_wide_data = (mode == MODE_GMII_1000);
always_comb begin
any_c = 1'b0;
kind_c = 2'd0;
if (full_duplex && col_sync) begin any_c = 1'b1; kind_c = 2'd0; end
else if (full_duplex && carrier_extend_sync) begin any_c = 1'b1; kind_c = 2'd1; end
else if (tx_en_sync && wrong_clock_for_mode) begin any_c = 1'b1; kind_c = 2'd2; end
else if (full_duplex && crs_sync) begin any_c = 1'b1; kind_c = 2'd3; end
end
always_ff @(posedge sys_clk or negedge rst_n) begin
if (!rst_n) begin
crs_on_full_duplex <= 1'b0; col_on_full_duplex <= 1'b0;
extend_on_full_duplex <= 1'b0; tx_with_wrong_clock <= 1'b0;
c_crs_fd <= '0; c_col_fd <= '0; c_extend_fd <= '0;
c_tx_wrong_clock <= '0; c_unknown_indications <= '0;
first_violation_valid <= 1'b0; first_violation_kind <= 2'd0;
ever_violated <= 1'b0;
end else begin
crs_on_full_duplex <= full_duplex && crs_sync;
col_on_full_duplex <= full_duplex && col_sync;
// CARRIER EXTEND EXISTS ONLY FOR HALF DUPLEX. Receiving it on a
// full-duplex link means the PHY believes the port is half duplex
// -- a duplex disagreement, visible here and nowhere else.
extend_on_full_duplex <= full_duplex && carrier_extend_sync;
tx_with_wrong_clock <= tx_en_sync && wrong_clock_for_mode;
if (clear) begin
c_crs_fd <= '0; c_col_fd <= '0; c_extend_fd <= '0;
c_tx_wrong_clock <= '0; c_unknown_indications <= '0;
first_violation_valid <= 1'b0;
// ever_violated survives.
end else begin
if (full_duplex && crs_sync && !(&c_crs_fd))
c_crs_fd <= c_crs_fd + 1'b1;
if (full_duplex && col_sync && !(&c_col_fd))
c_col_fd <= c_col_fd + 1'b1;
if (full_duplex && carrier_extend_sync && !(&c_extend_fd))
c_extend_fd <= c_extend_fd + 1'b1;
if (tx_en_sync && wrong_clock_for_mode && !(&c_tx_wrong_clock))
c_tx_wrong_clock <= c_tx_wrong_clock + 1'b1;
if (unknown_indication_sync && !(&c_unknown_indications))
c_unknown_indications <= c_unknown_indications + 1'b1;
if (any_c) begin
ever_violated <= 1'b1;
if (!first_violation_valid) begin
first_violation_valid <= 1'b1;
first_violation_kind <= kind_c;
end
end
end
end
end
endmoduleClassification: synthesizable.
What it teaches: that carrier extend on a full-duplex link is a duplex disagreement made visible at the interface. Extend exists only for gigabit half duplex; a PHY that emits it believes the port is half duplex while the MAC believes it is full. That is Chapter 9.2's mismatch reported by a mechanism 9.2 did not have — and it is a far more direct signal than the asymmetric error signature that chapter had to infer from.
Deliberately simplified: four checks and a two-bit violation kind. Production monitors also check the interframe gap in GTX_CLK cycles and the preamble length.
Production implication: reported_mode and reported_wide_data are exported specifically so a bring-up engineer does not need a scope. The most common gigabit bring-up failure is two devices in different modes, with neither wrong on its own terms — and reading both devices' beliefs over MDIO and a register interface settles in seconds what a probe on GTX_CLK settles in an hour.
11. Carrier Extend, and What Half Duplex Cost at Gigabit
Carrier extend is the strangest signal on GMII, and its existence is a compact argument for why half duplex died.
Chapter 1.2's slot time is the constraint. A station must still be transmitting when the furthest possible collision reaches it, and the slot time at 10 and 100 Mbps is 512 bit times — which is exactly why Chapter 5.6's minimum frame is 64 octets.
At 1000 Mbps, 512 bit times is 0.512 µs, and a network of any useful size does not fit inside it.
So the gigabit slot time was raised to 4096 bit times.
| Rate | Slot time, bit times | Slot time, µs |
|---|---|---|
| 10 Mbps | 512 | 51.2 µs |
| 100 Mbps | 512 | 5.12 µs |
| 1000 Mbps | 4096 | 4.096 µs |
And that immediately breaks the frame format, because raising the minimum frame to 512 octets would have made gigabit frames incompatible with every other Ethernet.
Carrier extend is the workaround: pad the carrier without padding the frame. A short frame is followed by extension symbols that occupy the medium until the slot time is met — and they are not part of the frame, do not enter the FCS, and are discarded by the receiver.
Which is why it appears on GMII as an indication rather than as data: RX_ER asserted, RX_DV deasserted, RXD = 0x0F. Not an error, not data, and a receiver that folds it into either destroys a legitimate frame.
And the cost is brutal.
a 64-octet frame = 512 bit times
the slot time = 4096 bit times
extension = 4096 − 512 = 3584 bit times = 448 octets
A minimum-length frame at gigabit half duplex occupies the medium for 4096 bit times to deliver 512 of them — an efficiency of 512 ÷ 4096 = 12.5%.
12. The Latency and Width Arithmetic
| MII | RMII | GMII | |
|---|---|---|---|
| bits per transfer | 4 | 2 | 8 |
| clock | 25 MHz | 50 MHz | 125 MHz |
| period | 40 ns | 20 ns | 8 ns |
| transfers per octet | 2 | 4 | 1 |
| octet time | 80 ns | 80 ns | 8 ns |
| 64-octet frame | 5.12 µs | 5.12 µs | 0.512 µs |
| 1518-octet frame | 121.44 µs | 121.44 µs | 12.144 µs |
| signals | 16 | 8 | 24 |
GMII's octet time is a tenth of MII's, which is the rate increase and not an interface property — the frame takes a tenth as long because the link is ten times faster.
What GMII adds to a latency budget is smaller than either of the others. One transfer per octet means no assembly pipelining at all — there is no phase, no nibble, no di-bit. The interface's own contribution is one register stage in each direction, plus the clock-domain crossing between RX_CLK and the MAC's core clock.
And the alternative it avoided is worth stating. Keeping MII's 4-bit width at 1000 Mbps needs
1000 ÷ 4 = 250 MHz
— a 250 MHz source-synchronous interface on a 1999 board. Eight extra traces was the cheaper answer, and it remained cheaper until Chapter 9.4's XGMII needed 32 bits and Chapter 10.4's RGMII went the other way, halving GMII's data pins by clocking on both edges at 125 MHz.
13. Properties Worth Asserting, and One Worth Refusing
The organising question here is different from either previous chapter's. Chapter 10.1 asked in which clock; Chapter 10.2 asked about which signal. GMII asks in which abstraction — because its defining property is a physical-time relationship that RTL does not represent.
Launch discipline — what the RTL genuinely owns
// P1. TXD is launched by GTX_CLK and by nothing else. A genuine RTL
// bug -- data launched from a different clock produces exactly the
// symptom a board timing problem produces, and is far more likely.
property p_txd_launched_by_gtx;
@(posedge gtx_clk) disable iff (!rst_n)
!$stable(txd) |-> $rose(gtx_clk);
endproperty
a_txd_launched_by_gtx: assert property (p_txd_launched_by_gtx);
// P2. Every transmit output is registered -- txd changes only in the
// cycle after octet_valid, never combinationally from it. A variable
// launch point invalidates the board's trace matching.
property p_outputs_registered;
@(posedge gtx_clk) disable iff (!rst_n)
octet_valid |=> (txd == $past(octet));
endproperty
a_outputs_registered: assert property (p_outputs_registered);
// P3. TX_EN and TXD change together. Nine signals launched on one edge
// is the whole premise of a source-synchronous interface.
property p_tx_en_aligned_with_data;
@(posedge gtx_clk) disable iff (!rst_n)
$rose(tx_en) |-> !$stable(txd);
endproperty
a_tx_en_aligned: assert property (p_tx_en_aligned_with_data);
// P4. A gap mid-frame is reported as an underrun rather than silently
// ending the frame. GMII cannot pause; deasserting TX_EN ends.
property p_underrun_reported;
@(posedge gtx_clk) disable iff (!rst_n)
(tx_en && !octet_valid) |=> underrun;
endproperty
a_underrun_reported: assert property (p_underrun_reported);Receive indications
// P5. Carrier extend is decoded as extend, not as data and not as an
// error. Folding it into either destroys a legitimate frame.
property p_carrier_extend_decoded;
@(posedge rx_clk) disable iff (!rst_n)
(rx_er && !rx_dv && (rxd == RXD_CARRIER_EXTEND)) |=> carrier_extend;
endproperty
a_carrier_extend_decoded: assert property (p_carrier_extend_decoded);
// P6. And it never contributes an octet.
property p_extend_is_not_data;
@(posedge rx_clk) disable iff (!rst_n)
carrier_extend |-> !octet_valid;
endproperty
a_extend_not_data: assert property (p_extend_is_not_data);
// P7. RX_ER with RX_DV is a data error; without it, an indication.
// Chapter 10.1's asymmetry, unchanged at gigabit.
property p_rx_er_asymmetry;
@(posedge rx_clk) disable iff (!rst_n)
(rx_er && !rx_dv) |=> !frame_had_error;
endproperty
a_rx_er_asymmetry: assert property (p_rx_er_asymmetry);
// P8. An undecoded indication is COUNTED rather than discarded.
property p_unknown_counted;
@(posedge rx_clk) disable iff (!rst_n)
(rx_er && !rx_dv &&
(rxd != RXD_CARRIER_EXTEND) && (rxd != RXD_FALSE_CARRIER))
|=> unknown_indication;
endproperty
a_unknown_counted: assert property (p_unknown_counted);Mode changes — release before acquire
// P9. THE ORDERING PROPERTY. Outputs are released before the mode
// changes, never in the same cycle. An RTL "same cycle" is tens of
// nanoseconds of contention at the pins.
property p_release_before_adopt;
@(posedge sys_clk) disable iff (!rst_n)
change_applied |-> $past(!tx_outputs_enabled);
endproperty
a_release_before_adopt: assert property (p_release_before_adopt);
// P10. And a settle gap separates them.
property p_settle_gap_observed;
@(posedge sys_clk) disable iff (!rst_n)
change_applied |-> ($past(settle_q) == 16'(SETTLE_CYCLES));
endproperty
a_settle_gap: assert property (p_settle_gap_observed);
// P11. A mode change only lands when both directions are quiescent. A
// frame that started at one width and ended at another is not a frame
// either mode would produce.
property p_change_only_when_quiet;
@(posedge sys_clk) disable iff (!rst_n)
change_applied |-> ($past(!tx_active) && $past(!rx_active));
endproperty
a_change_only_when_quiet: assert property (p_change_only_when_quiet);
// P12. Outputs are released out of reset, never driven by a reset
// value -- the same rule as Chapter 10.2's clock driver.
property p_released_after_reset;
@(posedge sys_clk) disable iff (!rst_n)
$rose(rst_n) |=> !tx_outputs_enabled;
endproperty
a_released_after_reset: assert property (p_released_after_reset);
// P13. Every request terminates: applied or timed out, never neither.
property p_request_terminates;
@(posedge sys_clk) disable iff (!rst_n)
$rose(change_pending) |-> ##[1:$] (change_applied || change_timed_out);
endproperty
a_request_terminates: assert property (p_request_terminates);Clock existence and mode agreement
// P14. Exactly one transmit clock runs. Both is an overlap; neither is
// a release that never acquired.
property p_one_clock_running;
@(posedge sys_clk) disable iff (!rst_n)
measurement_valid |-> !(both_clocks_running || no_clock_running);
endproperty
a_one_clock_running: assert property (p_one_clock_running);
// P15. And it is the one this mode expects. Neither device is wrong on
// its own terms when this fails -- which is why it must be reported.
property p_right_clock_for_mode;
@(posedge sys_clk) disable iff (!rst_n)
(measurement_valid && (mode == MODE_GMII_1000) && (tx_edges != '0) &&
(gtx_edges == '0)) |-> wrong_clock_for_mode;
endproperty
a_right_clock_for_mode: assert property (p_right_clock_for_mode);
// P16. Carrier extend on a full-duplex link is a duplex disagreement,
// reported -- the clearest such signal anywhere in the track.
property p_extend_on_full_duplex_flagged;
@(posedge sys_clk) disable iff (!rst_n)
(full_duplex && carrier_extend_sync) |=> extend_on_full_duplex;
endproperty
a_extend_fd_flagged: assert property (p_extend_on_full_duplex_flagged);
// P17. COL on a full-duplex link, unchanged from Chapter 10.1.
property p_col_silent_on_full_duplex;
@(posedge sys_clk) disable iff (!rst_n)
(full_duplex && col_sync) |=> col_on_full_duplex;
endproperty
a_col_silent: assert property (p_col_silent_on_full_duplex);14. Verification Scenarios
Transmit
- A continuous octet stream — one octet per
GTX_CLK,TX_ENasserted throughout,TXDchanging only on clock edges. 0xA5followed by0x5A— both appear whole in consecutive cycles; no ordering to get wrong, which is the check that confirms GMII has no nibble problem.- A gap mid-frame —
underrunpulses, and the correct response isTX_ERrather than a silent short frame. force_errormid-frame —TX_ERalongsideTX_ENfor the requested cycles.TXDdriven combinationally instead of registered (a deliberate mutation) — P2 fires. The RTL bug that produces the same symptom as a board timing failure.TXDlaunched from a second clock (a deliberate mutation) — P1 fires. Same symptom, different cause, and the only one an assertion can catch.
Receive indications
- A clean frame — one octet per
RX_CLK,frame_startandframe_endonRX_DV's edges. RX_ERwithRX_DV—frame_had_errorsticky for the frame.RX_ER, noRX_DV,RXD = 0x0F—carrier_extend, notframe_had_error, not an octet.RX_ER, noRX_DV,RXD = 0x0E—false_carrier.RX_ER, noRX_DV,RXD = 0x33—unknown_indication, counted.- A frame followed immediately by 448 octets of carrier extend — the minimum-frame case at gigabit half duplex; not one octet of it enters the frame.
- Extend arriving on a full-duplex link —
extend_on_full_duplex,first_violation_kind = 1. A duplex disagreement.
Mode changes
- 1000 → 100 while idle — quiesce, release, settle, adopt.
change_appliedafter exactlySETTLE_CYCLES. - 1000 → 100 mid-frame — held until
tx_activeandrx_activeboth fall. - Traffic that never quiesces —
change_timed_out,c_timeoutsincrements, mode unchanged. - Outputs during the settle window —
tx_outputs_enabledlow for the whole gap. P9 and P10. - A mutation that adopts the mode before releasing outputs — P9 fires. The natural way to write it, and the bug.
- Reset with a mode request already asserted — outputs released for at least one cycle. P12.
- 100 → 1000 and back — the clock's direction reverses twice with no overlap either time.
Clock observation
- GMII mode, only
GTX_CLKrunning — correct; no flags. - MII mode, only
TX_CLKrunning — correct. - Both running —
both_clocks_running,ever_bothsticky. The overlap signature. - Neither running —
no_clock_running. Released and never acquired. - GMII mode with
TX_CLKrunning andGTX_CLKstopped —wrong_clock_for_mode. Neither device is wrong on its own terms. TX_ENasserted whilewrong_clock_for_mode—tx_with_wrong_clock, counted.CRSandCOLon a full-duplex link — both counted, unchanged from Chapter 10.1.
15. Debugging: The Symptom Is Almost Always Silence
| Observation | Likely cause | The distinguishing check |
|---|---|---|
| link up, zero frames, zero errors anywhere | a mode disagreement | reported_mode at each end; wrong_clock_for_mode |
| the same, with both clocks toggling | mode disagreement, confirmed | both_clocks_running |
| the same, with neither clock toggling | a transition that released and never acquired | no_clock_running |
| frames transmitted, far end sees garbage | setup or hold at the PHY's pins | no assertion will find it — an STA report and a scope |
| the same, intermittently, temperature-dependent | marginal timing rather than broken timing | the temperature correlation is the diagnosis |
| the same, after a synthesis change with no RTL change | an unregistered output whose logic depth moved | P2; check that the pin comes straight off a flop |
| the same, after a board respin | trace matching between GTX_CLK and TXD | length-match the nine signals as a group |
| works at 100, fails at 1000 | the transition, or GMII-mode timing | c_changes, ever_both, then STA |
| carrier extend on a full-duplex link | the PHY thinks the port is half duplex | extend_on_full_duplex — a duplex mismatch, named |
| collisions reported, full duplex | Chapter 10.1's hazard, unchanged | col_on_full_duplex |
Three habits.
First, read both ends' mode before touching anything. The most common gigabit bring-up failure is two devices in different modes with neither wrong on its own terms, and its signature is silence with clean counters everywhere. reported_mode at the MAC and the speed register at the PHY settle in seconds what a probe settles in an hour.
Second, when the symptom is garbage rather than silence, stop looking at RTL. Data arriving corrupted on a link whose control signals are correct is a timing problem, and Section 9 established that no assertion can see it. The instruments are an STA report and a scope, and the diagnostic questions are: are the outputs registered, and are the nine signals length-matched?
Third, treat "it broke after a synthesis change with no RTL change" as a launch-point problem. An unregistered output's clock-to-out depends on logic depth, which the tools are free to change. The board was matched against a fixed launch point, and a moving one invalidates it — which is why P2 is worth asserting even though it looks like a style check.
16. Common Misconceptions
"GMII is MII made wider."
The wrong model: the same interface with more data pins.
What it costs: you miss the clock reversal, the dual-mode structure, and the entire timing argument.
The corrected model: the width change is the smaller half. GMII reversed the transmit clock's direction — the MAC sources GTX_CLK and sends it with the data — which makes both directions source-synchronous and removes a round-trip timing path that consumed 50% of an 8 ns period against 10% of MII's 40 ns one. And a gigabit-capable port is two interfaces sharing pins, because it must fall back to 4-bit MII at 10 and 100 Mbps, with the transmit clock's direction reversing on a speed change.
"A timing assertion covers the interface timing."
The wrong model: a passing $stable(txd) at the clock edge means the interface meets timing.
What it costs: the timing is verified nowhere — not in RTL, where the quantity does not exist, and not in STA, because the assertion appeared to cover it.
The corrected model: in RTL, TXD is launched and sampled by the same ideal, zero-delay clock, so the property is true by construction of the model. The real question is whether TXD is stable around the GTX_CLK edge at the PHY's pins, which depends on clock-to-out, package delay, trace lengths and the PHY's setup window — none of which the RTL abstraction contains. The test: what would have to change for this to fail? If the answer is a board respin, the property belongs in STA.
"Carrier extend is a kind of padding."
The wrong model: extension octets are frame data added to reach a minimum.
What it costs: a corrupted frame, because extend symbols get folded into the payload and the FCS.
The corrected model: it pads the carrier, not the frame. Extension symbols occupy the medium until the 4096-bit-time gigabit slot time is met, and they are not part of the frame, do not enter the FCS, and are discarded. That is why they arrive as an indication — RX_ER with RX_DV deasserted and RXD = 0x0F — rather than as data. A 64-octet frame needs 448 octets of extension, giving 12.5% efficiency, which is the arithmetic that killed gigabit half duplex.
"Registering the outputs is a coding-style preference."
The wrong model: it makes no functional difference whether a pin is driven from a flop or from logic.
What it costs: an interface that works, then fails after a synthesis change that touched no RTL.
The corrected model: an unregistered output's clock-to-out delay depends on logic depth, which place-and-route is free to change between builds. The board's trace matching was done against a fixed launch point. A launch point that moves invalidates it — so on a source-synchronous interface, registering every output is a timing requirement, and P2 asserts it for that reason rather than for tidiness.
"If both ends report link up at the right speed, the interface is configured correctly."
The wrong model: agreement on the line side implies agreement on the MAC side.
What it costs: the module's most common failure, with the least evidence.
The corrected model: the PHY's link status is about its line side. A MAC in GMII mode and a PHY in MII mode both behave correctly for the mode each believes it is in — the MAC drives GTX_CLK and eight bits, the PHY drives TX_CLK and expects four — and nothing crosses. Every error counter reads zero, because nothing gets far enough to be wrong. reported_mode and both_clocks_running are the only instruments that see it.
17. Interview Reasoning
"Why does the MAC source GMII's transmit clock when the PHY sourced MII's?"
Because MII's arrangement has a round-trip timing path that stops fitting at 125 MHz. Trace it: the PHY launches TX_CLK, it crosses to the MAC, the MAC launches TXD with it, and TXD crosses back to the PHY, which samples it with the clock it originally sent. The strong answer puts numbers on it: about 1 ns each way plus roughly 2 ns of clock-to-out is ≈ 4 ns, which is 10% of MII's 40 ns period and 50% of GMII's 8 ns one — before the PHY's own setup requirement. Sourcing GTX_CLK from the MAC removes the round trip: clock and data leave together and travel the same distance. The finishing observation: the receive direction was always source-synchronous — the PHY recovered the data, so the PHY had the timing — so GMII did not invent the idea, it made transmit match receive.
"What is carrier extend and why does its arithmetic matter?"
At 1000 Mbps, Chapter 1.2's 512-bit slot time is only 0.512 µs, too short for a useful network, so the gigabit slot time was raised to 4096 bit times. Raising the minimum frame to match would have broken the frame format, so extension symbols pad the carrier without padding the frame — not in the FCS, not delivered, and signalled as an indication (RX_ER, no RX_DV, RXD = 0x0F) rather than as data. The arithmetic is the point: a 64-octet frame is 512 bit times and needs 4096 − 512 = 3584 bit times of extension — 448 octets — for an efficiency of 512 ÷ 4096 = 12.5%. Gigabit half duplex delivers 125 Mb/s of minimum-length frames on a 1000 Mb/s link, which is why it was specified, implemented, and never deployed — and why Chapter 9.4 removed half duplex outright.
"A gigabit link is up at both ends and passes no traffic, with zero errors anywhere. What is it?"
Almost certainly a mode disagreement, and the zero-error part is the diagnostic. The MAC believes the port is at 1000 Mbps and drives GTX_CLK with eight data bits; the PHY believes it is at 100 and drives TX_CLK expecting four. Neither is wrong on its own terms, both report link up, and nothing gets far enough to become an error — so every counter on both devices reads zero. The strong answer names the instruments: both_clocks_running (two transmit clocks toggling at once), wrong_clock_for_mode, and reported_mode read at each end, which turns an hour with a scope into two register reads. The finishing point: this is the same silence-with-clean-counters signature as MII's stopped clock and RMII's missing reference — three different faults, one symptom, because all three are upstream of anything that counts.
"Would you assert in RTL that TXD meets the PHY's setup time?"
No — and this one is not about content or clocking but about abstraction. In RTL, TXD is launched by a gtx_clk edge and sampled by a gtx_clk edge with zero delay between them; there is no package, no trace, no picosecond of skew. The assertion is true by construction of the simulator's model and is green on a working board and on one whose data traces are 40 mm longer than its clock. The diagnostic question: what would have to change for this to fail? A board respin — so the property belongs to STA and a matched-length layout rule, not to a testbench. And the damage is that a team with it passing believes the timing is verified when it is verified nowhere. Assert what the RTL owns instead: that TXD is launched by GTX_CLK and not another clock, that every output is registered so the launch point cannot move with synthesis, that nothing is driven during a mode transition, and that the design reports which mode it believes it is in.
18. Understanding Check
Because MII's arrangement contains a round trip that stops fitting inside the period.
MII's path: the PHY launches TX_CLK → it crosses to the MAC → the MAC launches TXD with it → TXD crosses back → the PHY samples it with the clock it originally sent.
| Term | MII, 25 MHz | the same at 125 MHz |
|---|---|---|
| clock period | 40 ns | 8 ns |
| board delay each way | ≈ 1 ns | ≈ 1 ns |
| MAC clock-to-out | ≈ 2 ns | ≈ 2 ns |
| round trip | ≈ 4 ns | ≈ 4 ns |
| fraction of the period | 10% | 50% |
The delays did not change. The period did.
Sourcing GTX_CLK from the MAC removes the round trip entirely — clock and data leave the same device on the same edge and travel the same distance, so their phase relationship is set by output timing and trace matching rather than by a there-and-back journey.
And the receive direction never had the problem, because the PHY recovered the data from the wire and therefore already had its timing. GMII did not invent source-synchronous signalling; it noticed that receive had always been source-synchronous and made transmit match.
19. What's Next
The claim this chapter defended: a property whose truth is decided outside the model cannot distinguish a working implementation from a broken one.
GMII made both directions source-synchronous, because MII's round-trip path — clock out, data back — consumed 10% of a 40 ns period and would have consumed 50% of an 8 ns one. The MAC now sources GTX_CLK alongside TXD, so clock and data leave together and arrive together. The receive direction was already built that way, and had been since MII.
What that bought was speed, and what it cost was an abstraction. The interface's correctness is now a relationship between a clock edge and nine data signals at the PHY's input pins, decided by clock-to-out, package delay, trace lengths and a setup window. None of that exists in RTL, where launch and sample share one ideal zero-delay edge — so the natural assertion is true by construction, green on a working board and a broken one alike. That is the forty-first rejected class, and it is the most complete blindness in the track: not vacuous sometimes, but always.
The rest is the module's recurring shape. A gigabit-capable port is two interfaces sharing pins, with the transmit clock's direction reversing on a speed change, so a mode change must release before it acquires. Carrier extend is a half-duplex remnant that pads the carrier without padding the frame, at 12.5% efficiency on a minimum-length frame — arithmetic that argued for its own removal. And the failure signature, once again, is silence with clean counters: two devices in different modes, both correct on their own terms, nothing crossing and nothing to count.
Chapter 10.4 — RGMII takes GMII's timing problem and sharpens it.
RGMII halves GMII's data pins by carrying 4 bits on both edges of a 125 MHz clock — the same 1000 Mb/s on half the wires, which is Chapter 10.2's trick applied to a source-synchronous interface rather than a shared-clock one. And double-data-rate signalling removes the margin this chapter still had. With data changing on both edges, the setup and hold windows are halved, and the relationship between the clock and the data has to be established deliberately — by an internal delay in the PHY, by trace length on the board, or by both, which is exactly the combination that produces the most-reported bring-up failure in the whole of Ethernet.
The full path is on the Ethernet curriculum index.
Continue learning
Related tutorials
- Related topic
The Reconciliation Sublayer and the xMII Contract
The xMII generations are a record of what each had to give up — width, pins, timing margin, even parallelism — to keep carrying the same vocabulary as rates rose. That one vocabulary survived six unrelated physical forms is what media-independence actually means.
- Related topic
1 Gigabit — Four Pairs, Echo Cancellation and GMII
Four pairs, both directions at once, PAM-5 at the same 125 Mbaud — so every receiver hears its own echo louder than its partner and must subtract it. Correctness now depends on an adaptation the RTL does not control.
- Related topic
MII — The Original Media Independent Interface
Sixteen signals, four bits each way, and both clocks sourced by the PHY. Its defining failure is a transmit clock that stops — and every assertion sampled by that clock is green while it does.
- Related topic
The Shared-Medium Problem
Why several independent transmitters on one medium is a distributed timing problem, not a formatting problem. Propagation delay makes every station's view of the medium stale, so two locally correct decisions can still collide — and that is the constraint the Ethernet MAC was built around.
Standards & specifications
- Governing standard
- IEEE Std 802.3 (Ethernet)(opens IEEE in a new tab)
Defines the Ethernet MAC, the media-independent interfaces and the physical-layer sublayers, including framing, access control, auto-negotiation and per-rate PHY specifications. VLAN tagging, priority and time-sensitive shaping are defined by IEEE 802.1, not by 802.3.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the Ethernet curriculum.
