Skip to content
VLSI Mentor

Ethernet · Module 10

RMII — Half the Pins, One Shared Clock

Two bits at 50 MHz gives MII's exact octet time on half the signals, so the saving is free. What it spends is a clock nobody owns and a carrier signal multiplexed onto data valid.

Chapter 10.1 ended with an arithmetic problem. Sixteen signals per port is fine for one port and 384 pins on a 24-port switch, before management and before anything else — which is a package decision rather than a protocol one.

RMII halves it, and the remarkable part is that the halving is free.

Two bits each direction instead of four, at 50 MHz instead of 25, so an octet still crosses in 4 × 20 = 80 nsexactly MII's 2 × 40. The narrower bus and the faster clock cancel, and the interface gives up no throughput and adds no latency.

What it does give up is two structural assumptions, and each one buys a new failure mode.

The clock leaves the PHY. A single 50 MHz reference feeds both ends, which eliminates MII's two independent clock domains and its stopped-clock failure entirely. And it creates a question MII never had to answer: which device supplies that reference — because both ends have an opinion and nothing in the protocol reconciles them.

And carrier sense is multiplexed onto data valid. One wire, CRS_DV, carries both — with a toggling convention to separate them when they disagree.

1. Scope — What This Chapter Owns

This chapter owns RMII: its pin list, its shared reference clock, its di-bit ordering, its multiplexed carrier signal, and the bring-up failure that follows from having no clock owner.

It does not re-derive Module 9's rate arithmetic, and it does not re-derive Chapter 10.1's. MII's nibble ordering, its RX_ER asymmetry and its stopped-clock argument are established there; this chapter states what RMII changed and what each change cost.

Chapter 4.5 owns MDC and MDIO, which ride alongside RMII unchanged. Chapter 10.3 owns GMII, which makes the third of the three possible clock placements.

The claim this chapter defends: a signal that carries two meanings cannot be asserted about in either of them — the only assertable property is about the decode; and a clock with no owner is a clock two devices can both supply or neither.

2. Eight Signals

The reduced media independent interface carries two transmit data bits and transmit enable from the media access control layer to the physical layer, and two receive data bits, a combined carrier sense and data valid signal, and an optional receive error signal from the physical layer to the media access control layer. A single fifty megahertz reference clock feeds both devices and is drawn from a third source rather than from either end, which is the structural difference from the original interface where the physical layer supplied both clocks. Eight signals in total, against sixteen for the original interface, with the same octet transfer time.MACno clock of its ownTXD[1:0], TX_EN3 signals →PHYno clock of its ownRXD[1:0], CRS_DV,RX_ER4 signals ←REF_CLK, 50 MHzone, shared, unownedSourced by whom?the chapter's question12
Figure 1 — eight signals instead of sixteen, and the reference clock belongs to neither end.
SignalWidthDirectionNotes
TXD[1:0]2MAC → PHYa di-bit
TX_EN1MAC → PHY
RXD[1:0]2PHY → MACa di-bit
CRS_DV1PHY → MACcarrier sense AND data valid
RX_ER1PHY → MACoptional
REF_CLK1neither50 MHz, shared
87 without RX_ER

Three of MII's signals are simply gone.

TX_ER — a MAC that needs to abort a frame does so by ceasing to assert TX_EN, and the truncated frame fails the far end's FCS check. The deliberate-corruption channel is dropped, which loses Chapter 10.1 §9's distinction between an intentional abort and an accidental short frame.

COL — collision is derivable. A MAC in half duplex that is transmitting and sees CRS_DV asserted has a collision, so the pin is redundant with a MAC that does one AND.

CRS — merged into CRS_DV, which is Section 7's whole subject.

3. A Clock With No Owner

MII's TX_CLK came from the PHY, which meant it could stop. RMII's REF_CLK comes from wherever the board designer put it, which means something different can go wrong.

A single 50 MHz clock drives both ends, in both directions. The MAC clocks TXD and TX_EN out on it; the PHY clocks RXD and CRS_DV out on it; and there is only one clock domain on the whole interface.

Which removes Chapter 10.1 §8's entire problem. There is no TX_CLK to stop, no RX_CLK to disagree with it, no clock-domain crossing between the two directions, and no speed change that pauses the clock — at 10 Mbps the reference stays at 50 MHz and the data is simply sampled every tenth cycle.

And it creates a new question with three legal answers and no protocol to choose between them.

Where REF_CLK comes fromCommon inWhat goes wrong
an external oscillator feeding bothmulti-port switchesnothing, if both ends are configured to accept it
the MAC, driving the PHYSoCs with a spare clock outputthe PHY must be strapped to accept it
the PHY, driving the MACPHYs with a 50 MHz outputthe MAC must be configured not to drive

And the failures are the two obvious ones. Both ends driving puts two outputs on one net — contention, a clock that is neither frequency, and often heat. Neither end driving leaves the net floating, and an interface with no clock does nothing at all while every register on both devices reads exactly as it should.

4. RTL 1 — An Octet in Four Transfers

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE.
//
// Presents octets onto RMII's two transmit wires.
//
// THE NUMBERS:
//   TXD[1:0]  2 bits per REF_CLK
//   REF_CLK   50 MHz -> 20 ns per di-bit
//   one octet = 4 di-bits = 80 ns  -- IDENTICAL to MII's 2 x 40 ns
//   100 Mbps: 2 x 50 = 100 Mb/s
//    10 Mbps: the clock STAYS at 50 MHz and each di-bit is held for
//             TEN cycles, so the data rate divides by ten while the
//             clock does not change at all. (Chapter 10.1's speed
//             change stopped the clock; this one does not.)
//
// ORDERING: least-significant bits first, as everywhere in Ethernet.
// An octet goes out as bits [1:0], [3:2], [5:4], [7:6].
package rmii_pkg;
 
  localparam int unsigned DIBIT_BITS   = 2;
  localparam int unsigned REF_CLK_MHZ  = 50;
  localparam int unsigned DIBITS_PER_OCTET = 4;
  // At 10 Mbps every di-bit occupies ten REF_CLK cycles.
  localparam int unsigned SLOW_REPEAT  = 10;
  // A nibble is two di-bits, and CRS_DV's deassertion and its toggling
  // convention are both defined on NIBBLE boundaries.
  localparam int unsigned DIBITS_PER_NIBBLE = 2;
 
  typedef enum logic [1:0] {
    CLKSRC_EXTERNAL,
    CLKSRC_MAC,
    CLKSRC_PHY,
    CLKSRC_UNKNOWN
  } clk_source_e;
 
endpackage
 
module rmii_tx_serialiser
  import rmii_pkg::*;
#(
  parameter int unsigned CNT_W = 24
) (
  input  logic ref_clk,          // 50 MHz, shared
  input  logic rst_n,
  input  logic slow_mode,        // 1 = 10 Mbps
 
  input  logic [7:0] octet,
  input  logic       octet_valid,
  output logic       octet_ready,
 
  output logic [DIBIT_BITS-1:0] txd,
  output logic                  tx_en,
 
  output logic [CNT_W-1:0] c_octets,
  output logic             overrun
);
 
  logic [1:0] dibit_q;      // which di-bit of the octet, 0..3
  logic [3:0] repeat_q;     // 10 Mbps hold counter
  logic [7:0] held_q;
  logic       busy_q;
 
  wire advance = !slow_mode || (repeat_q == 4'(SLOW_REPEAT - 1));
 
  // Ready only at an octet boundary. RMII commits four transfers at a
  // time and there is no way to take any of them back.
  assign octet_ready = !busy_q;
 
  always_ff @(posedge ref_clk or negedge rst_n) begin
    if (!rst_n) begin
      dibit_q <= 2'd0; repeat_q <= 4'd0; held_q <= 8'd0; busy_q <= 1'b0;
      txd <= 2'd0; tx_en <= 1'b0; c_octets <= '0; overrun <= 1'b0;
    end else begin
      overrun <= 1'b0;
 
      if (!busy_q) begin
        if (octet_valid) begin
          held_q   <= octet;
          txd      <= octet[1:0];    // LEAST-SIGNIFICANT di-bit first
          tx_en    <= 1'b1;
          busy_q   <= 1'b1;
          dibit_q  <= 2'd1;
          repeat_q <= 4'd0;
        end else begin
          txd   <= 2'd0;
          tx_en <= 1'b0;
        end
      end else begin
        if (octet_valid) overrun <= 1'b1;
 
        // At 10 Mbps the SAME di-bit is held for ten REF_CLK cycles.
        // The clock does not change; the data does not advance.
        if (!advance) begin
          repeat_q <= repeat_q + 4'd1;
        end else begin
          repeat_q <= 4'd0;
          unique case (dibit_q)
            2'd1: txd <= held_q[3:2];
            2'd2: txd <= held_q[5:4];
            2'd3: txd <= held_q[7:6];
            default: ;
          endcase
          tx_en <= 1'b1;
 
          if (dibit_q == 2'd3) begin
            busy_q  <= 1'b0;
            dibit_q <= 2'd0;
            if (!(&c_octets)) c_octets <= c_octets + 1'b1;
          end else begin
            dibit_q <= dibit_q + 2'd1;
          end
        end
      end
    end
  end
 
endmodule

Classification: synthesizable.

What it teaches: that the 10 Mbps mode changes the data rate without changing the clock, which is the structural difference from Chapter 10.1's speed change. MII's PHY stopped TX_CLK and re-locked at 2.5 MHz; RMII holds REF_CLK at 50 MHz and repeats each di-bit ten times. The clock never goes away, so the entire stopped-clock failure class does not exist here.

Deliberately simplified: no TX_ER — RMII does not have one. A MAC aborting a frame simply drops TX_EN, which produces a truncated frame the far end rejects on FCS.

Production implication: octet_ready is asserted only at an octet boundary, because RMII commits four transfers at once. A source that ignores it does not stall the interface; it loses an octet, exactly as in MII — but the window is four cycles wide rather than two, so a marginally slow source misses it more often. The narrower bus made the commitment longer.

5. Two Bits at a Time, and Ten Cycles Each at 10 Mbps

The ordering rule is the same one Chapter 10.1 §5 established, applied two bits at a time: least-significant first. An octet leaves as bits [1:0], [3:2], [5:4], [7:6].

And a nibble is two di-bits, which matters because RMII's carrier convention in Section 7 is defined on nibble boundaries rather than di-bit ones — a fact that only makes sense once you notice RMII is a nibble interface underneath, running at twice the rate with half the width.

MIIRMII
transfer1 nibble1 di-bit
transfers per octet24
transfers per nibble12
clock period40 ns20 ns
octet time80 ns80 ns

The 10 Mbps mode is where RMII and MII genuinely differ, and the difference is the whole reason RMII has no stopped-clock failure.

MII changes speed by changing the clock — the PHY stops TX_CLK, re-locks its synthesiser, and restarts at 2.5 MHz, with the MAC's transmit domain frozen for the duration.

RMII changes speed by changing nothing. REF_CLK stays at 50 MHz, and each di-bit is simply held for ten cycles:

50 MHz ÷ 10 = 5 M di-bits/s × 2 bits = 10 Mb/s

So the clock never stops, never changes frequency, and never needs to re-lock. A speed change on RMII is a register write and a counter reload.

6. RTL 2 — Reassembling an Octet From Di-Bits

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE.
//
// Reassembles octets from RMII's two receive wires.
//
// Runs on REF_CLK -- the SAME clock as the transmit side, which is the
// structural simplification RMII buys. There is no clock-domain
// crossing anywhere on this interface.
//
// THE NIBBLE BOUNDARY MATTERS HERE, and it is easy to miss:
//   RMII's carrier convention (Section 7) is defined on nibble
//   boundaries -- CRS_DV deasserts only on a nibble boundary, and it
//   toggles on nibble boundaries. A deserialiser that tracks only
//   di-bit position within an octet cannot express either rule.
//   So this module tracks BOTH.
module rmii_rx_deserialiser
  import rmii_pkg::*;
#(
  parameter int unsigned CNT_W = 24
) (
  input  logic ref_clk,
  input  logic rst_n,
  input  logic slow_mode,
 
  input  logic [DIBIT_BITS-1:0] rxd,
  input  logic                  data_valid,   // decoded, from Section 7
  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,
 
  // Position within the octet and within the nibble, exported because
  // the CRS_DV decoder needs the nibble phase to apply its rules.
  output logic [1:0] dibit_index,
  output logic       nibble_boundary,
 
  // A frame that ended somewhere other than an octet boundary.
  output logic             partial_octet,
  output logic [CNT_W-1:0] c_frames,
  output logic [CNT_W-1:0] c_partial_octets
);
 
  logic [7:0] shift_q;
  logic [1:0] idx_q;
  logic [3:0] repeat_q;
  logic       dv_q;
 
  wire advance = !slow_mode || (repeat_q == 4'(SLOW_REPEAT - 1));
 
  assign dibit_index     = idx_q;
  // A nibble is two di-bits, so a nibble boundary falls at di-bit
  // index 0 and index 2.
  assign nibble_boundary = (idx_q == 2'd0) || (idx_q == 2'd2);
 
  always_ff @(posedge ref_clk or negedge rst_n) begin
    if (!rst_n) begin
      shift_q <= 8'd0; idx_q <= 2'd0; repeat_q <= 4'd0; dv_q <= 1'b0;
      octet <= 8'd0; octet_valid <= 1'b0; frame_start <= 1'b0;
      frame_end <= 1'b0; frame_had_error <= 1'b0; partial_octet <= 1'b0;
      c_frames <= '0; c_partial_octets <= '0;
    end else begin
      octet_valid   <= 1'b0;
      frame_start   <= 1'b0;
      frame_end     <= 1'b0;
      partial_octet <= 1'b0;
      dv_q          <= data_valid;
 
      if (data_valid) begin
        if (!dv_q) begin
          frame_start <= 1'b1;
          idx_q       <= 2'd0;
          repeat_q    <= 4'd0;
          frame_had_error <= 1'b0;
        end
 
        if (rx_er) frame_had_error <= 1'b1;
 
        if (!advance) begin
          repeat_q <= repeat_q + 4'd1;
        end else begin
          repeat_q <= 4'd0;
          // Di-bits arrive least-significant first, so each one shifts
          // in at the TOP and the octet fills from the bottom.
          shift_q <= {rxd, shift_q[7:2]};
 
          if (idx_q == 2'd3) begin
            octet       <= {rxd, shift_q[7:2]};
            octet_valid <= 1'b1;
            idx_q       <= 2'd0;
          end else begin
            idx_q <= idx_q + 2'd1;
          end
        end
 
      end else if (dv_q) begin
        frame_end <= 1'b1;
        if (!(&c_frames)) c_frames <= c_frames + 1'b1;
 
        // A frame that ended mid-octet. RMII's rules say CRS_DV
        // deasserts on a NIBBLE boundary, so ending at di-bit 1 or 3 is
        // a partner that broke the convention -- worth reporting
        // separately from a data error, because it points at the PHY.
        if (idx_q != 2'd0) begin
          partial_octet <= 1'b1;
          if (!(&c_partial_octets)) c_partial_octets <= c_partial_octets + 1'b1;
        end
        idx_q <= 2'd0;
      end
    end
  end
 
endmodule

Classification: synthesizable.

What it teaches: that RMII is a nibble interface underneath, and a deserialiser that tracks only octet position cannot express the standard's rules. CRS_DV deasserts only on nibble boundaries and toggles on nibble boundaries — both statements are about a phase this module has to compute and export, because the decoder in Section 7 needs it and has no other way to get it.

Deliberately simplified: data_valid arrives already separated from carrier sense. That separation is Section 7's entire job, and doing it here would hide the chapter's subject inside a deserialiser.

Production implication: partial_octet is reported separately from frame_had_error because it points at the partner rather than at the wire. A frame ending at di-bit 1 or 3 means CRS_DV deasserted off a nibble boundary, which the standard says cannot happen — so it is a PHY that broke the convention, and no amount of cable replacement will change it.

7. RTL 3 — One Wire, Two Meanings

The combined carrier sense and data valid signal carries two independent pieces of information on one wire. While carrier is present and data is being delivered, the signal is simply asserted and there is no ambiguity. When the physical layer loses carrier but still holds nibbles in its buffer that it has not delivered, the two meanings disagree, and the signal toggles once per nibble at twenty five megahertz so a receiver can recognise the state. Data remains valid throughout that toggling, so a receiver that treats a deasserted cycle as the end of the frame truncates the frame by however many nibbles were still pending.Carrier + dataasserted, unambiguousCarrier lostnibbles still pendingToggles at 25 MHzon nibble boundariesData STILL validthroughout the togglingA naive receiversees deassertionTruncates the frameand fails its FCS12
Figure 2 — when carrier and data agree, CRS_DV is simply asserted; when they disagree, it toggles on nibble boundaries so a receiver can tell which is which.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE.
//
// Separates CRS_DV back into the two signals it carries.
//
// WHY ONE WIRE CARRIES TWO THINGS: MII spent a pin on CRS and another
// on RX_DV. RMII has neither to spare, so it multiplexes -- and because
// the two can genuinely disagree, it needs a convention for the case
// where they do.
//
// THE THREE STATES:
//   DEASSERTED, steadily     -- no carrier, no data. Between frames.
//   ASSERTED, steadily       -- carrier present and data being
//                               delivered. The two meanings agree, so
//                               one wire is enough.
//   TOGGLING at 25 MHz, on nibble boundaries
//                            -- carrier LOST, but the PHY still holds
//                               nibbles it has not delivered. The two
//                               meanings DISAGREE, and the toggling is
//                               how the wire says so.
//
// DATA REMAINS VALID THROUGHOUT THE TOGGLING. A receiver that treats a
// deasserted cycle as end-of-frame truncates the frame by however many
// nibbles were still pending -- which is Section 13's rejected property
// implemented as a bug.
module rmii_crs_dv_decoder
  import rmii_pkg::*;
#(
  parameter int unsigned CNT_W = 20,
  // Toggles observed before the state is called "toggling" rather than
  // "one deassertion". Two is a pattern; one is an end of frame.
  parameter int unsigned TOGGLE_CONFIRM = 2
) (
  input  logic ref_clk,
  input  logic rst_n,
  input  logic clear,
 
  input  logic crs_dv,
  input  logic nibble_boundary,
 
  // The two signals MII had as separate pins, recovered.
  output logic carrier_sense,
  output logic data_valid,
  // The disagreement state, reported explicitly rather than folded
  // into data_valid -- because it is a diagnostic in its own right.
  output logic carrier_lost_data_pending,
 
  output logic [CNT_W-1:0] c_frames,
  output logic [CNT_W-1:0] c_carrier_lost_early,
  // CRS_DV deasserted off a nibble boundary, which the convention
  // forbids. A partner problem, not a wire problem.
  output logic             off_boundary_deassert,
  output logic [CNT_W-1:0] c_off_boundary,
  output logic             ever_off_boundary
);
 
  logic       prev_q;
  logic [2:0] toggles_q;
  logic       in_frame_q;
 
  always_ff @(posedge ref_clk or negedge rst_n) begin
    if (!rst_n) begin
      prev_q <= 1'b0; toggles_q <= 3'd0; in_frame_q <= 1'b0;
      carrier_sense <= 1'b0; data_valid <= 1'b0;
      carrier_lost_data_pending <= 1'b0;
      c_frames <= '0; c_carrier_lost_early <= '0;
      off_boundary_deassert <= 1'b0; c_off_boundary <= '0;
      ever_off_boundary <= 1'b0;
    end else if (clear) begin
      c_frames <= '0; c_carrier_lost_early <= '0; c_off_boundary <= '0;
      off_boundary_deassert <= 1'b0;
      // ever_off_boundary survives -- a partner that broke the
      // convention once is a partner, and it has not been replaced.
    end else begin
      off_boundary_deassert <= 1'b0;
      prev_q <= crs_dv;
 
      if (crs_dv && !prev_q && !in_frame_q) begin
        // FRAME START. Carrier and data both begin.
        in_frame_q                <= 1'b1;
        carrier_sense             <= 1'b1;
        data_valid                <= 1'b1;
        carrier_lost_data_pending <= 1'b0;
        toggles_q                 <= 3'd0;
        if (!(&c_frames)) c_frames <= c_frames + 1'b1;
 
      end else if (in_frame_q) begin
        if (crs_dv != prev_q) begin
          // A TRANSITION mid-frame. It is only meaningful on a nibble
          // boundary; anywhere else the partner has broken the rule.
          if (!nibble_boundary) begin
            off_boundary_deassert <= 1'b1;
            ever_off_boundary     <= 1'b1;
            if (!(&c_off_boundary)) c_off_boundary <= c_off_boundary + 1'b1;
          end
 
          if (toggles_q != 3'(TOGGLE_CONFIRM)) begin
            toggles_q <= toggles_q + 3'd1;
          end
 
          if (toggles_q + 3'd1 >= 3'(TOGGLE_CONFIRM)) begin
            // CONFIRMED TOGGLING: carrier is gone, data is not.
            // DATA_VALID STAYS HIGH. This is the line the whole module
            // exists for, and dropping it here truncates the frame.
            carrier_sense             <= 1'b0;
            data_valid                <= 1'b1;
            carrier_lost_data_pending <= 1'b1;
            if (!(&c_carrier_lost_early))
              c_carrier_lost_early <= c_carrier_lost_early + 1'b1;
          end
 
        end else if (!crs_dv && !prev_q && (toggles_q == 3'd0)) begin
          // Steadily deasserted with no toggling seen: a genuine end of
          // frame. Both meanings agree again.
          in_frame_q                <= 1'b0;
          carrier_sense             <= 1'b0;
          data_valid                <= 1'b0;
          carrier_lost_data_pending <= 1'b0;
        end else if (!crs_dv && !prev_q) begin
          // Steadily deasserted AFTER toggling: the pending nibbles are
          // done and the frame really has ended.
          in_frame_q                <= 1'b0;
          carrier_sense             <= 1'b0;
          data_valid                <= 1'b0;
          carrier_lost_data_pending <= 1'b0;
          toggles_q                 <= 3'd0;
        end
      end
    end
  end
 
endmodule

Classification: synthesizable.

What it teaches: that data_valid stays high throughout the toggling, and that single line is the module. A receiver that reads CRS_DV low and ends the frame truncates it by however many nibbles the PHY still held — producing a short frame with a bad FCS on a link where nothing is physically wrong. The toggling is not noise on the signal; it is the signal saying that its two meanings have diverged.

Deliberately simplified: the toggle confirmation is a small counter. Real decoders also bound how long the toggling may continue, because an unbounded toggle is a stuck PHY rather than a pending nibble.

Production implication: carrier_lost_data_pending is exported rather than absorbed, because it is a diagnostic about the medium. Carrier lost while data is still queued means the far end stopped transmitting before this end finished receiving — a runt on the wire, or a link that dropped mid-frame — and a receiver that silently handles it correctly has handled it correctly and told nobody.

8. Why a Toggle Rather Than a Level

The question worth asking about CRS_DV is why the disagreement is signalled by toggling rather than by some level.

Because there is no level left. The wire has two states and three things to say: no carrier and no data; carrier and data; carrier gone but data pending. Two states cannot encode three conditions, so the third is encoded in time rather than in level.

ConditionCRS_DV
between framessteadily low
carrier present, data being deliveredsteadily high
carrier lost, nibbles still pendingtoggling, 25 MHz, on nibble boundaries

And the toggle rate is not arbitrary. REF_CLK is 50 MHz and a nibble is two cycles, so a signal that changes once per nibble is

50 MHz ÷ 2 = 25 MHz

— which means the toggling is exactly "one transition per nibble delivered", not a fixed oscillation. It is a per-nibble acknowledgement that happens to look like a clock.

The consequence for a receiver is the thing to remember: a low cycle inside the toggling region does not mean the frame has ended. Data is valid on both phases. A receiver that samples CRS_DV and gates its data path on it directly drops every other nibble — and the resulting frame is not merely truncated, it is decimated.

9. RTL 4 — Deciding Who Supplies the Clock

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE.
//
// Establishes and checks the REF_CLK source, which nothing in the
// protocol establishes.
//
// THE PROBLEM (Section 3): RMII's 50 MHz reference can come from an
// external oscillator, from the MAC, or from the PHY. The choice is
// made by board wiring and register straps, and there is no exchange
// between the two ends -- so:
//
//   BOTH DRIVING  -- two outputs on one net. Contention, a frequency
//        that is neither device's, and often measurable heat.
//   NEITHER DRIVING -- a floating net. The interface does nothing at
//        all while every register on both devices reads correctly.
//
// This module cannot fix either. What it can do is REFUSE to enable its
// own driver without positive configuration, and MEASURE whether a
// clock is actually present -- from a domain that does not depend on
// the clock in question.
module rmii_clock_source_arbiter
  import rmii_pkg::*;
#(
  parameter int unsigned SYS_MHZ   = 100,
  parameter int unsigned WINDOW_US = 100,
  parameter int unsigned CNT_W     = 20,
  parameter int unsigned TOL_PCT   = 5
) (
  input  logic sys_clk,           // free-running, independent of REF_CLK
  input  logic rst_n,
 
  // Configuration, from straps or registers.
  input  clk_source_e configured_source,
  input  logic        drive_enable_request,
 
  // A synchronised toggle derived from REF_CLK.
  input  logic ref_clk_toggle_sync,
 
  // The only output that touches a pin.
  output logic drive_ref_clk,
 
  output logic       ref_clk_present,
  output logic       ref_clk_in_band,
  output logic [CNT_W-1:0] ref_edges_last_window,
  output logic       measurement_valid,
 
  // We are configured to drive and a clock is already present before we
  // start: somebody else is driving too.
  output logic       contention_suspected,
  // We are configured NOT to drive and no clock is present: nobody is.
  output logic       no_clock_source,
  output logic       ever_contention,
  output logic       ever_no_clock
);
 
  localparam int unsigned EXP_EDGES = REF_CLK_MHZ * WINDOW_US;
  localparam int unsigned WIN_CYC   = SYS_MHZ * WINDOW_US;
 
  logic [31:0]      win_q;
  logic [CNT_W-1:0] cnt_q;
  logic             prev_q;
  logic             driving_q;
 
  // REFUSE BY DEFAULT. A driver enabled by a reset value rather than by
  // positive configuration is exactly how both ends end up driving.
  assign drive_ref_clk = driving_q;
 
  always_ff @(posedge sys_clk or negedge rst_n) begin
    if (!rst_n) begin
      win_q <= '0; cnt_q <= '0; prev_q <= 1'b0; driving_q <= 1'b0;
      ref_clk_present <= 1'b0; ref_clk_in_band <= 1'b0;
      ref_edges_last_window <= '0; measurement_valid <= 1'b0;
      contention_suspected <= 1'b0; no_clock_source <= 1'b0;
      ever_contention <= 1'b0; ever_no_clock <= 1'b0;
    end else begin
      prev_q <= ref_clk_toggle_sync;
      if (ref_clk_toggle_sync != prev_q)
        if (!(&cnt_q)) cnt_q <= cnt_q + 1'b1;
 
      if (win_q == 32'(WIN_CYC - 1)) begin
        win_q                 <= '0;
        ref_edges_last_window <= cnt_q;
        measurement_valid     <= 1'b1;
        cnt_q                 <= '0;
 
        ref_clk_present <= (cnt_q != '0);
        ref_clk_in_band <= (cnt_q > CNT_W'((EXP_EDGES * (100 - TOL_PCT)) / 100)) &&
                           (cnt_q < CNT_W'((EXP_EDGES * (100 + TOL_PCT)) / 100));
 
        // CONTENTION. We are asked to drive, we are not driving yet,
        // and a clock is already there. Somebody else owns this net.
        if (drive_enable_request && !driving_q && (cnt_q != '0)) begin
          contention_suspected <= 1'b1;
          ever_contention      <= 1'b1;
          // And we do NOT start driving. Adding a second driver to a
          // net that already has one is the failure, not the fix.
        end else if (drive_enable_request && !driving_q && (cnt_q == '0)) begin
          driving_q            <= 1'b1;
          contention_suspected <= 1'b0;
        end
 
        // NO SOURCE. We are not driving, nobody else is, and the
        // interface will sit silent while every register looks correct.
        no_clock_source <= !driving_q && !drive_enable_request && (cnt_q == '0);
        if (!driving_q && !drive_enable_request && (cnt_q == '0))
          ever_no_clock <= 1'b1;
      end else begin
        win_q <= win_q + 1'b1;
      end
    end
  end
 
endmodule

Classification: synthesizable.

What it teaches: that a driver must be enabled by positive configuration and never by a reset value. driving_q resets to zero and is set only when the design is asked to drive and has confirmed the net is quiet. A design whose clock output enables itself out of reset is precisely how two devices end up driving one net — each one correct in isolation, and the board on fire between them.

Deliberately simplified: contention is inferred from "a clock is present before I started." Real detection also watches for a frequency that matches neither source and for supply current, both of which are board-level measurements.

Production implication: no_clock_source is the diagnostic that saves a day of bench time. An RMII interface with no reference clock does nothing whatsoever, and every register on both devices reads exactly as it should — the MAC is configured, the PHY reports link up from its own internal timing, and no counter anywhere moves. The measurement has to come from a domain that does not depend on REF_CLK, for exactly the reason Chapter 10.1 §7 gives about TX_CLK: a missing clock cannot report its own absence.

10. The Reference-Clock Mismatch

The reduced media independent interface's fifty megahertz reference clock may legally come from an external oscillator feeding both devices, from the media access control device driving the physical layer device, or from the physical layer device driving the media access control device. Nothing in the protocol establishes which, so the choice is made by board wiring and configuration straps at each end independently. If both ends are configured to drive, two outputs contend on one net. If neither is configured to drive, the net floats and the interface does nothing while every register on both devices reads correctly. Only a measurement taken from a clock domain that does not depend on the reference can distinguish these from a working link.External oscillatorboth ends receiveMAC drivesPHY strapped to receivePHY drivesMAC must notNothing in theprotocolchooses between themBoth drivecontentionNeither drivessilence, registers fine12
Figure 3 — three legal sources, no protocol to choose between them, and two ways for a board to get it wrong.

The two failures are opposite and the second is much worse.

Both ends driving is loud. Two push-pull outputs on one net produce a waveform that is neither device's clock, often at a frequency in no legal band, with measurable current and sometimes measurable heat. It is unpleasant and it is obvious on a scope in thirty seconds.

Neither end driving is silent, and it is silent in a specific and cruel way.

What you checkWhat it says
MAC configuration registerscorrect
PHY identity and status registerscorrect — MDIO does not use REF_CLK
PHY link statuslink up — the PHY's line side has its own timing
autonegotiation resultcompleted, 100 Mbps full duplex
MAC frame counterszero
PHY frame counterszero

Every single indicator says the link is fine, and no frame ever crosses.

The reason management still works is the important part. Chapter 4.5's MDC and MDIO are a separate clock and a separate wire — they do not use REF_CLK at all. So software can talk to the PHY, read its registers, confirm the link is up and the speed is negotiated, and be reading a device whose data path is not connected to anything.

11. RTL 5 — Checking What the Partner Does

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE.
//
// Checks the partner's conformance to the conventions RMII relies on
// and cannot enforce.
//
// The three rules worth checking, all of them about the PHY:
//   1. CRS_DV transitions only on NIBBLE boundaries. The convention
//      that makes the toggling decodable at all.
//   2. TX_EN and CRS_DV must not both be asserted on a FULL-DUPLEX
//      link -- that combination is RMII's collision indication, and on
//      a full-duplex port a collision is impossible.
//   3. RX_ER, if present, is asserted only during data valid. RMII
//      has no indication channel: Chapter 10.1's RX_ER-without-RX_DV
//      encoding does not exist here.
//
// Runs on REF_CLK, which is safe HERE because RMII has one clock domain
// and the clock's own absence is Section 9's job, not this module's.
module rmii_conformance_monitor
  import rmii_pkg::*;
#(
  parameter int unsigned CNT_W = 20
) (
  input  logic ref_clk,
  input  logic rst_n,
  input  logic clear,
 
  input  logic full_duplex,
  input  logic crs_dv,
  input  logic tx_en,
  input  logic rx_er,
  input  logic data_valid,        // decoded, from Section 7
  input  logic nibble_boundary,   // from Section 6
 
  output logic off_boundary_transition,
  output logic collision_on_full_duplex,
  output logic rx_er_outside_data,
 
  output logic [CNT_W-1:0] c_off_boundary,
  output logic [CNT_W-1:0] c_collisions_fd,
  output logic [CNT_W-1:0] c_rx_er_outside,
 
  output logic       first_violation_valid,
  output logic [1:0] first_violation_kind,
  output logic       ever_violated
);
 
  logic prev_crs_dv;
  logic any_c;
  logic [1:0] kind_c;
 
  always_comb begin
    any_c  = 1'b0;
    kind_c = 2'd0;
    if ((crs_dv != prev_crs_dv) && !nibble_boundary) begin
      any_c = 1'b1; kind_c = 2'd0;
    end else if (full_duplex && tx_en && crs_dv) begin
      any_c = 1'b1; kind_c = 2'd1;
    end else if (rx_er && !data_valid) begin
      any_c = 1'b1; kind_c = 2'd2;
    end
  end
 
  always_ff @(posedge ref_clk or negedge rst_n) begin
    if (!rst_n) begin
      prev_crs_dv <= 1'b0;
      off_boundary_transition <= 1'b0; collision_on_full_duplex <= 1'b0;
      rx_er_outside_data <= 1'b0;
      c_off_boundary <= '0; c_collisions_fd <= '0; c_rx_er_outside <= '0;
      first_violation_valid <= 1'b0; first_violation_kind <= 2'd0;
      ever_violated <= 1'b0;
    end else begin
      prev_crs_dv <= crs_dv;
 
      off_boundary_transition  <= (crs_dv != prev_crs_dv) && !nibble_boundary;
      // TX_EN AND CRS_DV TOGETHER is how RMII signals a collision --
      // it dropped MII's COL pin because the MAC can derive it. On a
      // full-duplex link the derivation must never fire.
      collision_on_full_duplex <= full_duplex && tx_en && crs_dv;
      // RMII has no RX_ER indication channel. RX_ER outside data valid
      // means nothing defined, so it is reported rather than decoded.
      rx_er_outside_data       <= rx_er && !data_valid;
 
      if (clear) begin
        c_off_boundary <= '0; c_collisions_fd <= '0; c_rx_er_outside <= '0;
        first_violation_valid <= 1'b0;
        // ever_violated survives.
      end else begin
        if ((crs_dv != prev_crs_dv) && !nibble_boundary && !(&c_off_boundary))
          c_off_boundary <= c_off_boundary + 1'b1;
        if (full_duplex && tx_en && crs_dv && !(&c_collisions_fd))
          c_collisions_fd <= c_collisions_fd + 1'b1;
        if (rx_er && !data_valid && !(&c_rx_er_outside))
          c_rx_er_outside <= c_rx_er_outside + 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 RMII derives collision from TX_EN && CRS_DV, which is how it afforded to drop MII's COL pin — and that the derivation must never fire on a full-duplex link. Chapter 10.1 §10's hazard applies with a twist: the collision indication is now computed by the MAC rather than delivered to it, so a spurious CRS_DV during transmission produces a collision that no pin ever asserted.

Deliberately simplified: three rules. A production monitor also bounds the toggling duration and checks the inter-frame gap in REF_CLK cycles.

Production implication: this monitor runs on REF_CLK, which would be the wrong choice on MII and is the right one here — because RMII has a single clock domain and the clock's own absence is Section 9's job, measured from sys_clk. The two chapters place their monitors in different domains for the same reason: put the check where the thing being checked can still be observed.

12. Pins and Latency, Computed

The pin saving is the point and the latency cost is zero. Both are worth doing arithmetic on rather than asserting.

MIIRMIIRMII, shared REF_CLK
signals per port1687 + a shared clock
4 ports643229
8 ports1286457
24 ports384192169

A 24-port design saves 215 signals, because a single 50 MHz oscillator feeds every port and each port needs only seven of its own.

And the latency is identical, which is the part that makes the saving free.

MIIRMII
bits per transfer42
clock25 MHz50 MHz
clock period40 ns20 ns
transfers per octet24
octet time2 × 40 = 80 ns4 × 20 = 80 ns
a 1518-octet frame121.44 µs121.44 µs

Halving the width and doubling the clock cancel exactly, so RMII gives up no throughput and adds no pipelining delay. The only thing it costs is a faster clock to route — 50 MHz instead of 25, on a board that now has one clock net instead of two per port.

13. Properties Worth Asserting, and One Worth Refusing

The organising split is between what this design produces — the transmit serialisation, the decode, the driver enable — and what the partner does, which can only be checked.

Serialisation and deserialisation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P1. Least-significant di-bit first. The ordering rule, two bits at a
// time, and the counterpart of MII's low-nibble-first.
property p_ls_dibit_first;
  @(posedge ref_clk) disable iff (!rst_n)
  (octet_valid && octet_ready) |=> (txd == $past(octet[1:0]));
endproperty
a_ls_dibit_first: assert property (p_ls_dibit_first);
 
// P2. Four transfers per octet, never three and never five.
property p_four_dibits_per_octet;
  @(posedge ref_clk) disable iff (!rst_n)
  (busy_q && (dibit_q == 2'd3) && advance) |=> !busy_q;
endproperty
a_four_dibits: assert property (p_four_dibits_per_octet);
 
// P3. TX_EN spans the whole octet. A gap between an octet's four
// transfers is a frame the far end will not reassemble.
property p_tx_en_spans_octet;
  @(posedge ref_clk) disable iff (!rst_n)
  (busy_q && (dibit_q != 2'd3)) |=> tx_en;
endproperty
a_tx_en_spans_octet: assert property (p_tx_en_spans_octet);
 
// P4. In slow mode each di-bit is held for exactly SLOW_REPEAT cycles.
// The speed change that does NOT touch the clock.
property p_slow_mode_repeat;
  @(posedge ref_clk) disable iff (!rst_n)
  (slow_mode && busy_q && (repeat_q != 4'(SLOW_REPEAT - 1)))
    |=> $stable(txd);
endproperty
a_slow_mode_repeat: assert property (p_slow_mode_repeat);
 
// P5. An octet offered mid-octet is REPORTED, not silently lost.
property p_overrun_reported;
  @(posedge ref_clk) disable iff (!rst_n)
  (busy_q && octet_valid) |=> overrun;
endproperty
a_overrun_reported: assert property (p_overrun_reported);
 
// P6. A frame that ended off an octet boundary is reported, because it
// means the partner deasserted off a nibble boundary.
property p_partial_octet_reported;
  @(posedge ref_clk) disable iff (!rst_n)
  (frame_end && ($past(idx_q) != 2'd0)) |-> partial_octet;
endproperty
a_partial_octet_reported: assert property (p_partial_octet_reported);

The decode — the only assertable statements about CRS_DV

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P7. THE PROPERTY THIS CHAPTER IS ABOUT. During confirmed toggling,
// data_valid stays HIGH. Dropping it truncates the frame by however
// many nibbles the PHY still held.
property p_data_valid_through_toggling;
  @(posedge ref_clk) disable iff (!rst_n)
  carrier_lost_data_pending |-> data_valid;
endproperty
a_data_valid_through_toggling: assert property (p_data_valid_through_toggling);
 
// P8. And carrier_sense is LOW there. The two recovered signals
// genuinely disagree, which is the state the wire was encoding.
property p_carrier_low_when_pending;
  @(posedge ref_clk) disable iff (!rst_n)
  carrier_lost_data_pending |-> !carrier_sense;
endproperty
a_carrier_low_when_pending: assert property (p_carrier_low_when_pending);
 
// P9. Steady assertion means both are true. The unambiguous case.
property p_steady_assert_means_both;
  @(posedge ref_clk) disable iff (!rst_n)
  (crs_dv && $past(crs_dv) && in_frame_q && (toggles_q == 3'd0))
    |-> (carrier_sense && data_valid);
endproperty
a_steady_assert_means_both: assert property (p_steady_assert_means_both);
 
// P10. One deassertion is not toggling. A frame does not enter the
// pending state on a single transition.
property p_one_transition_is_not_toggling;
  @(posedge ref_clk) disable iff (!rst_n)
  (toggles_q < 3'(TOGGLE_CONFIRM)) |-> !carrier_lost_data_pending;
endproperty
a_one_transition_not_toggling: assert property (p_one_transition_is_not_toggling);
 
// P11. The disagreement is REPORTED, not absorbed -- it is a statement
// about the medium and a receiver that handles it silently has handled
// it correctly and told nobody.
property p_pending_is_counted;
  @(posedge ref_clk) disable iff (!rst_n)
  $rose(carrier_lost_data_pending) |=> (c_carrier_lost_early > $past(c_carrier_lost_early));
endproperty
a_pending_is_counted: assert property (p_pending_is_counted);

The clock driver — refusal by default

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P12. The driver is never enabled out of reset. A clock output that
// enables itself is how two devices end up driving one net.
property p_no_drive_after_reset;
  @(posedge sys_clk) disable iff (!rst_n)
  $rose(rst_n) |=> !drive_ref_clk;
endproperty
a_no_drive_after_reset: assert property (p_no_drive_after_reset);
 
// P13. Driving requires positive configuration AND a quiet net.
property p_drive_needs_request_and_quiet;
  @(posedge sys_clk) disable iff (!rst_n)
  $rose(drive_ref_clk) |-> ($past(drive_enable_request) &&
                            ($past(ref_edges_last_window) == '0));
endproperty
a_drive_needs_request: assert property (p_drive_needs_request_and_quiet);
 
// P14. A clock already present when we are asked to drive is reported
// as contention, and we do NOT start driving.
property p_contention_refuses;
  @(posedge sys_clk) disable iff (!rst_n)
  contention_suspected |=> !$rose(drive_ref_clk);
endproperty
a_contention_refuses: assert property (p_contention_refuses);
 
// P15. No clock and nobody configured to drive is REPORTED -- the
// silent failure where every other register reads correctly.
property p_no_clock_reported;
  @(posedge sys_clk) disable iff (!rst_n)
  (measurement_valid && !drive_ref_clk && !drive_enable_request &&
   (ref_edges_last_window == '0)) |-> no_clock_source;
endproperty
a_no_clock_reported: assert property (p_no_clock_reported);

Partner conformance

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P16. A CRS_DV transition off a nibble boundary is a violation. The
// convention the toggling decode depends on.
property p_transitions_on_nibble_boundary;
  @(posedge ref_clk) disable iff (!rst_n)
  ((crs_dv != prev_crs_dv) && !nibble_boundary) |=> off_boundary_transition;
endproperty
a_transitions_on_boundary: assert property (p_transitions_on_nibble_boundary);
 
// P17. TX_EN and CRS_DV together on a full-duplex link is RMII's
// derived collision, and it must never fire there.
property p_no_derived_collision_full_duplex;
  @(posedge ref_clk) disable iff (!rst_n)
  (full_duplex && tx_en && crs_dv) |=> collision_on_full_duplex;
endproperty
a_no_derived_collision: assert property (p_no_derived_collision_full_duplex);

14. Verification Scenarios

Serialisation

  1. A single octet at 100 Mbps — four consecutive di-bits, [1:0] then [3:2] then [5:4] then [7:6], TX_EN asserted for all four.
  2. 0xB4TXD goes 00, 01, 11, 10. A value whose four di-bits all differ.
  3. Back-to-back octetsoctet_ready high only on the boundary cycle, low for the other three.
  4. An octet offered mid-octetoverrun pulses, the in-flight octet completes intact.
  5. Slow mode — each di-bit held for exactly ten REF_CLK cycles; REF_CLK never changes frequency.
  6. A speed change from 100 to 10 Mbps mid-idle — the clock is untouched; only the repeat counter changes. The scenario that has no MII equivalent.
  7. A frame boundary in slow modeTX_EN deasserts on an octet boundary after the tenth repeat, not on the first.

Decode — the toggling region

  1. A clean frame, CRS_DV steadily highcarrier_sense and data_valid both high throughout, carrier_lost_data_pending low.
  2. CRS_DV steadily low — both outputs low; no frame.
  3. CRS_DV toggling for six nibblescarrier_sense low, data_valid high on every cycle including the low ones, carrier_lost_data_pending high, c_carrier_lost_early increments once.
  4. Data sampled during the toggling regionevery nibble is captured. The decimation check: a receiver that gates on CRS_DV directly loses half of them.
  5. A single deassertion followed by reassertionnot classified as toggling; TOGGLE_CONFIRM is 2.
  6. Toggling, then steady low — the frame ends, data_valid falls, toggles_q resets.
  7. CRS_DV deasserting off a nibble boundaryoff_boundary_deassert, ever_off_boundary sticky.
  8. Toggling that begins on the first nibble of a frame — a PHY with nothing buffered; handled without a partial octet.
  9. clear after an off-boundary event — counters clear, ever_off_boundary survives.

Clock source

  1. Configured external, clock present and in bandref_clk_present, ref_clk_in_band, drive_ref_clk low.
  2. Configured to drive, net quietdrive_ref_clk rises after one measurement window.
  3. Configured to drive, clock already presentcontention_suspected high, drive_ref_clk stays low. The design refuses to add a second driver.
  4. Configured not to drive, no clockno_clock_source high, ever_no_clock sticky.
  5. Out of reset with drive_enable_request already highdrive_ref_clk is low for at least one window. P12: never enabled by a reset value.
  6. A clock at 25 MHz instead of 50ref_clk_present high, ref_clk_in_band low. Present is not the same as correct.
  7. The clock disappearing mid-operation — detected within one window from sys_clk, which does not depend on it.

Partner conformance

  1. TX_EN and CRS_DV together, half duplex — a legitimate collision; no violation.
  2. TX_EN and CRS_DV together, full duplexcollision_on_full_duplex, counted.
  3. RX_ER outside data_validrx_er_outside_data. RMII has no indication channel, so it means nothing defined.
  4. A violation storm — counters saturate, first_violation_kind unchanged from the first.

15. Debugging: The Silent Failure and the Loud One

ObservationLikely causeThe distinguishing check
link up, autonegotiated, zero frames either wayno REF_CLK — nobody drivingno_clock_source; MDIO works because it does not use REF_CLK
clock net at a frequency in no legal band, warmboth ends drivingcontention_suspected; a scope settles it in thirty seconds
link up, clock present, zero framesclock present but out of bandref_clk_in_band low — present is not correct
occasional short frames, FCS failures, clean cableCRS_DV gated directly — decimationcarrier_lost_data_pending rising with those failures
frames truncated by a few octets, load-correlatedthe same bug at frame endthe loss is always a small number of nibbles
rising c_off_boundarya PHY breaking the nibble-boundary conventionever_off_boundary; a partner problem, not a wire one
rising c_partial_octetsthe same fault seen from the deserialiserthe two counters should rise together
collisions reported on a full-duplex linkCRS_DV asserting during transmitcollision_on_full_duplexRMII derives collision, so no pin asserted
everything works at 100 Mbps, fails at 10the slow-mode repeat countREF_CLK unchanged; check the tenth-cycle sampling

Three habits.

First, on a link that reports perfect health and passes nothing, check the clock before anything else. MDIO uses its own clock, so every register read succeeds, the PHY reports link up, and autonegotiation reports a completed 100 Mbps full-duplex result — all of it true, and all of it about the PHY's line side. The data path to the MAC has no clock, and nothing in that list can say so.

Second, treat a handful of missing octets as a decode bug, not a wire problem. A cable fault produces FCS failures with random damage. A CRS_DV gating bug produces frames short by a specific small number of nibbles, correlated with carrier_lost_data_pending, on a link with no physical errors at all.

Third, remember that RMII derives collision. There is no COL pin, so a "collision" on a full-duplex link is the MAC's own TX_EN && CRS_DV computation firing — which means the fault is a spurious CRS_DV during transmission, and looking for a collision source is looking for something that does not exist.

16. Common Misconceptions

"RMII is a cut-down MII, so it is slower."

The wrong model: halving the bus halves the throughput.

What it costs: you cannot explain why RMII displaced MII everywhere it was an option.

The corrected model: the bus halves and the clock doubles, so 4 × 20 ns = 80 ns per octet against MII's 2 × 40 nsidentical. Same throughput, same latency, same frame time. RMII saves 215 signals on a 24-port design and costs nothing, which is why the choice was not close.

"CRS_DV is data valid with carrier sense folded in."

The wrong model: one signal with a primary meaning and a secondary hint.

What it costs: a receiver that gates its data path on CRS_DV and decimates every frame that ends with buffered nibbles.

The corrected model: it is two signals on one wire, resolved in time. When they agree, the level says everything. When they disagree — carrier lost, nibbles still pending — the wire toggles on nibble boundaries at 25 MHz, and data is valid on both phases. There is no correct way to use CRS_DV directly; it has to be decoded into the two signals it carries.

"RMII has no clocking problems because there is only one clock."

The wrong model: one clock domain means one less thing to get wrong.

What it costs: the entire reference-clock class, which is the interface's defining bring-up failure.

The corrected model: one clock domain removed MII's stopped-clock failure entirely — nothing stops, nothing re-locks, and the 10 Mbps mode does not touch the clock at all. And it created a question MII never had: who supplies the reference. Three legal answers, no protocol to choose, and two ways to get it wrong — both ends driving, or neither.

"If the link is up, the interface is connected."

The wrong model: PHY link status reflects the whole path.

What it costs: a day, and it is the most-reported RMII bring-up failure.

The corrected model: link status is about the PHY's line side. With no REF_CLK, the PHY still negotiates, still reports 100 Mbps full duplex, and still answers every MDIO read — because MDIO has its own clock. The MAC-side data path has no clock and moves nothing, and every register on both devices reads exactly as it should.

"Dropping TX_ER and COL cost nothing, since they were derivable or unused."

The wrong model: removed pins were redundant.

What it costs: one real diagnostic and one relocated hazard.

The corrected model: COL is genuinely derivableTX_EN && CRS_DV — but the derivation now happens inside the MAC, so a spurious CRS_DV during transmit produces a collision that no pin ever asserted and no PHY ever reported. And TX_ER was not redundant: it let a MAC deliberately corrupt an aborted frame so it could not be mistaken for a legitimate short one. Without it, Chapter 10.1 §9's distinction between an intentional abort and an accidental truncation is gone.

17. Interview Reasoning

"What does RMII save and what does it cost?"

It halves the signal count and costs nothing in throughput or latency, which is the answer that ends the topic. Two bits at 50 MHz against four at 25: 4 × 20 = 80 ns per octet versus 2 × 40 = 80 nsidentical, so the narrower bus and faster clock cancel exactly. Sixteen signals become eight, and because one 50 MHz oscillator can feed every port, a 24-port design goes from 384 pins to 169. What it costs is spent elsewhere: carrier sense multiplexed onto data valid, producing a wire that must be decoded rather than read; and a reference clock with no owner, which two devices can both drive or neither.

"Explain CRS_DV and what goes wrong if you use it directly."

It carries two signals on one wire. When carrier and data agree it is simply asserted. When they disagree — carrier lost while the PHY still holds nibbles it has not delivered — it toggles on nibble boundaries, which at 50 MHz with two cycles per nibble is a 25 MHz square wave. Data is valid on both phases of that toggling. So a receiver that gates its data path on CRS_DV captures every other nibble and produces a frame that is not truncated but decimated — short by a specific small number of nibbles, on a link with no physical errors. The strong answer names the encoding argument: two states, three conditions, so the third is encoded in time.

"What is RMII's defining bring-up failure?"

Nobody driving REF_CLK, and the reason it takes so long to find is that every diagnostic still works. MDIO has its own clock, so the PHY answers every register read, reports link up, and reports a completed 100 Mbps full-duplex negotiation — all true, and all about its line side. The MAC-side data path has no clock and moves nothing, and both devices' registers read exactly as they should. The strong answer adds the opposite failure and why it is easier: both ends driving puts two outputs on one net, producing a frequency in no legal band and measurable heat — loud, and found on a scope in thirty seconds. And the design-side fix: a clock driver must be enabled by positive configuration and never by a reset value.

"Would you assert that CRS_DV implies receive data valid?"

No — it is false, and false in exactly the region the signal's convention exists for. During the toggling state CRS_DV is deasserted on half the cycles while data is valid on all of them, so the property fires on every low cycle; and the reverse direction fails on the same cycles for the same reason. Neither holds, because the wire does not mean either thing. The deeper objection is what the property encourages: a design built to satisfy it gates its data path on CRS_DV and decimates frames — the rejected property is the bug written as a specification. Assert instead on the decoder's outputs: data_valid stays high through the toggling, carrier_sense goes low there, one transition is not toggling, and the disagreement is counted. A wire with two meanings has no true single-meaning property; it has a decode, and the decode is what can be asserted.

18. Understanding Check

Halving the width and doubling the clock cancel exactly.

MIIRMII
bits per transfer42
clock25 MHz50 MHz
period40 ns20 ns
transfers per octet24
octet time2 × 40 = 80 ns4 × 20 = 80 ns
1518-octet frame121.44 µs121.44 µs

Same throughput, same latency, same frame time.

And the pin saving compounds, because one 50 MHz oscillator can feed every port:

PortsMIIRMII, shared REF_CLK
46429
24384169

A 24-port design saves 215 signals for nothing — not throughput, not latency, not a clock domain. Which is why the choice was never close in any multi-port design.

The only genuine cost is routing a 50 MHz clock instead of a 25 MHz one, on a board that now has one clock net rather than two per port.

19. What's Next

The claim this chapter defended: a signal with two meanings has no true single-meaning property, and a clock with no owner is a clock two devices can both drive or neither.

RMII halves MII's signal count — sixteen to eight, and a 24-port design from 384 pins to 169 — and the halving is free. Two bits at 50 MHz against four at 25 gives the same 80 ns octet time, the same throughput and the same latency. What it spends is structural.

The clock left the PHY, which removed Chapter 10.1's stopped-clock failure completely: one domain, no crossing, and a 10 Mbps mode that holds each di-bit for ten cycles rather than changing the clock at all. And it created a question no protocol answers — who supplies the 50 MHz reference — whose silent failure is a link that reports itself up, negotiated and healthy through an MDIO channel that has its own clock, while nothing crosses.

And carrier sense was multiplexed onto data valid, giving a wire with three conditions and two levels, the third encoded in time as a per-nibble toggle. Data stays valid on both phases, so a receiver that reads the wire instead of decoding it decimates every frame that ends with buffered nibbles — which is the fortieth rejected class: a property that picks one meaning of a signal carrying two, and encodes the bug it was meant to prevent.

Chapter 10.3 — GMII makes the third of the three possible clock placements.

MII gave both clocks to the PHY; RMII gave the clock to neither. GMII splits them: the MAC sources the transmit clock and the PHY sources the receive clock, which is the only arrangement that keeps each clock with the device that also sources the data it times. And it introduces a failure neither of the others can have. The correctness of a source-synchronous interface at 125 MHz is decided by setup and hold at the pins — board delay, package delay, and the relationship between a clock and the data launched with it — none of which exists in the RTL abstraction at all. Which makes 10.3's rejected property the one about a truth that lives outside the model.

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.