Skip to content
VLSI Mentor

Ethernet · Module 5

Frame Check Sequence

The check sequence protects a range, and the range is shorter than the frame's journey — appended at one point in a transmitter, verified at one point in the next receiver, and recomputed at every hop, so a device's own memory is covered by nothing the frame carries.

Four octets close the frame. Chapter 5.1 placed them last and made an argument out of the position: the check sequence is at the end by construction, so every decision a receiver made before it arrived was provisional.

This chapter asks a different question, and it is the one that decides what the field is worth: what exactly does it cover?

The instinct is "the frame". That answer is close enough to be useful and wrong in three specific ways, each of which produces a distinct class of undetectable failure:

  • it does not cover the preamble or start delimiter;
  • it does not cover itself;
  • and — the one that matters most and is least often stated — it does not cover anything that happens outside the interval between the point where it is appended and the point where it is checked.

That last exclusion is not a gap in the mechanism. It is the mechanism's definition. A check sequence protects a range, and the range's two ends are locations in a datapath, not locations in a frame.

1. Scope — What This Chapter Owns, and What Module 6 Owns

This boundary needs stating first, because the obvious content of this chapter belongs to a later one.

Module 6 owns the mathematics. How CRC-32 is generated as polynomial division in a shift register, the initial-value, reflection and complement conventions that make independent implementations disagree, the residue property that lets a receiver check without comparing, and how a serial definition becomes a byte- or word-wide parallel engine — those are Chapter 6.2, Chapter 6.3 and Chapter 6.4, and this chapter deliberately does not touch them.

Chapter 6.1 owns the error model and what a detector can promise — the difference between "detects errors" and a stated undetected-error probability.

This chapter owns the field as a structural element: what range of octets it covers, where in a transmit datapath it is appended, where in a receive datapath it is verified, what falls outside those two points, and what a design can say about a frame based on the result.

In other words: Module 6 asks how well the check works. This chapter asks what the check is pointed at. Both questions have to be answered, and they fail independently — a perfect CRC over the wrong range is worth nothing, and the wrong range is much easier to build by accident.

It does not own: the frame's field order (Chapter 5.1), padding (Chapter 5.6), or the switching architecture that determines whether a device is store-and-forward or cut-through, which is a switching-module subject that Section 9 touches only where it changes what the check means.

The question this chapter answers that its neighbours do not: between which two points is a frame actually protected, and what is a valid check sequence evidence of?

2. The Covered Range, Stated Exactly

A frame on the wire is shown as five regions in order. The preamble and start frame delimiter come first and are not covered by the check sequence. The destination address, source address, length or type field, client data and padding follow, and all of these are covered. The four octet frame check sequence comes next and is not covered, because it is the result of the computation. The interframe gap follows and is not part of any frame.Preamble + SFD8 octets — outsideAddresses + L/T14 octets — coveredData + pad46 to 1500 — coveredFCS4 octets — outsideInterframe gap12 octets — outsideThe protectedrangefirst address octet tolast pad octetRegenerated perhopso covering it wouldmean nothing12
Figure 1 — the covered range starts at the first address octet and ends at the last pad octet. Everything else on the wire is outside it.

The range is contiguous and it starts and ends at points that are easy to get wrong by one octet in either direction. Two of the three failures are silent:

Starting one octet early — including the last octet of the start delimiter — produces a transmitter whose frames every conforming receiver rejects. Loud, and found in the first minute of bring-up.

Starting one octet late — omitting the first address octet — produces frames that this design accepts and others do not, and it makes a corruption of the first address octet undetectable. That octet carries the individual/group flag (Chapter 5.3), so an error there redirects the frame between two entirely different receive pipelines. Quiet, and severe.

Including the check sequence in its own computation is the classic one and it does not fail the way people expect. It produces a self-consistent scheme in which the transmitter and receiver agree with each other and with nobody else — which passes every loopback test, every simulation with a matching reference model, and no interoperability test at all.

3. Why Each Exclusion Is Deliberate

The three exclusions look like an arbitrary list and each has a reason that says something about the layer.

The preamble is excluded because it does not survive. Chapter 5.2 showed the preamble consumed by the receiving physical layer and regenerated by the next transmitter, with a length that varies along a path. A check computed over it would be invalid on arrival at every hop by design, and the octets it protects are ones no receiver ever forwards.

The gap is excluded because it is not data. It is an absence — Chapter 5.9's subject — and there is nothing there to protect.

The field cannot cover itself, and the reason is worth stating precisely rather than waving at circularity. The value is the result of a computation over the covered octets. Including itself would require the result to be a fixed point of its own function, which for a general input is not solvable — there is no value that, when appended, produces itself.

And the pad is included, which is the one people find surprising. Chapter 5.6 established that the pad is not the client's data and is never delivered. It is still transmitted, and a corrupted pad octet means the frame was damaged — which is information about the link, whatever the pad's contents were meant to be. Excluding it would create a region of every short frame in which corruption is invisible, and the frames concerned are the most numerous ones on most links.

4. RTL 1 — Marking the Covered Range Explicitly

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE.
//
// Produces the enable that gates a CRC engine, and nothing else.
//
// The range is defined by two events, not by two counts:
//
//   START -- the first octet of the destination address, which is the
//            first octet AFTER the start delimiter.
//   END   -- the last octet of the client-data region, INCLUDING pad.
//
// Defining it by counts instead is the standard bug: a count assumes a
// known frame length, and the length is not known until the frame ends.
package fcs_pkg;
 
  localparam int unsigned FCS_OCTETS = 4;
 
  typedef enum logic [2:0] {
    COV_IDLE,      // between frames
    COV_PREAMBLE,  // preamble and delimiter -- NOT covered
    COV_RANGE,     // addresses through pad  -- covered
    COV_FCS,       // the field itself       -- NOT covered
    COV_ERROR      // the range never closed
  } cov_state_e;
 
endpackage
 
module fcs_coverage_boundary
  import fcs_pkg::*;
(
  input  logic clk,
  input  logic rst_n,
 
  input  logic sfd_seen,        // start delimiter recognised (Chapter 5.2)
  input  logic octet_valid,
  input  logic last_data_octet, // last octet of client data or pad
  input  logic carrier_lost,
 
  // THE output. High for exactly the octets a CRC engine must consume.
  output logic crc_enable,
  output cov_state_e state,
 
  // Octets covered in this frame. Compared against the frame's own
  // accounting by Section 10, which is how a one-octet range error is
  // caught without needing a peer that disagrees.
  output logic [13:0] covered_count,
  output logic        covered_valid,
 
  // The range opened and the frame ended without it closing: a truncated
  // frame, and the CRC over a partial range is meaningless rather than
  // wrong. Reported so it is not silently checked anyway.
  output logic        range_unterminated
);
 
  cov_state_e  state_q;
  logic [13:0] count_q;
  logic [2:0]  fcs_q;
 
  assign state = state_q;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      state_q            <= COV_IDLE;
      count_q            <= '0;
      fcs_q              <= '0;
      crc_enable         <= 1'b0;
      covered_count      <= '0;
      covered_valid      <= 1'b0;
      range_unterminated <= 1'b0;
    end else begin
      crc_enable         <= 1'b0;
      covered_valid      <= 1'b0;
      range_unterminated <= 1'b0;
 
      if (carrier_lost && (state_q == COV_RANGE)) begin
        // The range opened and never closed. NOT a CRC failure -- there is
        // no complete computation to fail. Reporting it as one attributes
        // a truncation to a corruption, which sends the investigation to
        // the wrong layer entirely.
        state_q            <= COV_ERROR;
        range_unterminated <= 1'b1;
      end else begin
        case (state_q)
          COV_IDLE: if (sfd_seen) begin
            // Coverage begins on the octet AFTER the delimiter. Starting
            // on the delimiter itself is the loud failure; starting one
            // octet later is the quiet one (Section 2).
            state_q <= COV_RANGE;
            count_q <= '0;
          end
 
          COV_RANGE: if (octet_valid) begin
            crc_enable <= 1'b1;
            count_q    <= count_q + 1'b1;
            if (last_data_octet) begin
              state_q       <= COV_FCS;
              fcs_q         <= '0;
              covered_count <= count_q + 1'b1;
              covered_valid <= 1'b1;
            end
          end
 
          COV_FCS: if (octet_valid) begin
            // crc_enable stays LOW here. The four octets of the field are
            // on the bus and must not enter the computation that produced
            // them -- Section 3's fixed-point argument.
            if (fcs_q == 3'(FCS_OCTETS - 1)) state_q <= COV_IDLE;
            else                             fcs_q   <= fcs_q + 1'b1;
          end
 
          COV_ERROR: if (!carrier_lost) state_q <= COV_IDLE;
          default:                      state_q <= COV_IDLE;
        endcase
      end
    end
  end
 
endmodule

Classification: synthesizable.

What it teaches: that the range is delimited by events and not by counts. A count-based implementation needs the frame length in advance, and a receiver does not have it — the frame ends when carrier ends. Worse, a count that is one too large consumes the first octet of the check sequence into the computation, which produces the self-consistent scheme of Section 2 that passes every test involving only this design.

Deliberately simplified: last_data_octet arrives as an input. Deriving it is Chapter 5.6's problem on transmit, where the pad's end is known, and on receive it is simply the octet four before the end of carrier — which is why a receiver must delay by four octets to know where the payload stopped.

Production implication: range_unterminated is separated from any CRC result because a truncated frame has no meaningful check to fail. Reporting it as a CRC error attributes a truncation to a corruption; both indicate a problem on the link, but they point at different faults — truncation at a carrier or buffer event, corruption at a signal-integrity one — and merging them costs the distinction.

5. The Boundaries Are Points in a Datapath

Figure 1 is a diagram of a frame, and it encourages a false intuition: that the protection is a property of the octets.

It is not. It is a property of an interval in time, whose endpoints are two specific places in two different devices.

The append point is wherever, in a transmit path, the computation runs and the four octets are attached. Everything upstream of it — the client's buffer, the descriptor fetch, the DMA transfer, the transmit FIFO, every pipeline stage between them — is outside the protection. A bit that flips in any of those places is faithfully included in the computation, and the resulting frame is perfectly valid and carries wrong data.

The check point is wherever, in a receive path, the computation is verified. Everything downstream of it — the receive FIFO, the DMA to host memory, the driver's buffer — is likewise outside. A bit that flips there is delivered as data, after the frame was certified correct.

So a design's real protected interval is narrower than "the link", and how much narrower is a property of the implementation rather than of the standard. A transmit path with the CRC engine immediately before the interface has a short unprotected prefix; one that computes the CRC in software and passes the frame through several layers of memory has a long one.

6. RTL 2 — Measuring the Unprotected Prefix

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE INSTRUMENTATION.
//
// Reports how far a frame's octets travelled inside this device BEFORE
// the check sequence was computed over them -- the unprotected prefix of
// Section 5.
//
// The unit is deliberately STORAGE ELEMENTS, not cycles. A pipeline stage
// that a frame passes through is a place a bit can flip; a cycle spent
// waiting in the same register is not an additional exposure. Counting
// cycles overstates the exposure of a slow path and understates that of a
// deep one, which is exactly backwards.
module fcs_append_point
#(
  // Storage elements between the client handoff and the CRC engine's
  // input, on this design's transmit path. A structural property of the
  // implementation, declared rather than measured, because the design
  // knows it and the traffic cannot reveal it.
  parameter int unsigned STAGES_BEFORE_APPEND = 4,
  parameter int unsigned CNT_W = 40
) (
  input  logic clk,
  input  logic rst_n,
  input  logic clear,
 
  input  logic        frame_done,
  input  logic [13:0] covered_octets,
 
  // From whatever protects the internal path: parity or ECC on the
  // buffers the frame crossed before the append point. This is the
  // mechanism that covers what the CRC cannot.
  input  logic        internal_error_detected,
  input  logic        internal_error_corrected,
 
  output logic [CNT_W-1:0] octets_appended,
  output logic [CNT_W-1:0] frames_appended,
 
  // Octet-stages: octets multiplied by the storage elements they crossed
  // before being protected. The exposure figure, in the unit that lets two
  // designs be compared.
  output logic [CNT_W-1:0] unprotected_octet_stages,
 
  // Errors caught by the INTERNAL mechanism, in the region the frame check
  // sequence cannot see. Counted separately for exactly that reason.
  output logic [CNT_W-1:0] c_internal_detected,
  output logic [CNT_W-1:0] c_internal_corrected,
  output logic             internal_error_seen   // sticky
);
 
  function automatic logic [CNT_W-1:0] add(input logic [CNT_W-1:0] v,
                                           input logic [CNT_W-1:0] d);
    add = (v > ({CNT_W{1'b1}} - d)) ? {CNT_W{1'b1}} : (v + d);
  endfunction
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      octets_appended          <= '0;
      frames_appended          <= '0;
      unprotected_octet_stages <= '0;
      c_internal_detected      <= '0;
      c_internal_corrected     <= '0;
      internal_error_seen      <= 1'b0;
    end else begin
      if (clear) begin
        octets_appended          <= '0;
        frames_appended          <= '0;
        unprotected_octet_stages <= '0;
        c_internal_detected      <= '0;
        c_internal_corrected     <= '0;
        // internal_error_seen deliberately survives: an error in the
        // unprotected region is never routine, and a housekeeping clear
        // must not erase the fact that one occurred.
      end else begin
        if (frame_done) begin
          octets_appended <= add(octets_appended, CNT_W'(covered_octets));
          frames_appended <= add(frames_appended, CNT_W'(1));
          unprotected_octet_stages <=
            add(unprotected_octet_stages,
                CNT_W'(covered_octets) * CNT_W'(STAGES_BEFORE_APPEND));
        end
 
        if (internal_error_detected) begin
          c_internal_detected <= add(c_internal_detected, CNT_W'(1));
          internal_error_seen <= 1'b1;
        end
        if (internal_error_corrected)
          c_internal_corrected <= add(c_internal_corrected, CNT_W'(1));
      end
    end
  end
 
endmodule

Classification: synthesizable instrumentation.

What it teaches: that the exposure a design carries is a measurable quantity and almost never measured. unprotected_octet_stages is not a rate of failures — it is a rate of opportunity, and it is the correct denominator for interpreting c_internal_detected. Two designs with the same internal error count are not equally reliable if one exposes four times as many octet-stages.

Deliberately simplified: STAGES_BEFORE_APPEND is a parameter. It cannot be inferred from traffic, because the whole point is that the region is invisible from the wire — the number has to come from whoever built the datapath, and making it a parameter is what forces somebody to write it down.

Production implication: c_internal_detected and c_internal_corrected are separate. A corrected error is a healthy mechanism doing its job and a rising rate is an early warning; a detected but uncorrected error is a frame that had to be dropped inside the device, which is a loss the far end will attribute to the link. Merging them turns an early warning and an unattributable loss into one number, and the two have different urgencies.

7. RTL 3 — The Check Point, and What a Result Is Evidence Of

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE.
//
// Verifies the check sequence and -- more importantly -- classifies the
// RESULT into categories that mean different things.
//
// A binary pass/fail is not enough, because three situations produce a
// failure and one of them is not a corruption:
//
//   MISMATCH   -- the range completed and the value disagrees. A genuine
//                 detection. Something changed between append and check.
//   NO_RANGE   -- the frame ended before the range closed. There is no
//                 computation to compare; reporting a mismatch here is an
//                 attribution error (Section 4).
//   STOMPED    -- the value is the bitwise inverse of a valid one: an
//                 upstream device DELIBERATELY marked this frame bad
//                 (Section 9). Not damage on this link.
module fcs_check_point
  import fcs_pkg::*;
#(
  parameter int unsigned STAGES_AFTER_CHECK = 6,
  parameter int unsigned CNT_W = 32
) (
  input  logic clk,
  input  logic rst_n,
  input  logic clear,
 
  input  logic        frame_end,
  input  logic        range_completed,
  input  logic [31:0] computed,
  input  logic [31:0] received,
 
  output logic        result_valid,
  output logic        fcs_ok,
  output logic        fcs_mismatch,
  output logic        fcs_no_range,
  output logic        fcs_stomped,
 
  output logic [CNT_W-1:0] c_ok,
  output logic [CNT_W-1:0] c_mismatch,
  output logic [CNT_W-1:0] c_no_range,
  output logic [CNT_W-1:0] c_stomped,
 
  // First cause: the difference pattern of the first mismatch. A single
  // set bit points at a signal-integrity event; a dense pattern points at
  // a structural fault such as a mis-sized range.
  output logic             first_fail_seen,
  output logic [31:0]      first_fail_delta,
  // Octets delivered downstream after this point, which the check does
  // not cover. The receive-side twin of Section 6's prefix.
  output logic [CNT_W-1:0] unprotected_suffix_stages
);
 
  // A stomped frame carries the inverse of the value a correct frame would
  // have carried. Recognising it turns "this link is corrupting frames"
  // into "an upstream device already knew this frame was bad".
  wire is_stomp = (received == ~computed);
 
  function automatic logic [CNT_W-1:0] bump(input logic [CNT_W-1:0] v);
    bump = (&v) ? v : (v + 1'b1);
  endfunction
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      result_valid              <= 1'b0;
      fcs_ok                    <= 1'b0;
      fcs_mismatch              <= 1'b0;
      fcs_no_range              <= 1'b0;
      fcs_stomped               <= 1'b0;
      c_ok                      <= '0;
      c_mismatch                <= '0;
      c_no_range                <= '0;
      c_stomped                 <= '0;
      first_fail_seen           <= 1'b0;
      first_fail_delta          <= '0;
      unprotected_suffix_stages <= '0;
    end else begin
      result_valid <= 1'b0;
      fcs_ok       <= 1'b0;
      fcs_mismatch <= 1'b0;
      fcs_no_range <= 1'b0;
      fcs_stomped  <= 1'b0;
 
      if (clear) begin
        c_ok <= '0; c_mismatch <= '0; c_no_range <= '0; c_stomped <= '0;
        unprotected_suffix_stages <= '0;
        // first_fail_* deliberately survives.
      end
 
      if (frame_end) begin
        result_valid <= 1'b1;
 
        if (!range_completed) begin
          fcs_no_range <= 1'b1;
          c_no_range   <= bump(c_no_range);
        end else if (computed == received) begin
          fcs_ok <= 1'b1;
          c_ok   <= bump(c_ok);
          unprotected_suffix_stages <=
            unprotected_suffix_stages + CNT_W'(STAGES_AFTER_CHECK);
        end else if (is_stomp) begin
          fcs_stomped <= 1'b1;
          c_stomped   <= bump(c_stomped);
        end else begin
          fcs_mismatch <= 1'b1;
          c_mismatch   <= bump(c_mismatch);
          if (!first_fail_seen) begin
            first_fail_seen  <= 1'b1;
            first_fail_delta <= computed ^ received;
          end
        end
      end
    end
  end
 
endmodule

Classification: synthesizable.

What it teaches: that first_fail_delta is a diagnostic and a pass/fail bit is not. The exclusive-or of the computed and received values has a shape. A sparse pattern — a handful of set bits — is consistent with a small number of bit errors on the wire, which is a signal-integrity story. A dense pattern, with roughly half the bits set, is what a structurally wrong computation produces: a range off by an octet, an initial value that disagrees, the wrong bit ordering. The first says the link is marginal; the second says the two implementations were never computing the same thing, and no amount of cable replacement addresses it.

Deliberately simplified: the stomp test is exact inversion. Real deployments vary in how a frame is marked, and a design should treat the recognition as a heuristic hint rather than a certainty — it is a strong signal precisely because an inverted value is astronomically unlikely to arise from corruption.

Production implication: c_stomped must not be added to c_mismatch, and this is the single most valuable split in the module. A stomped frame means an upstream device found the error and told you — the fault is on some earlier link, and this link is behaving perfectly. Counting it as a local mismatch points the investigation at the one link that has been proven innocent.

8. Hop by Hop, Not End to End

A frame crosses two links between a sender, a switch, and a receiver. The sender appends a check sequence at its append point and the switch verifies it at its check point, so the first link is protected. Inside the switch the frame passes through memory between the check point and a new append point, and that region is not covered by any check the frame carries. The switch appends a new check sequence and the receiver verifies it, protecting the second link. The frame that arrives is therefore protected by a computation the switch performed, not by one the sender performed.Senderappend pointLink 1protectedSwitchcheck, then appendagainLink 2protectedReceivercheck pointSwitch memorycovered by nothing theframe carriesFinal check isthe switch'snot the sender's12
Figure 2 — the check is recomputed at every hop, so the gaps inside each device are covered by nothing the frame carries.

A store-and-forward switch checks the sequence on ingress and computes a new one on egress. It must, whenever it modifies the frame — inserting or removing a tag changes the covered octets, so the old value no longer describes them. Many devices recompute unconditionally, because a single path is simpler than one with a conditional pass-through.

So the check sequence arriving at a destination was computed by the last device in the path. It certifies the last link. It says nothing whatsoever about the first.

And the region between a switch's check and its append is genuinely uncovered by anything in the frame. A frame sits in that switch's memory — possibly for a long time, on a congested port — and any bit that flips there is included in the new computation. The frame leaves with a valid check sequence and wrong contents, and every subsequent device confirms it.

9. Cut-Through, and Marking a Frame Bad on Purpose

A store-and-forward switch can check before it forwards. A cut-through switch cannot, and the consequence is a mechanism that looks bizarre until its purpose is clear.

Cut-through forwarding begins transmitting a frame on the egress port as soon as enough of the header has arrived to make a forwarding decision — long before the check sequence arrives. It reduces latency substantially and it means the switch is committed: by the time the check sequence arrives and proves the frame corrupt, most of the frame has already been sent onward.

So the frame cannot be discarded. It can only be marked. And the marking has to be something every downstream device already rejects, because no new field can be added and no downstream device can be asked to understand a new convention.

The mechanism is to deliberately emit an invalid check sequence — commonly the bitwise inverse of the correct one. Every conforming receiver rejects the frame, exactly as intended, and the corrupt data does not reach an application.

What the inverse buys over an arbitrary wrong value is attribution. A random wrong value is indistinguishable from corruption on the link that just carried it. An exact inversion is astronomically unlikely to arise by chance, so a receiver that tests for it — Section 7's is_stomp — can conclude that an upstream device already detected this error, and that the local link is not the problem.

Which reframes the whole practice. It is not a workaround for cut-through's inability to drop; it is a way of propagating a diagnosis through a protocol that has no field in which to carry one. The frame becomes its own error report, in the only encoding every device on the path already understands.

And the cost is worth naming: a design that does not test for the inversion counts these as local corruption, and the investigation converges on the one link that has been proven good.

10. RTL 4 — Proving the Range Is the Range

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE CONFORMANCE CHECKER.
//
// Compares the number of octets the CRC engine was actually fed against
// the number the frame's own structure says it should have been.
//
// This is the check that no interoperability test performs cheaply and no
// self-test performs at all: a design that covers the wrong range is
// SELF-CONSISTENT, so every loopback passes and every simulation with a
// matching reference model passes. The disagreement only appears against
// a peer -- which is to say, in the field.
module protected_range_checker
  import fcs_pkg::*;
#(
  parameter int unsigned CNT_W = 32
) (
  input  logic clk,
  input  logic rst_n,
 
  // What the coverage boundary actually enabled.
  input  logic        covered_valid,
  input  logic [13:0] covered_count,
 
  // What the frame's structure implies, computed independently: the total
  // octets between the delimiter and the end of carrier, minus the four
  // octets of the field itself.
  input  logic        structure_valid,
  input  logic [13:0] frame_octets_after_sfd,
 
  output logic        conformant,
  output logic        mismatch,
  // Signed difference, because the two directions are different bugs:
  //   +1 -> the range consumed an octet of the check sequence itself
  //   -1 -> the range missed the first address octet
  output logic signed [14:0] delta,
 
  output logic [CNT_W-1:0] c_mismatch,
  output logic             first_mismatch_seen,
  output logic signed [14:0] first_delta
);
 
  wire [13:0] expected = frame_octets_after_sfd - 14'(FCS_OCTETS);
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      conformant          <= 1'b1;
      mismatch            <= 1'b0;
      delta               <= '0;
      c_mismatch          <= '0;
      first_mismatch_seen <= 1'b0;
      first_delta         <= '0;
    end else begin
      mismatch <= 1'b0;
 
      if (covered_valid && structure_valid) begin
        delta <= $signed({1'b0, covered_count}) - $signed({1'b0, expected});
 
        if (covered_count != expected) begin
          mismatch   <= 1'b1;
          conformant <= 1'b0;   // sticky: one non-conformant frame is enough
          if (!(&c_mismatch)) c_mismatch <= c_mismatch + 1'b1;
          if (!first_mismatch_seen) begin
            first_mismatch_seen <= 1'b1;
            first_delta <= $signed({1'b0, covered_count}) - $signed({1'b0, expected});
          end
        end
      end
    end
  end
 
endmodule

Classification: synthesizable conformance checker.

What it teaches: that the sign of the error names the bug. A delta of +4 means the whole check sequence was fed into its own computation. +1 means one octet of it was. −1 means the first address octet was missed — the quiet, severe failure of Section 2, which leaves the individual/group flag unprotected. A boolean "range wrong" says none of this, and the three have different fixes in different places.

Deliberately simplified: it needs an independently derived frame length. On receive that is the octet count between the delimiter and the loss of carrier, which the physical interface supplies; on transmit it is known from the frame being built. The independence is the requirement — a length derived from the same counter that drove the coverage enable proves nothing at all.

Production implication: conformant is sticky and never clears. A design that has ever covered the wrong range is not intermittently wrong — it is structurally wrong, and it will be wrong on every frame. Clearing the flag would suggest the condition could pass, and a single assertion of it is a build-stopping finding rather than a counter to trend.

11. Where an Error Could Have Come From

Four regions in which a frame's octets can be corrupted. Before the append point, inside the transmitting device, where the corruption is included in the computation and is undetectable by the check sequence. On the wire, between the append point and the check point, where the check sequence detects it. Inside a switch between its check point and its new append point, where the corruption is again included in a new computation and is undetectable. And after the check point in the receiving device, where the frame has already been certified. Only the wire region is covered.Before appendsender's own memoryOn the wireappend to checkInside a switchcheck to re-appendAfter checkreceiver's own memoryDetected herethe only covered regionDetected by ECC, ornot at allthe device's own problem12
Figure 3 — four regions a corruption can occur in, and only one of them is the region the check sequence covers.

Three of the four regions are invisible to this field, and the practical question is what a design can say about them anyway. The answer is: something, by elimination and by correlation.

A frame with a good check sequence and wrong contents cannot be attributed by the frame. But the application above will notice — its own checksum will fail, or its data will be wrong — and the correlation is diagnostic: an upper-layer checksum failing while every link-layer counter is clean is the signature of corruption in one of the three uncovered regions, and it eliminates the links.

A frame with a bad check sequence narrows to the wire since the last append. That is a strong localisation and it is what makes per-hop counters worth having.

A stomped frame narrows to a previous link — an upstream device saw the error, which means it is neither here nor in the region since.

And a device's own internal error counters — Section 6's parity and correction counts — cover the first and last regions from the inside. Where those counters exist, the elimination is complete; where they do not, there is a region of the system in which corruption occurs and nothing anywhere records it.

12. RTL 5 — Accounting by Origin, Not by Symptom

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE INSTRUMENTATION.
//
// Attributes every bad frame to a REGION of Figure 3 rather than to a
// symptom, and -- crucially -- records the frames it CANNOT attribute.
//
// The last output is the point of the module. A design that reports only
// what it detected implies that everything else was fine. A design that
// also reports what it could not have seen is honest about the size of
// its own blind spot, which is what makes the numbers usable.
module error_origin_accounting
#(
  parameter int unsigned CNT_W = 32
) (
  input  logic clk,
  input  logic rst_n,
  input  logic clear,
 
  input  logic result_valid,
  input  logic fcs_ok,
  input  logic fcs_mismatch,
  input  logic fcs_stomped,
  input  logic fcs_no_range,
 
  // From this device's internal protection (Section 6), covering the
  // regions the check sequence cannot.
  input  logic internal_error_detected,
 
  // From above: an upper-layer integrity check on a frame that PASSED
  // the frame check sequence. The only evidence available for corruption
  // in an uncovered region on some other device.
  input  logic upper_layer_mismatch,
 
  output logic [CNT_W-1:0] c_this_link,        // wire, since last append
  output logic [CNT_W-1:0] c_upstream_link,    // stomped: an earlier hop
  output logic [CNT_W-1:0] c_this_device,      // caught by internal ECC
  output logic [CNT_W-1:0] c_uncovered_region, // good FCS, bad payload
  output logic [CNT_W-1:0] c_unattributable,   // truncation: no range
 
  output logic [CNT_W-1:0] c_good,
 
  // Frames that passed and were NOT independently verified above. The
  // blind spot, stated as a number rather than left implicit.
  output logic [CNT_W-1:0] c_unverified_pass
);
 
  function automatic logic [CNT_W-1:0] bump(input logic [CNT_W-1:0] v);
    bump = (&v) ? v : (v + 1'b1);
  endfunction
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      c_this_link        <= '0;
      c_upstream_link    <= '0;
      c_this_device      <= '0;
      c_uncovered_region <= '0;
      c_unattributable   <= '0;
      c_good             <= '0;
      c_unverified_pass  <= '0;
    end else if (clear) begin
      c_this_link        <= '0;
      c_upstream_link    <= '0;
      c_this_device      <= '0;
      c_uncovered_region <= '0;
      c_unattributable   <= '0;
      c_good             <= '0;
      c_unverified_pass  <= '0;
    end else begin
      if (internal_error_detected) c_this_device <= bump(c_this_device);
 
      if (result_valid) begin
        if (fcs_mismatch)      c_this_link      <= bump(c_this_link);
        else if (fcs_stomped)  c_upstream_link  <= bump(c_upstream_link);
        else if (fcs_no_range) c_unattributable <= bump(c_unattributable);
        else if (fcs_ok) begin
          c_good <= bump(c_good);
          if (upper_layer_mismatch) begin
            // Passed the check and failed above it. By elimination the
            // corruption happened in a region no check sequence covers.
            c_uncovered_region <= bump(c_uncovered_region);
          end else begin
            // Passed, and nothing independent confirmed it. NOT proof of
            // correctness -- proof only that nothing looked.
            c_unverified_pass <= bump(c_unverified_pass);
          end
        end
      end
    end
  end
 
endmodule

Classification: synthesizable instrumentation.

What it teaches: that a counter for what the design cannot see is as important as counters for what it can. c_unverified_pass looks useless — it counts frames that were fine. What it actually reports is the size of the population about which nothing is known, and that is the denominator for every claim of the form "we saw no corruption". A design reporting a million good frames and no errors is saying something quite different depending on whether those million were independently verified above.

Deliberately simplified: upper_layer_mismatch has to be routed back down from a layer that is architecturally above this one. That is unusual, and it is the only way this evidence reaches a place that can correlate it with link counters — a genuine exception to the usual downward flow, made deliberately and narrowly.

Production implication: c_upstream_link being non-zero completely changes what to do about c_this_link. A device seeing both is on a path with a fault that is not local, and its own mismatch count may be entirely incidental. Without the split — Section 7's stomp recognition feeding this module — both populations land in one counter, and the strongest available evidence about where the fault is gets averaged away.

13. Assertions — About the Range, Not About the Arithmetic

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// P1 -- THE CENTRAL PROPERTY. The check sequence's own octets never enter
// the computation that produced them.
// ---------------------------------------------------------------------
property p_fcs_not_self_covered;
  @(posedge clk) disable iff (!rst_n)
    (state == COV_FCS) |-> !crc_enable;
endproperty
a_fcs_not_self_covered: assert property (p_fcs_not_self_covered)
  else $error("check sequence octets fed into their own computation");
 
// ---------------------------------------------------------------------
// P2 -- Coverage begins on the octet AFTER the delimiter, never on it.
// ---------------------------------------------------------------------
property p_coverage_starts_after_sfd;
  @(posedge clk) disable iff (!rst_n)
    sfd_seen |-> !crc_enable;
endproperty
a_coverage_starts_after_sfd: assert property (p_coverage_starts_after_sfd);
 
// ---------------------------------------------------------------------
// P3 -- The preamble is never covered.
// ---------------------------------------------------------------------
property p_preamble_not_covered;
  @(posedge clk) disable iff (!rst_n)
    (state == COV_PREAMBLE) |-> !crc_enable;
endproperty
a_preamble_not_covered: assert property (p_preamble_not_covered);
 
// ---------------------------------------------------------------------
// P4 -- The covered range is CONTIGUOUS. A gap in the enable means some
// octets between the addresses and the pad were skipped, which is not
// detectable from the CRC value at either end.
// ---------------------------------------------------------------------
property p_coverage_contiguous;
  @(posedge clk) disable iff (!rst_n)
    ($fell(crc_enable) && (state == COV_RANGE)) |-> !octet_valid;
endproperty
a_coverage_contiguous: assert property (p_coverage_contiguous);
 
// ---------------------------------------------------------------------
// P5 -- Pad IS covered. Excluding it creates a blind region in the most
// numerous frames on most links (Section 3).
// ---------------------------------------------------------------------
property p_pad_is_covered;
  @(posedge clk) disable iff (!rst_n)
    (octet_valid && octet_is_pad && (state == COV_RANGE)) |-> crc_enable;
endproperty
a_pad_is_covered: assert property (p_pad_is_covered);
 
// ---------------------------------------------------------------------
// P6 -- The range checker's expectation is derived INDEPENDENTLY. If the
// two counts always agree by construction, the checker proves nothing.
// ---------------------------------------------------------------------
property p_range_conformance;
  @(posedge clk) disable iff (!rst_n)
    (covered_valid && structure_valid) |=> (mismatch == (delta != 0));
endproperty
a_range_conformance: assert property (p_range_conformance);
 
// ---------------------------------------------------------------------
// P7 -- Non-conformance is sticky. A wrong range is structural, not
// intermittent, so it cannot un-happen.
// ---------------------------------------------------------------------
property p_conformant_is_sticky_low;
  @(posedge clk) disable iff (!rst_n)
    !conformant |=> !conformant;
endproperty
a_conformant_is_sticky_low: assert property (p_conformant_is_sticky_low);
 
// ---------------------------------------------------------------------
// P8 -- The four result classes partition. A frame is exactly one of ok,
// mismatch, stomped, no-range.
// ---------------------------------------------------------------------
property p_result_classes_onehot;
  @(posedge clk) disable iff (!rst_n)
    result_valid |-> $onehot({fcs_ok, fcs_mismatch, fcs_stomped, fcs_no_range});
endproperty
a_result_classes_onehot: assert property (p_result_classes_onehot);
 
// ---------------------------------------------------------------------
// P9 -- A truncated frame is NEVER reported as a mismatch. There is no
// completed computation to disagree with (Section 4).
// ---------------------------------------------------------------------
property p_no_range_not_mismatch;
  @(posedge clk) disable iff (!rst_n)
    fcs_no_range |-> (!fcs_mismatch && !fcs_ok);
endproperty
a_no_range_not_mismatch: assert property (p_no_range_not_mismatch);
 
// ---------------------------------------------------------------------
// P10 -- A stomped frame is not counted as local corruption. Section 7's
// most valuable split, asserted so a refactor cannot merge it.
// ---------------------------------------------------------------------
property p_stomp_not_local;
  @(posedge clk) disable iff (!rst_n)
    fcs_stomped |=> ($stable(c_mismatch) && (c_stomped == $past(c_stomped) + 1));
endproperty
a_stomp_not_local: assert property (p_stomp_not_local);
 
// ---------------------------------------------------------------------
// P11 -- A stomp is exactly the inverse. Anything else is a mismatch, and
// the recognition must not be loosened into a range.
// ---------------------------------------------------------------------
property p_stomp_is_exact_inverse;
  @(posedge clk) disable iff (!rst_n)
    fcs_stomped |-> ($past(received) == ~$past(computed));
endproperty
a_stomp_is_exact_inverse: assert property (p_stomp_is_exact_inverse);
 
// ---------------------------------------------------------------------
// P12 -- First cause is captured once and is the DELTA, not the values.
// The pattern of set bits is what distinguishes a wire fault from a
// structural one (Section 7).
// ---------------------------------------------------------------------
property p_first_delta_stable;
  @(posedge clk) disable iff (!rst_n)
    first_fail_seen |=> $stable(first_fail_delta);
endproperty
a_first_delta_stable: assert property (p_first_delta_stable);
 
// ---------------------------------------------------------------------
// P13 -- Every result increments exactly one counter.
// ---------------------------------------------------------------------
property p_one_counter_per_result;
  @(posedge clk) disable iff (!rst_n)
    result_valid |=> (counter_sum == $past(counter_sum) + 1);
endproperty
a_one_counter_per_result: assert property (p_one_counter_per_result);
 
// ---------------------------------------------------------------------
// P14 -- A frame that failed above while passing here is attributed to an
// uncovered region, never to this link.
// ---------------------------------------------------------------------
property p_upper_failure_not_local;
  @(posedge clk) disable iff (!rst_n)
    (result_valid && fcs_ok && upper_layer_mismatch)
      |=> ($stable(c_this_link) && (c_uncovered_region == $past(c_uncovered_region) + 1));
endproperty
a_upper_failure_not_local: assert property (p_upper_failure_not_local);
 
// ---------------------------------------------------------------------
// P15 -- Internal errors are counted separately from link errors. They
// occur in a region the check sequence provably cannot see.
// ---------------------------------------------------------------------
property p_internal_not_link;
  @(posedge clk) disable iff (!rst_n)
    internal_error_detected |=> $stable(c_this_link);
endproperty
a_internal_not_link: assert property (p_internal_not_link);
 
// ---------------------------------------------------------------------
// P16 -- COVERAGE. A frame at each result class, including the two that
// most environments never generate.
// ---------------------------------------------------------------------
c_stomped_seen: cover property (
  @(posedge clk) disable iff (!rst_n) fcs_stomped
);
c_no_range_seen: cover property (
  @(posedge clk) disable iff (!rst_n) fcs_no_range
);
 
// ---------------------------------------------------------------------
// P17 -- COVERAGE. Corruption in an uncovered region: a frame with a good
// check sequence whose payload is wrong. Section 14 builds it.
// ---------------------------------------------------------------------
c_uncovered_corruption_seen: cover property (
  @(posedge clk) disable iff (!rst_n) (fcs_ok && upper_layer_mismatch)
);
 
// ---------------------------------------------------------------------
// P18 -- COVERAGE. A minimum-size frame, so the pad is inside the range.
// ---------------------------------------------------------------------
c_padded_frame_covered: cover property (
  @(posedge clk) disable iff (!rst_n) (crc_enable && octet_is_pad)
);

14. Verification — Twenty-Two Scenarios and an Error the Wire Cannot Carry

#ScenarioStimulusWhat must be observed
1Clean framevalid frame, no injectionfcs_ok; covered_count equals frame octets after the delimiter minus four
2Minimum-size frame64 octets, paddedcrc_enable high across the pad (P5)
3Maximum-size frame1518 octetscoverage contiguous end to end (P4)
4One bit flipped in the payloadwire injectionfcs_mismatch; first_fail_delta sparse
5One bit flipped in the addresswire injection at octet 0fcs_mismatch — proves the first address octet is inside the range
6One bit flipped in the padwire injection in a pad octetfcs_mismatch — proves the pad is inside the range
7One bit flipped in the preamblewire injection before the delimiterno mismatch, and the frame still delimits correctly
8One bit flipped in the FCS fieldwire injection in the last four octetsfcs_mismatch — the field is checked, though not covered
9Truncated framecarrier lost mid-framefcs_no_range; no mismatch (P9)
10Stomped framereceived value is the exact inversefcs_stomped; c_mismatch unchanged (P10, P11)
11Near-stompreceived value inverse in all but one bitfcs_mismatch, not stomped — the recognition must stay exact
12Range one octet shortforce coverage to start at address octet 1mismatch with delta = −1
13Range one octet longforce coverage to include one FCS octetmismatch with delta = +1
14Range four octets longforce coverage over the whole fielddelta = +4; self-consistent loopback still passes
15Non-conformance is stickyone bad-range frame, then clean trafficconformant stays low (P7)
16Structural mismatch signaturea peer using a different rangefirst_fail_delta dense, roughly half the bits set
17Internal error, correctedassert internal_error_correctedc_internal_corrected increments; c_this_link unchanged (P15)
18Internal error, detected onlyassert internal_error_detectedc_this_device increments; internal_error_seen sticky
19Exposure accounting1000 frames of 1500 octetsunprotected_octet_stages equals octets × STAGES_BEFORE_APPEND
20Unverified passesclean traffic, no upper-layer signalc_unverified_pass tracks c_good exactly
21Good FCS, bad payloadcorruption before the append pointfcs_ok high; c_uncovered_region increments (P14)
22Counter clearassert clear after faultscounters zero; conformant, internal_error_seen, first_fail_delta survive

15. Debugging — Reading a Failure Back to a Region

Symptom — a link shows a steady rate of check-sequence mismatches.

Read first_fail_delta before touching anything physical. A sparse difference — a few set bits — is consistent with bit errors on the wire, and the physical layer is the right place to look. A dense difference, with roughly half the bits set, is what a structurally different computation produces: a range that differs by an octet, a different initial value, a different bit ordering. The second is not a link fault at all, and it appears at 100% on traffic from one particular peer and 0% from every other — which is the confirming test.

Symptom — mismatches on one link, and the neighbour's counters are clean.

Check c_stomped before c_mismatch. If the frames are stomped, an upstream device found the error and marked the frame, and this link is behaving perfectly — the fault is one or more hops back, on a link neither of these two devices touches. Without Section 7's inversion test the two populations are one counter, and the investigation converges on the link that has been proven good.

Symptom — an application reports corrupted data and every link-layer counter is clean.

This is the signature of corruption in one of the three uncovered regions of Figure 3, and the elimination is worth doing carefully. Clean link counters at every hop rule out the wire. What remains is: the sender's memory before its append point, some switch's memory between its check and re-append, or the receiver's memory after its check point. Read the internal error counters on each devicec_internal_detected from Section 6 — because where those exist the elimination completes, and where they do not, that device is the region nothing is watching.

Symptom — everything passes in loopback and the peer rejects every frame.

Suspect the covered range, not the arithmetic. A design whose range is wrong is self-consistent: its own transmitter and receiver compute over the same wrong octets and always agree, so loopback is exactly the test that cannot find it. Read delta from Section 10 — it is derived independently of the coverage logic — and its sign names the bug: +4 means the check sequence covered itself, +1 means one octet of it did, −1 means the first address octet was missed.

Symptom — fcs_no_range climbing with no mismatches.

Frames are being truncated rather than corrupted. Carrier is being lost mid-frame, or a buffer is running dry mid-transmission. This is not a signal-integrity problem and the usual signal-integrity remedies will not move it; look at flow control, buffer occupancy, and clock compensation (Chapter 4.4) instead.

Symptom — after a switch was reconfigured to insert tags, downstream devices report mismatches.

The switch is modifying the frame and not recomputing the check sequence. Inserting four octets changes the covered range, so the original value no longer describes it. Every frame that passes through the modification fails downstream, and frames that bypass it are fine — so the failure correlates perfectly with a configuration rather than with a physical path, which is the tell.

16. Common Misconceptions

"The FCS covers the frame."

The wrong model: everything on the wire between one gap and the next.

What it costs: you expect a corrupted preamble to be caught, and you cannot explain why a design needs internal parity when it already has a CRC. You also have no vocabulary for the failures that actually occur, because every one of them happens outside the region you believe is covered.

The corrected model: the covered octets are exactly the addresses, the length/type field, the client data and the pad. Preamble and delimiter are outside because they are regenerated at every hop; the field cannot cover itself; and the gap is not data.

"A frame with a good FCS is the frame the sender built."

The wrong model: the check is end-to-end.

What it costs: Section 13's rejected property, and with it the belief that corruption inside a device is impossible. The internal protection that would have caught it looks redundant and does not get built, and then the failures are both undetectable and unattributable.

The corrected model: the check spans one hop, from an append point in one device to a check point in the next — and every switch that modifies the frame recomputes it. The check sequence arriving at a destination was computed by the last device in the path, and says nothing about the first.

"A CRC error means a bad cable."

The wrong model: mismatches are always physical.

What it costs: you replace cables and optics on a link whose problem is that a peer computes over a different range, or that an upstream device already marked the frame bad. Neither responds to anything physical.

The corrected model: read the difference pattern and the stomp flag first. A sparse delta is a wire story; a dense one is two implementations disagreeing structurally; and an exact inversion is an upstream device telling you it already found the error.

"Cut-through switches corrupting the FCS is a bug."

The wrong model: deliberately emitting an invalid check sequence is bad behaviour.

What it costs: you count stomped frames as local corruption and investigate the one link that has been proven innocent — the strongest available evidence about where the fault is gets discarded.

The corrected model: a cut-through switch has already forwarded most of the frame by the time it knows the frame is bad, so it cannot drop it. Inverting the check sequence makes every conforming receiver reject it, and the exactness of the inversion is what lets a receiver tell "an upstream device found this" from "this link corrupted it". It is a diagnosis propagated through a protocol with no field for one.

"If the covered range were wrong, we would know."

The wrong model: a range error shows up in testing.

What it costs: the failure is missed by exactly the tests people run. A design whose range is wrong is self-consistent — its transmitter and receiver agree perfectly — so loopback passes, and a simulation whose reference model shares the assumption passes too.

The corrected model: it is found by comparing the covered count against the frame's independently derived structure, which is what Section 10 does — or in the field, against a peer, which is later and more expensive.

17. Interview Reasoning

"What does the Ethernet FCS cover?"

The weak answer is "the frame". The answer that ends the topic lists the range exactly — addresses, length/type, client data, pad — and then gives a reason for each exclusion: the preamble is regenerated at every hop so a check over it would fail everywhere by design; the field cannot cover itself, because no value is a fixed point of its own computation; and the gap is not data. The payoff is that the pad is included, and the reason: excluding it would blind the check across most of the shortest frames on the link.

"Is the FCS end-to-end?"

No, and the complete answer says what it is: a check from an append point in one device to a check point in the next, recomputed at every hop that modifies the frame. Then the consequence, which is the interesting half: the region inside a switch, between its check and its re-append, is covered by nothing the frame carries, so a bit that flips there leaves with a valid check sequence. And the constructive close — this is why devices carry ECC on internal buffers, and why end-to-end guarantees come from an end-to-end layer.

"You see CRC errors on a link. Walk me through it."

The strong answer refuses to start at the cable. First the difference pattern: sparse means bit errors on the wire, dense means two implementations computing over different ranges or with different conventions — and the latter correlates with a peer, not with a path. Then the stomp test: an exact inversion means an upstream device already found the error and marked the frame, so this link is proven good. Only after both is the physical layer the right place to look, and by then the search has been narrowed rather than guessed at.

"Your design passes loopback and the peer rejects everything. Where do you look?"

At the covered range, because a range error is self-consistent and loopback is precisely the test that cannot see it. The complete answer names the independent check — compare the octet count the engine consumed against the frame's own structure, derived separately — and reads the sign of the difference: four too many means the check sequence covered itself, one too few means the first address octet was missed, which additionally leaves the individual/group bit unprotected.

18. Understanding Check

Covered: destination address, source address, length/type, client data, and pad. Nothing before, nothing after.

The preamble and start delimiter are excluded because they do not survive a hop. Chapter 5.2 showed them consumed by the receiving physical layer and regenerated by the next transmitter, with a length that varies along the path. A check computed over them would be invalid on arrival everywhere, by design.

The field cannot cover itself, and the reason is stronger than circularity: the value is the result of a function of the covered octets, so including itself would require it to be a fixed point of that function — and for general input no such value exists.

The interframe gap is excluded because it is not data. It is an absence, and there is nothing there to protect.

And the pad is included, which surprises people because Chapter 5.6 established the pad is never delivered. It is still transmitted, so a corrupted pad octet is evidence the frame was damaged — information about the link, regardless of what the octets were supposed to contain. Excluding it would blind the check across most of the shortest frames on the link.

19. What's Next

The claim this chapter defended: the frame check sequence protects a range, and the range is shorter than the frame's journey.

The covered octets are exactly the addresses, the length/type field, the client data and the pad — the preamble excluded because it is regenerated at every hop, the field excluded because no value can be a fixed point of its own computation, the gap excluded because it is not data. And the covered interval runs from an append point in one device to a check point in the next, which makes the check hop-by-hop: the value arriving at a destination was computed by the last device in the path, and the memory inside every switch along the way is covered by nothing the frame carries.

Which is why the useful outputs of this chapter are not pass and fail. They are a range conformance check that can find a self-consistent design's structural error without a peer, a difference pattern that separates a marginal cable from two implementations that were never computing the same thing, a stomp test that recognises a diagnosis another device already made, and an honest count of the frames about which nothing is known.

Chapter 5.9 — Interframe Gap and Idle takes the last region of Figure 1, the one this chapter excluded in a single line. It is the other quantity inherited from a mechanism that is gone: a minimum gap of 96 bit times, which exists so a receiver can recover between frames.

And it turns out not to be an absence at all. Something occupies the gap — a signal the coding of Chapter 3.5 must keep alive — and on wide interfaces the gap cannot even be a fixed number of octets, because a frame must start on a lane boundary. 5.9 shows how a deficit is carried so the average gap is honoured while individual gaps are not, and why a gap measured in octets on one interface is a gap measured in time on another.

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.