Skip to content
VLSI Mentor

Ethernet · Module 12

Store-and-Forward against Cut-Through

A cut-through switch commits at octet 14 and the check sequence arrives at octet 1518. One of the six forwarding gates cannot be run at all — which is the discipline's defining property, not a defect.

Chapter 12.1 §10 introduced this choice and could only half-state it, because Chapter 12.3 had not yet established what a forwarding decision contains.

With the six gates in hand the statement becomes exact, and it is sharper than a latency trade-off.

A cut-through switch commits the frame to an egress port after 14 octets. Chapter 12.3's gate 1 — is this frame eligible to be forwarded at all — depends on the frame check sequence, which arrives in the last four octets of a frame up to 1518 octets long.

So the gate is not slow, not expensive, and not hard to pipeline. It cannot be run. Its input does not exist yet, and it will not exist until 12 032 bits after the decision has already been made.

That is not a defect to engineer away. It is what cut-through is — and every property, every counter and every constructive mechanism in this chapter follows from it.

1. Scope — What This Chapter Owns

This chapter owns the commit point and its consequences: where in the frame each discipline decides, which of Chapter 12.3's gates survive an early commit, what happens to a frame found corrupt after it has begun leaving, the rate rule that makes cut-through unavailable on a speed step upward, and why the latency benefit vanishes under load.

It does not own the decision itselfChapter 12.3 owns the six gates and this chapter asks only when each one's input is available.

It does not own the tableChapter 12.5 built the structure that answers gate 3 in four cycles, and that four-cycle answer is what makes an early commit arithmetically possible at all.

It does not own the cost of floodingChapter 12.4 priced it, and Section 9 spends that price when a corrupt frame is forwarded rather than contained.

And it does not own frame validityChapter 7.3 established what makes a frame valid and Chapter 5.8 established what the check sequence covers. This chapter takes both as given and asks only when they can be evaluated.

2. A Discipline Is a Commit Point

Write the frame out as a timeline and the two disciplines become one number: the octet at which the switch stops being able to change its mind.

Octet offsetWhat has arrivedWhat can be decided
0–5destination addressgate 3's lookup can be issued
6–11source addressChapter 12.2's learning has its evidence
12–13EtherType or length — Chapter 5.5classification complete
14cut-through commits here
14 … N−5payloadnothing new for forwarding
N−4 … N−1frame check sequencegate 1 can finally run
Nstore-and-forward commits here

The gap between the two commit points is the entire payload, and on a maximum-length frame it is 1518 − 14 = 1504 octets — 12 032 bits.

At 1 Gb/s that is 12.032 µs of frame that a cut-through switch has already committed to forwarding without having seen.

And the asymmetry in what the two disciplines give up is not symmetric at all. Store-and-forward gives up time, proportionally to frame length. Cut-through gives up one gate, permanently and unconditionally — and the gate it gives up is the one that decides whether the frame is worth forwarding.

3. RTL 1 — Tracking the Commit Point

The commit point is a position in the frame, and it is worth making it an explicit signal rather than an emergent property of the pipeline.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// cutthru_pkg -- shared types for forwarding-discipline selection.
// -----------------------------------------------------------------------
package cutthru_pkg;

  typedef enum logic [1:0] {
    FM_STORE_FORWARD = 2'd0,  // commit at the last octet
    FM_CUT_THROUGH   = 2'd1,  // commit at octet 14
    FM_FRAGMENT_FREE = 2'd2,  // commit at octet 64 -- runts excluded
    FM_ADAPTIVE      = 2'd3   // per-frame, Section 12
  } fwd_mode_e;

  // Why a frame could not be cut through. Each has a different remedy and
  // only one of them is a defect.
  typedef enum logic [2:0] {
    CR_OK             = 3'd0,
    CR_EGRESS_FASTER  = 3'd1,  // Section 7's rate rule -- would underrun
    CR_EGRESS_BUSY    = 3'd2,  // queued, so it has been stored anyway
    CR_MODE_FORCED    = 3'd3,  // configured store-and-forward
    CR_ERROR_RATE     = 3'd4,  // Section 12 fell back after errors
    CR_UNKNOWN_LENGTH = 3'd5   // Chapter 5.5's length/type ambiguity
  } cutthru_refusal_e;

  // What happened to a frame already in flight when it turned out bad.
  typedef enum logic [1:0] {
    LF_NONE     = 2'd0,
    LF_STOMPED  = 2'd1,  // FCS deliberately corrupted -- Section 11
    LF_TRUNCATED= 2'd2,  // transmission aborted mid-frame
    LF_PASSED   = 2'd3   // emitted intact -- the failure this chapter warns of
  } late_fault_e;

  localparam int COMMIT_CUT      = 14;    // through EtherType
  localparam int COMMIT_FRAGFREE = 64;    // Chapter 5.6's minimum
  localparam int FCS_OCTETS      = 4;

endpackage
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// commit_point_tracker -- says, for each octet of an arriving frame, what
// is known and whether the switch has committed.
//
// The value of making this explicit is that "committed" becomes a signal
// other modules can be checked against, rather than something implied by
// where a valid happens to assert in a pipeline.
// -----------------------------------------------------------------------
module commit_point_tracker
  import cutthru_pkg::*;
#(
  parameter int LEN_W = 14,
  parameter int CNT_W = 32
)(
  input  logic             clk,
  input  logic             rst_n,

  input  logic             frame_start,
  input  logic             octet_valid,
  input  logic             frame_end,
  input  fwd_mode_e        mode,

  output logic [LEN_W-1:0] octets_received,
  output logic             have_destination,   // >= 6
  output logic             have_source,        // >= 12
  output logic             have_ethertype,     // >= 14
  output logic             have_min_frame,     // >= 64
  output logic             have_fcs,           // frame_end

  output logic             committed,
  output logic [LEN_W-1:0] commit_octet,
  output logic [LEN_W-1:0] octets_after_commit,
  output logic [CNT_W-1:0] c_commits,
  output logic [CNT_W-1:0] c_octets_uninspected
);

  logic [LEN_W-1:0] cnt_q;
  logic             committed_q;

  assign octets_received  = cnt_q;
  assign have_destination = (cnt_q >= LEN_W'(6));
  assign have_source      = (cnt_q >= LEN_W'(12));
  assign have_ethertype   = (cnt_q >= LEN_W'(COMMIT_CUT));
  assign have_min_frame   = (cnt_q >= LEN_W'(COMMIT_FRAGFREE));
  assign have_fcs         = frame_end;

  always_comb begin
    unique case (mode)
      FM_CUT_THROUGH:   commit_octet = LEN_W'(COMMIT_CUT);
      FM_FRAGMENT_FREE: commit_octet = LEN_W'(COMMIT_FRAGFREE);
      default:          commit_octet = '1;   // the last octet
    endcase
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      cnt_q                <= '0;
      committed_q          <= 1'b0;
      c_commits            <= '0;
      c_octets_uninspected <= '0;
      octets_after_commit  <= '0;
    end else begin
      if (frame_start) begin
        cnt_q               <= '0;
        committed_q         <= 1'b0;
        octets_after_commit <= '0;
      end else if (octet_valid) begin
        cnt_q <= cnt_q + 1'b1;

        // THE COMMIT. After this edge the frame is leaving and no later
        // information can prevent it -- only Section 11's stomping can
        // mark it.
        if (!committed_q && (cnt_q + 1'b1 >= commit_octet) &&
            (mode != FM_STORE_FORWARD)) begin
          committed_q <= 1'b1;
          if (!(&c_commits)) c_commits <= c_commits + 1'b1;
        end

        // Every octet arriving after the commit is an octet the switch
        // forwarded without inspecting. On a maximum frame under cut-
        // through that is 1504 octets -- 12032 bits.
        if (committed_q) begin
          octets_after_commit  <= octets_after_commit + 1'b1;
          c_octets_uninspected <= c_octets_uninspected + 1'b1;
        end
      end

      if (frame_end && (mode == FM_STORE_FORWARD)) begin
        committed_q <= 1'b1;
        if (!(&c_commits)) c_commits <= c_commits + 1'b1;
      end
    end
  end

  assign committed = committed_q;

endmodule

Classification: synthesizable.

What it teaches: that committed is the signal every other module in this chapter is checked against. A design where the commit point is implicit — an emergent consequence of where a valid happens to assert three pipeline stages down — cannot be asserted about, because no property can name the moment after which a decision is irrevocable.

And c_octets_uninspected is the honest measure of what the discipline gave up. Under store-and-forward it is zero, always. Under cut-through on maximum-length frames it is 1504 octets per frame — and at 1.4881 Mpps of maximum frames that is 1504 × 8 × 812 k = a substantial fraction of a link's worth of content forwarded sight unseen.

Deliberately simplified: an octet counter, where a real datapath is 8 or 64 octets wide and the commit lands mid-word. Production designs commit on a word boundary at or after octet 14, which makes the true commit point implementation-dependent and is exactly why it deserves an explicit signal.

Production implication: have_min_frame exists because of a discipline this chapter has not introduced yet. Fragment-free forwarding commits at octet 64 — Chapter 5.6's minimum frame size — which excludes runts and collision fragments while still committing 1454 octets early on a maximum frame. Section 12's selector uses all three commit points, and the tracker is what makes them interchangeable.

4. RTL 2 — Which of the Six Gates Survive an Early Commit

Chapter 12.3 established six gates. Schedule each one against the commit point and the answer is not "some are harder" — it is that one of them is impossible.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// gate_schedule_checker -- for each of Chapter 12.3's six gates, is its
// input available before the commit point?
//
// This module computes nothing the datapath needs. It exists so that the
// answer is a signal rather than a claim in a design document, and so
// that a design which quietly runs a gate on unavailable data is caught.
// -----------------------------------------------------------------------
module gate_schedule_checker
  import cutthru_pkg::*;
#(
  parameter int LEN_W = 14,
  parameter int CNT_W = 32
)(
  input  logic             clk,
  input  logic             rst_n,

  input  logic             frame_active,
  input  logic [LEN_W-1:0] octets_received,
  input  logic             committed,
  input  fwd_mode_e        mode,

  // The six gates asserting that they have evaluated.
  input  logic             g1_eligibility_done,   // needs the FCS
  input  logic             g2_ingress_state_done,
  input  logic             g3_lookup_done,
  input  logic             g4_filter_done,
  input  logic             g5_egress_state_done,
  input  logic             g6_admission_done,

  output logic [5:0]       gates_available,       // input has arrived
  output logic [5:0]       gates_evaluated,
  output logic             g1_impossible,         // the chapter's thesis
  output logic [CNT_W-1:0] v_gate_ran_early,      // evaluated without input
  output logic [CNT_W-1:0] c_frames_uninspected
);

  // GATE 1 needs the frame check sequence, which is the LAST four octets.
  // Under cut-through the commit is at octet 14 and the FCS arrives at
  // octet N-4 for N up to 1518. The input is not late; it is absent.
  logic g1_input_available;
  assign g1_input_available = 1'b0;   // never, before a cut-through commit

  // GATES 2 and 5 read a port-state register. Available at octet 0.
  // GATE 3 needs the destination address -- octet 6 -- plus Chapter
  // 12.5's four-cycle lookup, which at 500 MHz is 8 ns, comfortably
  // inside the 48 ns the next 6 octets take at 1 Gb/s.
  // GATE 4 compares the lookup's answer against the ingress port.
  // GATE 6 reads queue occupancy, available at octet 0 -- but see the
  // note below on why its answer can be invalidated.
  always_comb begin
    gates_available[0] = (mode == FM_STORE_FORWARD);          // g1
    gates_available[1] = 1'b1;                                // g2
    gates_available[2] = (octets_received >= LEN_W'(6));      // g3
    gates_available[3] = (octets_received >= LEN_W'(6));      // g4
    gates_available[4] = 1'b1;                                // g5
    gates_available[5] = 1'b1;                                // g6
  end

  assign gates_evaluated = {g6_admission_done, g5_egress_state_done,
                            g4_filter_done,    g3_lookup_done,
                            g2_ingress_state_done, g1_eligibility_done};

  // THE THESIS, AS A SIGNAL. Under any early-commit mode, gate 1 cannot
  // be evaluated before the commit, because its input arrives up to
  // 12032 bits afterwards.
  assign g1_impossible = frame_active && (mode != FM_STORE_FORWARD);

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      v_gate_ran_early     <= '0;
      c_frames_uninspected <= '0;
    end else begin
      // A gate that claims to have evaluated before its input arrived is
      // evaluating on reset values, on the previous frame's data, or on
      // an X. All three produce a decision that looks valid.
      for (int g = 0; g < 6; g++)
        if (gates_evaluated[g] && !gates_available[g])
          if (!(&v_gate_ran_early)) v_gate_ran_early <= v_gate_ran_early + 1'b1;

      if (committed && g1_impossible && !$past(committed))
        if (!(&c_frames_uninspected))
          c_frames_uninspected <= c_frames_uninspected + 1'b1;
    end
  end

endmodule

Classification: synthesizable, and intended to stay in silicon as a standing check.

What it teaches: the chapter's central fact, expressed as a truth table rather than a claim.

GateInputAvailable at octetSurvives a commit at 14
1 — eligibilitythe FCSN−4, up to 1514no — never
2 — ingress port statea register0yes
3 — the lookupdestination address6yesChapter 12.5 answers in 8 ns
4 — ingress filterthe lookup's answer6 + 8 nsyes
5 — egress port statea register0yes
6 — admissionqueue occupancy0yes, but revocably

Five of six survive. One cannot be run at all. And it is worth being precise about why gate 3 comfortably survives: the destination address completes at octet 6, and Chapter 12.5 §16 established a four-cycle lookup — 8 ns at 500 MHz — while octets 6 to 14 take 8 × 8 = 64 ns to arrive at 1 Gb/s. The lookup finishes with 56 ns to spare.

And it teaches why gate 6 is marked revocably. Queue occupancy is available immediately and can change while the frame is still arriving. A cut-through frame admitted at octet 14 may find, 12 µs later, that the egress queue filled behind it — and by then the frame's head is already on the wire. Section 13 is about that case.

Deliberately simplified: g1_input_available is hard-wired to zero under early commit rather than being computed from a frame-length model. That is deliberate — the point is that no computation makes it true, and writing it as a constant says so more clearly than an expression that happens to evaluate false.

Production implication: v_gate_ran_early catches a specific and plausible bug. A pipeline that structurally expects all six gates to report will, under cut-through, receive nothing from gate 1 — and the natural repair is to tie its done high and its result to pass. That converts "this gate could not run" into "this gate ran and approved", silently, and every corrupt frame is then forwarded with a positive eligibility result recorded against it.

A frame arrives as a stream of octets. The destination address completes at octet six, the source address at octet twelve, and the EtherType at octet fourteen, which is where a cut-through switch commits the frame to an egress port. Gates two and five read port state registers and are available immediately. Gate three, the table lookup, needs only the destination address at octet six and Chapter 12.5's structure answers in four cycles, or eight nanoseconds, while octets six through fourteen take sixty-four nanoseconds to arrive at one gigabit per second, so it finishes with fifty-six nanoseconds to spare. Gate four compares the lookup's answer against the ingress port. Gate six reads queue occupancy immediately but its answer can be invalidated while the frame is still arriving. Gate one, eligibility, requires the frame check sequence, which occupies the last four octets of a frame up to 1518 octets long, arriving up to twelve thousand and thirty-two bits after the commit. That gate cannot be run under cut-through at all.Octet 0gates 2, 5, 6 readyOctet 6destination — gate 3issuesOctet 14gate 4 done — COMMITFrame is leavingno later input can stopit1504 octetsuninspected12 032 bitsFCS at octet N−4gate 1's input, at lastOnly stompingremainsmark it, cannot recall it12
Figure 1 — five of the six gates have their inputs by octet 14; the sixth needs the last four octets of the frame, and no implementation moves that.

5. RTL 3 — A Forwarding Engine That Commits Early

Put the five available gates in front of the commit and the sixth nowhere, and the engine writes itself — which is exactly the danger.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// cutthrough_forwarder -- runs the five available gates and commits at
// octet 14.
//
// Note what is NOT in this module: any evaluation of frame validity. It
// is absent because it is impossible, and the absence is marked with an
// explicit output rather than left as a gap somebody later fills with a
// tied-off constant.
// -----------------------------------------------------------------------
module cutthrough_forwarder
  import cutthru_pkg::*;
#(
  parameter int N_PORTS   = 24,
  parameter int PORT_BITS = 5,
  parameter int LEN_W     = 14,
  parameter int CNT_W     = 32
)(
  input  logic                 clk,
  input  logic                 rst_n,

  input  logic                 frame_start,
  input  logic [LEN_W-1:0]     octets_received,
  input  fwd_mode_e            mode,

  // The five gates whose inputs exist by octet 14.
  input  logic                 ingress_forwarding,   // gate 2
  input  logic                 lookup_valid,         // gate 3
  input  logic                 lookup_hit,
  input  logic [PORT_BITS-1:0] lookup_port,
  input  logic [PORT_BITS-1:0] ingress_port,         // gate 4
  input  logic [N_PORTS-1:0]   forwarding_mask,      // gate 5
  input  logic                 egress_has_room,      // gate 6

  output logic                 commit,
  output logic [N_PORTS-1:0]   egress_mask,
  output logic                 eligibility_unknown,  // ALWAYS high here
  output logic [CNT_W-1:0]     c_committed_unchecked,
  output logic [2:0]           deciding_gate
);

  logic committed_q;
  logic [N_PORTS-1:0] all_but_ingress;

  assign all_but_ingress = ~(N_PORTS'(1) << ingress_port);

  // THE ADMISSION. Chapter 12.3's composer, minus gate 1, at octet 14.
  always_comb begin
    egress_mask   = '0;
    deciding_gate = 3'd0;

    if (!ingress_forwarding) begin
      deciding_gate = 3'd2;
    end else if (!lookup_valid) begin
      // The lookup has not answered by the commit point. Chapter 12.3
      // Section 4's deadline applies here too, and it is much tighter:
      // the answer must exist by octet 14, not by 28 ns.
      deciding_gate = 3'd3;
      egress_mask   = forwarding_mask & all_but_ingress;
    end else if (lookup_hit && (lookup_port == ingress_port)) begin
      deciding_gate = 3'd4;                       // filter -- emit nothing
    end else if (lookup_hit && !forwarding_mask[lookup_port]) begin
      deciding_gate = 3'd5;
    end else if (lookup_hit && !egress_has_room) begin
      // Gate 6 at the commit point. NOTE: this answer can be invalidated
      // by the queue filling while the remaining 1504 octets arrive --
      // Section 13.
      deciding_gate = 3'd6;
    end else if (lookup_hit) begin
      egress_mask   = (N_PORTS'(1) << lookup_port);
    end else begin
      egress_mask   = forwarding_mask & all_but_ingress;
    end
  end

  // GATE 1 IS NOT HERE AND CANNOT BE. This output states that in the
  // interface rather than leaving a hole for somebody to tie off.
  assign eligibility_unknown = (mode != FM_STORE_FORWARD);

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      committed_q           <= 1'b0;
      commit                <= 1'b0;
      c_committed_unchecked <= '0;
    end else begin
      commit <= 1'b0;
      if (frame_start) committed_q <= 1'b0;

      if (!committed_q && (mode != FM_STORE_FORWARD) &&
          (octets_received >= LEN_W'(COMMIT_CUT))) begin
        committed_q <= 1'b1;
        commit      <= 1'b1;
        // Every commit under an early-commit mode is a frame forwarded
        // without its validity established. The count is not an error --
        // it is the discipline's operating volume, and it belongs on a
        // report next to the error counters it can explain.
        if (!(&c_committed_unchecked))
          c_committed_unchecked <= c_committed_unchecked + 1'b1;
      end
    end
  end

endmodule

Classification: synthesizable.

What it teaches: that eligibility_unknown is an output rather than an omission, and the distinction is the difference between a design that is honest about its discipline and one that quietly lies. A module that simply has no eligibility input looks complete, and the next engineer integrating it will connect a frame_ok signal from somewhere plausible — and whatever that signal is, it is not the FCS result, because the FCS result does not exist yet.

And it teaches that gate 3's deadline is tighter here than Chapter 12.3 stated. That chapter's budget was 28 ns, derived from the frame rate. Under cut-through the lookup must answer by octet 14 — 112 ns at 1 Gb/s, but only 11.2 ns at 10 Gb/s and 4.5 ns at 25 Gb/s. At high line rates the commit point arrives faster than the table can be searched, which is why cut-through above 10 Gb/s requires either a faster lookup or a later commit point.

Deliberately simplified: an octet-granular commit and a combinational gate chain. A production engine pipelines the gates across the octets as they arrive, which is what makes the 11.2 ns budget at 10 Gb/s achievable at all.

Production implication: c_committed_unchecked is not an error counter and should not sit among the error counters — it is the discipline's operating volume, and its value is that it explains the error counters elsewhere. A downstream switch reporting FCS errors and this switch reporting a large c_committed_unchecked on the same path is a complete diagnosis: the corrupt frames are being relayed, not generated, and Section 9 shows why the distinction is otherwise very hard to make.

6. The Latency Difference, Measured

Store-and-forward's latency floor is the whole frame's serialisation time. Cut-through's is a constant. Put both on the same table.

FrameStore-and-forward at 1 Gb/sat 10 Gb/sat 100 Mb/s
64 octets512 ns51.2 ns5.12 µs
128 octets1.024 µs102.4 ns10.24 µs
512 octets4.096 µs409.6 ns40.96 µs
1518 octets12.144 µs1.214 µs121.44 µs
9000 octets — jumbo72 µs7.2 µs720 µs

Cut-through's wait depends only on how far into the frame the commit point sits:

Commit pointat 1 Gb/sat 10 Gb/sat 100 Mb/s
destination address, 6 octets48 ns4.8 ns480 ns
through EtherType, 14 octets112 ns11.2 ns1.12 µs
fragment-free, 64 octets512 ns51.2 ns5.12 µs

The ratio, at 1 Gb/s with a 14-octet commit:

FrameStore-and-forwardCut-throughSpeed-up
64 octets512 ns112 ns4.6×
512 octets4.096 µs112 ns36.6×
1518 octets12.144 µs112 ns108.4×
9000 octets72 µs112 ns642.9×

The advantage is real, large, and largest exactly where store-and-forward is worst — long frames on slow links.

And the fragment-free row is worth reading against the 64-octet row of the first table. Committing at octet 64 costs 512 ns at 1 Gb/s, which is exactly store-and-forward's latency on a minimum-length frame — so fragment-free forwarding is store-and-forward for small frames and cut-through for large ones, without needing to decide which it is.

7. RTL 4 — The Rate Rule

Cut-through requires that the egress port drain no faster than the ingress port fills. Violate it and the transmitter runs out of frame mid-transmission, and Ethernet has no way to pause inside a frame.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// rate_compatibility_gate -- decides whether cut-through is even available
// for this ingress/egress pair.
//
// The arithmetic: with a head start of H bits, an egress at rate Re and
// an ingress at rate Ri, the egress has sent more than has been received
// after t = H / (Re - Ri). For a 1 Gb/s ingress feeding a 10 Gb/s egress
// with a 14-octet head start, t = 12.4 ns -- the transmitter runs dry
// having emitted 15.6 octets.
// -----------------------------------------------------------------------
module rate_compatibility_gate
  import cutthru_pkg::*;
#(
  parameter int N_PORTS   = 24,
  parameter int PORT_BITS = 5,
  parameter int CNT_W     = 32
)(
  input  logic                 clk,
  input  logic                 rst_n,

  // Rates in units of 100 Mb/s, so 1 G is 10 and 10 G is 100.
  input  logic [11:0]          port_rate [N_PORTS],

  input  logic                 req_valid,
  input  logic [PORT_BITS-1:0] ingress_port,
  input  logic [PORT_BITS-1:0] egress_port,
  input  fwd_mode_e            requested_mode,
  input  logic [7:0]           head_start_octets,

  output logic                 cutthru_available,
  output fwd_mode_e            granted_mode,
  output cutthru_refusal_e     refusal,
  output logic [15:0]          underrun_ns,        // 0 when compatible
  output logic [CNT_W-1:0]     c_refused_rate,
  output logic [CNT_W-1:0]     c_granted
);

  logic [11:0] ri, re;
  assign ri = port_rate[ingress_port];
  assign re = port_rate[egress_port];

  // THE RULE. Egress must be no faster than ingress. Equal is fine --
  // the transmitter consumes exactly as fast as the receiver supplies,
  // and the head start is never spent. SLOWER egress is also fine: the
  // buffer grows, which is what a queue is for.
  logic rate_ok;
  assign rate_ok = (re <= ri);

  // How long the frame survives if the rule is violated, in nanoseconds.
  // t = H_bits / (Re - Ri), with rates in 100 Mb/s units:
  //   bits/ns at 100 Mb/s = 0.1, so (re - ri) * 0.1 bits per ns.
  always_comb begin
    if (rate_ok || (re == ri)) underrun_ns = 16'd0;
    else                       underrun_ns =
      16'((16'(head_start_octets) * 16'd8 * 16'd10) / 16'(re - ri));
  end

  always_comb begin
    cutthru_available = 1'b0;
    granted_mode      = FM_STORE_FORWARD;
    refusal           = CR_OK;

    if (req_valid) begin
      if (requested_mode == FM_STORE_FORWARD) begin
        granted_mode = FM_STORE_FORWARD;
      end else if (!rate_ok) begin
        // A speed step UPWARD -- access to aggregation, the direction of
        // almost every real network -- forces store-and-forward. This is
        // not a policy choice; the alternative emits a truncated frame.
        refusal      = CR_EGRESS_FASTER;
        granted_mode = FM_STORE_FORWARD;
      end else begin
        cutthru_available = 1'b1;
        granted_mode      = requested_mode;
      end
    end
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      c_refused_rate <= '0;
      c_granted      <= '0;
    end else if (req_valid) begin
      if (cutthru_available) begin
        if (!(&c_granted)) c_granted <= c_granted + 1'b1;
      end else if (refusal == CR_EGRESS_FASTER) begin
        if (!(&c_refused_rate)) c_refused_rate <= c_refused_rate + 1'b1;
      end
    end
  end

endmodule

Classification: synthesizable.

What it teaches: that the rule is egress ≤ ingress and that equality is the interesting case. With equal rates the transmitter consumes exactly as fast as the receiver supplies, so the 14-octet head start is never spent and the frame streams indefinitely. With a slower egress the head start grows — which is simply a queue forming, and is fine. With a faster egress the head start shrinks at Re − Ri and runs out.

And the arithmetic is brutal. t = H ÷ (Re − Ri):

Ingress → egressHead start 6 oct14 oct64 oct
1 Gb/s → 10 Gb/s5.3 ns12.4 ns56.9 ns
100 Mb/s → 1 Gb/s53.3 ns124.4 ns568.9 ns
10 Gb/s → 25 Gb/s3.2 ns7.5 ns34.1 ns

A 1 Gb/s frame cut through to a 10 Gb/s port survives 12.4 nanoseconds, having emitted 15.6 octets. The rest of the frame is not there yet and never will be in time. Ethernet has no mechanism for pausing inside a frame — the transmitter must produce a continuous stream from the start delimiter to the FCS — so the result is a truncated frame on the wire, which the receiver discards as a runt or an FCS error.

Deliberately simplified: rates as integers in 100 Mb/s units and a combinational divide. A production gate uses a small lookup indexed by the two ports' rate codes, because the rates are known at link-up and change only on a renegotiation — Chapter 11.3's bring-up sequence.

Production implication: c_refused_rate will be the dominant refusal reason in almost every real deployment, and that is worth knowing before enabling the feature. Access-to-aggregation traffic is a speed step upward by definition — a 1 Gb/s host port feeding a 10 Gb/s uplink — so every frame taking that path is store-and-forward regardless of configuration. A network whose value from cut-through was assumed to be network-wide typically gets it only on the small fraction of traffic that stays within one speed tier.

8. RTL 5 — Detecting the Underrun When the Rule Is Broken

The rate gate prevents this. It is worth building the detector anyway, because the condition also arises from causes the gate does not see.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// underrun_detector -- catches a transmitter that has run out of frame.
//
// The rate gate in Section 7 prevents the configured case. This detector
// exists for the cases it cannot see: an ingress link that slows
// mid-frame (Chapter 11.4's renegotiation), a receive FIFO that stalls,
// or a rate table that disagrees with the link's actual negotiated speed.
// -----------------------------------------------------------------------
module underrun_detector
  import cutthru_pkg::*;
#(
  parameter int PORT_BITS = 5,
  parameter int LEN_W     = 14,
  parameter int CNT_W     = 32
)(
  input  logic                 clk,
  input  logic                 rst_n,

  input  logic                 tx_active,          // mid-frame on egress
  input  logic                 tx_octet_ready,     // egress wants an octet
  input  logic                 rx_octet_available, // ingress has supplied one
  input  logic                 frame_complete,     // whole frame received
  input  logic [PORT_BITS-1:0] egress_port,
  input  logic [LEN_W-1:0]     octets_sent,

  output logic                 underrun,
  output logic                 abort_transmission,
  output late_fault_e          fault,
  output logic [CNT_W-1:0]     c_underruns,
  output logic [LEN_W-1:0]     shortest_underrun_at,
  output logic [PORT_BITS-1:0] last_underrun_port
);

  // THE CONDITION. The egress wants an octet, the ingress has not
  // supplied one, and the frame is not finished. There is no way to
  // pause: Ethernet's transmitter must emit a continuous stream from the
  // start delimiter to the FCS.
  assign underrun = tx_active && tx_octet_ready &&
                    !rx_octet_available && !frame_complete;

  // Abort rather than emit filler. Emitting idle or padding produces a
  // frame that is the WRONG LENGTH with a VALID-looking body, which a
  // receiver may accept. Aborting produces something a receiver will
  // certainly reject.
  assign abort_transmission = underrun;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      fault                <= LF_NONE;
      c_underruns          <= '0;
      shortest_underrun_at <= '1;
      last_underrun_port   <= '0;
    end else begin
      fault <= LF_NONE;
      if (underrun) begin
        fault <= LF_TRUNCATED;
        if (!(&c_underruns)) c_underruns <= c_underruns + 1'b1;
        // How far in the frame died. Section 7 predicts 15.6 octets for
        // a 1 G to 10 G step with a 14-octet head start; a measurement
        // far from the prediction means the rate table is wrong.
        if (octets_sent < shortest_underrun_at)
          shortest_underrun_at <= octets_sent;
        last_underrun_port <= egress_port;
      end
    end
  end

endmodule

Classification: synthesizable.

What it teaches: that aborting is better than filling, and the reasoning is the same one Section 11 will apply to stomping. A transmitter that runs dry could emit idle octets or padding to keep the stream continuous — and the result is a frame of the wrong length whose body looks structurally valid. Chapter 7.3's checks may or may not catch it depending on where the filler landed. Aborting produces something the receiver will certainly reject, which is the correct outcome for a frame that cannot be completed.

And shortest_underrun_at is a cross-check on the configuration rather than on the datapath. Section 7 predicts 15.6 octets for a 1 Gb/s ingress feeding a 10 Gb/s egress with a 14-octet head start. A measured underrun at 15 or 16 octets confirms the rate table matches the links. An underrun at 400 octets means the rates are not what the table says — a link that renegotiated down without the table being updated, which Chapter 11.4 showed produces no error anywhere.

Deliberately simplified: an octet-granular ready/available handshake. A real datapath runs a word-wide FIFO with a programmable low-water mark, and the design question becomes how many octets of head start to accumulate before starting the transmitter — which is precisely the H in Section 7's t = H ÷ (Re − Ri).

Production implication: c_underruns should be exactly zero, always, because Section 7's gate prevents the configured case entirely. A non-zero value means the rate table disagrees with reality, and the frames it produced are truncated frames on the wire that the downstream device will report as FCS errors on its receive port. The error appears one hop away from its cause, which is Section 9's general problem in miniature.

9. Where the Error Appears Is Not Where It Was Made

A corrupt frame forwarded by a cut-through switch is discarded by the destination. The error counter that increments is at the destination, and nothing anywhere points back at the link that corrupted it.

Trace a single bit error through a chain of cut-through switches:

HopWhat happensWhat increments
link A→S1 corrupts a bitnothing yet — S1 has not seen the FCS
S1 commits at octet 14, forwards1518 octets sent onwardS1's c_committed_unchecked
S1 receives the bad FCStoo late — the frame has leftS1's ingress FCS error counter
S2 commits, forwardsanother 1518 octets spentS2's c_committed_unchecked
S3, S4 …the same, per hop
destination receives itdiscards itthe destination's FCS error counter

The bandwidth cost is linear in the hop count:

Cut-through hopsLink capacity spent on one corrupt 1518-octet frame
1 — store-and-forward at the first hop12 144 bits
336 432 bits
560 720 bits
897 152 bits

Store-and-forward at the first hop contains the frame there. The bad link's own switch discards it, its ingress FCS counter increments, and the counter is on the port attached to the faulty cable.

Under cut-through the frame travels the whole path and is discarded at the end — and the destination's error counter says only a frame arrived corrupt, with no indication of where it was corrupted. On a network of cut-through switches, every host downstream of one bad cable reports FCS errors, and none of them is near the cable.

A cable corrupts one bit of a frame entering the first switch. Under store-and-forward that switch buffers the whole frame, checks the frame check sequence, discards the frame, and increments an ingress error counter on the port attached to the faulty cable, so the counter is itself the location of the fault. Under cut-through the same switch commits at octet fourteen and has forwarded the frame before the check sequence arrives, so the frame crosses every intermediate switch, spending twelve thousand one hundred and forty-four bits of link capacity at each hop, and is finally discarded by the destination, whose error counter reports only that a corrupt frame arrived and gives no indication where it was corrupted. One failing cable therefore produces errors on many hosts across a building, none of them adjacent to it. Stomping the outgoing check sequence bounds the propagation at one extra hop, because the next switch sees a deliberately invalid frame and discards it.Cable corrupts a bitthe actual faultStore-and-forward S1checks, discardsCounter on the badportthe counter IS thelocationCut-through S1committed at octet 14S2, S3, S4 …12 144 bits per hopDestination discardsreports a symptom, not aplaceStomping bounds itcontained at hop 212
Figure 3 — a corrupt frame under store-and-forward stops at the faulty link's own switch; under cut-through it crosses the whole path and is discarded where nobody can act on it.

10. Why the Latency Benefit Cancels Under Load

Cut-through's saving is available only when the egress port is idle at the moment the commit point arrives. Chapter 12.1 §10 asserted this; here is the arithmetic.

End-to-end switch latency is serialisation + lookup + queueing, and cut-through attacks only the first term.

LoadSerialisationLookupQueueingCut-through saves
idle egress12.144 µs8 ns012.03 µs — 99%
50% utilisation12.144 µs8 ns~12 µs12.03 µs of 24 µs — 50%
90% utilisation12.144 µs8 ns~109 µs12.03 µs of 121 µs — 10%
congested12.144 µs8 nsunbounded→ 0%

And the deeper point is not that the saving shrinks proportionally — it is that it disappears entirely.

A frame whose egress port is busy must be queued. A queued frame has been stored. The switch has performed store-and-forward whether or not it was configured to, and the commit point is irrelevant because the frame is not going anywhere until the port is free.

So the benefit is not merely diluted under load. The mechanism does not engage.

Egress state at the commit pointWhat happensCut-through engaged
idleframe streams straight throughyes — full benefit
transmitting another frameframe is queuedno — it has been stored
queue non-emptyframe joins the queueno

Which produces the shape that makes cut-through a niche rather than a default: the benefit is inversely proportional to the load, and latency is a problem only under load.

At low utilisation, where the egress is usually idle, cut-through saves 12 µs on an end-to-end path that was already fast. At high utilisation, where a millisecond of queueing delay is the actual complaint, the egress is usually busy and the mechanism does not fire.

The networks where it earns its correctness cost are therefore the ones that engineer the third term to zero — storage fabrics and trading networks run deliberately underloaded so the queue is empty by construction. Everywhere else the load profile cancels the benefit and keeps the cost, which is Section 9's relocated evidence and Section 4's missing gate.

11. RTL 6 — FCS Stomping: The Constructive Answer

The frame has left and cannot be recalled. What is still available is to make certain nobody downstream mistakes it for a good one — and to make certain the next switch does not relay it too.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// fcs_stomper -- deliberately corrupts the outgoing check sequence when
// the incoming one turns out to be bad.
//
// THE CONSTRUCTIVE ANSWER TO SECTION 4. Gate 1 cannot run before the
// commit. What CAN be done is to ensure the frame that was committed is
// unambiguously marked as bad by the time it finishes leaving -- so that
// every downstream device discards it, and so that no downstream
// cut-through switch relays it further.
// -----------------------------------------------------------------------
module fcs_stomper
  import cutthru_pkg::*;
#(
  parameter int CNT_W = 32
)(
  input  logic             clk,
  input  logic             rst_n,

  input  logic             tx_active,
  input  logic             tx_in_fcs,          // emitting the last 4 octets
  input  logic             rx_fcs_bad,         // ingress check failed
  input  logic             rx_frame_end,
  input  logic             committed,

  input  logic [31:0]      computed_fcs,       // over what was forwarded

  output logic [31:0]      tx_fcs,
  output logic             stomped,
  output late_fault_e      fault,
  output logic [CNT_W-1:0] c_stomped,
  output logic [CNT_W-1:0] c_bad_after_commit,
  output logic [CNT_W-1:0] c_bad_before_commit
);

  logic stomp_q;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      stomp_q             <= 1'b0;
      stomped             <= 1'b0;
      fault               <= LF_NONE;
      c_stomped           <= '0;
      c_bad_after_commit  <= '0;
      c_bad_before_commit <= '0;
    end else begin
      stomped <= 1'b0;
      fault   <= LF_NONE;

      if (rx_frame_end && rx_fcs_bad) begin
        if (committed) begin
          // THE CASE THIS MODULE EXISTS FOR. The frame is already
          // leaving. Nothing can stop it; the outgoing FCS can be made
          // wrong on purpose.
          stomp_q <= 1'b1;
          if (!(&c_bad_after_commit))
            c_bad_after_commit <= c_bad_after_commit + 1'b1;
        end else begin
          // Not committed yet -- a store-and-forward path, or a frame
          // shorter than the commit point. Ordinary gate 1 rejection.
          if (!(&c_bad_before_commit))
            c_bad_before_commit <= c_bad_before_commit + 1'b1;
        end
      end

      if (stomp_q && tx_in_fcs) begin
        stomped   <= 1'b1;
        fault     <= LF_STOMPED;
        stomp_q   <= 1'b0;
        if (!(&c_stomped)) c_stomped <= c_stomped + 1'b1;
      end
    end
  end

  // INVERT the computed value rather than emitting a constant. A constant
  // could, on some frame, coincidentally BE the correct FCS -- and then
  // the corrupt frame is forwarded with a valid check sequence, which is
  // strictly the worst outcome available.
  assign tx_fcs = stomp_q ? ~computed_fcs : computed_fcs;

endmodule

Classification: synthesizable.

What it teaches: that the stomp inverts the computed value rather than writing a constant, and the reason is a genuine hazard rather than fastidiousness. A fixed "bad FCS" constant will, on some frame, happen to equal that frame's correct check sequence — one frame in 2³², which at 1.4881 Mpps is once every 48 minutes on a fully loaded gigabit link. On that frame the corrupt payload is forwarded with a valid FCS, and every device downstream accepts it. Inverting the computed value cannot coincide, because a value and its complement differ in all 32 bits.

And it teaches why stomping is the right answer rather than a consolation prize. The frame is already gone; the choice is only between a corrupt frame that looks valid and a corrupt frame that is unmistakably marked. The marked version is discarded at the very next hop — including by a downstream cut-through switch, whose own gate 1 sees the stomped FCS and stomps in turn — so Section 9's linear bandwidth cost is bounded at one extra hop instead of the whole path.

Deliberately simplified: a single stomp flag with no per-frame identity. A pipelined design carries the stomp decision with the frame's descriptor, since the ingress FCS result for frame n may arrive while frame n+1 is already being transmitted.

Production implication: c_bad_after_commit against c_bad_before_commit is the diagnostic pair. Both count frames that failed their check sequence; the split says whether the switch was able to contain them. A path where c_bad_after_commit dominates is relaying corruption — Section 9's condition — and the remedy is to force store-and-forward on that ingress port, which contains the damage at the cost of that port's latency and nothing else.

A cut-through switch commits a frame at octet fourteen and transmits fifteen hundred and four further octets without inspecting them. When the frame check sequence finally arrives at the ingress and fails, the frame cannot be recalled because its head reached the destination microseconds earlier. Four octets remain to be transmitted, and they are the outgoing check sequence. Emitting the correct value delivers a corrupt payload wearing a valid integrity check, which every device downstream accepts as data. Emitting the inverse of the computed value marks the frame unmistakably, so the next hop discards it and, if that hop is also a cut-through switch, stomps in turn, bounding the propagation. A fixed constant must not be used because it coincides with some frame's correct check sequence once in four billion frames, which at line rate is once every forty-eight minutes.Committed at octet14irrevocable1504 octets gonehead already deliveredIngress FCS fails4 octets left to sendEmit ~computed_fcscannot coincideEmit correct FCScorruption wearing avalid checkEmit a constantcoincides once per 48 minNext hop discardsand re-stomps ifcut-through12
Figure 4 — the commit is irrevocable, so the last four octets are the only remaining channel: mark the frame, or deliver corruption wearing a valid check sequence.

12. RTL 7 — Choosing the Discipline Per Frame

The discipline does not have to be a configuration. Every input the choice depends on is available at the commit point, so it can be made per frame.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// latency_mode_selector -- picks store-and-forward, fragment-free or
// cut-through for THIS frame.
//
// Every input is available by octet 14, which is what makes a per-frame
// choice possible rather than a per-port configuration. The selector is
// also where an error-rate fallback lives: a port that has recently
// delivered corrupt frames should not have its frames relayed onward.
// -----------------------------------------------------------------------
module latency_mode_selector
  import cutthru_pkg::*;
#(
  parameter int N_PORTS      = 24,
  parameter int PORT_BITS    = 5,
  parameter int ERR_WINDOW   = 1_000_000,
  parameter int ERR_THRESH   = 10,          // bad frames per window
  parameter int CNT_W        = 32
)(
  input  logic                 clk,
  input  logic                 rst_n,

  input  logic                 sel_valid,
  input  logic [PORT_BITS-1:0] ingress_port,
  input  logic                 rate_ok,          // Section 7's gate
  input  logic                 egress_idle,      // Section 10
  input  fwd_mode_e            configured_mode,
  input  logic                 length_ambiguous, // Chapter 5.5

  input  logic                 rx_frame_done,
  input  logic                 rx_frame_bad,

  output fwd_mode_e            selected_mode,
  output cutthru_refusal_e     refusal,
  output logic                 fallback_active [N_PORTS],
  output logic [CNT_W-1:0]     c_sf,
  output logic [CNT_W-1:0]     c_ff,
  output logic [CNT_W-1:0]     c_ct,
  output logic [PORT_BITS-1:0] worst_error_port
);

  logic [CNT_W-1:0] err_win [N_PORTS];
  logic [CNT_W-1:0] frames_win [N_PORTS];

  always_comb begin
    selected_mode = FM_STORE_FORWARD;
    refusal       = CR_OK;

    if (sel_valid) begin
      if (configured_mode == FM_STORE_FORWARD) begin
        refusal = CR_MODE_FORCED;
      end else if (!rate_ok) begin
        // Section 7. Not a policy -- the alternative truncates the frame.
        refusal = CR_EGRESS_FASTER;
      end else if (fallback_active[ingress_port]) begin
        // This port has recently delivered corrupt frames. Relaying them
        // spends Section 9's bandwidth on every downstream hop, so the
        // switch contains them at the cost of this port's latency.
        refusal = CR_ERROR_RATE;
      end else if (length_ambiguous) begin
        // Chapter 5.5's length/type ambiguity: a value at octets 12-13
        // below 1536 is a LENGTH, and a frame whose length is not yet
        // decidable cannot have its end predicted.
        refusal = CR_UNKNOWN_LENGTH;
      end else if (!egress_idle) begin
        // Section 10. The frame will be queued, so it has been stored
        // anyway -- and saying so keeps the counters honest about how
        // often the mechanism actually engages.
        refusal       = CR_EGRESS_BUSY;
        selected_mode = FM_STORE_FORWARD;
      end else begin
        selected_mode = configured_mode;
      end
    end
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      for (int p = 0; p < N_PORTS; p++) begin
        err_win[p] <= '0; frames_win[p] <= '0; fallback_active[p] <= 1'b0;
      end
      c_sf <= '0; c_ff <= '0; c_ct <= '0;
      worst_error_port <= '0;
    end else begin
      if (sel_valid) begin
        unique case (selected_mode)
          FM_STORE_FORWARD: c_sf <= c_sf + 1'b1;
          FM_FRAGMENT_FREE: c_ff <= c_ff + 1'b1;
          FM_CUT_THROUGH:   c_ct <= c_ct + 1'b1;
          default: ;
        endcase
      end

      if (rx_frame_done) begin
        frames_win[ingress_port] <= frames_win[ingress_port] + 1'b1;
        if (rx_frame_bad) err_win[ingress_port] <= err_win[ingress_port] + 1'b1;

        if (frames_win[ingress_port] >= CNT_W'(ERR_WINDOW)) begin
          // Latch the fallback and keep it until the window is clean.
          // Oscillating between disciplines on a marginal link produces
          // a latency that varies by two orders of magnitude frame to
          // frame, which is worse for a real-time application than
          // uniformly slow.
          fallback_active[ingress_port] <=
            (err_win[ingress_port] > CNT_W'(ERR_THRESH));
          if (err_win[ingress_port] > CNT_W'(ERR_THRESH))
            worst_error_port <= ingress_port;
          err_win[ingress_port]    <= '0;
          frames_win[ingress_port] <= '0;
        end
      end
    end
  end

endmodule

Classification: synthesizable.

What it teaches: that the CR_EGRESS_BUSY branch keeps the counters honest. A design that "selects cut-through" and then queues the frame anyway has performed store-and-forward while reporting cut-through, and c_ct becomes a count of intentions rather than events. Reporting the refusal makes c_ct a count of frames that actually streamed through, which is the number Section 10's argument needs and the one an operator would use to decide whether the feature is doing anything.

And it teaches the error-rate fallback, which is the direct answer to Section 9. A port delivering corrupt frames is a port whose frames should be contained rather than relayed, because relaying them spends k × 12 144 bits across k downstream hops and scatters the evidence. Falling back to store-and-forward on that one port costs that port's latency and nothing else — and it puts the FCS error counter back next to the failing cable.

Deliberately simplified: a per-port error window with a latching fallback. Production designs add hysteresis and a minimum dwell time, because a link that is marginal rather than broken will otherwise toggle the discipline continuously.

Production implication: the latch matters more than the threshold. A selector that re-evaluates every frame produces a latency that alternates between 112 ns and 12.144 µs unpredictably — a 108× variation, frame to frame, on the same path. For the real-time applications that are the reason cut-through was enabled, a consistently slow path is far better than an unpredictable one, because jitter is what their buffers are sized against, not mean latency.

13. When the Admission Decision Is Revoked

Gate 6 was marked revocably in Section 4, and it is the one gate whose input exists early and whose answer can become wrong afterwards.

A cut-through frame is admitted at octet 14 because the egress queue had room. On a 1518-octet frame at 1 Gb/s the remaining 1504 octets take 12.032 µs to arrive — and in that time the queue can fill from other ports.

MomentQueue stateWhat the switch can still do
octet 14 — commitroomadmit, begin transmitting
octet 500queue fillingnothing — the head is on the wire
octet 1200fullnothing
octet 1514fullnothing

And this is where cut-through's failure differs qualitatively from store-and-forward's.

A store-and-forward switch that finds its egress queue full discards the whole frameChapter 12.1 §9's tail drop, a complete frame, cleanly gone, counted.

A cut-through switch has already emitted the first 500 octets. It cannot discard what has left. The only options are to truncate — abort the transmission, producing a runt the receiver discards — or to have reserved the buffer at admission time.

So a cut-through egress must reserve worst-case buffer at the commit point, which means reserving MAX_FRAME octets for a frame whose length is not yet known. On a 24-port switch with jumbo frames enabled that is 24 × 9000 = 216 KB of reservation for frames that will mostly be 64 octets — and the reservation is what a store-and-forward design does not need, because it knows the length before it commits.

14. The Three Commit Points, Compared

Store-and-forward, fragment-free and cut-through are the same mechanism with three different values of one parameter.

store-and-forwardfragment-freecut-through
commits at octetN6414
latency, 64-octet frame at 1 Gb/s512 ns512 ns112 ns
latency, 1518-octet frame at 1 Gb/s12.144 µs512 ns112 ns
octets forwarded uninspected, max frame014541504
gate 1 — eligibilityrunscannot runcannot run
rejects runts — Chapter 7.3yesyesno
rejects collision fragmentsyesyesno
buffer reserved per in-flight frameactual lengthmaxmax
requires egress ingress ratenoyesyes

Fragment-free is the row worth studying, because it is nearly free.

Committing at octet 64 — Chapter 5.6's minimum frame size — costs 512 ns at 1 Gb/s, which is exactly store-and-forward's latency on a minimum-length frame. So on small frames fragment-free is no slower than store-and-forward, and on a 1518-octet frame it is 23.7× faster.

And it buys back one of the two things cut-through gave up. A frame shorter than 64 octets is a runt or a collision fragment, and fragment-free has seen the whole thing before committing, so it rejects them. It still cannot run gate 1 — the FCS is still at the end — but it no longer relays fragments from a legacy segment or a failing transceiver.

What it does not buy back is the FCS check, and that is the irreducible part. Any commit point before the last octet gives up gate 1, and the only commit point that does not give it up is the last one, which is store-and-forward by definition.

The three forwarding disciplines differ only in where the switch commits the frame to an egress port. Cut-through commits at octet fourteen, after the destination address, source address and EtherType, giving a constant latency of 112 nanoseconds at one gigabit per second but forwarding up to 1504 octets uninspected and rejecting neither runts nor collision fragments nor corrupt frames. Fragment-free commits at octet sixty-four, the minimum frame size, costing 512 nanoseconds which is exactly store-and-forward's latency on a minimum length frame, and it rejects runts and fragments while still being unable to check the frame check sequence. Store-and-forward commits at the last octet, so its latency equals the whole frame's serialisation time, up to 12.144 microseconds for a maximum frame at one gigabit, and it is the only discipline that can run the eligibility gate because that gate needs the last four octets.Commit at 14cut-through — 112 nsCommit at 64fragment-free — 512 nsCommit at Nstore-and-forward —12.144 µsOnly here can gate 1runthe FCS is the last 4octetsRelays runts andfragmentsnothing was fully seenRejects runts64 octets were seenContains corruptframesat this hop12
Figure 2 — three disciplines, one parameter: the commit point, which fixes both the latency and which gates can be run.

15. RTL 8 — Conformance for an Irrevocable Decision

The monitor's difficulty here is that the thing it must check happens after the thing it constrains, which is the same structure as Section 16's rejected property — and the resolution is to check the response rather than the decision.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// cutthrough_conformance_monitor -- checks a discipline whose defining
// property is that it decides before it knows.
//
// What it CAN check: that the commit happened at the configured octet;
// that gate 1 was never claimed to have run; that a frame found bad after
// commit was STOMPED; that the rate rule was honoured; that no frame was
// silently truncated.
//
// What it CANNOT check: that the committed frame was worth forwarding.
// That is Section 16's rejected property, and its evidence arrives 12032
// bits after the decision.
// -----------------------------------------------------------------------
module cutthrough_conformance_monitor
  import cutthru_pkg::*;
#(
  parameter int LEN_W = 14,
  parameter int CNT_W = 32
)(
  input  logic             clk,
  input  logic             rst_n,

  input  logic             frame_start,
  input  logic             committed,
  input  logic [LEN_W-1:0] commit_octet_actual,
  input  logic [LEN_W-1:0] commit_octet_expected,
  input  fwd_mode_e        mode,

  input  logic             g1_claimed_done,
  input  logic             rate_ok,

  input  logic             rx_frame_end,
  input  logic             rx_fcs_bad,
  input  logic             tx_frame_end,
  input  late_fault_e      fault,

  output logic [CNT_W-1:0] v_commit_wrong_octet,
  output logic [CNT_W-1:0] v_gate1_claimed,      // claimed the impossible
  output logic [CNT_W-1:0] v_bad_passed_intact,  // THE critical failure
  output logic [CNT_W-1:0] v_rate_violated,
  output logic [CNT_W-1:0] v_silent_truncation,
  output logic             conformant
);

  logic committed_q, bad_pending_q;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      committed_q          <= 1'b0;
      bad_pending_q        <= 1'b0;
      v_commit_wrong_octet <= '0;
      v_gate1_claimed      <= '0;
      v_bad_passed_intact  <= '0;
      v_rate_violated      <= '0;
      v_silent_truncation  <= '0;
    end else begin
      if (frame_start) begin
        committed_q   <= 1'b0;
        bad_pending_q <= 1'b0;
      end

      if (committed && !committed_q) begin
        committed_q <= 1'b1;

        // The commit landed where the configured discipline says it
        // should. A datapath wider than one octet will commit on a word
        // boundary, and this catches a design whose real commit point
        // drifted from its documented one.
        if (commit_octet_actual != commit_octet_expected)
          if (!(&v_commit_wrong_octet))
            v_commit_wrong_octet <= v_commit_wrong_octet + 1'b1;

        // THE STRUCTURAL CHECK. Under any early-commit mode, a gate 1
        // that claims to have completed is claiming to have read the FCS
        // before it arrived -- which means it read reset values, the
        // previous frame's result, or a tie-off.
        if ((mode != FM_STORE_FORWARD) && g1_claimed_done)
          if (!(&v_gate1_claimed))
            v_gate1_claimed <= v_gate1_claimed + 1'b1;

        if (!rate_ok && (mode != FM_STORE_FORWARD))
          if (!(&v_rate_violated))
            v_rate_violated <= v_rate_violated + 1'b1;
      end

      if (rx_frame_end && rx_fcs_bad && committed_q) bad_pending_q <= 1'b1;

      if (tx_frame_end) begin
        // A frame that was known bad and left with an intact check
        // sequence is the failure this whole chapter is organised
        // around: a corrupt frame wearing a valid FCS, which every
        // device downstream will accept.
        if (bad_pending_q && (fault != LF_STOMPED) && (fault != LF_TRUNCATED))
          if (!(&v_bad_passed_intact))
            v_bad_passed_intact <= v_bad_passed_intact + 1'b1;

        // A truncation that was not reported. Chapter 12.4's missing
        // copy, in a different guise: the frame simply stopped, and
        // nothing said so.
        if ((fault == LF_TRUNCATED) && !bad_pending_q && rate_ok)
          if (!(&v_silent_truncation))
            v_silent_truncation <= v_silent_truncation + 1'b1;

        bad_pending_q <= 1'b0;
      end
    end
  end

  assign conformant = (v_commit_wrong_octet == '0) &&
                      (v_gate1_claimed      == '0) &&
                      (v_bad_passed_intact  == '0) &&
                      (v_rate_violated      == '0) &&
                      (v_silent_truncation  == '0);

endmodule

Classification: synthesizable, and intended to stay in silicon.

What it teaches: the resolution to the chapter's central difficulty. The monitor cannot check that a committed frame was worth forwarding — the evidence arrives after the decision, which is Section 16's rejected property. What it can check is the response to the evidence when it finally arrives: was the frame that turned out bad stomped, truncated, or passed intact?

v_bad_passed_intact is the failure the whole chapter is organised around. A frame whose ingress FCS check failed, emitted with a valid outgoing check sequence, is a corrupt frame that every device downstream will accept — and it will be accepted as data, by an application, silently. That is strictly worse than any latency, and it is what Section 11's stomper exists to prevent.

And v_gate1_claimed catches Section 4's tie-off directly. A pipeline that structurally expects six gate results and receives five will have its sixth input tied to pass, and this counter fires on the first frame.

Deliberately simplified: one frame tracked at a time, with bad_pending_q assuming the ingress FCS result arrives before the egress frame ends. At high line rates with deep egress queues that is not guaranteed, and a production monitor tags the stomp decision to a frame identifier.

Production implication: conformant here means the discipline behaved as specified: it committed where it said, it never claimed a gate it could not run, it honoured the rate rule, and every frame that turned out bad was marked. It does not mean the forwarded frames were good — Chapter 12.4 §17 established that claims about a frame's downstream fate are not assertable, and this chapter adds a second reason: for a cut-through switch, the claim is not even evaluable at the moment it would need to be made.

16. Properties Worth Asserting, and One Worth Refusing

Every property here is about the commit, the response to late evidence, or the rate rule. None is about whether the committed frame was good, and Section 16's rejected class is why.

The commit point

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P1. The commit lands at the octet the configured discipline names.
property p_commit_at_configured_octet;
  @(posedge clk) disable iff (!rst_n)
  $rose(committed) |-> (octets_received >= commit_octet);
endproperty
a_commit_octet: assert property (p_commit_at_configured_octet);

// P2. Store-and-forward commits ONLY at the last octet.
property p_sf_commits_at_end;
  @(posedge clk) disable iff (!rst_n)
  ((mode == FM_STORE_FORWARD) && $rose(committed)) |-> frame_end;
endproperty
a_sf_late_commit: assert property (p_sf_commits_at_end);

// P3. The commit happens at most once per frame.
property p_commit_once;
  @(posedge clk) disable iff (!rst_n)
  $rose(committed) |=> committed until_with frame_start;
endproperty
a_commit_once: assert property (p_commit_once);

// P4. Every octet after the commit is counted as uninspected -- the
// honest measure of what the discipline gave up.
property p_uninspected_counted;
  @(posedge clk) disable iff (!rst_n)
  (committed && octet_valid) |=> (octets_after_commit > $past(octets_after_commit));
endproperty
a_uninspected: assert property (p_uninspected_counted);

// P5. Fragment-free commits no earlier than the minimum frame size, so a
// runt is always fully received before any decision is irrevocable.
property p_fragfree_after_min_frame;
  @(posedge clk) disable iff (!rst_n)
  ((mode == FM_FRAGMENT_FREE) && $rose(committed))
    |-> (octets_received >= LEN_W'(COMMIT_FRAGFREE));
endproperty
a_fragfree_min: assert property (p_fragfree_after_min_frame);

The gates that can and cannot run

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P6. THE STRUCTURAL PROPERTY. Under any early-commit mode, gate 1 never
// claims to have completed. Its input does not exist yet.
property p_gate1_never_claimed_early;
  @(posedge clk) disable iff (!rst_n)
  ((mode != FM_STORE_FORWARD) && committed) |-> !g1_eligibility_done;
endproperty
a_gate1_impossible: assert property (p_gate1_never_claimed_early);

// P7. eligibility_unknown is asserted whenever the discipline cannot run
// gate 1 -- the interface states the gap rather than leaving one.
property p_eligibility_unknown_declared;
  @(posedge clk) disable iff (!rst_n)
  (mode != FM_STORE_FORWARD) |-> eligibility_unknown;
endproperty
a_unknown_declared: assert property (p_eligibility_unknown_declared);

// P8. No gate evaluates before its input has arrived.
property p_no_gate_runs_early;
  @(posedge clk) disable iff (!rst_n)
  frame_active |-> ((gates_evaluated & ~gates_available) == '0);
endproperty
a_no_early_gate: assert property (p_no_gate_runs_early);

// P9. Gate 3's answer exists by the commit point. Under cut-through at
// 10 Gb/s that is 11.2 ns, which is tighter than Chapter 12.3's 28 ns.
property p_lookup_answers_before_commit;
  @(posedge clk) disable iff (!rst_n)
  ($rose(committed) && (mode != FM_STORE_FORWARD)) |-> lookup_valid;
endproperty
a_lookup_in_time: assert property (p_lookup_answers_before_commit);

// P10. Gates 2, 4 and 5 are evaluated before the commit -- they are
// available and skipping them would be a choice, not a necessity.
property p_available_gates_all_run;
  @(posedge clk) disable iff (!rst_n)
  $rose(committed) |-> (g2_ingress_state_done && g4_filter_done &&
                        g5_egress_state_done);
endproperty
a_available_gates_run: assert property (p_available_gates_all_run);

The rate rule

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P11. Cut-through is granted only when the egress is no faster than the
// ingress. Violating this truncates the frame; it is not a policy.
property p_rate_rule_enforced;
  @(posedge clk) disable iff (!rst_n)
  (req_valid && cutthru_available) |-> (port_rate[egress_port] <=
                                        port_rate[ingress_port]);
endproperty
a_rate_rule: assert property (p_rate_rule_enforced);

// P12. Equal rates are permitted -- the head start is never spent.
property p_equal_rates_allowed;
  @(posedge clk) disable iff (!rst_n)
  (req_valid && (requested_mode == FM_CUT_THROUGH) &&
   (port_rate[egress_port] == port_rate[ingress_port]))
    |-> cutthru_available;
endproperty
a_equal_ok: assert property (p_equal_rates_allowed);

// P13. A rate refusal names its reason, so it is not confused with a
// configuration or an error-rate fallback.
property p_rate_refusal_classified;
  @(posedge clk) disable iff (!rst_n)
  (req_valid && !cutthru_available &&
   (port_rate[egress_port] > port_rate[ingress_port]))
    |-> (refusal == CR_EGRESS_FASTER);
endproperty
a_refusal_named: assert property (p_rate_refusal_classified);

// P14. An underrun ABORTS rather than emitting filler. Filler produces a
// wrong-length frame with a structurally valid body.
property p_underrun_aborts;
  @(posedge clk) disable iff (!rst_n)
  underrun |-> abort_transmission;
endproperty
a_underrun_aborts: assert property (p_underrun_aborts);

// P15. Under the rate rule, no underrun occurs at all.
property p_no_underrun_when_rate_ok;
  @(posedge clk) disable iff (!rst_n)
  (tx_active && rate_ok) |-> !underrun;
endproperty
a_no_underrun: assert property (p_no_underrun_when_rate_ok);

The response to late evidence

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P16. THE CENTRAL PROPERTY. A frame found bad AFTER the commit leaves
// with a deliberately wrong check sequence.
property p_bad_after_commit_is_stomped;
  @(posedge clk) disable iff (!rst_n)
  (rx_frame_end && rx_fcs_bad && committed) |-> ##[1:$] (stomped or abort_transmission);
endproperty
a_stomp_or_abort: assert property (p_bad_after_commit_is_stomped);

// P17. A frame known bad NEVER leaves with a valid check sequence. This
// is the failure the whole chapter is organised around.
property p_bad_never_passes_intact;
  @(posedge clk) disable iff (!rst_n)
  (tx_frame_end && bad_pending_q) |-> (fault inside {LF_STOMPED, LF_TRUNCATED});
endproperty
a_never_pass_bad: assert property (p_bad_never_passes_intact);

// P18. The stomp INVERTS the computed value. A constant could, once in
// 2^32 frames, coincide with the correct FCS -- once every 48 minutes on
// a loaded gigabit link.
property p_stomp_inverts;
  @(posedge clk) disable iff (!rst_n)
  stomped |-> (tx_fcs == ~computed_fcs);
endproperty
a_stomp_inverts: assert property (p_stomp_inverts);

// P19. A good frame is never stomped.
property p_good_never_stomped;
  @(posedge clk) disable iff (!rst_n)
  (tx_in_fcs && !rx_fcs_bad) |-> !stomped;
endproperty
a_good_intact: assert property (p_good_never_stomped);

// P20. Bad frames are counted by WHETHER they were containable, because
// the split is the diagnosis.
property p_bad_split_by_commit;
  @(posedge clk) disable iff (!rst_n)
  (rx_frame_end && rx_fcs_bad)
    |=> ($changed(c_bad_after_commit) ^ $changed(c_bad_before_commit));
endproperty
a_bad_split: assert property (p_bad_split_by_commit);

// P21. A truncation is never silent.
property p_truncation_reported;
  @(posedge clk) disable iff (!rst_n)
  abort_transmission |-> ##[1:4] (fault == LF_TRUNCATED);
endproperty
a_truncation_reported: assert property (p_truncation_reported);

Discipline selection

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P22. A frame whose egress is busy is reported as CR_EGRESS_BUSY, so
// c_ct counts frames that actually streamed rather than intentions.
property p_busy_egress_reported;
  @(posedge clk) disable iff (!rst_n)
  (sel_valid && !egress_idle && (configured_mode != FM_STORE_FORWARD))
    |-> (refusal == CR_EGRESS_BUSY);
endproperty
a_busy_reported: assert property (p_busy_egress_reported);

// P23. A port with a recent error history falls back to store-and-forward
// -- containing corruption at the cost of one port's latency.
property p_error_fallback_engages;
  @(posedge clk) disable iff (!rst_n)
  (sel_valid && fallback_active[ingress_port])
    |-> (selected_mode == FM_STORE_FORWARD);
endproperty
a_error_fallback: assert property (p_error_fallback_engages);

// P24. The fallback LATCHES for a whole window. Oscillating between
// disciplines produces a latency that varies 108x frame to frame.
property p_fallback_latches;
  @(posedge clk) disable iff (!rst_n)
  ($rose(fallback_active[ingress_port]) && !window_end)
    |=> fallback_active[ingress_port];
endproperty
a_fallback_stable: assert property (p_fallback_latches);

// P25. Chapter 5.5's length/type ambiguity forces store-and-forward -- a
// frame whose end cannot be predicted cannot be streamed safely.
property p_ambiguous_length_forces_sf;
  @(posedge clk) disable iff (!rst_n)
  (sel_valid && length_ambiguous) |-> (selected_mode == FM_STORE_FORWARD);
endproperty
a_ambiguous_sf: assert property (p_ambiguous_length_forces_sf);

// P26. Exactly one discipline counter moves per selection.
property p_one_mode_counted;
  @(posedge clk) disable iff (!rst_n)
  sel_valid |=> (($changed(c_sf) + $changed(c_ff) + $changed(c_ct)) == 1);
endproperty
a_one_mode: assert property (p_one_mode_counted);

Conformance

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P27. Conformance means the discipline behaved as specified -- never
// that the forwarded frames were good.
property p_conformant_definition;
  @(posedge clk) disable iff (!rst_n)
  conformant |-> ((v_commit_wrong_octet == '0) && (v_gate1_claimed == '0) &&
                  (v_bad_passed_intact == '0) && (v_rate_violated == '0) &&
                  (v_silent_truncation == '0));
endproperty
a_conformant_def: assert property (p_conformant_definition);

// P28. Store-and-forward forwards ZERO uninspected octets. The chapter's
// baseline, asserted rather than assumed.
property p_sf_inspects_everything;
  @(posedge clk) disable iff (!rst_n)
  ((mode == FM_STORE_FORWARD) && frame_end) |-> (octets_after_commit == '0);
endproperty
a_sf_full_inspection: assert property (p_sf_inspects_everything);

17. Verification Scenarios

Sixty scenarios. The commit and response scenarios have no acceptable failure; the latency scenarios have expected values that include giving the benefit up entirely.

The commit point

#ScenarioExpected
1Cut-through, 1518-octet framecommit at octet 14, octets_after_commit = 1504
2Cut-through, 64-octet framecommit at 14, octets_after_commit = 50
3Fragment-free, 1518-octet framecommit at 64, uninspected = 1454
4Fragment-free, 40-octet runtnever commits — the frame ends first
5Store-and-forward, any framecommit at the last octet, uninspected = 0
6Any frame, any modecommit asserts at most once
7Cut-through, frame shorter than 14 octetsno commit; the frame is rejected outright
8Datapath 8 octets wide, cut-throughcommit on the word containing octet 14 — P1 must still hold

Gate scheduling

#ScenarioExpected
9Cut-through, any frameg1_impossible high, g1_eligibility_done never asserts
10Cut-through, gate 1 tied to donev_gate1_claimed increments on the first frame
11Cut-through, any frameeligibility_unknown high
12Store-and-forwardeligibility_unknown low, gate 1 runs
13Cut-through at 1 Gb/slookup answers by octet 14 = 112 ns; Chapter 12.5's 8 ns fits
14Cut-through at 10 Gb/sbudget is 11.2 ns — tighter than Chapter 12.3's 28 ns
15Cut-through at 25 Gb/sbudget 4.5 ns — the lookup must be pipelined into the octets
16Gates 2, 4, 5 at the commitall three evaluated — they are available
17Any gate evaluated before its inputv_gate_ran_early

The rate rule

#ScenarioExpected
181 Gb/s ingress → 1 Gb/s egresscut-through granted
191 Gb/s ingress → 100 Mb/s egressgranted — the head start grows
201 Gb/s ingress → 10 Gb/s egressrefused, CR_EGRESS_FASTER
21Same, rule bypassedunderrun after 12.4 ns, having sent 15.6 octets
22100 Mb/s → 1 Gb/s, rule bypassedunderrun after 124.4 ns, 15.6 octets
2310 Gb/s → 25 Gb/s, rule bypassedunderrun after 7.5 ns
24Underrun with a 64-octet head start, 1 G → 10 Gunderrun after 56.9 ns, 71.1 octets
25Any underrunabort, never filler; LF_TRUNCATED
26Rate rule honoured, sustained line ratec_underruns = 0
27Link renegotiates down mid-run, table staleunderrun at an octet far from the prediction
28Typical access-to-aggregation trafficc_refused_rate dominates c_granted

Late evidence and stomping

#ScenarioExpected
29Bad FCS, store-and-forwardframe never leaves; c_bad_before_commit
30Bad FCS discovered after a cut-through commitstomped; c_bad_after_commit and c_stomped
31Stomped frame's outgoing FCS~computed_fcs — inverted, never a constant
32Good framenever stomped; FCS intact
33Bad frame emitted with a valid FCSv_bad_passed_intact — the critical failure
34Stomped frame at the next switchdiscarded, and stomped again if that switch is cut-through
35Corrupt frame across 5 cut-through hops with stompingcontained at hop 2, not hop 5
36Corrupt frame across 5 cut-through hops without stomping60 720 bits of link capacity spent
37A constant "bad FCS" valuecoincides with a correct FCS once per 2³² frames — 48 min at line rate
38Truncation with no fault reportedv_silent_truncation

Latency

#ScenarioExpected
39Store-and-forward, 1518 octets at 1 Gb/s12.144 µs
40Cut-through, 1518 octets at 1 Gb/s112 ns108.4×
41Store-and-forward, 64 octets at 1 Gb/s512 ns
42Cut-through, 64 octets at 1 Gb/s112 ns — 4.6×
43Fragment-free, 64 octets512 ns — identical to store-and-forward
44Fragment-free, 1518 octets512 ns — 23.7× faster than store-and-forward
45Cut-through, 9000-octet jumbo at 1 Gb/s112 ns against 72 µs — 642.9×
46Cut-through with an idle egressfull benefit, c_ct increments
47Cut-through with a busy egressCR_EGRESS_BUSY, store-and-forward performed
4890% egress utilisationqueueing dominates; the saving is ~10% of total latency
49Congested egressthe mechanism does not engage at all

Selection, buffering and conformance

#ScenarioExpected
50Port exceeding the error thresholdfallback_active, CR_ERROR_RATE, store-and-forward
51Fallback engaged mid-windowlatches — no per-frame oscillation
52Chapter 5.5 length/type ambiguityforced to store-and-forward, CR_UNKNOWN_LENGTH
53Any selectionexactly one of c_sf, c_ff, c_ct moves
54Cut-through egress with jumbo enabledreserves 9000 octets per in-flight frame
55Egress queue fills after a cut-through commitcannot discard — the head is on the wire
56Healthy run, one million framesconformant high throughout
57Cut-through, mixed frame sizeslatency constant at 112 ns — zero spread
58Store-and-forward, mixed frame sizeslatency spread 23.7× — 512 ns to 12.144 µs
59Stomped frame received by a store-and-forward switchdiscarded; its ingress FCS counter increments
60c_stomped on the emitting switchthe only record that a downstream error was manufactured

18. Debugging a Cut-Through Path

Every row produces a switch forwarding correctly with healthy links. The middle column is the hypothesis and the right column settles it — and several of these appear on a device other than the one at fault.

SymptomLikely causeThe observable that decides it
FCS errors on many hosts across a buildingone bad cable, relayed by cut-throughthe first-hop switch's ingress FCS counter plus c_committed_unchecked
FCS errors at a destination, none in betweencorruption relayed and never containedc_bad_after_commit on the ingress switch
Corrupt data accepted by an applicationa bad frame left with a valid FCSv_bad_passed_intact — the critical failure
Runts arriving from a switched pathcut-through relayed a collision fragmentmode = cut-through; fragment-free would reject it
Truncated frames on one egress portingress slower than egress, rule bypassedc_underruns and shortest_underrun_at
Truncated frames at an unexpected octetthe rate table disagrees with the negotiated linkshortest_underrun_at far from Section 7's prediction
Cut-through enabled, latency unchangedthe egress is never idleCR_EGRESS_BUSY dominating; Section 10
Cut-through enabled, c_ct near zerothe rate rule refuses the pathc_refused_rate — access-to-aggregation is a speed step up
Latency alternating between 112 ns and 12 µsthe discipline is being re-selected per framefallback_active toggling — the latch is missing
Burst tolerance fell after enabling cut-throughworst-case buffer reservationSection 13 — reservation is MAX_FRAME, not actual
Every frame reports eligibility pass under cut-throughgate 1 tied offv_gate1_claimed — it cannot have run
Commit at a different octet than documenteda wide datapath commits on a word boundaryv_commit_wrong_octet
Latency spread across frame sizes on a cut-through paththe discipline is silently falling backc_sf non-zero where c_ct was expected
An FCS error that cannot be attributed to a cableit may be a stomped frame from upstreamthe upstream switch's c_stomped — the only record

19. Common Misconceptions

1 — "Cut-through is faster, so it is better."

The wrong model: latency is the metric, cut-through wins on it, therefore it wins.

What it costs: every one of this chapter's trades is invisible. Corrupt frames propagate hop by hop instead of being contained (Section 9). Runts and collision fragments are relayed. A speed step upward forces store-and-forward anyway (Section 7), and buffer must be reserved at the maximum frame size (Section 13), reducing the burst tolerance that absorbs congestion.

The corrected model: cut-through trades error containment and buffer efficiency for a saving of the serialisation term only — and end-to-end latency is serialisation + lookup + queueing. It earns its cost only where the queueing term is engineered to zero, which is a deliberately underloaded storage or trading fabric and nowhere else.

2 — "A cut-through switch checks the FCS, it just checks it later."

The wrong model: the check still happens, so validity is still enforced — merely after a delay.

What it costs: the belief that the frame can still be stopped. It cannot. By the time the FCS arrives, up to 1504 octets have already been transmitted and the head of the frame reached the destination microseconds ago. A check whose result cannot change the outcome is not enforcement.

The corrected model: the check happens and its only remaining use is to mark the frame — Section 11's stomp — so that downstream devices discard it and no downstream cut-through switch relays it further. The switch's ingress FCS counter still increments, which is diagnostically valuable and operationally powerless.

3 — "Enabling cut-through will reduce my network's latency."

The wrong model: the feature applies to traffic generally.

What it costs: disappointment, and a feature enabled for nothing. The rate rule eliminates every upward speed step — host to uplink, access to aggregation, aggregation to core — which is the direction most traffic travels. And Section 10's load argument eliminates the rest: a frame whose egress is busy is queued, and a queued frame has been stored.

The corrected model: measure before enabling. c_refused_rate against c_granted gives the fraction of paths where the rule permits it, and CR_EGRESS_BUSY against c_ct gives the fraction where the egress was actually idle. On a typical hierarchical network both are small, and their product is the fraction of frames that see any benefit at all.

4 — "Fragment-free is a compromise that gets neither benefit."

The wrong model: committing at octet 64 is halfway between the two and therefore half as good as each.

What it costs: dismissing the option that is nearly free. Committing at octet 64 costs 512 ns at 1 Gb/s — which is exactly store-and-forward's latency on a minimum-length frame — and on a 1518-octet frame it is 23.7× faster than store-and-forward.

The corrected model: fragment-free is store-and-forward for small frames and cut-through for large ones, without needing to know which is which in advance. It buys back runt and fragment rejection for a cost that is zero on the frames where store-and-forward would have been cheap anyway. What it cannot buy back is gate 1, because the FCS is at the end regardless of where the commit sits.

5 — "Stomping is a hack."

The wrong model: deliberately corrupting a check sequence is crude, and a better design would avoid needing it.

What it costs: the alternative, which is worse in every respect. Once the frame has committed, the choice is between a corrupt frame that looks valid and a corrupt frame that is unmistakably marked. A design that "avoids needing" stomping is a design that forwards the first kind.

The corrected model: stomping is the correct response to information that arrives after a decision has become irrevocable. It is the mechanism that bounds Section 9's error propagation at one extra hop instead of the whole path, and P18's inversion rather than a constant is what stops it failing once every 48 minutes.

6 — "The FCS could have been put at the front of the frame."

The wrong model: the trailing checksum is a legacy choice, and a modern format would place it where a streaming switch could use it.

What it costs: a misunderstanding of why the constraint exists, which then shows up as an expectation that some future standard will fix it. A checksum covers everything before it. It cannot precede what it protects — a transmitter cannot compute a checksum over data it has not generated yet, and the whole frame must be buffered before transmission if it did.

The corrected model: the trailing position is the only position available in a streamed format, and it is what makes cut-through's trade permanent rather than incidental. Chapter 5.1's field order simultaneously enables cut-through — routing information first — and bounds it, by putting the integrity information last. Both fall out of the same constraint: a field can only be used after it has arrived.

20. Interview Reasoning

Q1 — "Why can a cut-through switch not check the frame check sequence before forwarding?"

Reason through it. Because the check sequence is the last four octets and the commit is at octet 14. On a maximum-length frame the gap is 1518 − 14 = 1504 octets — 12 032 bits, 12.032 µs at 1 Gb/s — and the decision was made at the start of it. The strong answer states that this is not an implementation shortfall: the FCS covers everything before it, so Chapter 5.8's trailing position is the only one a checksum can occupy in a streamed format. No pipeline depth, no faster logic and no additional buffering makes the input arrive sooner. The only way to satisfy the check before committing is to move the commit to the last octet — which is choosing store-and-forward, not fixing cut-through.

Q2 — "A 1 Gb/s port cuts through to a 10 Gb/s port. What happens?"

Reason through it. The transmitter runs out of frame. With a 14-octet head start, t = H ÷ (Re − Ri) = 112 bits ÷ 9 Gb/s = 12.4 ns, by which point the egress has emitted 15.6 octets and the ingress has supplied nothing more. Ethernet has no mechanism for pausing inside a frame — the transmitter must produce a continuous stream from the start delimiter to the FCS — so the frame is truncated on the wire and the receiver discards it as a runt or an FCS error. The strong answer generalises the rule as egress ≤ ingress and names its operational consequence: every upward speed step is excluded, which is host to uplink, access to aggregation and aggregation to core — the direction most traffic in a hierarchical network travels.

Q3 — "Your cut-through switch discovers a bad FCS after 1500 octets have left. What should it do, and what must it not do?"

Reason through it. It cannot recall the frame, so it must mark it: deliberately corrupt the outgoing check sequence so that every downstream device discards it and no downstream cut-through switch relays it further. What it must not do is emit the correct FCS, which would deliver a corrupt payload wearing a valid integrity check — accepted as data, by an application, silently. The strong answer adds the implementation detail that matters: the stomp must invert the computed value rather than write a constant, because a constant will coincide with some frame's correct FCS once in 2³² frames — once every 48 minutes on a fully loaded gigabit link — and on that frame the mechanism silently does the opposite of its purpose.

Q4 — "Cut-through was enabled and end-to-end latency did not improve. Explain."

Reason through it. Two independent reasons, and both are usually present. The rate rule eliminates every path whose egress is faster than its ingress, which in a hierarchical network is most of them — c_refused_rate against c_granted measures it. And Section 10's load argument eliminates the rest: cut-through's saving is available only when the egress is idle at the commit point, and a frame whose egress is busy is queued — a queued frame has been stored, so the switch performed store-and-forward regardless of configuration. The strong answer closes with the shape that makes this a niche feature: end-to-end latency is serialisation + lookup + queueing, cut-through attacks only the first term, and the third term dominates exactly under the load where latency is the complaint.

Q5 — "What does cut-through cost in buffer, and why is that counter-intuitive?"

Reason through it. A store-and-forward egress buffers what it received, which is the frame's actual length. A cut-through egress must reserve MAX_FRAME at the commit point, because the length is not known — the frame is still arriving — and the transmission cannot be stopped once started. With jumbo frames enabled that is 9000 octets per in-flight frame, against a real distribution in which most frames are minimum-length. The strong answer names why this is counter-intuitive: the feature was enabled to reduce latency, and the reservation comes directly out of the buffer that Chapter 12.1 §6 showed absorbs oversubscription bursts — so a switch configured for minimum latency has quietly reduced its tolerance for the congestion that causes latency.

Q6 — "A building's hosts all report FCS errors. The switches report healthy links. Where do you look?"

Reason through it. At the first-hop switch's ingress counters, not at the hosts. If the path is cut-through, a single bad cable's corrupt frames are relayed rather than contained — the first switch commits at octet 14, discovers the bad FCS 12 µs later, and the frame is already gone. Every downstream device discards it and increments its own FCS counter, so the symptom appears everywhere and the cause appears nowhere. The strong answer names the two numbers that close it: the first-hop switch's ingress FCS error counter — which increments even though the frame was forwarded — and c_committed_unchecked, which says frames from that port are being relayed without validation. Together they distinguish a relay from a source, and they sit on the one switch nobody is looking at because it is not complaining.

21. Understanding Check

22. What's Next

Module 12 is complete. Six chapters built a switch: the per-frame decision, learning, the six gates, flooding, the table, and now the commit point that decides which gates can run at all.

And the module ends on a limit it cannot pass. Chapter 12.4 §10 derived that a broadcast domain tops out around 200 stations — not for any reason inside the switch, but because every station must process every broadcast and no hardware filter can discard one. Section 12 of that chapter showed storm control cannot hold a domain inside that budget: the setting that would is 43.5 frames per second per port, 0.00292% of line rate, finer than the hardware expresses.

The only variable left is N — how many stations can hear one another.

Chapter 13.1 — Why VLANs Exist takes that up. It is not a chapter about a tag; it is about what happens to everything Module 12 built when one physical switch must behave as several independent ones — and what a switch that kept one table, one flood mask and one port state must now keep several of.

Then Chapter 13.2 — The 802.1Q Tag examines the four octets that carry the distinction, and what inserting them in the middle of a frame does to every field offset after it — including Chapter 5.5's length/type resolution, which this chapter's Section 12 already needed.

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.