Skip to content
VLSI Mentor

Ethernet · Module 10

SGMII — Four Wires, and the Same Four at Every Speed

Two differential pairs at a fixed 1.25 Gbaud, with 10 and 100 Mb/s served by repeating code groups. Timing is closed once for every speed, and speed and duplex arrive continuously inside the idle stream.

Chapter 10.4 got the pin count to twelve by folding the data bus in half and clocking both edges — and paid for it with a 4 ns bit time, a delay that nobody owns, and two symmetric failures that both come up looking healthy.

SGMII stops folding.

The whole interface, both directions, on two differential pairs:

1000 Mb/s × 10/8 = 1250 Mbaud

Four wires instead of twelve, and the ratio is 8B/10B's — every octet becomes a ten-bit code group, which is Chapter 3.5's block coding applied to a chip-to-chip interface rather than to a cable.

And the remarkable part is the second sentence of the specification, not the first. SGMII runs at 1.25 Gbaud at every speed — 10, 100 and 1000 Mb/s — because the lower rates are carried by repeating each code group rather than by changing the clock.

Which removes, at a stroke, everything Module 10 has been about. No per-speed clock frequency. No re-lock when the speed changes. No sampling delay to configure. No TX_CLK that stops, no REF_CLK that nobody drives, no mode that reverses a clock's direction, no eye to sweep. One rate, one clock, one configuration, forever.

1. Scope — What This Chapter Owns

This chapter owns SGMII: its serialisation, its ordered sets, the configuration it carries in band, the replication that serves the lower speeds, and the 8B/10B machinery that all of it rests on.

It does not re-derive what other chapters own. Chapter 3.5 owns block coding as a mechanism, including 8B/10B's ratio and purpose; Chapter 9.3 §11 owns GMII's width arithmetic; Chapter 10.4 owns the DDR fold and its delay problem, which this chapter's argument is measured against. Chapter 4.5 owns MDC and MDIO, which still ride alongside.

Chapter 10.6 owns XGMII and XAUI, which apply the same serialisation argument at 10 Gigabit and to four lanes rather than one.

The claim this chapter defends: a fixed-rate serial interface that carries configuration as data eliminates every per-speed timing problem in the module — and replaces them with a single new class of failure, in which a mechanism's legal, self-healing transient is mistaken for an error.

2. Four Wires

The serial gigabit media independent interface uses one differential pair in each direction, four wires in total, carrying a fixed one point two five gigabaud stream regardless of the link speed. Each octet is encoded as a ten bit code group using eight bit to ten bit block coding, which is where the ratio of one thousand megabits to one point two five gigabaud comes from. Frame data, idle, and the configuration information that carries speed and duplex all travel in the same stream as different ordered sets. At ten and one hundred megabits per second the same stream is used and each code group is simply repeated, so the physical layer of the interface never changes with the link speed.MACone pair out, one inTX±1.25 Gbaud →PHYsame, every speedRX±← 1.25 Gbaud8B/10B1000 × 10/8 = 1250Ordered setsdata, idle, config12
Figure 1 — two differential pairs carry the entire interface, and they carry it identically at 10, 100 and 1000 Mb/s.
SignalWiresDirectionNotes
TX±2MAC → PHY1.25 Gbaud, at every speed
RX±2PHY → MAC1.25 Gbaud, at every speed
4

Plus MDC and MDIO: six pins per port, and the management channel is now genuinely optional for link status — Chapter 10.4's in-band nibble becomes a full 16-bit configuration word carried in the idle stream.

Compare the whole module in one column, because the trend is the argument.

InterfaceData signalsPer port with management
GMII2426
MII1618
RGMII1214
RMII810
SGMII46

And at scale the difference stops being an optimisation and becomes an architecture.

PortsGMIIRGMIISGMII
4964816
81929632
2457628896

A 24-port switch spends 96 signals on SGMII against 576 on GMII — and the 96 are differential pairs that route as a bundle rather than 576 single-ended signals that must be length-matched in groups.

3. The Rate, Computed

One multiplication sets everything, and it is 8B/10B's ratio.

StepWorkingResult
payload rate1000 Mb/s
8B/10B expansion× 10/81250 Mbaud
unit interval1 ÷ 1.25 GHz800 ps
code groups per second1250 ÷ 10125 M
octets per secondone per code group125 M
back to bits125 M × 81000 Mb/s

The last three rows close the loop, and the middle one is the number worth carrying: 125 million code groups per second, which is exactly Chapter 10.3's GMII octet rate. SGMII is GMII with the parallel bus replaced by a serialiser and nothing else changed — the same 125 M octets/s, the same octet boundaries, the same control semantics, delivered one bit at a time.

And the lower speeds do not change the rate. They change how many times each code group is sent.

Link speedReplicationWorkingEffective payload
1000 Mb/s125 M × 8 ÷ 11000 Mb/s
100 Mb/s10×125 M × 8 ÷ 10100 Mb/s
10 Mb/s100×125 M × 8 ÷ 10010 Mb/s

The serialiser, the clock, the encoder and the differential pair see no difference at all. At 100 Mb/s the same code group appears ten times in a row; at 10 Mb/s, a hundred times. The receiver takes one of each run and discards the rest.

4. RTL 1 — Building the Stream

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE (with an 8B/10B encoder instantiated below).
//
// Builds the SGMII transmit stream: frame data, idle, and the
// configuration ordered sets that replace autonegotiation's link code
// word.
//
// THE NUMBERS:
//   1000 Mb/s x 10/8      = 1250 Mbaud
//   1250 / 10             =  125 M code groups/s
//   125 M x 8             = 1000 Mb/s      <- closes
//   1 UI                  =  800 ps
//   AND THE RATE NEVER CHANGES. 100 Mb/s repeats each code group 10x;
//   10 Mb/s repeats it 100x. Section 8 does the repeating; this module
//   does not know the link speed exists.
//
// THE ORDERED SETS. SGMII has no control pins, so every control event
// is a code group:
//   /I/  idle          -- fills the stream between frames
//   /C/  configuration -- carries the 16-bit config word, two at a time
//   /S/  start         -- replaces GMII's TX_EN rising
//   /T/  terminate     -- replaces TX_EN falling
//   /V/  error         -- replaces TX_ER
// Everything Module 10 spent pins on is a value in this stream.
package sgmii_pkg;
 
  localparam int unsigned CODE_BITS   = 10;
  localparam int unsigned OCTET_BITS  = 8;
  localparam int unsigned BAUD_MBAUD  = 1250;
  localparam int unsigned CODE_GROUPS_PER_S_M = 125;
 
  // Replication factors. 1000/100 = 10 and 1000/10 = 100.
  localparam int unsigned REP_1000 = 1;
  localparam int unsigned REP_100  = 10;
  localparam int unsigned REP_10   = 100;
 
  // The 802.3z link timer is 10 ms; SGMII shortens it to 1.6 ms so a
  // speed or link change reaches the MAC promptly.
  localparam int unsigned LINK_TIMER_US = 1600;
 
  // Special code groups, named rather than valued -- the 8B/10B
  // mapping is Chapter 3.5's subject.
  typedef enum logic [3:0] {
    K_IDLE1,      // /I1/
    K_IDLE2,      // /I2/
    K_CONFIG1,    // /C1/
    K_CONFIG2,    // /C2/
    K_START,      // /S/
    K_TERMINATE,  // /T/
    K_ERROR,      // /V/
    K_NONE
  } ordered_set_e;
 
  typedef enum logic [1:0] {
    SPD_10, SPD_100, SPD_1000, SPD_RESERVED
  } sgmii_speed_e;
 
  // The SGMII configuration word, PHY -> MAC:
  //   bit 15    link      1 = up
  //   bit 14    ACK       auto-negotiation acknowledge
  //   bit 12    duplex    1 = full
  //   bits 11:10 speed    00 = 10, 01 = 100, 10 = 1000, 11 reserved
  //   bit 0     always 1
  // MAC -> PHY sends 0x0001 -- bit 0 set, everything else zero,
  // because the MAC has nothing to advertise. The asymmetry is
  // deliberate: SGMII is not a negotiation, it is a REPORT.
  localparam int unsigned CFG_LINK_BIT   = 15;
  localparam int unsigned CFG_ACK_BIT    = 14;
  localparam int unsigned CFG_DUPLEX_BIT = 12;
  localparam logic [15:0] CFG_FROM_MAC   = 16'h0001;
 
endpackage
 
module sgmii_ordered_set_encoder
  import sgmii_pkg::*;
#(
  parameter int unsigned CNT_W = 24
) (
  input  logic clk,                  // 125 MHz code-group clock
  input  logic rst_n,
 
  // Frame interface, identical in shape to GMII's.
  input  logic [7:0] octet,
  input  logic       octet_valid,
  input  logic       frame_start,
  input  logic       frame_end,
  input  logic       tx_error,
 
  // Configuration to advertise. On a MAC this is CFG_FROM_MAC; on a
  // PHY it carries link, speed and duplex.
  input  logic        send_config,
  input  logic [15:0] config_word,
 
  // To the 8B/10B encoder.
  output logic [7:0]  code_octet,
  output logic        code_is_k,     // control code group
  output ordered_set_e code_kind,
  output logic        code_valid,
 
  output logic [CNT_W-1:0] c_frames,
  output logic [CNT_W-1:0] c_config_sets,
  output logic [CNT_W-1:0] c_idle_sets,
  // A frame octet arrived while a two-code-group ordered set was still
  // being emitted. Ordered sets are ATOMIC -- splitting one produces a
  // stream the far end cannot parse.
  output logic             ordered_set_collision
);
 
  // /C/ and /I/ are TWO code groups each: a K character followed by a
  // data character. The pair is atomic.
  logic       in_pair_q;
  logic [7:0] second_q;
  logic       second_is_k_q;
  ordered_set_e pair_kind_q;
  logic       config_phase_q;    // /C1/ then /C2/, alternating
  logic       idle_phase_q;      // /I1/ then /I2/, alternating
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      in_pair_q <= 1'b0; second_q <= 8'd0; second_is_k_q <= 1'b0;
      pair_kind_q <= K_NONE; config_phase_q <= 1'b0; idle_phase_q <= 1'b0;
      code_octet <= 8'd0; code_is_k <= 1'b0; code_kind <= K_NONE;
      code_valid <= 1'b0;
      c_frames <= '0; c_config_sets <= '0; c_idle_sets <= '0;
      ordered_set_collision <= 1'b0;
    end else begin
      code_valid            <= 1'b1;
      ordered_set_collision <= 1'b0;
 
      if (in_pair_q) begin
        // SECOND HALF of an ordered set. Unconditional -- once the K
        // character is on the wire the pair is committed, and a frame
        // octet offered here must wait.
        code_octet <= second_q;
        code_is_k  <= second_is_k_q;
        code_kind  <= pair_kind_q;
        in_pair_q  <= 1'b0;
        if (octet_valid) ordered_set_collision <= 1'b1;
 
      end else if (frame_start) begin
        // /S/ -- one code group, no pair.
        code_octet <= 8'd0;
        code_is_k  <= 1'b1;
        code_kind  <= K_START;
        if (!(&c_frames)) c_frames <= c_frames + 1'b1;
 
      end else if (frame_end) begin
        code_octet <= 8'd0;
        code_is_k  <= 1'b1;
        code_kind  <= K_TERMINATE;
 
      end else if (tx_error) begin
        // /V/ replaces GMII's TX_ER pin. A control event with no wire.
        code_octet <= 8'd0;
        code_is_k  <= 1'b1;
        code_kind  <= K_ERROR;
 
      end else if (octet_valid) begin
        code_octet <= octet;
        code_is_k  <= 1'b0;
        code_kind  <= K_NONE;
 
      end else if (send_config) begin
        // /C/ ordered set: K character, then a config byte. Two sets
        // carry the 16-bit word, low byte then high.
        code_octet    <= 8'd0;
        code_is_k     <= 1'b1;
        code_kind     <= config_phase_q ? K_CONFIG2 : K_CONFIG1;
        second_q      <= config_phase_q ? config_word[15:8]
                                        : config_word[7:0];
        second_is_k_q <= 1'b0;
        pair_kind_q   <= config_phase_q ? K_CONFIG2 : K_CONFIG1;
        in_pair_q     <= 1'b1;
        config_phase_q <= ~config_phase_q;
        if (!(&c_config_sets)) c_config_sets <= c_config_sets + 1'b1;
 
      end else begin
        // /I/ idle, alternating I1 and I2 so the running disparity is
        // driven back toward neutral rather than allowed to walk.
        code_octet    <= 8'd0;
        code_is_k     <= 1'b1;
        code_kind     <= idle_phase_q ? K_IDLE2 : K_IDLE1;
        second_q      <= 8'd0;
        second_is_k_q <= 1'b0;
        pair_kind_q   <= idle_phase_q ? K_IDLE2 : K_IDLE1;
        in_pair_q     <= 1'b1;
        idle_phase_q  <= ~idle_phase_q;
        if (!(&c_idle_sets)) c_idle_sets <= c_idle_sets + 1'b1;
      end
    end
  end
 
endmodule

Classification: synthesizable, with an 8B/10B encoder instantiated at its output.

What it teaches: that every control pin in Module 10 has become a code group here. GMII's TX_EN rising is /S/; its falling is /T/; TX_ER is /V/; Chapter 10.4's in-band status nibble is /C/ carrying sixteen bits instead of four. The interface has no control signals because it does not need any — a serial stream can say anything, and the cost is that everything must be said in sequence rather than asserted in parallel.

Deliberately simplified: ordered sets are shown as a two-code-group pair with a phase bit. The real alternation rules for /I1/, /I2/ and the /C/ sequence depend on the current running disparity, which Section 10's monitor tracks.

Production implication: ordered_set_collision exists because ordered sets are atomic and a serial stream has no way to interleave. Once the K character of a /C/ pair is on the wire, the config byte must follow it — a frame octet inserted between them produces a stream the far end parses as a corrupt ordered set followed by garbage. On a parallel interface a control signal and a data bus are independent; on a serial one they compete for the same slot, and that competition is the single structural cost of serialising.

5. An Idle Stream That Says Something

Every interface in this module idles. SGMII is the first one where idling carries information.

InterfaceWhat the idle period carries
MIInothingTX_EN low, TXD don't-care
RMIInothing
GMIInothing
RGMIIfour bits — link, speed, duplex, in the gap
SGMII/I/ or /C/ — and /C/ carries a full 16-bit configuration word

And the reason SGMII can is that it has no choice. A serial link must send something every 800 ps; there is no such thing as an idle differential pair that is still up. So the question is not whether to fill the idle stream but what to fill it with, and the answer is either /I/ — which costs nothing and keeps the receiver's clock recovery fed — or /C/, which costs nothing extra and carries the link's state.

The configuration word is the interesting part, and its asymmetry is deliberate.

BitPHY → MACMAC → PHY
15link — 1 = upreserved, 0
14ACK — acknowledgeACK
12duplex — 1 = full0
11:10speed — 00 = 10, 01 = 100, 10 = 1000, 11 reserved0
011
a report0x0001

The MAC sends 0x0001 — bit zero and nothing else.

Which is the sentence that separates SGMII from Chapter 9.2's autonegotiation entirely. Clause 28 autonegotiation is a negotiation: two peers advertise abilities and each resolves a common one. SGMII is a report: the PHY has already negotiated with the far end over the cable, and it is telling the MAC what the result was. The MAC has nothing to advertise, because it has no say.

6. RTL 2 — Reading the Stream Back

The received serial stream is first aligned to code group boundaries using the comma character, then decoded from ten bit code groups back to octets. Each decoded code group is then classified: a start character opens a frame, data characters fill it, a terminate character closes it, an error character marks it corrupt, idle ordered sets fill the gaps, and configuration ordered sets carry the sixteen bit word describing link status, speed and duplex. Only after that classification can any of the three consumers act, so the classifier sits between the decoder and everything else in the design.Comma alignmentfind the boundary8B/10B decodecode group → octetClassifyK or data, which KFrame path/S/ … data … /T/Config path/C/ → 16-bit wordIdle/I/ — keeps CDR fed12
Figure 2 — one stream carries frame data, idle and configuration, and the receiver's job is to sort them before anything else can happen.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE (with an 8B/10B decoder ahead of it).
//
// Classifies the received stream and extracts the configuration word.
//
// THE ORDERING THAT CANNOT BE REARRANGED:
//   1. comma alignment -- find where code groups begin
//   2. 8B/10B decode   -- ten bits back to eight, plus K/data
//   3. CLASSIFY        -- this module
//   4. act
// Nothing above step 3 can consume anything, because in a serial stream
// a frame octet and a config byte are the same eight bits and differ
// only in what preceded them.
module sgmii_ordered_set_decoder
  import sgmii_pkg::*;
#(
  parameter int unsigned CNT_W = 24
) (
  input  logic clk,
  input  logic rst_n,
 
  input  logic       aligned,        // comma alignment acquired
  input  logic [7:0] code_octet,
  input  logic       code_is_k,
  input  ordered_set_e code_kind,
  input  logic       code_valid,
  input  logic       code_invalid,   // 8B/10B said this is not a code
 
  // Frame output.
  output logic [7:0] octet,
  output logic       octet_valid,
  output logic       frame_start,
  output logic       frame_end,
  output logic       frame_had_error,
 
  // Configuration output.
  output logic [15:0] config_word,
  output logic        config_valid,
  output logic        link_up,
  output sgmii_speed_e speed,
  output logic        full_duplex,
  output logic        config_ack,
 
  output logic [CNT_W-1:0] c_frames,
  output logic [CNT_W-1:0] c_config_words,
  output logic [CNT_W-1:0] c_invalid_codes,
  // A data code group with no frame open and no ordered set expecting
  // it. In a serial stream this is what a lost alignment looks like.
  output logic             orphan_data,
  output logic [CNT_W-1:0] c_orphan_data
);
 
  logic in_frame_q;
  logic expect_cfg_lo_q;
  logic expect_cfg_hi_q;
  logic [7:0] cfg_lo_q;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      in_frame_q <= 1'b0; expect_cfg_lo_q <= 1'b0; expect_cfg_hi_q <= 1'b0;
      cfg_lo_q <= 8'd0;
      octet <= 8'd0; octet_valid <= 1'b0; frame_start <= 1'b0;
      frame_end <= 1'b0; frame_had_error <= 1'b0;
      config_word <= 16'd0; config_valid <= 1'b0;
      link_up <= 1'b0; speed <= SPD_RESERVED; full_duplex <= 1'b0;
      config_ack <= 1'b0;
      c_frames <= '0; c_config_words <= '0; c_invalid_codes <= '0;
      orphan_data <= 1'b0; c_orphan_data <= '0;
    end else begin
      octet_valid  <= 1'b0;
      frame_start  <= 1'b0;
      frame_end    <= 1'b0;
      config_valid <= 1'b0;
      orphan_data  <= 1'b0;
 
      if (!aligned) begin
        // Nothing can be believed before alignment. Not "assume idle" --
        // NOTHING, because an unaligned stream decodes to plausible
        // code groups that mean something else entirely.
        in_frame_q      <= 1'b0;
        expect_cfg_lo_q <= 1'b0;
        expect_cfg_hi_q <= 1'b0;
 
      end else if (code_valid) begin
        if (code_invalid) begin
          // An 8B/10B code group that is not in the table. Counted, and
          // it condemns any open frame -- but note that it CANNOT catch
          // a single-bit error that turned one valid code into another.
          if (!(&c_invalid_codes)) c_invalid_codes <= c_invalid_codes + 1'b1;
          if (in_frame_q) frame_had_error <= 1'b1;
 
        end else if (code_is_k) begin
          // A control code group ends any pending ordered set, which is
          // how a truncated /C/ pair fails safely rather than merging
          // its first half with the next set's second.
          expect_cfg_lo_q <= 1'b0;
          expect_cfg_hi_q <= 1'b0;
 
          unique case (code_kind)
            K_START: begin
              in_frame_q      <= 1'b1;
              frame_start     <= 1'b1;
              frame_had_error <= 1'b0;
              if (!(&c_frames)) c_frames <= c_frames + 1'b1;
            end
            K_TERMINATE: begin
              in_frame_q <= 1'b0;
              frame_end  <= 1'b1;
            end
            K_ERROR: begin
              frame_had_error <= 1'b1;
            end
            K_CONFIG1: expect_cfg_lo_q <= 1'b1;
            K_CONFIG2: expect_cfg_hi_q <= 1'b1;
            default: ;                       // idle
          endcase
 
        end else begin
          // A DATA code group. What it means depends entirely on what
          // preceded it -- which is the structural difference between a
          // serial interface and a parallel one.
          if (in_frame_q) begin
            octet       <= code_octet;
            octet_valid <= 1'b1;
 
          end else if (expect_cfg_lo_q) begin
            cfg_lo_q        <= code_octet;
            expect_cfg_lo_q <= 1'b0;
 
          end else if (expect_cfg_hi_q) begin
            expect_cfg_hi_q <= 1'b0;
            config_word     <= {code_octet, cfg_lo_q};
            config_valid    <= 1'b1;
            if (!(&c_config_words)) c_config_words <= c_config_words + 1'b1;
 
            // Extract the fields. The PHY's report, not a negotiation.
            link_up     <= code_octet[CFG_LINK_BIT - 8];
            config_ack  <= code_octet[CFG_ACK_BIT - 8];
            full_duplex <= code_octet[CFG_DUPLEX_BIT - 8];
            speed       <= sgmii_speed_e'(code_octet[3:2]);
 
          end else begin
            // ORPHAN DATA. No frame, no ordered set expecting it. In a
            // serial stream this is the signature of alignment that was
            // lost and re-acquired at the wrong boundary -- every code
            // group decodes to something, and none of it is right.
            orphan_data <= 1'b1;
            if (!(&c_orphan_data)) c_orphan_data <= c_orphan_data + 1'b1;
          end
        end
      end
    end
  end
 
endmodule

Classification: synthesizable, with an 8B/10B decoder ahead of it.

What it teaches: that a data code group's meaning is determined entirely by what preceded it, and this is the structural difference between a serial interface and every parallel one in Module 10. On GMII, RXD[7:0] is a frame octet because RX_DV is asserted — a fact carried on a separate wire, available in the same instant. On SGMII the same eight bits are a frame octet, a configuration byte, or nothing at all, depending on a code group that has already gone past.

Deliberately simplified: the configuration word is assembled from two /C/ pairs. The real sequence requires three consecutive identical config words before the value is acted upon, which Section 12's reconciler handles.

Production implication: orphan_data is the signature of an alignment that was lost and re-acquired at the wrong boundary, and it is worth its own counter because of what it looks like otherwise. An unaligned 8B/10B stream still decodes — most ten-bit windows land on valid code groups — so the receiver sees a plausible mixture of data and control that simply never forms a frame. Without an orphan counter, that state reads as "the link is up and idle", which is exactly what a working link with no traffic also reads as.

7. Configuration Without a Negotiation

The SGMII configuration exchange looks like Chapter 9.2's autonegotiation and is doing something different, and confusing them makes the whole interface incoherent.

Clause 28 autonegotiationSGMII configuration
whereacross the cable, PHY to PHYacross the interface, PHY to MAC
who participatestwo peers, symmetricallya reporter and a listener
what is exchangedabilities — what each can doa result — what the cable link is
the MAC's advertisement0x0001: bit zero, nothing else
is a resolution computedyes, by both ends independentlyno — there is nothing to resolve
what ACK meansI received your abilitiesI received your report

The row that matters is the fourth. The MAC advertises nothing, because it has nothing to advertise. The cable-side negotiation already happened, between this PHY and the far end's PHY, using clause 28's mechanism over twisted pair or 1000BASE-X's over fibre. By the time anything appears on SGMII, the answer exists.

So SGMII's /C/ exchange is a delivery mechanism with an acknowledgement, and calling it autonegotiation is a statement about which state machine was reused rather than about what is happening.

8. RTL 3 — Repeating, and Picking One of Each Run

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE.
//
// Serves 10 and 100 Mb/s by REPEATING each code group on transmit and
// picking one of each run on receive.
//
// THE ARITHMETIC:
//   1000 Mb/s -> 1x   (125 M code groups/s, all of them distinct)
//    100 Mb/s -> 10x  (125 M / 10 = 12.5 M distinct)
//     10 Mb/s -> 100x (125 M / 100 = 1.25 M distinct)
//
// AND THE CLOCK NEVER CHANGES. This is the module that makes SGMII's
// central claim true: the serialiser, the CDR, the encoder and the
// differential pair see exactly the same 1.25 Gbaud stream at every
// speed, and the only thing that varies is a counter.
//
// THE RECEIVE SIDE IS THE HARD HALF, and the reason is clock tolerance.
// The two ends' 125 MHz references are independent and each is within
// its own tolerance -- so over a long stream the receiver does NOT see
// runs of exactly 10. It sees mostly 10, occasionally 9 or 11, and the
// boundaries drift. A decimator that counts to exactly 10 and resets
// will slip; one that RE-SYNCHRONISES on every code-group CHANGE will
// not.
module sgmii_rate_replicator
  import sgmii_pkg::*;
#(
  parameter int unsigned CNT_W = 24
) (
  input  logic clk,
  input  logic rst_n,
 
  input  sgmii_speed_e speed,
 
  // Transmit: one code group in per REP periods out.
  input  logic [7:0]   tx_code_octet,
  input  logic         tx_code_is_k,
  input  logic         tx_code_valid,
  output logic         tx_code_ready,
  output logic [7:0]   rep_code_octet,
  output logic         rep_code_is_k,
  output logic         rep_code_valid,
 
  // Receive: REP identical code groups in, one out.
  input  logic [7:0]   rx_code_octet,
  input  logic         rx_code_is_k,
  input  logic         rx_code_valid,
  output logic [7:0]   dec_code_octet,
  output logic         dec_code_is_k,
  output logic         dec_code_valid,
 
  // The observed run length, exported. It is nominally REP and will
  // legitimately vary by one because the two ends' clocks are
  // independent -- so this is a distribution, not a constant.
  output logic [6:0]       last_run_length,
  output logic [6:0]       shortest_run,
  output logic [6:0]       longest_run,
  // A run so far from nominal that it is not clock tolerance. Reported
  // rather than absorbed, because it is the signature of the two ends
  // disagreeing about the SPEED rather than about the clock.
  output logic             run_length_anomaly,
  output logic [CNT_W-1:0] c_anomalies
);
 
  function automatic logic [6:0] rep_for (input sgmii_speed_e s);
    unique case (s)
      SPD_1000: rep_for = 7'(REP_1000);
      SPD_100:  rep_for = 7'(REP_100);
      SPD_10:   rep_for = 7'd100;          // REP_10, clipped to 7 bits
      default:  rep_for = 7'(REP_1000);
    endcase
  endfunction
 
  logic [6:0] rep_c;
  assign rep_c = rep_for(speed);
 
  // ---- transmit: hold each code group for rep_c periods ----
  logic [6:0] tx_hold_q;
  logic [7:0] tx_held_octet_q;
  logic       tx_held_k_q;
 
  assign tx_code_ready = (tx_hold_q == 7'd0);
 
  // ---- receive: pick one of each run ----
  logic [7:0] rx_prev_octet_q;
  logic       rx_prev_k_q;
  logic       rx_have_prev_q;
  logic [6:0] rx_run_q;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      tx_hold_q <= 7'd0; tx_held_octet_q <= 8'd0; tx_held_k_q <= 1'b0;
      rep_code_octet <= 8'd0; rep_code_is_k <= 1'b0; rep_code_valid <= 1'b0;
      rx_prev_octet_q <= 8'd0; rx_prev_k_q <= 1'b0; rx_have_prev_q <= 1'b0;
      rx_run_q <= 7'd0;
      dec_code_octet <= 8'd0; dec_code_is_k <= 1'b0; dec_code_valid <= 1'b0;
      last_run_length <= 7'd0; shortest_run <= 7'd127; longest_run <= 7'd0;
      run_length_anomaly <= 1'b0; c_anomalies <= '0;
    end else begin
      rep_code_valid     <= 1'b0;
      dec_code_valid     <= 1'b0;
      run_length_anomaly <= 1'b0;
 
      // ---------------- TRANSMIT ----------------
      if (tx_hold_q != 7'd0) begin
        rep_code_octet <= tx_held_octet_q;
        rep_code_is_k  <= tx_held_k_q;
        rep_code_valid <= 1'b1;
        tx_hold_q      <= tx_hold_q - 7'd1;
      end else if (tx_code_valid) begin
        tx_held_octet_q <= tx_code_octet;
        tx_held_k_q     <= tx_code_is_k;
        rep_code_octet  <= tx_code_octet;
        rep_code_is_k   <= tx_code_is_k;
        rep_code_valid  <= 1'b1;
        tx_hold_q       <= rep_c - 7'd1;
      end
 
      // ---------------- RECEIVE ----------------
      if (rx_code_valid) begin
        if (rx_have_prev_q &&
            (rx_code_octet == rx_prev_octet_q) &&
            (rx_code_is_k == rx_prev_k_q)) begin
          // Same code group again: still inside the run.
          if (rx_run_q != 7'd127) rx_run_q <= rx_run_q + 7'd1;
 
        end else begin
          // A DIFFERENT code group. The run ended here -- and this is
          // the resynchronisation point. Nothing counts to a fixed
          // number, so a run of 9 or 11 caused by ordinary clock
          // tolerance costs nothing at all.
          if (rx_have_prev_q) begin
            last_run_length <= rx_run_q;
            if (rx_run_q < shortest_run) shortest_run <= rx_run_q;
            if (rx_run_q > longest_run)  longest_run  <= rx_run_q;
 
            // ANOMALY: not a clock-tolerance excursion of one, but a
            // run that is wrong by a factor. That is the two ends
            // disagreeing about the speed, and it must not be absorbed.
            if ((rx_run_q > (rep_c + (rep_c >> 1))) ||
                ((rep_c > 7'd2) && (rx_run_q < (rep_c - (rep_c >> 1))))) begin
              run_length_anomaly <= 1'b1;
              if (!(&c_anomalies)) c_anomalies <= c_anomalies + 1'b1;
            end
          end
 
          // Emit exactly one code group per run, at its boundary.
          dec_code_octet <= rx_code_octet;
          dec_code_is_k  <= rx_code_is_k;
          dec_code_valid <= 1'b1;
 
          rx_prev_octet_q <= rx_code_octet;
          rx_prev_k_q     <= rx_code_is_k;
          rx_have_prev_q  <= 1'b1;
          rx_run_q        <= 7'd1;
        end
      end
    end
  end
 
endmodule

Classification: synthesizable.

What it teaches: that the receive decimator must resynchronise on change and never count to a fixed number, and the reason is clock tolerance rather than robustness. The two ends' 125 MHz references are independent, so over any long stream the receiver sees runs of nominally ten that are sometimes nine and sometimes eleven — entirely legally. A decimator that counts to exactly ten and resets accumulates the difference and slips a code group; one that emits at each change has no state to accumulate.

Deliberately simplified: the replication factor for 10 Mb/s is clipped into seven bits and the decimator compares whole code groups. A production implementation compares the running-disparity-corrected symbols and handles the case where consecutive distinct code groups happen to be equal.

Production implication: run_length_anomaly is deliberately loose — it fires at a factor, not at an off-by-one — because the two failures it must separate are separated by a factor. Clock tolerance moves a run by one; a speed disagreement moves it by ten. A MAC replicating for 100 Mb/s against a PHY replicating for 10 sees runs of a hundred where it expects ten, and that is not a timing problem to be absorbed but a configuration disagreement to be reported.

9. Replication, Computed

The three columns that every other interface in this module has, reduced to one line each.

Link speedLine rateCode groups/sReplicationDistinct code groups/sPayload
1000 Mb/s1250 Mbaud125 M125 M125 M × 8 = 1000 Mb/s
100 Mb/s1250 Mbaud125 M10×12.5 M12.5 M × 8 = 100 Mb/s
10 Mb/s1250 Mbaud125 M100×1.25 M1.25 M × 8 = 10 Mb/s

The second column never changes, and that is the entire chapter in one table.

Compare what every other interface does at the same three speeds.

10 Mb/s100 Mb/s1000 Mb/sThings that change
MII2.5 MHz25 MHzclock frequency
RMII50 MHz, ÷1050 MHza repeat count
GMII2.5 MHz, 4 bits25 MHz, 4 bits125 MHz, 8 bitsclock, width, and the clock's direction
RGMII2.5 MHz25 MHz125 MHzclock, and the sampling calibration
SGMII1.25 Gbaud1.25 Gbaud1.25 Gbauda counter

RMII is the closest, and it is instructive that it is close. RMII also holds its clock fixed and repeats — Chapter 10.2 §5 — so the idea was already in the module. SGMII applies it to the whole interface rather than to one speed step, and gets a design with no per-speed timing at all.

10. RTL 4 — Comma Alignment and Running Disparity

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE.
//
// Tracks the two pieces of 8B/10B state a receiver must hold, and --
// crucially -- distinguishes their TRANSIENTS from their failures.
//
// COMMA ALIGNMENT. A serial stream arrives as bits; code-group
// boundaries are found by searching for the comma pattern, which is
// constructed so it cannot appear across a boundary of any legal code
// group sequence. Before alignment, NOTHING can be believed -- and the
// dangerous part is that an unaligned stream still decodes to plausible
// code groups most of the time.
//
// RUNNING DISPARITY. 8B/10B keeps the count of ones and zeros bounded
// by choosing, for each octet, whichever of two code groups moves the
// running total back toward neutral. The receiver tracks the same
// state, and if its state diverges from the transmitter's, decoded
// values are wrong.
//
// AND HERE IS THE PART SECTION 16 IS ABOUT: running disparity is
// SELF-HEALING. After alignment is regained, the receiver's disparity
// state re-converges with the transmitter's within a bounded number of
// code groups, because the encoding drives it there. So there is a
// legal, expected, REQUIRED window during which disparity is wrong --
// and asserting that disparity is always correct fires during exactly
// the recovery the mechanism was designed to perform.
module sgmii_comma_disparity_monitor
  import sgmii_pkg::*;
#(
  // Code groups after alignment within which disparity must re-converge.
  parameter int unsigned CONVERGE_CODES = 16'd64,
  // Consecutive good code groups required to declare alignment.
  parameter int unsigned ALIGN_CONFIRM  = 16'd4,
  parameter int unsigned CNT_W = 24
) (
  input  logic clk,
  input  logic rst_n,
  input  logic clear,
 
  input  logic comma_seen,
  input  logic code_valid,
  input  logic code_invalid,
  input  logic disparity_error,
 
  output logic aligned,
  output logic align_lost,
  // TRUE while the design is inside its legal re-convergence window.
  // Every disparity check must be qualified by this, and Section 16's
  // rejected property is what happens when it is not.
  output logic converging,
  output logic converged,
  // The window expired with disparity still wrong. THIS is a fault --
  // as distinct from disparity being wrong during the window, which is
  // the mechanism working.
  output logic convergence_failed,
 
  output logic [15:0]      codes_to_converge,
  output logic [15:0]      worst_convergence,
  output logic [CNT_W-1:0] c_aligns,
  output logic [CNT_W-1:0] c_align_losses,
  output logic [CNT_W-1:0] c_disparity_errors_in_window,
  output logic [CNT_W-1:0] c_disparity_errors_steady,
  output logic             ever_convergence_failed
);
 
  logic [15:0] good_run_q;
  logic [15:0] converge_q;
  logic        aligned_q;
 
  assign aligned = aligned_q;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      good_run_q <= '0; converge_q <= '0; aligned_q <= 1'b0;
      align_lost <= 1'b0; converging <= 1'b0; converged <= 1'b0;
      convergence_failed <= 1'b0;
      codes_to_converge <= '0; worst_convergence <= '0;
      c_aligns <= '0; c_align_losses <= '0;
      c_disparity_errors_in_window <= '0;
      c_disparity_errors_steady <= '0;
      ever_convergence_failed <= 1'b0;
    end else if (clear) begin
      c_aligns <= '0; c_align_losses <= '0;
      c_disparity_errors_in_window <= '0;
      c_disparity_errors_steady <= '0;
      worst_convergence <= '0;
      // ever_convergence_failed survives.
    end else begin
      align_lost         <= 1'b0;
      convergence_failed <= 1'b0;
 
      if (code_valid) begin
        if (!aligned_q) begin
          // HUNTING. A comma plus a run of decodable code groups is
          // alignment; a comma alone is not, because an unaligned
          // stream produces comma-like patterns by chance.
          if (comma_seen && !code_invalid) begin
            if (good_run_q == ALIGN_CONFIRM - 16'd1) begin
              aligned_q  <= 1'b1;
              converging <= 1'b1;      // the window OPENS here
              converged  <= 1'b0;
              converge_q <= '0;
              good_run_q <= '0;
              if (!(&c_aligns)) c_aligns <= c_aligns + 1'b1;
            end else begin
              good_run_q <= good_run_q + 16'd1;
            end
          end else if (code_invalid) begin
            good_run_q <= '0;
          end
 
        end else begin
          // ALIGNED. Two invalid code groups in a row is a lost
          // alignment; one is a bit error the frame layer will catch.
          if (code_invalid) begin
            if (good_run_q == 16'd1) begin
              aligned_q  <= 1'b0;
              align_lost <= 1'b1;
              converging <= 1'b0;
              converged  <= 1'b0;
              good_run_q <= '0;
              if (!(&c_align_losses)) c_align_losses <= c_align_losses + 1'b1;
            end else begin
              good_run_q <= good_run_q + 16'd1;
            end
          end else begin
            good_run_q <= '0;
          end
 
          // THE CONVERGENCE WINDOW. Disparity errors inside it are
          // EXPECTED -- the receiver's state is catching up with the
          // transmitter's, and the encoding drives it there. They are
          // counted separately and they are not faults.
          if (converging) begin
            converge_q <= converge_q + 16'd1;
 
            if (disparity_error) begin
              if (!(&c_disparity_errors_in_window))
                c_disparity_errors_in_window <= c_disparity_errors_in_window + 1'b1;
 
            end else if (converge_q > 16'd8) begin
              // Eight clean code groups: the states agree.
              converging        <= 1'b0;
              converged         <= 1'b1;
              codes_to_converge <= converge_q;
              if (converge_q > worst_convergence)
                worst_convergence <= converge_q;
            end
 
            if (converge_q == CONVERGE_CODES) begin
              // The window expired and disparity is STILL wrong. Now it
              // is a fault, and it is a different fault from every
              // disparity error inside the window.
              converging              <= 1'b0;
              convergence_failed      <= 1'b1;
              ever_convergence_failed <= 1'b1;
            end
 
          end else if (converged && disparity_error) begin
            // STEADY-STATE disparity error. A genuine fault: the two
            // states had agreed and no longer do.
            if (!(&c_disparity_errors_steady))
              c_disparity_errors_steady <= c_disparity_errors_steady + 1'b1;
          end
        end
      end
    end
  end
 
endmodule

Classification: synthesizable.

What it teaches: that a disparity error means two completely different things depending on when it happens, and separating them is the module's whole purpose. Inside the convergence window it is the mechanism working — the receiver's running-disparity state is catching up with the transmitter's, the encoding is driving it there, and errors during that period are expected and bounded. In steady state it is a fault, because two states that had agreed no longer do.

Deliberately simplified: convergence is declared after eight clean code groups. The real bound depends on the code group sequence, since some sequences drive disparity to a known state faster than others, and a production implementation may force known idles to shorten it.

Production implication: c_disparity_errors_in_window and c_disparity_errors_steady are two counters for one event type, and merging them destroys the diagnosis. A link that bounces frequently accumulates window errors and is behaving correctly. A link that has been up for hours and accumulates steady-state errors is failing. One counter reports the same number for both and says nothing about either.

11. What 8B/10B Buys Here

After comma alignment is acquired, the receiver's running disparity state has not yet caught up with the transmitter's, so disparity errors during this period are expected and the encoding drives the two states into agreement within a bounded number of code groups. That period is the convergence window. Errors inside it are counted separately and are not faults. Once convergence is declared, the two states agree, and any further disparity error means they have diverged again, which is a genuine fault. If the window expires with disparity still wrong, that is a third and different outcome. Asserting that disparity is always correct fires throughout the window, turning a required recovery into an assertion failure.Alignment acquiredthe window opensConvergence windowdisparity errors EXPECTEDConvergedthe states agreeError now = faultthey diverged againWindow expireda third outcomeA naive assertionfires across the window12
Figure 3 — a disparity error inside the convergence window is the mechanism working; the same error in steady state is a fault.

Chapter 3.5 owns 8B/10B's construction. What it buys on a chip-to-chip interface is worth stating separately, because the four benefits are not the same as they are on a cable.

A comma to align on. A serial stream is a bit stream; there is no other way to find where a code group starts. The comma pattern is chosen so it cannot occur across the boundary of any legal code group sequence, which makes the search unambiguous once it succeeds.

Control code groups that are not data. Chapter 10.4 spent a pin on TX_CTL to distinguish control from data. 8B/10B spends nothing/S/, /T/, /V/, /I/ and /C/ are simply code groups outside the data set, and the distinction arrives with the value.

Bounded run length, so the receiver's clock-data recovery always has transitions to lock to. Which matters more here than on a cable, because SGMII has no separate clock at all: the 1.25 GHz timing is recovered from the data, and a long run of identical bits would starve it.

And DC balance, which is what running disparity is for. The interface is AC-coupled, so a sustained DC component would drift the receiver's decision threshold.

BenefitCosts a pin on a parallel interfaceCosts on SGMII
control vs data1 pin (TX_CTL, TX_EN)nothing
a boundary to align toimplicit in the clocknothing
transitions for clock recoverya separate clock pinnothing
DC balancenot needednothing
the 25% expansion1250 Mbaud for 1000 Mb/s

All four are paid for by one number: 10/8.

12. RTL 5 — Comparing What the PHY Says With What the MAC Believes

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE.
//
// Compares the PHY's continuous report against the MAC's actual
// configuration, and reports disagreement.
//
// WHY THIS EXISTS. Section 7 established that a link with an SGMII
// interface has TWO configuration events:
//   1. the cable-side negotiation between two PHYs, which is a real
//      negotiation with Chapter 9.2's failure modes;
//   2. the near PHY REPORTING the outcome to its MAC.
//
// And a MAC can be forced -- configured by software to a fixed speed
// and duplex, decoding the /C/ ordered sets and discarding them. So a
// third disagreement becomes possible, distinct from anything in
// Chapter 9.2: the cable is at 100 half, the PHY says so in every /C/
// set, and the MAC is forced to 1000 full and does not care.
//
// NOTHING ELSE IN THE SYSTEM COMPARES THE TWO. The PHY is right, the
// MAC is doing what it was told, and no counter anywhere is wrong.
module sgmii_status_reconciler
  import sgmii_pkg::*;
#(
  // Consecutive identical config words before the report is believed.
  // 802.3z's rule is three, and it is not defensive -- it is what
  // separates a report from a transient during the far end's own
  // negotiation.
  parameter int unsigned CONFIRM_COUNT = 3,
  parameter int unsigned CNT_W = 20
) (
  input  logic clk,
  input  logic rst_n,
  input  logic clear,
 
  // The PHY's report, from Section 6.
  input  logic         config_valid,
  input  logic [15:0]  config_word,
  input  logic         reported_link,
  input  sgmii_speed_e reported_speed,
  input  logic         reported_duplex,
 
  // The MAC's actual configuration.
  input  logic         mac_forced,
  input  sgmii_speed_e mac_speed,
  input  logic         mac_duplex,
 
  output logic         report_confirmed,
  output sgmii_speed_e confirmed_speed,
  output logic         confirmed_duplex,
  output logic         confirmed_link,
 
  // The disagreements, each named separately because each is a
  // different work order.
  output logic speed_disagreement,
  output logic duplex_disagreement,
  // The MAC is transmitting on a link the PHY says is DOWN. Legal in
  // forced mode, always wrong in practice, and invisible everywhere else.
  output logic tx_on_reported_down_link,
 
  output logic [CNT_W-1:0] c_reports,
  output logic [CNT_W-1:0] c_speed_disagreements,
  output logic [CNT_W-1:0] c_duplex_disagreements,
  output logic             ever_disagreed,
  // The single most useful output: this MAC is ignoring its PHY.
  output logic             operating_on_forced_config
);
 
  logic [15:0] last_word_q;
  logic [3:0]  match_q;
 
  assign operating_on_forced_config = mac_forced && report_confirmed;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      last_word_q <= 16'd0; match_q <= 4'd0;
      report_confirmed <= 1'b0; confirmed_speed <= SPD_RESERVED;
      confirmed_duplex <= 1'b0; confirmed_link <= 1'b0;
      speed_disagreement <= 1'b0; duplex_disagreement <= 1'b0;
      tx_on_reported_down_link <= 1'b0;
      c_reports <= '0; c_speed_disagreements <= '0;
      c_duplex_disagreements <= '0; ever_disagreed <= 1'b0;
    end else if (clear) begin
      c_reports <= '0; c_speed_disagreements <= '0;
      c_duplex_disagreements <= '0;
      speed_disagreement <= 1'b0; duplex_disagreement <= 1'b0;
      // ever_disagreed survives -- a MAC that has ever ignored its PHY
      // is a configuration worth remembering.
    end else begin
      if (config_valid) begin
        if (config_word == last_word_q) begin
          if (match_q != 4'(CONFIRM_COUNT)) begin
            match_q <= match_q + 4'd1;
          end else if (!report_confirmed ||
                       (confirmed_speed  != reported_speed) ||
                       (confirmed_duplex != reported_duplex) ||
                       (confirmed_link   != reported_link)) begin
            // THREE IDENTICAL WORDS. Now the report is believed -- and
            // the rule matters because the far end's own negotiation
            // produces transient config words while it converges.
            report_confirmed <= 1'b1;
            confirmed_speed  <= reported_speed;
            confirmed_duplex <= reported_duplex;
            confirmed_link   <= reported_link;
            if (!(&c_reports)) c_reports <= c_reports + 1'b1;
          end
        end else begin
          match_q     <= 4'd0;
          last_word_q <= config_word;
        end
      end
 
      // THE COMPARISON. Both sides are internally correct; only the
      // pair is wrong, which is why nothing else can see it.
      if (report_confirmed) begin
        speed_disagreement  <= mac_forced && (mac_speed  != confirmed_speed);
        duplex_disagreement <= mac_forced && (mac_duplex != confirmed_duplex);
        tx_on_reported_down_link <= mac_forced && !confirmed_link;
 
        if (mac_forced && (mac_speed != confirmed_speed)) begin
          ever_disagreed <= 1'b1;
          if (!(&c_speed_disagreements))
            c_speed_disagreements <= c_speed_disagreements + 1'b1;
        end
        if (mac_forced && (mac_duplex != confirmed_duplex)) begin
          ever_disagreed <= 1'b1;
          if (!(&c_duplex_disagreements))
            c_duplex_disagreements <= c_duplex_disagreements + 1'b1;
        end
      end
    end
  end
 
endmodule

Classification: synthesizable.

What it teaches: that the three-identical-words rule is not debouncing — it is what separates a report from a transient. While the far end's cable-side negotiation is converging, the near PHY emits config words describing intermediate states, and a MAC that acts on the first one reconfigures itself several times during every link-up. Three consecutive identical words means the PHY has settled, which is a statement about the far end's negotiation having completed.

Deliberately simplified: the confirmation counter is a small register with a single threshold. The 802.3z state machine also runs the link_timer — shortened by SGMII from 10 ms to 1.6 ms — which bounds how long the exchange may take before it restarts.

Production implication: operating_on_forced_config is the single most useful output here, and nothing else in the system produces it. The PHY is correct — it negotiated properly and is reporting accurately. The MAC is correct — it is doing exactly what software told it. Every counter on both devices reads clean, and the link runs at a speed the cable does not support or in a duplex the far end does not share. Only a block that compares the two can say so, and it can only say so because the report arrives continuously rather than on request.

13. RTL 6 — Bounding the Exchange

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE.
//
// Runs the SGMII configuration exchange to completion or to a bounded
// failure, using 802.3z's state machine with SGMII's timing.
//
// THE TIMER THAT CHANGED. 802.3z's link_timer is 10 ms, sized for a
// fibre link establishing itself. SGMII shortens it to 1.6 ms, because
// the event being reported -- a cable-side change the PHY has ALREADY
// completed -- should reach the MAC promptly.
//
//   10 ms  -> up to 10 ms of stale MAC configuration after a change
//   1.6 ms -> up to 1.6 ms
//
// THE ACKNOWLEDGE MECHANISM, and what it does NOT establish:
//   ACK means "I received your configuration word". It does not mean
//   "I agree", "I have applied it", or "we are both configured the
//   same". Section 12's reconciler exists because ACK establishes
//   receipt and nothing more.
module sgmii_link_timer
  import sgmii_pkg::*;
#(
  parameter int unsigned CLK_MHZ = 125,
  parameter int unsigned CNT_W   = 16
) (
  input  logic clk,
  input  logic rst_n,
 
  input  logic restart,
  input  logic aligned,
  input  logic config_valid,
  input  logic partner_ack,        // the far side's ACK bit
 
  output logic        send_config,
  output logic        send_ack,
  output logic        exchange_complete,
  output logic        exchange_timed_out,
  output logic [2:0]  exchange_state,
 
  output logic [CNT_W-1:0] c_exchanges,
  output logic [CNT_W-1:0] c_timeouts,
  output logic [CNT_W-1:0] c_restarts,
  // How long the last successful exchange took, in microseconds. A
  // link that consistently completes near the timer is a link one
  // disturbance away from not completing.
  output logic [CNT_W-1:0] last_exchange_us,
  output logic [CNT_W-1:0] worst_exchange_us,
  output logic             ever_timed_out
);
 
  localparam int unsigned TICKS_PER_US = CLK_MHZ;
  localparam int unsigned LINK_TICKS   = LINK_TIMER_US * CLK_MHZ;
 
  typedef enum logic [2:0] {
    X_IDLE, X_WAIT_ALIGN, X_EXCHANGE, X_ACK_WAIT, X_DONE, X_FAILED
  } xstate_e;
 
  xstate_e     st_q;
  logic [31:0] timer_q;
  logic [31:0] us_q;
  logic [15:0] us_count_q;
 
  assign exchange_state = st_q;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      st_q <= X_IDLE; timer_q <= '0; us_q <= '0; us_count_q <= '0;
      send_config <= 1'b0; send_ack <= 1'b0;
      exchange_complete <= 1'b0; exchange_timed_out <= 1'b0;
      c_exchanges <= '0; c_timeouts <= '0; c_restarts <= '0;
      last_exchange_us <= '0; worst_exchange_us <= '0;
      ever_timed_out <= 1'b0;
    end else begin
      exchange_complete  <= 1'b0;
      exchange_timed_out <= 1'b0;
 
      // A free-running microsecond counter, so the exchange duration is
      // reported in a unit a human can compare against 1600.
      if (us_q == 32'(TICKS_PER_US - 1)) begin
        us_q       <= '0;
        us_count_q <= us_count_q + 16'd1;
      end else begin
        us_q <= us_q + 1'b1;
      end
 
      if (restart) begin
        st_q        <= X_WAIT_ALIGN;
        timer_q     <= '0;
        us_count_q  <= '0;
        send_config <= 1'b0;
        send_ack    <= 1'b0;
        if (!(&c_restarts)) c_restarts <= c_restarts + 1'b1;
      end else begin
        unique case (st_q)
          X_IDLE: if (aligned) st_q <= X_WAIT_ALIGN;
 
          X_WAIT_ALIGN: begin
            // Nothing can be exchanged before alignment, because the
            // ordered sets that carry it are not decodable yet.
            if (aligned) begin
              st_q        <= X_EXCHANGE;
              send_config <= 1'b1;
              timer_q     <= '0;
              if (!(&c_exchanges)) c_exchanges <= c_exchanges + 1'b1;
            end
          end
 
          X_EXCHANGE: begin
            if (!aligned) begin
              st_q        <= X_WAIT_ALIGN;
              send_config <= 1'b0;
            end else if (config_valid) begin
              // A config word arrived: acknowledge it. ACK is RECEIPT,
              // not agreement -- and the distinction is the whole of
              // Section 12.
              send_ack <= 1'b1;
              st_q     <= X_ACK_WAIT;
              timer_q  <= '0;
            end else if (timer_q == 32'(LINK_TICKS)) begin
              st_q               <= X_FAILED;
              exchange_timed_out <= 1'b1;
              ever_timed_out     <= 1'b1;
              if (!(&c_timeouts)) c_timeouts <= c_timeouts + 1'b1;
            end else begin
              timer_q <= timer_q + 1'b1;
            end
          end
 
          X_ACK_WAIT: begin
            if (!aligned) begin
              st_q        <= X_WAIT_ALIGN;
              send_config <= 1'b0;
              send_ack    <= 1'b0;
            end else if (partner_ack) begin
              st_q              <= X_DONE;
              exchange_complete <= 1'b1;
              last_exchange_us  <= us_count_q;
              if (us_count_q > worst_exchange_us)
                worst_exchange_us <= us_count_q;
            end else if (timer_q == 32'(LINK_TICKS)) begin
              st_q               <= X_FAILED;
              exchange_timed_out <= 1'b1;
              ever_timed_out     <= 1'b1;
              if (!(&c_timeouts)) c_timeouts <= c_timeouts + 1'b1;
            end else begin
              timer_q <= timer_q + 1'b1;
            end
          end
 
          X_DONE: begin
            // Keep sending idle; a new config word restarts the
            // exchange, which is how a speed change propagates.
            send_config <= 1'b0;
            send_ack    <= 1'b0;
            if (config_valid) st_q <= X_EXCHANGE;
          end
 
          X_FAILED: begin
            send_config <= 1'b0;
            send_ack    <= 1'b0;
            st_q        <= X_WAIT_ALIGN;
          end
 
          default: st_q <= X_IDLE;
        endcase
      end
    end
  end
 
endmodule

Classification: synthesizable.

What it teaches: that ACK establishes receipt and nothing else, which is the same limitation Chapter 9.2 §4 identified in clause 28's acknowledge — and it is why Section 12's reconciler is not redundant. Acknowledging a configuration word says "I got it." It does not say "I agree", "I applied it", or "we are now configured the same. A forced MAC acknowledges every config word it receives and ignores all of them, entirely conformantly.

Deliberately simplified: the state machine is a compressed form of 802.3z's, which has more states around ability detection and the acknowledge count. The 1.6 ms timer and the ordering — alignment, then exchange, then acknowledge — are the parts that matter here.

Production implication: worst_exchange_us against the 1600 µs timer is a margin measurement in the same spirit as Chapter 10.4 §12's eye width. A link whose exchange completes in 40 µs has enormous margin; one that consistently completes at 1500 µs is one disturbance from timing out — and both report exchange_complete identically. The duration is free to measure and nothing else reports it.

14. RTL 7 — Conformance on a Stream With No Control Pins

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE.
//
// Checks the partner's conformance to the rules a serial stream cannot
// enforce structurally.
//
// A PARALLEL interface has rules about which pin may assert when, and
// most violations are electrically visible. A SERIAL stream has rules
// about which code group may FOLLOW which, and every violation is a
// perfectly valid code group in the wrong place.
//
// THE FIVE ORDERING RULES:
//   1. /S/ opens a frame; a second /S/ before /T/ is illegal.
//   2. /T/ closes one; a /T/ with no frame open is illegal.
//   3. /C/ ordered sets are ATOMIC pairs -- a K character followed by
//      its data byte, with nothing between.
//   4. Data code groups appear only inside a frame or as the second
//      half of a /C/ pair. Anywhere else they are orphans.
//   5. Idle must appear between frames. A stream with no idle at all
//      leaves the far end no room for clock compensation.
module sgmii_conformance_monitor
  import sgmii_pkg::*;
#(
  parameter int unsigned CNT_W = 20,
  // Minimum idle code groups required between frames.
  parameter int unsigned MIN_IDLE = 4
) (
  input  logic clk,
  input  logic rst_n,
  input  logic clear,
 
  input  logic         aligned,
  input  logic         code_valid,
  input  logic         code_is_k,
  input  ordered_set_e code_kind,
  input  logic         code_invalid,
 
  output logic start_inside_frame,
  output logic terminate_no_frame,
  output logic split_ordered_set,
  output logic orphan_data_code,
  output logic insufficient_idle,
 
  output logic [CNT_W-1:0] c_start_inside_frame,
  output logic [CNT_W-1:0] c_terminate_no_frame,
  output logic [CNT_W-1:0] c_split_ordered_sets,
  output logic [CNT_W-1:0] c_orphan_data,
  output logic [CNT_W-1:0] c_insufficient_idle,
 
  // First cause, latched. A stream that has lost alignment produces a
  // violation on nearly every code group, and only the first one names
  // what happened.
  output logic       first_violation_valid,
  output logic [2:0] first_violation_kind,
  output logic       ever_violated
);
 
  logic       in_frame_q;
  logic       in_pair_q;
  logic [7:0] idle_run_q;
  logic       any_c;
  logic [2:0] kind_c;
 
  always_comb begin
    any_c  = 1'b0;
    kind_c = 3'd0;
    if      (start_inside_frame) begin any_c = 1'b1; kind_c = 3'd0; end
    else if (terminate_no_frame) begin any_c = 1'b1; kind_c = 3'd1; end
    else if (split_ordered_set)  begin any_c = 1'b1; kind_c = 3'd2; end
    else if (orphan_data_code)   begin any_c = 1'b1; kind_c = 3'd3; end
    else if (insufficient_idle)  begin any_c = 1'b1; kind_c = 3'd4; end
  end
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      in_frame_q <= 1'b0; in_pair_q <= 1'b0; idle_run_q <= 8'd0;
      start_inside_frame <= 1'b0; terminate_no_frame <= 1'b0;
      split_ordered_set <= 1'b0; orphan_data_code <= 1'b0;
      insufficient_idle <= 1'b0;
      c_start_inside_frame <= '0; c_terminate_no_frame <= '0;
      c_split_ordered_sets <= '0; c_orphan_data <= '0;
      c_insufficient_idle <= '0;
      first_violation_valid <= 1'b0; first_violation_kind <= 3'd0;
      ever_violated <= 1'b0;
    end else if (clear) begin
      c_start_inside_frame <= '0; c_terminate_no_frame <= '0;
      c_split_ordered_sets <= '0; c_orphan_data <= '0;
      c_insufficient_idle <= '0; first_violation_valid <= 1'b0;
      // ever_violated survives.
    end else begin
      start_inside_frame <= 1'b0;
      terminate_no_frame <= 1'b0;
      split_ordered_set  <= 1'b0;
      orphan_data_code   <= 1'b0;
      insufficient_idle  <= 1'b0;
 
      // Nothing is checked before alignment. An unaligned stream
      // violates every rule simultaneously, and reporting that is
      // noise rather than information.
      if (aligned && code_valid && !code_invalid) begin
        if (code_is_k) begin
          if (in_pair_q) begin
            // A K character where the second half of a /C/ or /I/ pair
            // was owed. The pair was SPLIT, which a parallel interface
            // has no equivalent of.
            split_ordered_set <= 1'b1;
            if (!(&c_split_ordered_sets))
              c_split_ordered_sets <= c_split_ordered_sets + 1'b1;
          end
          in_pair_q <= 1'b0;
 
          unique case (code_kind)
            K_START: begin
              if (in_frame_q) begin
                start_inside_frame <= 1'b1;
                if (!(&c_start_inside_frame))
                  c_start_inside_frame <= c_start_inside_frame + 1'b1;
              end else if (idle_run_q < 8'(MIN_IDLE)) begin
                // A frame beginning with too little idle behind it
                // leaves the far end no room for clock compensation.
                insufficient_idle <= 1'b1;
                if (!(&c_insufficient_idle))
                  c_insufficient_idle <= c_insufficient_idle + 1'b1;
              end
              in_frame_q <= 1'b1;
              idle_run_q <= 8'd0;
            end
 
            K_TERMINATE: begin
              if (!in_frame_q) begin
                terminate_no_frame <= 1'b1;
                if (!(&c_terminate_no_frame))
                  c_terminate_no_frame <= c_terminate_no_frame + 1'b1;
              end
              in_frame_q <= 1'b0;
              idle_run_q <= 8'd0;
            end
 
            K_IDLE1, K_IDLE2: begin
              in_pair_q <= 1'b1;
              if (idle_run_q != 8'hFF) idle_run_q <= idle_run_q + 8'd1;
            end
 
            K_CONFIG1, K_CONFIG2: begin
              in_pair_q  <= 1'b1;
              idle_run_q <= 8'd0;
            end
 
            default: ;
          endcase
 
        end else begin
          // A DATA code group. Legal inside a frame, or as the second
          // half of an ordered set. Nowhere else.
          if (in_pair_q) begin
            in_pair_q <= 1'b0;
          end else if (!in_frame_q) begin
            orphan_data_code <= 1'b1;
            if (!(&c_orphan_data)) c_orphan_data <= c_orphan_data + 1'b1;
          end
        end
      end
 
      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
 
endmodule

Classification: synthesizable.

What it teaches: that on a serial interface every protocol violation is a valid code group in the wrong place, which is a categorically different verification problem from a parallel one. On GMII, TX_EN asserting during another frame is electrically visible as a signal doing something it should not. On SGMII, /S/ inside a frame is a perfectly formed, perfectly decodable, disparity-correct code group — and only the sequence makes it wrong.

Deliberately simplified: five rules and a three-bit violation kind. A production monitor also checks the ordered-set alternation (/I1/ versus /I2/ by disparity), the minimum and maximum /C/ repetition, and the interframe gap in code groups.

Production implication: every check is gated on aligned, and that gate is doing real work. An unaligned stream violates every rule at once — orphan data everywhere, splits everywhere, terminates with no frames — and reporting all of it buries the one fact that matters, which is that alignment was lost. The conformance monitor's job begins where Section 10's alignment monitor's ends, and running it earlier produces noise.

15. Pins, Latency and What Serialising Cost

The pin saving is the largest in the module and the latency is the only thing SGMII genuinely gives up.

PortsGMIIRGMIISGMII
124124
4964816
81929632
2457628896

And the latency, computed.

StageCostNote
8B/10B encode~1 code group8 ns at 125 M code groups/s
serialise~1 code groupthe shift register fills
deserialise~1 code groupand empties
comma alignmentone-time, at link-upnot per frame
8B/10B decode~1 code group
total, steady state≈ 4 code groups ≈ 32 nseach way

Against GMII's one register stage of roughly 8 ns, SGMII adds about 24 ns in each direction — which is real and is essentially never the binding term.

Put it beside Chapter 8.4's decomposition at 1000 Mb/s:

TermValue
SGMII's added latency, one direction≈ 24 ns
serialising a 64-octet frame at 1000 Mb/s64 × 8 ÷ 1000 M = 512 ns
serialising a 1518-octet frame12 144 ns
100 m of Cat 5 propagation≈ 513 ns

SGMII's entire pipeline costs less than 5% of the time it takes to serialise the smallest legal frame.

And at 10 Mb/s the replication changes nothing about latency either — the code groups are repeated, but the first of each run is emitted immediately, so the pipeline depth is unchanged and only the throughput is divided.

16. Properties Worth Asserting, and One Worth Refusing

Every earlier chapter in this module asked where a property should be written — in which clock, about which signal, in which abstraction. SGMII asks a question about time: at what point in the mechanism's own life cycle is the property supposed to hold?

The transmit stream

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P1. Ordered sets are ATOMIC. Once a K character of a pair is on the
// wire, its data byte follows immediately.
property p_ordered_set_atomic;
  @(posedge clk) disable iff (!rst_n)
  in_pair_q |=> !in_pair_q;
endproperty
a_ordered_set_atomic: assert property (p_ordered_set_atomic);
 
// P2. A frame octet offered during a pair is REPORTED, never inserted.
// A serial stream cannot interleave, so the collision must surface.
property p_pair_collision_reported;
  @(posedge clk) disable iff (!rst_n)
  (in_pair_q && octet_valid) |=> ordered_set_collision;
endproperty
a_pair_collision_reported: assert property (p_pair_collision_reported);
 
// P3. /S/ is emitted exactly once per frame_start.
property p_start_once_per_frame;
  @(posedge clk) disable iff (!rst_n)
  frame_start |=> (code_is_k && (code_kind == K_START));
endproperty
a_start_once_per_frame: assert property (p_start_once_per_frame);
 
// P4. Idle alternates I1/I2, so running disparity is driven toward
// neutral rather than allowed to walk.
property p_idle_alternates;
  @(posedge clk) disable iff (!rst_n)
  (code_is_k && (code_kind == K_IDLE1)) |-> ##[1:4]
    (code_is_k && (code_kind == K_IDLE2));
endproperty
a_idle_alternates: assert property (p_idle_alternates);
 
// P5. Config sets alternate C1/C2, which is what carries a 16-bit word
// through an 8-bit channel.
property p_config_alternates;
  @(posedge clk) disable iff (!rst_n)
  (code_is_k && (code_kind == K_CONFIG1)) |-> ##[1:4]
    (code_is_k && (code_kind == K_CONFIG2));
endproperty
a_config_alternates: assert property (p_config_alternates);
 
// P6. The stream is never empty. A serial link must send something
// every 800 ps; there is no idle differential pair that is still up.
property p_stream_never_empty;
  @(posedge clk) disable iff (!rst_n)
  code_valid;
endproperty
a_stream_never_empty: assert property (p_stream_never_empty);

Alignment and classification

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P7. Nothing is classified before alignment. An unaligned 8B/10B
// stream decodes to plausible code groups meaning something else.
property p_nothing_before_alignment;
  @(posedge clk) disable iff (!rst_n)
  !aligned |-> (!octet_valid && !config_valid && !frame_start);
endproperty
a_nothing_before_alignment: assert property (p_nothing_before_alignment);
 
// P8. Alignment requires a comma AND a run of decodable code groups.
// A comma alone is not alignment, because misaligned streams produce
// comma-like patterns by chance.
property p_alignment_needs_confirmation;
  @(posedge clk) disable iff (!rst_n)
  $rose(aligned_q) |-> ($past(good_run_q) == ALIGN_CONFIRM - 16'd1);
endproperty
a_alignment_needs_confirmation: assert property (p_alignment_needs_confirmation);
 
// P9. A data code group outside a frame and outside an ordered set is
// REPORTED. The signature of alignment acquired at the wrong boundary.
property p_orphan_data_reported;
  @(posedge clk) disable iff (!rst_n)
  (aligned && code_valid && !code_is_k && !in_frame_q &&
   !expect_cfg_lo_q && !expect_cfg_hi_q) |=> orphan_data;
endproperty
a_orphan_data_reported: assert property (p_orphan_data_reported);
 
// P10. An invalid code group never yields an octet.
property p_invalid_code_yields_nothing;
  @(posedge clk) disable iff (!rst_n)
  code_invalid |=> !octet_valid;
endproperty
a_invalid_yields_nothing: assert property (p_invalid_code_yields_nothing);

Replication

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P11. Transmit holds each code group for exactly the replication
// factor. The only thing that varies with link speed.
property p_replication_factor_held;
  @(posedge clk) disable iff (!rst_n)
  (tx_code_valid && tx_code_ready) |=> (tx_hold_q == rep_c - 7'd1);
endproperty
a_replication_held: assert property (p_replication_factor_held);
 
// P12. THE RESYNCHRONISATION PROPERTY. The receive decimator emits on
// a code-group CHANGE, never on a count -- so a run of 9 or 11 caused
// by ordinary clock tolerance costs nothing.
property p_decimate_on_change;
  @(posedge clk) disable iff (!rst_n)
  dec_code_valid |-> ((rx_code_octet != rx_prev_octet_q) ||
                      (rx_code_is_k != rx_prev_k_q) || !rx_have_prev_q);
endproperty
a_decimate_on_change: assert property (p_decimate_on_change);
 
// P13. Exactly one code group is emitted per run.
property p_one_per_run;
  @(posedge clk) disable iff (!rst_n)
  dec_code_valid |=> !dec_code_valid;
endproperty
a_one_per_run: assert property (p_one_per_run);
 
// P14. A run wrong by a FACTOR is reported. Clock tolerance moves a run
// by one; a speed disagreement moves it by ten.
property p_run_anomaly_reported;
  @(posedge clk) disable iff (!rst_n)
  (dec_code_valid && rx_have_prev_q &&
   (rx_run_q > (rep_c + (rep_c >> 1)))) |-> run_length_anomaly;
endproperty
a_run_anomaly_reported: assert property (p_run_anomaly_reported);

Convergence — properties about a state's LIFE CYCLE

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P15. THE PROPERTY THIS CHAPTER IS ABOUT. Disparity converges within
// the window -- not "is always correct", but "reaches correctness
// within a bound after alignment".
property p_disparity_converges;
  @(posedge clk) disable iff (!rst_n)
  $rose(converging) |-> ##[1:CONVERGE_CODES] (converged || convergence_failed);
endproperty
a_disparity_converges: assert property (p_disparity_converges);
 
// P16. Disparity errors are counted in the RIGHT bucket. Inside the
// window they are the mechanism working; outside it they are a fault.
property p_window_errors_bucketed;
  @(posedge clk) disable iff (!rst_n)
  (converging && disparity_error)
    |=> (c_disparity_errors_in_window > $past(c_disparity_errors_in_window));
endproperty
a_window_errors_bucketed: assert property (p_window_errors_bucketed);
 
// P17. And steady-state errors in the other bucket.
property p_steady_errors_bucketed;
  @(posedge clk) disable iff (!rst_n)
  (converged && !converging && disparity_error)
    |=> (c_disparity_errors_steady > $past(c_disparity_errors_steady));
endproperty
a_steady_errors_bucketed: assert property (p_steady_errors_bucketed);
 
// P18. Converging and converged are mutually exclusive states.
property p_convergence_states_exclusive;
  @(posedge clk) disable iff (!rst_n)
  !(converging && converged);
endproperty
a_convergence_states_exclusive: assert property (p_convergence_states_exclusive);
 
// P19. A failed convergence is DISTINCT from an error inside the
// window -- three outcomes, not two.
property p_convergence_failure_distinct;
  @(posedge clk) disable iff (!rst_n)
  convergence_failed |-> !converged;
endproperty
a_convergence_failure_distinct: assert property (p_convergence_failure_distinct);

Configuration reconciliation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P20. A report is believed only after three identical config words.
// Not debouncing -- this is what separates a report from the far end's
// negotiation transient.
property p_report_needs_three;
  @(posedge clk) disable iff (!rst_n)
  $rose(report_confirmed) |-> ($past(match_q) == 4'(CONFIRM_COUNT));
endproperty
a_report_needs_three: assert property (p_report_needs_three);
 
// P21. A forced MAC disagreeing with a confirmed report is REPORTED.
// Both sides are internally correct; only the pair is wrong.
property p_disagreement_reported;
  @(posedge clk) disable iff (!rst_n)
  (report_confirmed && mac_forced && (mac_speed != confirmed_speed))
    |-> speed_disagreement;
endproperty
a_disagreement_reported: assert property (p_disagreement_reported);
 
// P22. The exchange terminates: complete or timed out, never neither.
property p_exchange_terminates;
  @(posedge clk) disable iff (!rst_n)
  $rose(send_config) |-> ##[1:$] (exchange_complete || exchange_timed_out ||
                                  !aligned);
endproperty
a_exchange_terminates: assert property (p_exchange_terminates);
 
// P23. Nothing is exchanged before alignment, because the ordered sets
// that carry it are not decodable yet.
property p_exchange_needs_alignment;
  @(posedge clk) disable iff (!rst_n)
  send_config |-> aligned;
endproperty
a_exchange_needs_alignment: assert property (p_exchange_needs_alignment);

17. Verification Scenarios

The transmit stream

  1. An idle link — alternating /I1/ and /I2/ pairs, forever, with no gaps in code_valid.
  2. A single frame/S/, data code groups, /T/, then idle.
  3. tx_error mid-frame/V/ emitted in place of a data code group; the frame continues.
  4. A configuration exchange/C1/ with the low byte, /C2/ with the high byte, repeated.
  5. A frame octet offered during a /C/ pairordered_set_collision, and the pair completes intact.
  6. frame_start during an idle pair — the pair completes first; /S/ follows. Atomicity under pressure.
  7. Back-to-back frames with the minimum idle between them/T/, idle, /S/, and the idle count is at least MIN_IDLE.
  8. A MAC's configuration word0x0001 exactly; bit zero and nothing else.
  9. A PHY's configuration word for 1000 full up — bit 15, bit 12 and bits 11:10 = 10.

Alignment

  1. A stream from cold with no commaaligned stays low, and nothing is classified at all.
  2. A comma followed by four decodable code groupsaligned rises, converging rises with it.
  3. A comma followed by an invalid code group — the run resets; alignment is not declared.
  4. Alignment acquired one bit off the true boundary — most code groups still decode, no frame ever forms, and orphan_data accumulates. The failure that reads as "up and idle".
  5. Two consecutive invalid code groups while alignedalign_lost, c_align_losses increments.
  6. A single invalid code group while aligned — alignment held; one bad code group is a bit error, not a lost boundary.

Replication and clock tolerance

  1. 1000 Mb/s — replication 1, every code group distinct, dec_code_valid on every code group.
  2. 100 Mb/s, ideal clocks — runs of exactly 10, one code group emitted per run.
  3. 100 Mb/s with the transmitter 100 ppm fast — runs of 9, 10 and 11 appear; run_length_anomaly never fires and no code group is lost. The scenario a fixed-count decimator fails.
  4. 100 Mb/s with the transmitter 100 ppm slow — the same, in the other direction.
  5. 10 Mb/s — runs of 100; shortest_run and longest_run bracket it.
  6. MAC replicating for 100, PHY replicating for 10 — runs of 100 where 10 is expected; run_length_anomaly fires. A speed disagreement, not clock tolerance.
  7. A fixed-count decimator (deliberate mutation) — slips a code group within a few thousand runs under 100 ppm offset. P12, demonstrated.

Convergence

  1. Alignment acquired with the receiver's disparity already matchingconverging opens and closes within a few code groups; codes_to_converge small.
  2. Alignment acquired with disparity oppositedisparity errors occur, are counted in the window bucket, and convergence completes. The transient the rejected property fires on.
  3. c_disparity_errors_in_window versus c_disparity_errors_steady — the window bucket non-zero, the steady bucket zero, on a link behaving perfectly.
  4. A steady-state disparity error after convergence — the steady bucket increments; a genuine fault.
  5. A window that expires with disparity still wrongconvergence_failed, converged low, ever_convergence_failed sticky. The third outcome.
  6. The rejected property instantiated in the environment — it fires during scenario 24, on conforming stimulus. Demonstrated rather than argued.

Configuration and reconciliation

  1. Three identical config wordsreport_confirmed, fields extracted.
  2. Two identical words then a different onenot confirmed; the counter restarts.
  3. The far end's negotiation converging — a sequence of differing config words followed by three identical ones; the MAC reconfigures once, at the end.
  4. A forced MAC at 1000 full, PHY reporting 100 halfspeed_disagreement and duplex_disagreement, operating_on_forced_config high, and every other counter in the design clean.
  5. A forced MAC transmitting while the PHY reports link downtx_on_reported_down_link.
  6. An exchange completing in 40 µslast_exchange_us = 40, well inside the 1600 µs timer.
  7. An exchange that never receives a config wordexchange_timed_out after 1.6 ms, c_timeouts increments.
  8. Alignment lost mid-exchange — the state machine returns to waiting; nothing is half-applied.

Conformance

  1. /S/ inside an open framestart_inside_frame, first_violation_kind = 0.
  2. /T/ with no frame openterminate_no_frame.
  3. A K character where an ordered set's data byte was owedsplit_ordered_set.
  4. A frame beginning with two idle code groups behind itinsufficient_idle.
  5. A violation storm while unalignedno violations reported at all, because every check is gated on aligned. The gate, tested.

18. Debugging: Everything Is a Code Group in the Wrong Place

ObservationLikely causeThe distinguishing check
link "up and idle", no traffic everalignment at the wrong boundaryc_orphan_data rising while aligned is high
aligned never risesno comma, or the far end is not transmittingc_aligns at zero; check the SerDes is enabled
aligned toggling repeatedlymarginal signal, or a disparity fault mistaken for lost alignmentc_align_losses, and codes_to_converge on each re-acquisition
disparity errors at every link-up, none afterwardsthe convergence window — normalc_disparity_errors_in_window non-zero, _steady zero; nothing to fix
disparity errors on a link up for hoursa genuine faultc_disparity_errors_steady non-zero
link works, worst_convergence near the bounda recovery with no marginnothing else reports it; the analogue of a narrow eye
link runs at the wrong speed, everything cleana forced MAC ignoring its PHYoperating_on_forced_config, speed_disagreement
traffic at 100 Mb/s corrupted, 1000 finereplication factor mismatchrun_length_anomaly; runs wrong by a factor, not by one
occasional lost code group at 10/100a fixed-count decimator against clock toleranceshortest_run and longest_run bracketing the nominal
the MAC reconfigures several times per link-upacting on unconfirmed config wordsc_reports rising faster than link events
exchange completes near 1600 µs every timea marginal exchangeworst_exchange_us against the timer

Four habits.

First, treat "up and idle with no traffic" as alignment until proved otherwise. It is SGMII's signature failure and its most misleading one — a misaligned 8B/10B stream mostly decodes, so the receiver sees a plausible mix of code groups that never forms a frame, and every status register agrees the link is fine. c_orphan_data rising while aligned is high is the tell, and it exists for no other reason.

Second, always read the two disparity counters together. They count the same event and mean opposite things. Window errors are the mechanism working; steady-state errors are it failing. A single merged counter reports the same number for a link that bounces hourly and a link that is dying, and says nothing about either.

Third, check run_length_anomaly before suspecting the cable at 10 or 100 Mb/s. Clock tolerance moves a run by one; a replication mismatch moves it by a factor of ten. The first is legal and must be absorbed; the second is a configuration disagreement.

Fourth, on any "wrong speed" complaint, read operating_on_forced_config first. The PHY is reporting correctly, the MAC is obeying software correctly, and no counter anywhere is wrong — the disagreement exists only between the two, and only the reconciler compares them.

19. Common Misconceptions

"SGMII is GMII over a serial link, so it is basically the same interface."

The wrong model: a serialiser bolted onto GMII, functionally identical.

What it costs: you cannot explain the fixed 1.25 Gbaud at every speed, the replication, or why every per-speed timing problem in Module 10 vanishes.

The corrected model: the octet rate is identical — 125 M octets/s, exactly GMII's — and the physical layer is completely different. SGMII runs at 1.25 Gbaud at every link speed, serving 100 and 10 Mb/s by repeating each code group 10 and 100 times rather than by changing the clock. The serialiser, the clock recovery, the encoder and the differential pair never learn that the link's speed changed — which is why there is no re-lock, no width change, no sampling delay and no per-speed timing to close.

"Replicating a code group a hundred times is wasteful."

The wrong model: bandwidth spent for nothing.

What it costs: you cannot see why the design is right, and you reach for a variable-rate serialiser that is three designs instead of one.

The corrected model: the wasted bandwidth had no other customer. The pair is already there, already running at 1.25 Gbaud, already burning its power. What replication buys is that there is only ever one line rate — one set of timing constraints, closed once, and a clock-data recovery loop that never loses lock at a speed change because the line rate never changed. Chapter 10.2's RMII already used the idea at one speed step; SGMII applies it to the whole interface.

"SGMII autonegotiation is autonegotiation."

The wrong model: two peers negotiating a configuration.

What it costs: you cannot explain why the MAC advertises 0x0001, or why a MAC and its own PHY can disagree.

The corrected model: it is a report with an acknowledgement. The negotiation happened on the cable, between two PHYs; SGMII's /C/ exchange is the near PHY telling its MAC the result. The MAC advertises nothing because it has no say — and ACK means I received your report, not I agree or I applied it. A MAC forced by software acknowledges every config word and ignores all of them, entirely conformantly, which is a disagreement no counter on either device can see.

"Disparity errors mean the link is broken."

The wrong model: any disparity error is a fault.

What it costs: Section 16's rejected property, plus either disabling the check or "fixing" the design by removing the detection that mattered.

The corrected model: running disparity is cumulative and self-healing. After alignment is acquired, the receiver's state has not yet caught up with the transmitter's, and the encoding drives them into agreement within a bounded number of code groups. There is a legal, required window during which disparity is wrong — every link-up passes through it. Errors inside the window are the mechanism working; errors in steady state are a fault, and they need separate counters because one number cannot mean both.

"If the code group decoded, the data is correct."

The wrong model: 8B/10B validity implies correctness.

What it costs: you trust an invalid-code-group counter as an error rate, and misread a misaligned stream as a healthy one.

The corrected model: 8B/10B detects many errors and not all of them — a single bit error can transform one valid code group into another valid one, which decodes cleanly to the wrong octet. And more importantly, a stream aligned one bit off the true boundary still decodes: most ten-bit windows land on valid code groups meaning something else entirely. So c_invalid_codes at zero is not evidence of a healthy link; c_orphan_data and the frame layer's FCS are what actually establish it.

20. Interview Reasoning

"What does SGMII change relative to every other interface in Module 10?"

It stops being a parallel bus that changes shape with the link's speed and becomes a fixed-rate serial link that carries the speed as information. Four wires — two differential pairs — at 1.25 Gbaud, which is 1000 Mb/s × 10/8 for 8B/10B, at every link speed: 10 and 100 Mb/s are served by repeating each code group 100 and 10 times rather than by changing the clock. The strong answer names what that eliminates: no clock frequency that varies, so no re-lock; no bus width that changes; no sampling delay to configure, so Chapter 10.4's two symmetric failures cannot occur; no clock that stops, and none that nobody owns. The timing is closed once, at one rate, for every speed the link will ever run at. The finishing point: the pin count falls from GMII's 24 to 4, but the larger saving is closing timing once instead of three times.

"Why does SGMII carry configuration in the idle stream rather than over MDIO?"

Because a serial link must send something every 800 ps, so the idle stream exists whether or not it carries anything — and the question is only what to fill it with. /I/ costs nothing and keeps the clock recovery fed; /C/ costs nothing extra and carries a full 16-bit configuration word: link on bit 15, ACK on 14, duplex on 12, speed on 11:10. The strong answer says what it is not: this is not a negotiation. The cable-side negotiation already happened between two PHYs; SGMII's exchange is the near PHY reporting the result to a MAC that advertises 0x0001 — bit zero and nothing else — because it has no say. The finishing observation: SGMII reuses 802.3z's state machine but had to change one constant, shortening link_timer from 10 ms to 1.6 ms, because the event being reported has already happened and should reach the MAC promptly.

"At 100 Mb/s, why must the receive decimator resynchronise on change rather than count to ten?"

Because the two ends' 125 MHz references are independent and each is legally within its own tolerance, so over any long stream the receiver does not see runs of exactly ten. It sees mostly ten, occasionally nine or eleven, with the boundaries drifting — entirely conformantly. A decimator that counts to ten and resets accumulates that difference and eventually slips a code group; one that emits at each code-group change has no state to accumulate and is immune. The strong answer adds the discriminator: clock tolerance moves a run by one; a speed disagreement moves it by a factor of ten, so the anomaly check must fire at a factor rather than at an off-by-one. A MAC replicating for 100 Mb/s against a PHY replicating for 10 sees runs of a hundred, and that is a configuration fault rather than a timing one.

"Would you assert that running disparity is always correct?"

No — and it fires on conforming hardware, which is what makes it worse than merely useless. Running disparity is cumulative state computed from every code group since reset, so a receiver that has just acquired alignment has a value that has not yet caught up with the transmitter's. 8B/10B is self-healing here: the encoding drives the two into agreement within a bounded number of code groups. So there is a legal, expected, required window during which disparity is wrong, and every link-up and every bounce passes through it. The property asserts an invariant across a transient the mechanism is obliged to have — it describes a link recovering exactly as designed, and a team that has it will disable it during bring-up or suppress the disparity check entirely, losing the steady-state detection that was the point. Assert the life cycle instead: disparity converges within a bound of alignment; errors inside and outside the window go into different counters; converging and converged are exclusive; and a window that expires is a third outcome. The test: does this mechanism have a defined transient? Then the property is about the bound on it, not about the invariant it eventually restores.

21. Understanding Check

One multiplication, and it is 8B/10B's ratio.

StepWorkingResult
payload1000 Mb/s
8B/10B expansion× 10/81250 Mbaud
unit interval1 ÷ 1.25 GHz800 ps
code groups per second1250 ÷ 10125 M
octets per secondone per code group125 M
back to payload125 M × 81000 Mb/s

And 125 M octets/s is exactly Chapter 10.3's GMII octet rate, which is the observation worth carrying: SGMII is GMII with the parallel bus replaced by a serialiser and nothing else changed — same octet rate, same octet boundaries, same control semantics, delivered one bit at a time.

The lower speeds do not change the rate at all.

SpeedReplicationDistinct code groups/sPayload
1000 Mb/s125 M1000 Mb/s
100 Mb/s10×12.5 M100 Mb/s
10 Mb/s100×1.25 M10 Mb/s

The line rate column would read 1250 Mbaud three times, which is why it is not in the table.

22. What's Next

The claim this chapter defended: a fixed-rate serial interface that carries configuration as data eliminates every per-speed timing problem in the module.

SGMII puts the whole interface on two differential pairs at 1.25 Gbaud1000 Mb/s × 10/8, which is 8B/10B's ratio — and runs at that rate at every link speed, serving 100 and 10 Mb/s by repeating each code group ten and a hundred times. Four wires instead of twenty-four, and a 24-port design spends 96 signals where GMII spends 576.

The pin count is the smaller half of the argument. Because the line rate never changes, the serialiser's timing is closed once and covers every speed the link will ever run at. No clock frequency to change, no PLL to re-lock, no bus width to switch, no sampling delay to configure, and no per-speed calibration to invalidate — which removes, between them, the defining failure of every other chapter in Module 10.

Every control pin became a code group. TX_EN is /S/ and /T/; TX_ER is /V/; the interframe gap is /I/; and Chapter 10.4's four-bit in-band nibble became /C/ carrying a full sixteen-bit configuration word — link, duplex, speed and an acknowledge — which is a report rather than a negotiation, because the real negotiation already happened on the cable between two PHYs.

And what it cost is roughly 24 ns of pipeline, a 25% expansion, and one new class of failure — in which a mechanism's legal, self-healing transient is mistaken for an error. Running disparity is wrong on purpose for a bounded window after every alignment, and the naive assertion that it is always correct fires on conforming hardware every single time the link comes up. The honest property is about the bound on the recovery, not the invariant it restores.

Chapter 10.6 — XGMII takes the same argument to ten times the rate, and finds that serialising is no longer optional.

XGMII is 32 data bits and 4 control bits in each directionChapter 9.4 established why, and owns its lane-0 rule and its deficit idle mechanism. What this chapter has not yet confronted is the pin count that width implies: 74 signals, clocked at 156.25 MHz on both edges, across a board. That is not a route anybody wants, and the answer is XAUI — four lanes at 3.125 Gbaud with 8B/10B, sixteen wires instead of seventy-four. Which brings back everything this chapter just built, multiplied by four: comma alignment per lane, running disparity per lane, and a deskew problem XGMII never had, because four independently serialised lanes arrive at genuinely different times.

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.