Skip to content
VLSI Mentor

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

The gigabit media independent interface carries eight transmit data bits, transmit enable and transmit error from the media access control layer to the physical layer, and crucially the gigabit transmit clock also travels in that direction, sourced by the media access control layer. In the receive direction the physical layer sends eight receive data bits, receive data valid, receive error and the receive clock. Carrier sense and collision also come from the physical layer. A separate transmit clock from the physical layer is retained so the interface can fall back to the original four bit mode at ten and one hundred megabits per second. Each direction's clock therefore originates at the same device that launches the data it times, which is what source synchronous means.MACsources GTX_CLKTXD[7:0], TX_EN,TX_ER10 signals →GTX_CLK, 125 MHz→ with the dataPHYsources RX_CLKRXD[7:0], RX_DV,RX_ER, RX_CLK11 signals ←TX_CLK retained← for 10/100 fallback12
Figure 1 — for the first time in this module, a clock travels with the data it times.
SignalWidthDirectionNotes
TXD[7:0]8MAC → PHYa full octet per clock
TX_EN, TX_ER2MAC → PHY
GTX_CLK1MAC → PHY125 MHz, sourced by the MAC
RXD[7:0]8PHY → MAC
RX_DV, RX_ER2PHY → MAC
RX_CLK1PHY → MAC125 MHz
CRS, COL2PHY → MAChalf duplex
24
TX_CLK1PHY → MACretained 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.

TermMII at 25 MHzGMII if built the same way, 125 MHz
clock period40 ns8 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 period10%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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
 
endmodule

Classification: 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
 
endmodule

Classification: 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 operates in one of two configurations on the same set of pins. At one thousand megabits per second it uses all eight transmit data bits, clocked by the gigabit transmit clock which the media access control layer sources at one hundred and twenty five megahertz. At ten or one hundred megabits per second it uses only the lower four transmit data bits, clocked by the transmit clock which the physical layer sources at two point five or twenty five megahertz. So the transmit clock's direction of travel reverses when the speed changes, and the data width changes at the same moment. A speed change is therefore a reconfiguration of the interface itself rather than a change of rate within it.1000 Mbps8 bits, GTX_CLK →10 / 100 Mbps4 bits, TX_CLK ←The same pinsTXD[3:0] sharedClock directionreverseson a speed changeBoth driving TXD?the transition hazardChange only whenidlethe interlock12
Figure 2 — a gigabit-capable port is two interfaces sharing pins, and the transmit clock's direction reverses between them.

A gigabit-capable port has to work at 10 and 100 Mbps too, and GMII's answer is to become MII.

1000 Mbps100 Mbps10 Mbps
data width8 bits, TXD[7:0]4 bits, TXD[3:0]4 bits
transmit clockGTX_CLKTX_CLKTX_CLK
sourced bythe MACthe PHYthe PHY
frequency125 MHz25 MHz2.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_CLKor 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
 
endmodule

Classification: 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
 
endmodule

Classification: 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

In register transfer level simulation, transmit data is launched by an ideal clock edge and sampled by the same ideal clock edge, with zero delay between them, so any assertion that the data is stable at the clock edge is true by construction of the model. In hardware, the launched data must cross the package, the board trace and the receiver's package before it reaches the physical layer device's input, where it must satisfy that device's setup and hold window relative to the clock edge that arrived by a parallel path. The difference between those two paths, in picoseconds, decides whether the interface works. None of that difference exists in the register transfer level abstraction, so an assertion written there is green whether the board is correct or not.RTL launch edgeideal, zero delayAssertion seesstable, alwaysPackage + tracepicoseconds, unmodelledPHY input pinsetup / hold windowDecided by STAand by the boardGreen either wayworking or broken12
Figure 3 — the RTL's launch edge and the PHY's sampling window are separated by delays the RTL does not represent.

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 simulationhardware
launchideal edgeedge + clock-to-out + package
pathnonetrace, tens to hundreds of ps
samplesame ideal edgeedge + package, against a setup window
the questionnot representablethe whole question
what decides itSTA, 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
 
endmodule

Classification: 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.

RateSlot time, bit timesSlot time, µs
10 Mbps51251.2 µs
100 Mbps5125.12 µs
1000 Mbps40964.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

MIIRMIIGMII
bits per transfer428
clock25 MHz50 MHz125 MHz
period40 ns20 ns8 ns
transfers per octet241
octet time80 ns80 ns8 ns
64-octet frame5.12 µs5.12 µs0.512 µs
1518-octet frame121.44 µs121.44 µs12.144 µs
signals16824

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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

  1. A continuous octet stream — one octet per GTX_CLK, TX_EN asserted throughout, TXD changing only on clock edges.
  2. 0xA5 followed by 0x5A — both appear whole in consecutive cycles; no ordering to get wrong, which is the check that confirms GMII has no nibble problem.
  3. A gap mid-frameunderrun pulses, and the correct response is TX_ER rather than a silent short frame.
  4. force_error mid-frameTX_ER alongside TX_EN for the requested cycles.
  5. TXD driven combinationally instead of registered (a deliberate mutation) — P2 fires. The RTL bug that produces the same symptom as a board timing failure.
  6. TXD launched from a second clock (a deliberate mutation) — P1 fires. Same symptom, different cause, and the only one an assertion can catch.

Receive indications

  1. A clean frame — one octet per RX_CLK, frame_start and frame_end on RX_DV's edges.
  2. RX_ER with RX_DVframe_had_error sticky for the frame.
  3. RX_ER, no RX_DV, RXD = 0x0Fcarrier_extend, not frame_had_error, not an octet.
  4. RX_ER, no RX_DV, RXD = 0x0Efalse_carrier.
  5. RX_ER, no RX_DV, RXD = 0x33unknown_indication, counted.
  6. 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.
  7. Extend arriving on a full-duplex linkextend_on_full_duplex, first_violation_kind = 1. A duplex disagreement.

Mode changes

  1. 1000 → 100 while idle — quiesce, release, settle, adopt. change_applied after exactly SETTLE_CYCLES.
  2. 1000 → 100 mid-frame — held until tx_active and rx_active both fall.
  3. Traffic that never quiesceschange_timed_out, c_timeouts increments, mode unchanged.
  4. Outputs during the settle windowtx_outputs_enabled low for the whole gap. P9 and P10.
  5. A mutation that adopts the mode before releasing outputs — P9 fires. The natural way to write it, and the bug.
  6. Reset with a mode request already asserted — outputs released for at least one cycle. P12.
  7. 100 → 1000 and back — the clock's direction reverses twice with no overlap either time.

Clock observation

  1. GMII mode, only GTX_CLK running — correct; no flags.
  2. MII mode, only TX_CLK running — correct.
  3. Both runningboth_clocks_running, ever_both sticky. The overlap signature.
  4. Neither runningno_clock_running. Released and never acquired.
  5. GMII mode with TX_CLK running and GTX_CLK stoppedwrong_clock_for_mode. Neither device is wrong on its own terms.
  6. TX_EN asserted while wrong_clock_for_modetx_with_wrong_clock, counted.
  7. CRS and COL on a full-duplex link — both counted, unchanged from Chapter 10.1.

15. Debugging: The Symptom Is Almost Always Silence

ObservationLikely causeThe distinguishing check
link up, zero frames, zero errors anywherea mode disagreementreported_mode at each end; wrong_clock_for_mode
the same, with both clocks togglingmode disagreement, confirmedboth_clocks_running
the same, with neither clock togglinga transition that released and never acquiredno_clock_running
frames transmitted, far end sees garbagesetup or hold at the PHY's pinsno assertion will find it — an STA report and a scope
the same, intermittently, temperature-dependentmarginal timing rather than broken timingthe temperature correlation is the diagnosis
the same, after a synthesis change with no RTL changean unregistered output whose logic depth movedP2; check that the pin comes straight off a flop
the same, after a board respintrace matching between GTX_CLK and TXDlength-match the nine signals as a group
works at 100, fails at 1000the transition, or GMII-mode timingc_changes, ever_both, then STA
carrier extend on a full-duplex linkthe PHY thinks the port is half duplexextend_on_full_duplex — a duplex mismatch, named
collisions reported, full duplexChapter 10.1's hazard, unchangedcol_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 indicationRX_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.

TermMII, 25 MHzthe same at 125 MHz
clock period40 ns8 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 period10%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

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.