Ethernet · Module 17
Frame Preemption (802.1Qbu / 802.3br)
Interrupting a frame in flight takes the guard band from 12.192 to 0.560 microseconds, and gives Chapter 6.3's FCS checker a case it was never designed for.
Chapter 17.2 §18 ended on a table: at every line rate below 100 Gb/s the maximum transmission unit is more than 90% of the guard band, and at 1 Gb/s it is 99.6%.
The band exists because Chapter 12.6 §8 established that a frame in flight cannot be aborted. This chapter is what happens when that stops being true.
Preemption interrupts a transmission mid-frame, sends the urgent frame, and resumes. So the quantity a gate must reserve against is no longer a maximum frame — it is a minimum fragment, because that is the most that can still be in flight when the interruption is decided.
| Line rate | Guard band without | With | Factor |
|---|---|---|---|
| 1 Gb/s | 12.192 µs | 0.560 µs | 21.8× |
| 10 Gb/s | 1.263 µs | 0.100 µs | 12.7× |
| 25 Gb/s | 0.534 µs | 0.069 µs | 7.8× |
| 100 Gb/s | 0.170 µs | 0.054 µs | 3.2× |
And Chapter 17.2 §8's collapse disappears. That chapter's 62.5 µs cycle delivered 0.49% of the link because a 12.5 µs window was almost entirely guard band. With preemption the same window delivers 19.10% — and a 31.25 µs cycle, which was zero, delivers 18.21%.
The cost is a second MAC, a second framing, and a new kind of thing on the wire. A preempted frame arrives as fragments, each terminated by an mCRC that is the bitwise complement of an ordinary FCS — so Chapter 6.3's residue check on a fragment produces 0x38FB2284 instead of 0xC704DD7B, and a receiver must be able to tell a legitimate fragment from a corrupt frame.
1. Scope — What This Chapter Owns
This chapter owns the interruption: the two MACs, the SMD codes and fragment framing, the mCRC and how a receiver distinguishes a fragment from corruption, the preemption decision, reassembly, and the verify/response handshake.
It does not own the schedule. Chapter 17.2 built the gate-control list, the cycle and the guard band's formula. Section 16 recomputes that chapter's tables with the MTU term replaced.
It does not own the requirement. Chapter 17.1 established that two of a hop's seven latency terms are unbounded and that a schedule bounds them. Preemption reduces the price of the schedule and changes none of its guarantees.
And it does not own the CRC. Chapter 6.2 built the generator and Chapter 6.3 built the residue check. Sections 7 to 9 give that checker a second valid residue and a new failure to distinguish, which is the largest change this chapter makes outside its own module.
2. The Term That Dominates the Guard Band
Chapter 17.2 §7's band is MTU/R + 2 × sync_error, and the first term is there for one reason: a frame that has started cannot be stopped.
Chapter 12.6 §8 established it and every chapter since has taken it as given. Chapter 14.2 §5's dead time contains it — the sender finishes its current frame, up to 12.144 µs. Chapter 17.1 §4's blocking term is it. And Chapter 17.2's guard band reserves for it.
Three chapters, three appearances, one fact — and preemption is the first mechanism in the track that changes it.
The substitution is precise. A gate that is about to shut must reserve against whatever can still be transmitting when it does. Without preemption that is a whole maximum frame. With preemption, a transmission in progress can be interrupted, so what remains is the fragment that is already committed — and the standard bounds that at 64 octets, because a shorter one would be a runt.
guard_band = L_uninterruptible / R + 2 × sync_errorL_uninterruptible | At 1 Gb/s | |
|---|---|---|
| without preemption | 1518 octets | 12.144 µs |
| with preemption | 64 octets | 0.512 µs |
And the sync term is unchanged at 0.048 µs, which changes its share dramatically:
| Line rate | Without: MTU's share | With: fragment's share |
|---|---|---|
| 1 Gb/s | 99.6% | 91.4% |
| 10 Gb/s | 96.2% | 51.6% |
| 25 Gb/s | 90.9% | 30.1% |
| 100 Gb/s | 71.5% | 10.6% |
At 100 Gb/s with preemption the guard band is 89.4% synchronisation error, which is Chapter 16.5's budget becoming the dominant term — and the second reason that chapter's 24.2 ns was worth the work.
3. RTL 1 — Two MACs, One PHY
The mechanism's structure: two independent MAC state machines sharing one transmit path, and a multiplexer that can switch between them mid-frame.
// -----------------------------------------------------------------------
// preempt_pkg -- shared types for 802.1Qbu / 802.3br frame preemption.
// -----------------------------------------------------------------------
package preempt_pkg;
// The Start mData Delimiter replaces the last octet of the preamble,
// where 5.2's SFD sits. Its value says what follows.
localparam logic [7:0] SMD_E = 8'hD5; // express -- the ordinary SFD
localparam logic [7:0] SMD_V = 8'h07; // verify
localparam logic [7:0] SMD_R = 8'h19; // response
// Four SMD-S codes and four SMD-C codes, cycled so a receiver can
// detect a lost fragment rather than silently concatenating.
localparam logic [7:0] SMD_S [4] = '{8'hE6, 8'h4C, 8'h7F, 8'hB3};
localparam logic [7:0] SMD_C [4] = '{8'h61, 8'h52, 8'h9E, 8'h2A};
localparam int MIN_FRAG_OCTETS = 64; // 7.3's runt threshold
localparam int MCRC_OCTETS = 4;
typedef enum logic [1:0] {
MAC_EXPRESS, // never preempted, never fragmented
MAC_PREEMPTABLE // may be interrupted at a legal boundary
} mac_kind_e;
typedef enum logic [2:0] {
SMD_KIND_EXPRESS, SMD_KIND_START, SMD_KIND_CONT,
SMD_KIND_VERIFY, SMD_KIND_RESPONSE, SMD_KIND_INVALID
} smd_kind_e;
// 6.3's residue for a good frame, and its complement for a
// fragment terminated by an mCRC. Section 8.
localparam logic [31:0] FCS_RESIDUE = 32'hC704_DD7B;
localparam logic [31:0] MCRC_RESIDUE = 32'h38FB_2284;
endpackage// -----------------------------------------------------------------------
// dual_mac_mux -- arbitrates one PHY interface between an express MAC
// and a preemptable one, and performs the interruption itself.
//
// The express MAC is an ordinary 7.1 transmit path. The preemptable
// one must be able to STOP mid-frame and resume later with its state
// intact -- which is the whole difference.
// -----------------------------------------------------------------------
module dual_mac_mux
import preempt_pkg::*;
(
input logic clk,
input logic rst_n,
// Express MAC -- 7.1's transmit path, unchanged.
input logic exp_valid,
input logic [7:0] exp_octet,
input logic exp_sop,
input logic exp_eop,
output logic exp_ready,
// Preemptable MAC.
input logic pre_valid,
input logic [7:0] pre_octet,
input logic pre_sop,
input logic pre_eop,
output logic pre_ready,
// From section 10: may we cut here, and should we.
input logic cut_legal,
input logic cut_wanted,
input logic preemption_enabled, // section 13's handshake
output logic tx_valid,
output logic [7:0] tx_octet,
output logic tx_sop,
output logic tx_eop,
output logic emit_mcrc, // terminate this fragment
output logic resuming, // the next SMD is a CONT
output logic [31:0] c_preemptions,
output logic [31:0] c_express_frames,
output logic [31:0] c_fragments,
output logic express_waited_ns_valid,
output logic [31:0] express_wait_ns
);
typedef enum logic [2:0] {
S_IDLE, S_EXPRESS, S_PREEMPTABLE, S_CUTTING, S_HELD
} st_e;
st_e st;
logic [31:0] wait_cnt;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
st <= S_IDLE; wait_cnt <= '0;
emit_mcrc <= 1'b0; resuming <= 1'b0;
c_preemptions <= '0; c_express_frames <= '0; c_fragments <= '0;
express_waited_ns_valid <= 1'b0; express_wait_ns <= '0;
end else begin
emit_mcrc <= 1'b0;
express_waited_ns_valid <= 1'b0;
unique case (st)
S_IDLE: begin
if (exp_valid && exp_sop) begin
st <= S_EXPRESS;
c_express_frames <= c_express_frames + 1;
end else if (pre_valid && pre_sop) begin
st <= S_PREEMPTABLE;
end
end
S_EXPRESS: begin
// Express frames are NEVER preempted. An express frame
// cannot be interrupted by another express frame either --
// there is only one express MAC.
if (exp_valid && exp_eop) st <= S_IDLE;
end
S_PREEMPTABLE: begin
wait_cnt <= wait_cnt + 1;
if (cut_wanted && preemption_enabled) begin
if (cut_legal) begin
// Terminate this fragment with an mCRC and yield.
emit_mcrc <= 1'b1;
st <= S_CUTTING;
c_preemptions <= c_preemptions + 1;
c_fragments <= c_fragments + 1;
end
// If the cut is not legal here -- section 11's 64-octet
// floor -- we keep transmitting and the express frame
// waits. That wait is the residual guard band.
end else if (pre_valid && pre_eop) begin
st <= S_IDLE;
c_fragments <= c_fragments + 1;
end
end
S_CUTTING: begin
// One cycle to let the mCRC leave, then the express MAC has
// the wire.
st <= S_EXPRESS;
express_wait_ns <= wait_cnt;
express_waited_ns_valid <= 1'b1;
wait_cnt <= '0;
resuming <= 1'b1;
end
S_HELD: begin
if (!exp_valid) st <= S_PREEMPTABLE;
end
endcase
if ((st == S_EXPRESS) && exp_valid && exp_eop && resuming) begin
st <= S_PREEMPTABLE;
resuming <= 1'b0;
end
end
end
assign tx_valid = (st == S_EXPRESS) ? exp_valid
: (st == S_PREEMPTABLE) ? pre_valid : 1'b0;
assign tx_octet = (st == S_EXPRESS) ? exp_octet : pre_octet;
assign tx_sop = (st == S_EXPRESS) ? exp_sop : pre_sop;
assign tx_eop = (st == S_EXPRESS) ? exp_eop : pre_eop;
assign exp_ready = (st == S_EXPRESS) || (st == S_IDLE);
assign pre_ready = (st == S_PREEMPTABLE);
endmoduleClassification: a five-state arbiter between two transmit sources, with a mid-frame yield. The yield is the novelty.
What it teaches: that express frames are never preempted and never fragmented, which is what makes the mechanism simple enough to build. There is exactly one express MAC, so an express frame cannot be interrupted by another; and a preemptable frame can only be interrupted by an express one. The relation is a strict two-level hierarchy rather than a general priority scheme, and Chapter 13.4 §11's eight classes are mapped onto it by configuration.
And it teaches that a cut which is wanted but not legal costs the express frame a wait, and that wait is the residual guard band. Section 11: a fragment must be at least 64 octets and so must the remainder. A preemptable frame in its first 64 octets cannot be cut, so an express frame arriving then waits up to 0.512 µs at 1 Gb/s — which is exactly the L_uninterruptible term Section 2 substituted.
Deliberately simplified: the preemptable MAC's state is assumed to survive the yield without being described. A real design must hold the frame's remaining octets, its running CRC state — Chapter 6.4's register — and its position, because on resumption the mCRC of the next fragment covers only that fragment while the final FCS covers the whole frame. Section 12's reassembly is the receiver's half of the same problem.
Production implication: express_wait_ns is the measurement that says whether preemption is delivering what it promised. On a working port it is bounded by the minimum-fragment time — 0.512 µs at 1 Gb/s — and a value approaching a full frame time means cuts are being wanted and refused: either preemption is disabled (Section 13's handshake never completed) or the preemptable traffic is all short frames that cannot be cut.
==
4. Express and Preemptable
Two MACs, and which class goes to which is a configuration with consequences the configuration does not state.
| Express | Preemptable | |
|---|---|---|
| may be interrupted | no | yes |
| may interrupt | yes | no |
| fragmented on the wire | never | when interrupted |
| terminating CRC | FCS | mCRC, then FCS on the last fragment |
| latency, worst case | one fragment's wait — 0.512 µs | plus every express frame |
| reassembly needed at the receiver | no | yes |
The assignment is per traffic class, and Chapter 13.2's PCP is what selects it — so preemption reuses the same three bits Chapter 13.4 §9 mapped to queues and Chapter 14.4 §2 mapped to pause classes. Those three bits now carry four independent meanings, which Chapter 14.4 §22's last callout observed is a shared namespace allocated once and argued about for ever.
And the assignment has one property that is easy to get backwards.
Making a class express does not make it fast. It makes it uninterruptible — so a class marked express is one that cannot be cut, and if it is a bulk class it now blocks every other express class for a full frame time. The express MAC is a single resource with no internal priority, so two express classes contend and the loser waits a whole frame.
| Configuration | Express classes | Result |
|---|---|---|
| control only | 1 | correct — 0.512 µs worst wait |
| control and audio | 2 | they block each other for a full frame |
| everything marked express | 8 | preemption is disabled in effect |
| nothing express | 0 | nothing to preempt for |
Row three is the configuration a cautious operator produces — mark everything important as express — and it removes the mechanism entirely, because preemption requires something preemptable to interrupt.
The right assignment is narrow: one class express, everything else preemptable, and the gain is that the one express class's worst wait falls from 12.144 µs to 0.512.
5. RTL 2 — The SMD Codes and Fragment Framing
A fragment must announce itself, and the announcement has to occupy a position that already exists in the frame.
// -----------------------------------------------------------------------
// smd_framer -- replaces 5.2's SFD with a Start mData Delimiter that
// says what kind of thing follows, and adds a fragment counter to
// continuation fragments.
//
// The SMD occupies the SFD's octet. Nothing is added to the frame's
// length -- which is why preemption does not change 8.3's efficiency.
// -----------------------------------------------------------------------
module smd_framer
import preempt_pkg::*;
(
input logic clk,
input logic rst_n,
input logic start,
input smd_kind_e kind,
input logic [1:0] seq, // cycles 0..3 across fragments
input logic [7:0] frag_count, // continuation fragments only
output logic tx_valid,
output logic [7:0] tx_octet,
output logic tx_sop,
output logic preamble_done,
output logic [31:0] c_by_kind [6]
);
logic [3:0] idx;
logic busy;
// 5.2's preamble is seven 0x55 octets then the SFD. The SMD takes
// the SFD's place, so the preamble's length is unchanged.
localparam logic [7:0] PREAMBLE = 8'h55;
function automatic logic [7:0] smd_of(input smd_kind_e k, input logic [1:0] s);
case (k)
SMD_KIND_EXPRESS: smd_of = SMD_E;
SMD_KIND_START: smd_of = SMD_S[s];
SMD_KIND_CONT: smd_of = SMD_C[s];
SMD_KIND_VERIFY: smd_of = SMD_V;
SMD_KIND_RESPONSE: smd_of = SMD_R;
default: smd_of = SMD_E;
endcase
endfunction
always_ff @(posedge clk or negedge rst_n) begin
int i;
if (!rst_n) begin
idx <= '0; busy <= 1'b0; preamble_done <= 1'b0;
for (i = 0; i < 6; i++) c_by_kind[i] <= '0;
end else begin
preamble_done <= 1'b0;
if (start && !busy) begin
busy <= 1'b1; idx <= '0;
c_by_kind[kind] <= c_by_kind[kind] + 1;
end else if (busy) begin
// A continuation fragment carries one extra octet after the
// SMD: the fragment count, so a receiver can detect a LOST
// fragment rather than concatenating across a gap.
if (idx == ((kind == SMD_KIND_CONT) ? 4'd8 : 4'd7)) begin
busy <= 1'b0;
preamble_done <= 1'b1;
idx <= '0;
end else begin
idx <= idx + 1'b1;
end
end
end
end
assign tx_valid = busy;
assign tx_sop = busy && (idx == 4'd0);
assign tx_octet = (idx < 4'd7) ? PREAMBLE
: (idx == 4'd7) ? smd_of(kind, seq)
: frag_count;
endmoduleClassification: a preamble generator with a substituted last octet. No datapath, and its entire content is where the SMD sits.
What it teaches: that the SMD occupies Chapter 5.2's SFD octet rather than being added to the frame, and that is what makes preemption cost nothing in bandwidth. A mechanism that prefixed a new header would add octets to every frame and would show up in Chapter 8.3's efficiency; replacing an octet that was already a constant costs nothing at all. The only addition is the continuation fragment's one-octet count.
And it teaches why there are four SMD-S and four SMD-C codes rather than one of each. The codes cycle across successive fragments, so a receiver that sees C1 followed by C3 knows C2 was lost — where a single code would let it concatenate two fragments from different frames into one plausible-looking frame with a valid FCS. Four values give a detection window of three lost fragments, which is ample for a link whose frame loss is Chapter 6.1's residual error rate.
Deliberately simplified: the SMD values here are illustrative rather than the standard's exact encodings, and a real implementation must use the specified values because they are chosen for Hamming distance from one another and from 0xD5. A code one bit from another is a code that a single-bit error turns into a different valid delimiter — which Section 8's probability calculation assumes does not happen.
Production implication: c_by_kind is the histogram that says what a link is actually carrying, and the ratio of SMD_KIND_CONT to SMD_KIND_START is the average number of times a frame was preempted. A value near zero means preemption is configured and never used — Section 4's "everything express" configuration — and a value above two or three means express traffic is arriving faster than preemptable frames can be sent, which is a traffic-mix problem rather than a preemption problem.
6. Why a Fragment Needs a Different CRC
This is the chapter's central complication and it exists for a reason that is not obvious until it is stated.
A receiver sees octets. When they stop, it must decide what it received.
| What arrived | Without preemption | With preemption |
|---|---|---|
| 1518 octets, FCS valid | a good frame | a good frame |
| 300 octets, FCS invalid | a corrupt frame — discard | a corrupt frame, or a fragment |
| 40 octets | a runt — Chapter 7.3 | a runt |
Row two is the problem. A fragment is a prefix of a frame, so its last four octets are ordinary frame content rather than an FCS — and Chapter 6.3's residue check on it fails, exactly as it would on a corrupt frame. A receiver applying the ordinary check discards every fragment.
So a fragment must carry its own CRC, and that CRC must be distinguishable from an FCS.
The standard's answer is elegant: the mCRC is the ordinary CRC-32 of the fragment, bitwise complemented.
FCS = CRC32(frame)
mCRC = ~CRC32(fragment)And Chapter 6.3's residue check — running the CRC over the data and its trailing check value — then produces two different constants:
| Terminated by | Residue |
|---|---|
| an FCS | 0xC704DD7B |
| an mCRC | 0x38FB2284 |
The two are complements of each other, which is not a coincidence: complementing the check value complements the residue. So the same Chapter 6.4 engine, run unchanged, produces a value that says which of the two terminations it saw — and a third value that says neither, which is corruption.
Which gives the receiver a three-way decision where it previously had two:
| Residue | Means |
|---|---|
0xC704DD7B | a complete frame, or a final fragment |
0x38FB2284 | a non-final fragment — more is coming |
| anything else | corruption — discard |
And the cost of that third case is one comparator. The CRC engine is unchanged, the polynomial is unchanged, and Chapter 6.3's checker gains one constant and one output bit.
7. RTL 3 — The mCRC Generator
The transmit half of Section 6, and its subtlety is that a fragment's CRC and the whole frame's CRC are both being computed at once.
// -----------------------------------------------------------------------
// mcrc_generator -- produces both the per-fragment mCRC and the
// whole-frame FCS from one datapath.
//
// The two registers run over different spans:
// frame_crc covers every octet of the frame, across all fragments
// fragment_crc covers only the octets of the CURRENT fragment
// The first terminates the LAST fragment; the second terminates every
// earlier one, complemented.
// -----------------------------------------------------------------------
module mcrc_generator
import preempt_pkg::*;
(
input logic clk,
input logic rst_n,
input logic frame_start, // a new preemptable frame
input logic fragment_start, // a new fragment of it
input logic octet_valid,
input logic [7:0] octet,
input logic terminate_fragment, // section 10 decided to cut
input logic terminate_frame, // the frame's last octet
output logic [31:0] mcrc_out,
output logic [31:0] fcs_out,
output logic emit_valid,
output logic emit_is_mcrc,
output logic [31:0] c_mcrc_emitted,
output logic [31:0] c_fcs_emitted
);
// 6.2's CRC-32: polynomial 0x04C11DB7, initial value all ones,
// reflected input and output, final complement. Written here as a
// bit-serial update for clarity; 6.4 unrolls it.
localparam logic [31:0] POLY = 32'h04C1_1DB7;
logic [31:0] frame_crc, frag_crc;
function automatic logic [31:0] crc_step(input logic [31:0] c, input logic [7:0] d);
logic [31:0] r;
int i;
begin
r = c;
for (i = 7; i >= 0; i--) begin
if (r[31] ^ d[i]) r = {r[30:0], 1'b0} ^ POLY;
else r = {r[30:0], 1'b0};
end
crc_step = r;
end
endfunction
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
frame_crc <= 32'hFFFF_FFFF;
frag_crc <= 32'hFFFF_FFFF;
emit_valid <= 1'b0; emit_is_mcrc <= 1'b0;
mcrc_out <= '0; fcs_out <= '0;
c_mcrc_emitted <= '0; c_fcs_emitted <= '0;
end else begin
emit_valid <= 1'b0;
// The FRAME register is initialised once per frame and runs
// across every fragment. The FRAGMENT register is reinitialised
// at every fragment boundary.
if (frame_start) frame_crc <= 32'hFFFF_FFFF;
if (fragment_start) frag_crc <= 32'hFFFF_FFFF;
if (octet_valid) begin
frame_crc <= crc_step(frame_crc, octet);
frag_crc <= crc_step(frag_crc, octet);
end
if (terminate_fragment) begin
// The mCRC is the ordinary CRC of the fragment, COMPLEMENTED
// a second time -- once by 6.2's final complement and once by
// the mCRC rule. Section 6's two residues follow from this.
mcrc_out <= ~(~frag_crc);
emit_valid <= 1'b1;
emit_is_mcrc <= 1'b1;
c_mcrc_emitted <= c_mcrc_emitted + 1;
end else if (terminate_frame) begin
fcs_out <= ~frame_crc;
emit_valid <= 1'b1;
emit_is_mcrc <= 1'b0;
c_fcs_emitted <= c_fcs_emitted + 1;
end
end
end
endmoduleClassification: two CRC registers over the same octet stream with different reset points. The duplication is the design.
What it teaches: that a preempted frame needs two CRC accumulations simultaneously, and they cannot be derived from one another. The frame's FCS must cover every octet of the frame across all its fragments; each non-final fragment's mCRC covers only that fragment. Chapter 6.2's CRC is linear, so the frame's CRC is not simply a function of the fragments' CRCs without knowing their lengths — and carrying both registers is cheaper than reconstructing one.
And it teaches that the double complement is deliberate rather than a typo. Chapter 6.2's CRC-32 applies a final complement; the mCRC rule applies another. The two cancel for the value and do not cancel for the residue, because the residue is computed by running the CRC over the data plus the check value — and a complemented check value complements the residue. Which is exactly what Section 6's table needs.
Deliberately simplified: the CRC is bit-serial for readability and a real design uses Chapter 6.4's parallel form. Doubling it doubles that logic — two 32 × 8 GF(2) matrices on a byte-wide datapath, or two 32 × 512 matrices at 100 Gb/s — which is the single largest gate cost this chapter adds.
Production implication: c_mcrc_emitted against c_fcs_emitted is the fragmentation ratio in its most direct form. One FCS per frame and one mCRC per interruption, so the ratio is the average number of cuts per preempted frame. On a healthy link it is well under one — most frames are never interrupted — and a ratio above two means express traffic is arriving faster than preemptable frames complete.
8. RTL 4 — Distinguishing a Fragment From a Corrupt Frame
The receive half, and it is where Chapter 6.3's checker gains a case.
// -----------------------------------------------------------------------
// fragment_classifier -- decides what a received unit is.
//
// Three-way, where 6.3 had two. The SMD says what was CLAIMED and the
// residue says what was DELIVERED, and the classification is the
// conjunction -- which is what keeps a corrupt frame from being taken
// for a fragment.
// -----------------------------------------------------------------------
module fragment_classifier
import preempt_pkg::*;
(
input logic clk,
input logic rst_n,
input logic unit_end,
input logic [31:0] running_residue, // 6.3's engine, unchanged
input smd_kind_e smd_kind,
input logic [1:0] smd_seq,
input logic [13:0] unit_octets,
output logic is_complete_frame,
output logic is_nonfinal_fragment,
output logic is_corrupt,
output logic is_runt,
output logic [31:0] c_frames,
output logic [31:0] c_fragments,
output logic [31:0] c_corrupt,
output logic [31:0] c_runts,
output logic [31:0] c_smd_mismatch // SMD and residue disagree
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
is_complete_frame <= 1'b0; is_nonfinal_fragment <= 1'b0;
is_corrupt <= 1'b0; is_runt <= 1'b0;
c_frames <= '0; c_fragments <= '0; c_corrupt <= '0;
c_runts <= '0; c_smd_mismatch <= '0;
end else begin
is_complete_frame <= 1'b0;
is_nonfinal_fragment <= 1'b0;
is_corrupt <= 1'b0;
is_runt <= 1'b0;
if (unit_end) begin
// Gate 0 -- 7.3's runt check, before anything else. A unit
// shorter than 64 octets is not a fragment however its CRC
// comes out, because the standard forbids emitting one.
if (unit_octets < MIN_FRAG_OCTETS) begin
is_runt <= 1'b1;
c_runts <= c_runts + 1;
end
// A frame or a FINAL fragment: the FCS residue.
else if (running_residue == FCS_RESIDUE) begin
if ((smd_kind == SMD_KIND_EXPRESS) ||
(smd_kind == SMD_KIND_START) ||
(smd_kind == SMD_KIND_CONT)) begin
is_complete_frame <= 1'b1;
c_frames <= c_frames + 1;
end else begin
// A valid FCS on a VERIFY or RESPONSE unit is a protocol
// error, not a frame.
c_smd_mismatch <= c_smd_mismatch + 1;
end
end
// A NON-FINAL fragment: the complemented residue.
else if (running_residue == MCRC_RESIDUE) begin
// And the SMD must agree that more is coming. A unit with
// an mCRC residue and an EXPRESS delimiter is corrupt, not
// a fragment -- section 9.
if ((smd_kind == SMD_KIND_START) || (smd_kind == SMD_KIND_CONT)) begin
is_nonfinal_fragment <= 1'b1;
c_fragments <= c_fragments + 1;
end else begin
is_corrupt <= 1'b1;
c_corrupt <= c_corrupt + 1;
c_smd_mismatch <= c_smd_mismatch + 1;
end
end
else begin
is_corrupt <= 1'b1;
c_corrupt <= c_corrupt + 1;
end
end
end
end
endmoduleClassification: a three-way classifier over one residue and one delimiter. Two comparators where Chapter 6.3 had one.
What it teaches: that the classification is a conjunction of the SMD and the residue, and requiring both is what keeps the error rate where Chapter 6.1 put it. The SMD says what was claimed; the residue says what was delivered. A corrupt frame that happens to produce the mCRC residue is still rejected unless its delimiter also says a fragment is coming — and c_smd_mismatch counts exactly those disagreements.
And it teaches that the runt check must come first. Chapter 7.3's 64-octet minimum applies to fragments too — Section 11's floor exists so that it does — so a 40-octet unit is a runt whatever its residue says. A classifier that checked the residue first would accept a 40-octet corrupt frame that happened to produce the mCRC residue as a legitimate fragment.
Deliberately simplified: the SMD sequence number is captured and not checked. Section 12's reassembly is where it is used — a receiver that sees C1 then C3 has lost C2 — and the classifier deliberately does not do that, because a lost fragment is a reassembly failure rather than a unit failure and the two counters must stay separate.
Production implication: c_smd_mismatch is the counter that catches a link where the two ends disagree about whether preemption is in use. A transmitter sending fragments to a receiver that did not negotiate them produces units whose SMD is unrecognised — and a receiver whose classifier treats an unknown SMD as express sees a stream of corrupt frames. Section 13's handshake exists to prevent exactly this, and this counter is how a design notices that it failed.
==
9. What Preemption Costs Chapter 6.3's Checker
Chapter 6.3 built a checker with one constant and a binary answer. Preemption gives it a second constant, a third answer, and an error case it did not have — and the cost is smaller than that sounds.
| Chapter 6.3's checker | With preemption | |
|---|---|---|
| CRC engine | Chapter 6.4's, unchanged | unchanged |
| polynomial | 0x04C11DB7 | unchanged |
| residue constants | one — 0xC704DD7B | two |
| outputs | good / bad | frame / fragment / corrupt |
| additional logic | — | one 32-bit comparator |
| new failure mode | — | a corrupt frame taken for a fragment |
Row five is the whole hardware cost: one comparator. The engine, the polynomial, the reflection and the final complement are all identical — because complementing the check value complements the residue, and nothing else about the computation changes.
Row six is the interesting cost, and it is worth pricing rather than worrying about.
For a corrupt frame to be accepted as a fragment, two independent things must happen:
Its residue must equal 0x38FB2284 — a 32-bit coincidence, probability 2⁻³² ≈ 2.33 × 10⁻¹⁰ for a random corruption.
And its SMD must decode as SMD-S or SMD-C — eight valid codes out of 256 octet values, probability 1/32.
P(corrupt frame accepted as a fragment) ≈ (8/256) × 2⁻³² ≈ 7.3 × 10⁻¹²Against Chapter 6.1's undetected-error requirement that is comfortably inside the budget — the ordinary FCS already admits 2⁻³² of corrupt frames as good, and this adds a case thirty-two times rarer than that.
And there is a third guard that the arithmetic does not capture: the SMD codes are chosen for Hamming distance. A single bit error in 0xD5 — the express delimiter — does not produce a valid SMD-C, so the common single-bit case is excluded by construction rather than by probability.
Which gives the honest summary: preemption costs Chapter 6.3's checker one comparator and one output bit, and costs its error model a term that is two orders of magnitude below the one already there.
The larger cost is elsewhere and Section 7 already paid it: the generator now needs two CRC registers, because a fragment's mCRC and the frame's FCS cover different spans and neither can be derived from the other. On a byte-wide datapath that is two 32 × 8 matrices; at 100 Gb/s it is two 32 × 512 ones, and it is the single largest gate addition in the chapter.
10. RTL 5 — The Preemption Decision
Section 3's arbiter asked whether a cut was legal and wanted. This is what answers it.
// -----------------------------------------------------------------------
// preemption_decider -- decides whether the preemptable frame in
// flight may be, and should be, interrupted right now.
//
// Two independent questions with different answers:
// LEGAL -- section 11's 64-octet floor, on both sides of the cut
// WANTED -- an express frame is waiting, or a gate is about to shut
// -----------------------------------------------------------------------
module preemption_decider
import preempt_pkg::*;
#(
parameter int LINE_RATE_MBPS = 1000
)(
input logic clk,
input logic rst_n,
input logic pre_in_flight,
input logic [13:0] octets_sent, // of this fragment
input logic [13:0] octets_remaining, // of the frame
input logic express_pending,
input logic [31:0] time_to_gate_close_ns, // 17.2 section 7
input logic preemption_enabled,
output logic cut_legal,
output logic cut_wanted,
output logic [13:0] octets_until_legal,
output logic [31:0] c_wanted_but_illegal,
output logic [31:0] c_cut,
output logic [31:0] worst_illegal_wait_ns
);
logic [31:0] illegal_wait;
always_comb begin
// A cut is legal only if BOTH pieces will be at least 64 octets.
// The fragment being closed needs 64 including its 4-octet mCRC;
// the remainder needs 64 including its FCS. Section 11.
cut_legal = pre_in_flight &&
(octets_sent >= 14'(MIN_FRAG_OCTETS)) &&
(octets_remaining >= 14'(MIN_FRAG_OCTETS));
octets_until_legal = (octets_sent >= 14'(MIN_FRAG_OCTETS))
? 14'd0
: (14'(MIN_FRAG_OCTETS) - octets_sent);
// Two reasons to want a cut, and the second is why this module
// belongs to Module 17 rather than to Module 13.
cut_wanted = preemption_enabled &&
(express_pending ||
(time_to_gate_close_ns <
((32'(octets_remaining) * 8000) / LINE_RATE_MBPS)));
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
c_wanted_but_illegal <= '0; c_cut <= '0;
worst_illegal_wait_ns <= '0; illegal_wait <= '0;
end else begin
if (cut_wanted && !cut_legal) begin
c_wanted_but_illegal <= c_wanted_but_illegal + 1;
illegal_wait <= illegal_wait + 1;
if (illegal_wait > worst_illegal_wait_ns)
worst_illegal_wait_ns <= illegal_wait;
end else begin
illegal_wait <= '0;
end
if (cut_wanted && cut_legal) c_cut <= c_cut + 1;
end
end
endmoduleClassification: two combinational predicates with a divergence counter. No state machine, and the two predicates are independent.
What it teaches: that the second reason to want a cut has nothing to do with an express frame, and it is the one that ties this chapter to Chapter 17.2. A preemptable frame whose remaining octets will not fit before the gate shuts must be cut anyway — otherwise it overruns the window, which is exactly what Chapter 17.2 §16's overran_the_window fires on. So preemption serves the schedule directly and not only the express class.
And it teaches that octets_until_legal is the residual guard band, expressed in octets. A frame in its first 64 octets cannot be cut, so an express frame arriving then waits — up to 64 octets, which is 0.512 µs at 1 Gb/s, and that is Section 2's L_uninterruptible. The number in the guard-band formula and the number in this comparator are the same number, and a design that used a different minimum in the two places has a guard band that does not cover its own decider.
Deliberately simplified: cut_wanted is evaluated combinationally from express_pending, which assumes the express MAC's readiness is visible without latency. In a real pipeline there are cycles between an express frame becoming ready and the decider seeing it, and those cycles add to the express frame's wait — so a production guard band uses MIN_FRAG + pipeline_depth rather than MIN_FRAG alone.
Production implication: worst_illegal_wait_ns is the measurement that validates the guard band empirically. It should never exceed the minimum-fragment time plus the pipeline depth — 0.512 µs at 1 Gb/s plus a few tens of nanoseconds — and a larger value means either the decider is seeing cut_wanted late or the preemptable traffic is short frames that are never legal to cut. Section 11's table says which.
11. The Minimum Fragment, and Why It Exists
Section 10's cut_legal has a 64-octet floor on both sides of the cut, and both halves of that are forced by chapters written long before preemption.
The fragment being closed must be at least 64 octets because Chapter 7.3's receive path discards anything shorter as a runt — and a runt is discarded silently, so a shorter fragment would vanish and its frame would never reassemble.
And the remainder must be at least 64 octets for the same reason: the continuation fragment is itself a unit on the wire and is subject to the same check.
Which means a frame is preemptible only in a window, and the window closes on short frames:
| Frame | Earliest cut | Latest cut | Legal cut points |
|---|---|---|---|
| 64 octets | 64 | 0 | none — not preemptible |
| 128 octets | 64 | 64 | exactly one |
| 256 octets | 64 | 192 | 129 |
| 512 octets | 64 | 448 | 385 |
| 1024 octets | 64 | 960 | 897 |
| 1518 octets | 64 | 1454 | 1391 |
The first two rows are the ones that matter operationally. A link carrying minimum-size preemptable frames cannot be preempted at all — every frame is 64 octets and none has a legal cut point — so an express frame waits a full 0.672 µs including preamble and interframe gap, and the guard band must still cover it.
And that is a genuinely awkward case, because it is the traffic pattern a congested link produces. Chapter 8.3's efficiency argument says small frames are inefficient; a link carrying many of them is a link where preemption buys nothing, and the guard band's saving evaporates.
The honest formula, then, is not MIN_FRAG / R but:
L_uninterruptible = max(MIN_FRAG, shortest_preemptable_frame_that_cannot_be_cut)which for a link carrying frames of at least 128 octets is 64, and for one carrying 64-octet frames is 64 anyway — because a 64-octet frame is itself only 64 octets long. The two cases coincide numerically and differ in cause: one is a fragment's floor and the other is a whole frame that fits under it.
Which is a small mercy: the guard-band formula is correct either way, and the mechanism simply does nothing on short-frame traffic rather than doing something wrong.
==
12. RTL 6 — The Reassembly State Machine
The receiver's half. It must hold a partial frame across an arbitrary interruption and detect every way the sequence can go wrong.
// -----------------------------------------------------------------------
// reassembly_fsm -- rebuilds a preempted frame from its fragments.
//
// Three failure modes, each with its own counter, and all three
// produce a discarded frame rather than a corrupt one:
// a lost fragment -- the SMD sequence skips
// an interleaved frame -- an SMD-S arrives mid-reassembly
// an abandoned frame -- no continuation ever arrives
// -----------------------------------------------------------------------
module reassembly_fsm
import preempt_pkg::*;
#(
parameter int MAX_FRAME = 1518,
parameter int TIMEOUT_CYC = 500_000 // 1 ms at 500 MHz
)(
input logic clk,
input logic rst_n,
input logic unit_valid,
input smd_kind_e smd_kind,
input logic [1:0] smd_seq,
input logic [7:0] frag_count,
input logic is_nonfinal_fragment,
input logic is_complete_frame,
input logic [13:0] unit_octets,
output logic frame_out_valid,
output logic [13:0] frame_out_octets,
output logic reassembling,
output logic [31:0] c_reassembled,
output logic [31:0] c_lost_fragment,
output logic [31:0] c_interleaved,
output logic [31:0] c_timed_out,
output logic [31:0] c_oversize,
output logic [7:0] worst_fragments_seen
);
logic [1:0] expect_seq;
logic [7:0] expect_count;
logic [13:0] accumulated;
logic [31:0] age;
logic [7:0] frags_this_frame;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
reassembling <= 1'b0; expect_seq <= '0; expect_count <= '0;
accumulated <= '0; age <= '0; frags_this_frame <= '0;
frame_out_valid <= 1'b0; frame_out_octets <= '0;
c_reassembled <= '0; c_lost_fragment <= '0;
c_interleaved <= '0; c_timed_out <= '0; c_oversize <= '0;
worst_fragments_seen <= '0;
end else begin
frame_out_valid <= 1'b0;
// A reassembly that never completes must be abandoned, or one
// lost final fragment stalls the receiver for ever.
if (reassembling) begin
age <= age + 1;
if (age == TIMEOUT_CYC) begin
reassembling <= 1'b0;
accumulated <= '0;
c_timed_out <= c_timed_out + 1;
end
end
if (unit_valid) begin
unique case (smd_kind)
SMD_KIND_EXPRESS: begin
// An express frame passes through untouched and does NOT
// disturb an in-progress reassembly -- the two MACs are
// independent all the way to the receiver.
if (is_complete_frame) begin
frame_out_valid <= 1'b1;
frame_out_octets <= unit_octets;
end
end
SMD_KIND_START: begin
// An SMD-S while already reassembling means the previous
// frame's continuation never arrived. Discard it.
if (reassembling) c_interleaved <= c_interleaved + 1;
reassembling <= is_nonfinal_fragment;
accumulated <= unit_octets;
expect_seq <= smd_seq + 2'd1;
expect_count <= 8'd1;
age <= '0;
frags_this_frame <= 8'd1;
// A START that is already complete is an unpreempted
// frame that happened to be announced as preemptable.
if (is_complete_frame) begin
frame_out_valid <= 1'b1;
frame_out_octets <= unit_octets;
c_reassembled <= c_reassembled + 1;
end
end
SMD_KIND_CONT: begin
if (!reassembling) begin
// A continuation with nothing to continue: its START
// was lost.
c_lost_fragment <= c_lost_fragment + 1;
end else if ((smd_seq != expect_seq) ||
(frag_count != expect_count)) begin
// The sequence skipped. Four SMD-C codes give a
// detection window of three lost fragments -- section 5.
c_lost_fragment <= c_lost_fragment + 1;
reassembling <= 1'b0;
accumulated <= '0;
end else if ((accumulated + unit_octets) > 14'(MAX_FRAME)) begin
c_oversize <= c_oversize + 1;
reassembling <= 1'b0;
accumulated <= '0;
end else begin
accumulated <= accumulated + unit_octets;
expect_seq <= smd_seq + 2'd1;
expect_count <= expect_count + 8'd1;
age <= '0;
frags_this_frame <= frags_this_frame + 8'd1;
if (is_complete_frame) begin
reassembling <= 1'b0;
frame_out_valid <= 1'b1;
frame_out_octets <= accumulated + unit_octets;
c_reassembled <= c_reassembled + 1;
if (frags_this_frame + 8'd1 > worst_fragments_seen)
worst_fragments_seen <= frags_this_frame + 8'd1;
end
end
end
default: ;
endcase
end
end
end
endmoduleClassification: a sequence-checked accumulator with a timeout. Three distinct failure paths, all leading to a discard.
What it teaches: that an express frame arriving mid-reassembly must not disturb it, and that is what makes the two MACs genuinely independent. The whole point of preemption is that an express frame arrives between two fragments; a receiver that treated any new SMD as ending the current reassembly would discard every preempted frame. The classification in Section 8 keeps them apart, and this module acts on that.
And it teaches why the timeout exists and what it costs. A lost final fragment leaves the receiver reassembling for ever — holding a partial frame and refusing every subsequent continuation — so the state must age out. One millisecond at 1 Gb/s is 125 000 octets of subsequent traffic, all of which reassembles correctly once the stale state clears; without the timeout, one lost fragment stops the preemptable path permanently.
Deliberately simplified: the accumulated octets are counted and not stored, so this module verifies the sequence rather than rebuilding the frame. A real receiver holds the fragments in a buffer of at least one maximum frame — 1518 octets — and the buffer is per port, because two ports can be reassembling simultaneously.
Production implication: worst_fragments_seen is the number that sizes the sequence space. Four SMD-C codes detect up to three consecutive lost fragments; a frame that is legitimately cut more than four times wraps the sequence, and a subsequent loss becomes undetectable. A value approaching four means express traffic is interrupting preemptable frames repeatedly, which is Section 5's ratio seen from the receiving end — and it is a traffic-engineering finding rather than a hardware one.
13. RTL 7 — The Verify/Response Handshake
Preemption cannot be switched on unilaterally, and the reason is that a receiver which does not understand fragments sees corruption.
// -----------------------------------------------------------------------
// verify_response_fsm -- establishes that the link partner understands
// preemption before any fragment is transmitted.
//
// The shape is 15.3's LACP in miniature: a periodic assertion, a
// timeout, and a capability that stays DISABLED until confirmed.
// -----------------------------------------------------------------------
module verify_response_fsm
import preempt_pkg::*;
#(
parameter int VERIFY_PERIOD_CYC = 64_000_000, // 128 ms at 500 MHz
parameter int VERIFY_LIMIT = 3
)(
input logic clk,
input logic rst_n,
input logic cfg_preemption_requested,
input logic link_up,
input logic rx_verify, // an SMD-V arrived
input logic rx_response, // an SMD-R arrived
output logic tx_verify,
output logic tx_response,
output logic preemption_enabled,
output logic [2:0] state_out,
output logic [31:0] c_verify_sent,
output logic [31:0] c_verify_failed,
output logic [31:0] c_responses_sent
);
typedef enum logic [2:0] {
V_DISABLED, V_INIT, V_VERIFYING, V_SUCCEEDED, V_FAILED
} vstate_e;
vstate_e st;
logic [31:0] timer;
logic [2:0] attempts;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
st <= V_DISABLED; timer <= '0; attempts <= '0;
tx_verify <= 1'b0; tx_response <= 1'b0;
preemption_enabled <= 1'b0;
c_verify_sent <= '0; c_verify_failed <= '0; c_responses_sent <= '0;
end else begin
tx_verify <= 1'b0;
tx_response <= 1'b0;
// Responding is UNCONDITIONAL: a device that understands
// preemption answers a verify even if it is not requesting it
// itself. 15.3's passive/passive deadlock is avoided this way.
if (rx_verify) begin
tx_response <= 1'b1;
c_responses_sent <= c_responses_sent + 1;
end
if (!link_up || !cfg_preemption_requested) begin
st <= V_DISABLED;
preemption_enabled <= 1'b0;
attempts <= '0;
end else begin
unique case (st)
V_DISABLED: begin
st <= V_INIT;
timer <= '0;
attempts <= '0;
end
V_INIT: begin
st <= V_VERIFYING;
tx_verify <= 1'b1;
timer <= '0;
attempts <= 3'd1;
c_verify_sent <= c_verify_sent + 1;
end
V_VERIFYING: begin
timer <= timer + 1;
if (rx_response) begin
st <= V_SUCCEEDED;
preemption_enabled <= 1'b1;
end else if (timer == VERIFY_PERIOD_CYC) begin
timer <= '0;
if (attempts == VERIFY_LIMIT) begin
// The partner does not answer. Preemption stays OFF,
// which is the safe state: whole frames always work.
st <= V_FAILED;
c_verify_failed <= c_verify_failed + 1;
end else begin
attempts <= attempts + 3'd1;
tx_verify <= 1'b1;
c_verify_sent <= c_verify_sent + 1;
end
end
end
V_SUCCEEDED: begin
preemption_enabled <= 1'b1;
end
V_FAILED: begin
preemption_enabled <= 1'b0;
end
endcase
end
end
end
assign state_out = st;
endmoduleClassification: a five-state negotiation with a bounded retry. The unconditional response is the design decision.
What it teaches: that responding is unconditional and requesting is not, which avoids Chapter 15.3 §13's passive/passive deadlock by construction. A device that understands preemption answers every verify whether or not it wants preemption itself; only the requesting side needs configuration. Chapter 15.3's LACP made both sides' activity configurable and produced a legal configuration that never forms an aggregate — this protocol removed that possibility.
And it teaches that failure leaves preemption off, which is the safe direction. A link whose partner does not answer transmits whole frames, which always work — the guard band reverts to 12.192 µs and the schedule's usable fraction drops, but nothing is lost. The opposite default would send fragments to a receiver that classifies them as corruption, producing a link that is up and discards a large fraction of its traffic.
Deliberately simplified: the verify period is a single constant at 128 ms. The standard allows it to be configured, and the trade is the usual one: a shorter period re-establishes preemption faster after a link event and puts more verify frames on a link that may not support them. At 128 ms the cost is 5250 bit/s — 5.25 × 10⁻⁴% of a gigabit link.
Production implication: c_verify_failed is the counter that explains a schedule whose usable fraction is unexpectedly low. A port whose preemption never enabled has Chapter 17.2's full 12.192 µs guard band, so a 12.5 µs window delivers 0.49% instead of 19.10% — a factor of 39 — and the cause is a handshake that never completed on a link that looks perfectly healthy.
14. Why Preemption Must Be Verified Before Use
Section 13's handshake looks like caution. It prevents a specific failure whose symptom points nowhere useful.
A preempting transmitter sends units whose delimiter is SMD-S or SMD-C and whose non-final fragments end in an mCRC. A receiver that does not implement preemption:
| What the receiver does | Result |
|---|---|
| sees an unrecognised SMD | most implementations treat it as no SFD at all |
| or accepts it as an SFD | then the mCRC fails Chapter 6.3's residue check |
| either way | the fragment is discarded |
| and the final fragment | has a valid FCS over the whole frame, not over itself |
The last row is the one that produces the confusing symptom. A final fragment's FCS covers every octet of the original frame, but the receiver only saw the last fragment — so the residue is computed over a prefix of what the FCS covers and fails. Every fragment of every preempted frame is discarded, and the counter that increments is Chapter 6.3's FCS error count.
Which means the symptom of unnegotiated preemption is a link with a high CRC error rate — and Chapter 6.1's whole framing says a CRC error is a physical-layer problem. So the investigation goes to the cable, the optics and Chapter 3.7's FEC, and none of them is at fault.
And the rate is not small. A link where 10% of frames are preempted once discards 10% of its preemptable traffic, with FCS errors at a rate that on any other link would mean a failing transceiver.
Which is why the handshake is mandatory rather than advisory, and why it fails closed:
| Handshake outcome | Preemption | Guard band at 1 Gb/s |
|---|---|---|
| response received | enabled | 0.560 µs |
| no response after 3 attempts | disabled | 12.192 µs |
| link down | disabled | — |
Row two is a working link with a worse guard band, which is a performance loss. The alternative — enabling optimistically — is a working link that silently discards a tenth of its traffic and blames the cabling.
And one more consideration makes the handshake worth its 5250 bit/s: it must be redone after every link event. A link that flaps has a new partner as far as this protocol is concerned — possibly literally, if somebody re-patched it — and a device that remembered "preemption worked last time" is exactly the optimistic enable that row two exists to prevent.
15. RTL 8 — Conformance for a Preemptable Port
The monitor checks the framing, the floors and the handshake. It cannot check that the far end reassembled correctly.
// -----------------------------------------------------------------------
// preempt_conformance_monitor -- one bit.
//
// It asserts the local invariants: fragments meet the 64-octet floor,
// express frames are never fragmented, the mCRC is emitted on every
// non-final fragment, and nothing was preempted before the handshake
// completed.
// -----------------------------------------------------------------------
module preempt_conformance_monitor
import preempt_pkg::*;
(
input logic clk,
input logic rst_n,
input logic fragment_below_floor,
input logic remainder_below_floor,
input logic express_was_fragmented,
input logic mcrc_missing_on_fragment,
input logic fcs_on_nonfinal_fragment,
input logic preempted_before_verify,
input logic smd_sequence_reused, // same code twice running
input logic cfg_all_classes_express, // section 4's row three
input logic cfg_no_class_express, // nothing to preempt for
output logic conformant,
output logic [15:0] fault_vector,
output logic [31:0] c_violations
);
logic v_frag, v_rem, v_exp, v_mcrc, v_fcs, v_early, v_seq;
logic v_allexp, v_noexp;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
v_frag <= 1'b0; v_rem <= 1'b0; v_exp <= 1'b0; v_mcrc <= 1'b0;
v_fcs <= 1'b0; v_early <= 1'b0; v_seq <= 1'b0;
v_allexp <= 1'b0; v_noexp <= 1'b0; c_violations <= '0;
end else begin
// A fragment below the floor is discarded as a runt by every
// receiver -- 7.3 -- so its frame never reassembles.
if (fragment_below_floor) begin v_frag <= 1'b1; c_violations <= c_violations + 1; end
if (remainder_below_floor) begin v_rem <= 1'b1; c_violations <= c_violations + 1; end
if (express_was_fragmented) begin v_exp <= 1'b1; c_violations <= c_violations + 1; end
if (mcrc_missing_on_fragment) begin v_mcrc <= 1'b1; c_violations <= c_violations + 1; end
if (fcs_on_nonfinal_fragment) begin v_fcs <= 1'b1; c_violations <= c_violations + 1; end
// Section 14: fragments to a partner that never answered.
if (preempted_before_verify) begin v_early <= 1'b1; c_violations <= c_violations + 1; end
if (smd_sequence_reused) begin v_seq <= 1'b1; c_violations <= c_violations + 1; end
// Standing configuration properties -- section 4's table.
v_allexp <= cfg_all_classes_express;
v_noexp <= cfg_no_class_express;
end
end
assign conformant = !(v_frag || v_rem || v_exp || v_mcrc || v_fcs ||
v_early || v_seq || v_allexp || v_noexp);
assign fault_vector = {7'b0, v_noexp, v_allexp, v_seq, v_early,
v_fcs, v_mcrc, v_exp, v_rem, v_frag};
endmoduleClassification: a sticky aggregator with seven framing violations and two standing configuration terms.
What it teaches: that fcs_on_nonfinal_fragment and mcrc_missing_on_fragment are the same bug with opposite signs and both must be checked. A non-final fragment terminated by an ordinary FCS is classified by Section 8 as a complete frame — so the receiver delivers a truncated frame upward with a valid checksum, which is worse than discarding it. The complement is a final fragment terminated by an mCRC, which leaves the receiver reassembling until Section 12's timeout.
And it teaches that cfg_all_classes_express and cfg_no_class_express are opposite configurations with the same effect and different causes. All-express is a cautious operator marking everything important; none-express is a port where preemption is enabled and there is nothing to preempt for. Both leave the guard band at 12.192 µs, and both look like a working configuration.
Deliberately simplified: smd_sequence_reused checks only for an immediately repeated code. The four-code cycle detects up to three losses, so the meaningful check is that consecutive fragments advance by exactly one — which requires remembering the previous code and is one more comparator.
Production implication: conformant here means this port framed and floored its fragments correctly and did not preempt before it was allowed to. It does not mean the frames reassembled — that happens at the far end, and Section 12's c_lost_fragment, c_interleaved and c_timed_out are the far end's counters. A port with conformant high whose partner reports lost fragments has a link-layer problem between them, which is the honest division and the same one Chapter 15.3 §15 drew.
16. The Guard Band, Recomputed With Preemption
Chapter 17.2 §8 built a table whose last row delivered 0.49% of the link. This is that table with L_uninterruptible changed from 1518 octets to 64.
The band first:
| Line rate | Without | With | Factor | Sync's share, with |
|---|---|---|---|---|
| 1 Gb/s | 12.192 µs | 0.560 µs | 21.8× | 8.6% |
| 10 Gb/s | 1.263 µs | 0.100 µs | 12.7× | 48.4% |
| 25 Gb/s | 0.534 µs | 0.069 µs | 7.8× | 69.9% |
| 100 Gb/s | 0.170 µs | 0.054 µs | 3.2× | 89.4% |
The factor falls with line rate because the sync term does not shrink — Chapter 17.2 §18's observation, now on the other side of the substitution. At 100 Gb/s with preemption the guard band is 89.4% synchronisation error, so the next improvement is Chapter 16.5's and not this chapter's.
And the usable fraction, which is what a deployment feels:
| Cycle | Window | Usable without | Usable with |
|---|---|---|---|
| 2000 µs | 200 µs | 9.39% | 9.97% |
| 1000 µs | 100 µs | 8.78% | 9.94% |
| 500 µs | 75 µs | 12.56% | 14.89% |
| 250 µs | 50 µs | 15.12% | 19.78% |
| 125 µs | 25 µs | 10.25% | 19.55% |
| 62.5 µs | 12.5 µs | 0.49% | 19.10% |
| 31.25 µs | 6.25 µs | 0.00% | 18.21% |
Chapter 17.2 §8's collapse is gone. Without preemption the usable fraction peaked at a 250 µs cycle and fell off a cliff below it; with preemption it is flat from 250 µs down to 31.25 µs at around 19% — and the cliff has moved to a cycle whose window approaches 0.560 µs, which is fifty times shorter.
Which changes the sizing rule Chapter 17.2 §12 gave. That chapter said choose the longest cycle that meets the deadline, because shortening cost bandwidth. With preemption, shortening costs almost nothing down to 31 µs — so the rule becomes choose the cycle that meets the deadline, and the bandwidth is nearly indifferent.
And the latency that buys, using Chapter 17.1 §4's 37.12 µs per hop over five hops:
| Cycle | Schedule wait | Five-hop worst case | Usable |
|---|---|---|---|
| 1000 µs | 900 µs | 1085.6 µs | 9.94% |
| 250 µs | 200 µs | 385.6 µs | 19.78% |
| 62.5 µs | 50 µs | 235.6 µs | 19.10% |
| 31.25 µs | 25 µs | 210.6 µs | 18.21% |
A 31.25 µs cycle delivers a 210 µs worst case and 18.21% of the link. Without preemption the same cycle delivers zero.
==
17. What Preemption Can and Cannot Promise
| Claim | Status |
|---|---|
| an express frame waits at most one minimum fragment | guaranteed — 0.512 µs at 1 Gb/s |
| the guard band falls 21.8× at 1 Gb/s | guaranteed |
| a fragment is distinguishable from corruption | guaranteed to 7.3 × 10⁻¹² |
| a frame under 128 octets is preemptible | no — Section 11 |
| a link of 64-octet frames benefits | no — nothing can be cut |
| fragments cost bandwidth | no — the SMD replaces the SFD |
| the far end reassembled it | not checkable from here |
| preemption is on | only after Section 13's handshake |
Rows four and five are the mechanism's real limitation and they coincide in an unfortunate way. A link carrying short preemptable frames cannot be preempted, and short frames are what a congested link produces — so the guard band's saving is smallest exactly when the schedule is under most pressure.
Row six is worth stating because it is the one thing preemption does not cost. The SMD occupies Chapter 5.2's SFD octet; the only addition is one octet of fragment count on continuation fragments. A frame cut twice carries two extra octets in total, which against 1518 is 0.13% — invisible in Chapter 8.3's efficiency.
And row eight is the one that produces a silent shortfall. A port whose handshake failed runs Chapter 17.2's full guard band, so a 12.5 µs window delivers 0.49% instead of 19.10% — a factor of 39 — with a link that is up, a schedule that is valid and a conformance bit that is high.
18. The Cost of Preemption, Accounted
| Component | Cost | Note |
|---|---|---|
| a second MAC state machine | ≈200 flops | express, and it is an ordinary Chapter 7.1 path |
| Section 3's arbiter | 5 states, ≈40 flops | the mid-frame yield |
| preemptable MAC state held across a yield | a frame buffer + CRC state | 1518 octets |
| Section 7's second CRC engine | two 32 × 8 GF(2) matrices | at 100 Gb/s, two 32 × 512 |
| Section 8's classifier | one 32-bit comparator | Chapter 6.3 gains one constant |
| Section 12's reassembly buffer | 1518 octets, per port | receive side |
| Section 13's handshake | 5 states, two timers | — |
| total logic | ≈4000 gate-equivalents + two 1518-octet buffers | — |
| bandwidth cost | one octet per continuation fragment | 0.13% on a twice-cut frame |
| verify traffic | 5250 bit/s at a 128 ms period | 5.25 × 10⁻⁴% of 1 Gb/s |
| what it buys | the guard band, 21.8× smaller | and Chapter 17.2 §8's collapse removed |
Rows three and six are the real cost and they are memory rather than logic. A preempted frame must be held at both ends — the transmitter holds the remainder and the receiver holds the fragments — so a preemptable port carries two maximum-frame buffers it did not need before. At 1518 octets each that is 3 KiB per port, which against Chapter 14.1's 12 MiB pool is 0.024%.
Row four is the largest logic addition and it is the one Section 9 identified. Two CRC accumulations over the same octet stream, because a fragment's mCRC and the frame's FCS cover different spans.
And the comparison across Module 17 puts it in proportion:
| Mechanism | Cost | What it buys |
|---|---|---|
| Chapter 17.2's schedule | ≈700 octets | a bounded interference term |
| this chapter's preemption | ≈3 KiB + 4000 gates | the guard band, 21.8× smaller |
Four times the state and an order of magnitude more logic than the schedule, for a mechanism that adds no guarantee at all — it only makes the schedule's guarantee affordable at short cycles. Which is the honest characterisation: preemption is not a determinism feature; it is a bandwidth-recovery feature for a determinism feature.
19. Properties Worth Asserting, and One Worth Refusing
The properties divide by object: the arbiter, the framing, the CRCs, the classifier, reassembly, and the handshake.
Group 1 — the arbiter.
// P1. An express frame is never fragmented. There is one express MAC
// and nothing can interrupt it.
property p_express_never_fragmented;
@(posedge clk) disable iff (!rst_n)
(st == S_EXPRESS) |-> !emit_mcrc;
endproperty
// P2. A preemptable frame yields only at a LEGAL cut.
property p_cut_only_when_legal;
@(posedge clk) disable iff (!rst_n)
emit_mcrc |-> $past(cut_legal);
endproperty
// P3. And only when preemption has been verified -- section 13.
property p_cut_only_when_enabled;
@(posedge clk) disable iff (!rst_n)
emit_mcrc |-> $past(preemption_enabled);
endproperty
// P4. After a cut the express MAC gets the wire.
property p_cut_yields_to_express;
@(posedge clk) disable iff (!rst_n)
(st == S_CUTTING) |=> (st == S_EXPRESS);
endproperty
// P5. And the preemptable frame resumes after the express one ends.
property p_preemptable_resumes;
@(posedge clk) disable iff (!rst_n)
(resuming && (st == S_EXPRESS) && exp_eop) |=> (st == S_PREEMPTABLE);
endpropertyGroup 2 — framing.
// P6. The SMD occupies the SFD's octet. Nothing is added to the
// frame's length -- which is why preemption costs no bandwidth.
property p_smd_replaces_sfd;
@(posedge clk) disable iff (!rst_n)
(tx_valid && (idx == 4'd7)) |-> (tx_octet == smd_of(kind, seq));
endproperty
// P7. The preamble is otherwise unchanged -- seven 0x55 octets.
property p_preamble_unchanged;
@(posedge clk) disable iff (!rst_n)
(tx_valid && (idx < 4'd7)) |-> (tx_octet == 8'h55);
endproperty
// P8. Only continuation fragments carry a fragment count.
property p_count_only_on_cont;
@(posedge clk) disable iff (!rst_n)
(tx_valid && (idx == 4'd8)) |-> (kind == SMD_KIND_CONT);
endproperty
// P9. The SMD sequence advances by one across fragments, so a loss
// is detectable rather than silently concatenated.
property p_seq_advances;
@(posedge clk) disable iff (!rst_n)
(start && (kind == SMD_KIND_CONT)) |-> (seq == ($past(seq) + 2'd1));
endpropertyGroup 3 — the CRCs.
// P10. The frame CRC is initialised once per FRAME and the fragment
// CRC once per FRAGMENT. They cover different spans.
property p_two_spans;
@(posedge clk) disable iff (!rst_n)
fragment_start |=> (frag_crc == 32'hFFFF_FFFF);
endproperty
property p_frame_span_survives_fragments;
@(posedge clk) disable iff (!rst_n)
(fragment_start && !frame_start) |=> $stable(frame_crc);
endproperty
// P11. A non-final fragment is terminated by an mCRC and never by
// an FCS. The opposite delivers a truncated frame that CHECKS.
property p_nonfinal_gets_mcrc;
@(posedge clk) disable iff (!rst_n)
terminate_fragment |=> (emit_valid && emit_is_mcrc);
endproperty
// P12. And the final one by an FCS over the WHOLE frame.
property p_final_gets_fcs;
@(posedge clk) disable iff (!rst_n)
terminate_frame |=> (emit_valid && !emit_is_mcrc && (fcs_out == ~frame_crc));
endproperty
// P13. The two residues are complements -- which is why one engine
// distinguishes them. Section 6.
property p_residues_are_complements;
@(posedge clk) disable iff (!rst_n)
(MCRC_RESIDUE == ~FCS_RESIDUE);
endpropertyGroup 4 — classification.
// P14. The runt check comes FIRST. A 40-octet unit is a runt whatever
// its residue -- 7.3, and section 11's floor exists so that it is.
property p_runt_check_is_first;
@(posedge clk) disable iff (!rst_n)
(unit_end && (unit_octets < MIN_FRAG_OCTETS))
|=> (is_runt && !is_nonfinal_fragment && !is_complete_frame);
endproperty
// P15. A fragment requires BOTH the mCRC residue and a fragment SMD.
// Requiring both is what keeps 6.1's error budget.
property p_fragment_needs_both;
@(posedge clk) disable iff (!rst_n)
is_nonfinal_fragment |-> (($past(running_residue) == MCRC_RESIDUE) &&
(($past(smd_kind) == SMD_KIND_START) ||
($past(smd_kind) == SMD_KIND_CONT)));
endproperty
// P16. A disagreement between the SMD and the residue is corruption,
// and it is counted.
property p_mismatch_is_corrupt;
@(posedge clk) disable iff (!rst_n)
(unit_end && (running_residue == MCRC_RESIDUE) &&
(smd_kind == SMD_KIND_EXPRESS)) |=> (is_corrupt && $changed(c_smd_mismatch));
endproperty
// P17. Exactly one classification per unit.
property p_one_classification;
@(posedge clk) disable iff (!rst_n)
unit_end |=> ($countones({is_complete_frame, is_nonfinal_fragment,
is_corrupt, is_runt}) == 1);
endpropertyGroup 5 — reassembly.
// P18. An express frame passes through without disturbing an
// in-progress reassembly. The two MACs are independent to the end.
property p_express_does_not_disturb;
@(posedge clk) disable iff (!rst_n)
(unit_valid && (smd_kind == SMD_KIND_EXPRESS) && reassembling)
|=> $stable(accumulated);
endproperty
// P19. A sequence skip discards rather than concatenating.
property p_skip_discards;
@(posedge clk) disable iff (!rst_n)
(unit_valid && (smd_kind == SMD_KIND_CONT) && reassembling &&
(smd_seq != expect_seq)) |=> (!reassembling && $changed(c_lost_fragment));
endproperty
// P20. A reassembly always terminates -- completed or timed out.
property p_reassembly_terminates;
@(posedge clk) disable iff (!rst_n)
$rose(reassembling) |-> ##[1:TIMEOUT_CYC+1] !reassembling;
endproperty
// P21. The reassembled length never exceeds the MTU.
property p_no_oversize;
@(posedge clk) disable iff (!rst_n)
frame_out_valid |-> (frame_out_octets <= 14'(MAX_FRAME));
endproperty
// P22. An SMD-S mid-reassembly abandons the old frame and starts a
// new one -- it never merges them.
property p_start_abandons;
@(posedge clk) disable iff (!rst_n)
(unit_valid && (smd_kind == SMD_KIND_START) && reassembling)
|=> ($changed(c_interleaved) && (accumulated == $past(unit_octets)));
endpropertyGroup 6 — the floor and the handshake.
// P23. Both sides of a cut meet the 64-octet floor.
property p_both_sides_meet_the_floor;
@(posedge clk) disable iff (!rst_n)
cut_legal |-> ((octets_sent >= 14'(MIN_FRAG_OCTETS)) &&
(octets_remaining >= 14'(MIN_FRAG_OCTETS)));
endproperty
// P24. A frame under 128 octets has no legal cut point.
property p_short_frames_not_preemptible;
@(posedge clk) disable iff (!rst_n)
((octets_sent + octets_remaining) < 14'd128) |-> !cut_legal;
endproperty
// P25. The guard band's L_uninterruptible and the decider's floor are
// the SAME number. A design using two has a band that does not cover
// its own decider.
property p_band_matches_decider;
@(posedge clk) disable iff (!rst_n)
(GUARD_L_UNINTERRUPTIBLE == MIN_FRAG_OCTETS);
endproperty
// P26. Preemption stays disabled until a response arrives.
property p_disabled_until_response;
@(posedge clk) disable iff (!rst_n)
preemption_enabled |-> ($past(st) inside {V_SUCCEEDED, V_VERIFYING});
endproperty
// P27. A verify is answered unconditionally -- 15.3's passive/passive
// deadlock avoided by construction.
property p_verify_always_answered;
@(posedge clk) disable iff (!rst_n)
rx_verify |=> tx_response;
endproperty
// P28. Failure leaves preemption OFF, which is the safe direction:
// whole frames always work.
property p_fails_closed;
@(posedge clk) disable iff (!rst_n)
(st == V_FAILED) |-> !preemption_enabled;
endproperty
// P29. A link event re-runs the handshake -- a remembered capability
// is the optimistic enable section 14 exists to prevent.
property p_link_event_reverifies;
@(posedge clk) disable iff (!rst_n)
$fell(link_up) |=> !preemption_enabled;
endproperty
// P30. Standing property: at least one class is express and not all
// of them are. Both extremes disable the mechanism.
property p_sensible_class_split;
@(posedge clk) disable iff (!rst_n)
(!cfg_all_classes_express && !cfg_no_class_express);
endproperty
// P31. conformant is exactly the conjunction it claims.
property p_conformant_definition;
@(posedge clk) disable iff (!rst_n)
conformant <-> (fault_vector == 16'h0000);
endpropertyP11 and P12 are the framing's central pair, P15 keeps Chapter 6.1's error budget, P25 ties the guard band to the decider, and P28 makes the handshake fail closed. Every one of them is about a fragment, a unit or a configuration — and none of them is about the frame as a thing transmitted without interruption, which is the property this chapter refuses.
20. Verification Scenarios
Seventy-four scenarios. The ones that matter are on the wire, because a testbench that checks reassembled frames never sees a fragment.
The arbiter
| # | Scenario | Expected |
|---|---|---|
| 1 | Express frame in flight, another express arrives | waits — one express MAC |
| 2 | Preemptable in flight, express arrives at octet 200 | cut, mCRC, yield |
| 3 | Same at octet 30 | not legal — express waits |
| 4 | Same, wait measured | up to 0.512 µs at 1 Gb/s |
| 5 | Preemption disabled | express waits a full frame — 12.144 µs |
| 6 | After the express frame ends | preemptable resumes |
| 7 | Express frame arrives during S_CUTTING | queued behind the first |
| 8 | c_preemptions vs c_fragments | fragments = preemptions + frames |
Class assignment
| # | Scenario | Expected |
|---|---|---|
| 9 | One class express | 0.512 µs worst express wait |
| 10 | Two classes express | they block each other a full frame |
| 11 | All eight express | preemption disabled in effect |
| 12 | No class express | nothing to preempt for |
| 13 | 11 or 12 configured | conformant low |
| 14 | PCP reused for queue, pause and preemption | four meanings, three bits |
Framing
| # | Scenario | Expected |
|---|---|---|
| 15 | SMD in the SFD octet | frame length unchanged |
| 16 | Preamble octets 0–6 | 0x55, unchanged |
| 17 | Continuation fragment | one extra octet: the count |
| 18 | Frame cut twice | two extra octets — 0.13% of 1518 |
| 19 | SMD-C codes across fragments | cycle 0,1,2,3 |
| 20 | C1 then C3 received | C2 lost — detected |
| 21 | One SMD-C code only | two frames concatenate into one plausible frame |
| 22 | Four lost fragments | the sequence wraps — undetectable |
The CRCs
| # | Scenario | Expected |
|---|---|---|
| 23 | Frame CRC across fragments | covers every octet of the frame |
| 24 | Fragment CRC | covers only this fragment |
| 25 | Frame CRC derived from fragment CRCs | not possible without the lengths |
| 26 | mCRC = complement of the ordinary FCS | — |
| 27 | Residue on an FCS-terminated unit | 0xC704DD7B |
| 28 | Residue on an mCRC-terminated fragment | 0x38FB2284 |
| 29 | The two residues | complements of each other |
| 30 | Chapter 6.4's engine | unchanged — one comparator added |
| 31 | Two CRC engines at 100 Gb/s | two 32 × 512 matrices — the largest gate cost |
Classification
| # | Scenario | Expected |
|---|---|---|
| 32 | 40-octet unit, mCRC residue | runt — the check comes first |
| 33 | 300 octets, FCS residue, SMD-E | a complete frame |
| 34 | 300 octets, mCRC residue, SMD-C | a non-final fragment |
| 35 | 300 octets, mCRC residue, SMD-E | corrupt — c_smd_mismatch |
| 36 | 300 octets, neither residue | corrupt |
| 37 | P(corrupt taken for a fragment) | ≈7.3 × 10⁻¹² |
| 38 | Against Chapter 6.1's 2⁻³² | 32× rarer than the existing case |
| 39 | Single-bit error in 0xD5 | not a valid SMD-C — excluded by Hamming distance |
| 40 | Exactly one classification per unit | always |
Reassembly
| # | Scenario | Expected |
|---|---|---|
| 41 | Express frame mid-reassembly | reassembly undisturbed |
| 42 | A receiver that ended reassembly on any SMD | every preempted frame discarded |
| 43 | Sequence skip | c_lost_fragment, discard |
| 44 | SMD-S mid-reassembly | c_interleaved, old frame abandoned |
| 45 | Final fragment lost | reassembling for ever without a timeout |
| 46 | Same, with the timeout | c_timed_out after 1 ms |
| 47 | Same, traffic during the stall | 125 000 octets at 1 Gb/s |
| 48 | Accumulated exceeds the MTU | c_oversize, discard |
| 49 | worst_fragments_seen near 4 | sequence space nearly exhausted |
| 50 | Reassembly buffer | 1518 octets, per port |
The floor
| # | Scenario | Expected |
|---|---|---|
| 51 | 64-octet frame | not preemptible — no cut point |
| 52 | 128-octet frame | exactly one cut point |
| 53 | 256-octet frame | 129 cut points |
| 54 | 1518-octet frame | 1391 |
| 55 | Cut at octet 30 | fragment_below_floor |
| 56 | Cut leaving 30 octets | remainder_below_floor |
| 57 | A link of 64-octet preemptable frames | preemption buys nothing |
| 58 | Same, guard band | still 0.560 µs — the formula holds |
| 59 | Guard band's floor ≠ decider's floor | the band does not cover the decider |
The handshake
| # | Scenario | Expected |
|---|---|---|
| 60 | Verify sent, response received | preemption enabled |
| 61 | Three verifies, no response | V_FAILED, preemption off |
| 62 | Fragments to a non-preemption receiver | every fragment discarded |
| 63 | Same, symptom | a high FCS error rate |
| 64 | Same, investigation | goes to the cable and the optics |
| 65 | 10% of frames preempted | 10% of preemptable traffic lost |
| 66 | Verify received while not requesting | answered unconditionally |
| 67 | Both ends passive | no deadlock — response is unconditional |
| 68 | Link flaps | handshake re-runs |
| 69 | Capability remembered across a flap | the optimistic enable §14 prevents |
| 70 | Verify at 128 ms | 5250 bit/s — 5.25 × 10⁻⁴% |
The guard band
| # | Scenario | Expected |
|---|---|---|
| 71 | 1 Gb/s, without / with | 12.192 / 0.560 µs — 21.8× |
| 72 | 100 Gb/s, with | 0.054 µs — 89.4% is sync error |
| 73 | 62.5 µs cycle, 12.5 µs window | 0.49% → 19.10% |
| 74 | 31.25 µs cycle, 6.25 µs window | 0.00% → 18.21% |
The directed test random stimulus will not produce
A fragment is not a frame, so a testbench that checks reassembled frames never observes one — and every failure in Sections 6 to 9 is a property of what is on the wire between the two ends. Random stimulus varies frame contents and arrival times; it does not vary whether the far end understands preemption, which is Section 14's failure and the one whose symptom points at the cabling.
Setup: one preemptable port at 1 Gb/s with a wire-level monitor that classifies every unit by SMD and residue independently of the DUT. An express class carrying 64-octet frames at random intervals, a preemptable class carrying a fixed frame-size distribution. Chapter 6.4's engine in the monitor, run twice — once expecting 0xC704DD7B and once 0x38FB2284.
Stimulus, five runs of 10⁶ preemptable frames. Run A — verified, 1518-octet preemptable frames. Run B — verified, 64-octet preemptable frames. Run C — verified, 128-octet preemptable frames. Run D — handshake never completed, preemption requested and enabled optimistically. Run E — Run A with the receiver's classifier using only Chapter 6.3's single residue.
Oracle:
| # | Observable | A — 1518 | B — 64 | C — 128 | D — unverified | E — single residue |
|---|---|---|---|---|---|---|
| 1 | c_preemptions | high | 0 | low | high | high |
| 2 | cut_legal ever true | yes | never | at octet 64 only | yes | yes |
| 3 | express worst wait | 0.512 µs | 0.672 µs | 0.512 µs | 0.512 µs | 0.512 µs |
| 4 | effective guard band needed | 0.560 µs | 0.560 µs | 0.560 µs | 12.192 µs | 0.560 µs |
| 5 | fragments on the wire | many | none | few | many | many |
| 6 | fragments classified correctly | all | — | all | none — receiver has no classifier | none |
| 7 | frames reassembled | all | n/a | all | none | none |
| 8 | FCS error rate at the receiver | 0 | 0 | 0 | ≈ the preemption rate | ≈ the preemption rate |
| 9 | c_smd_mismatch | 0 | 0 | 0 | n/a — no classifier | n/a |
| 10 | conformant at the transmitter | high | high | high | low — preempted_before_verify | high |
| 11 | where an operator would look | — | — | — | the cable and the optics | the cable and the optics |
| 12 | c_verify_failed | 0 | 0 | 0 | >0 — the only true signal | 0 |
| 13 | usable fraction, 62.5 µs cycle | 19.10% | 0.49% | 19.10% | 0.49% | 19.10% |
| 14 | rerun E with two residues | — | — | — | — | all reassembled |
| 15 | rerun B with 128-octet frames | — | becomes Run C | — | — | — |
Rows 6 to 11 together are the finding. Runs D and E produce identical symptoms at the receiver — every fragment discarded, an FCS error rate equal to the preemption rate — from two entirely different causes, one a handshake that never completed and one a classifier missing a constant. And both send an operator to the physical layer, because Chapter 6.1's framing says a CRC error is a cable problem.
Row 12 is the only signal that separates them, and it exists at the transmitter rather than where the errors are counted.
Rows 1, 2 and 13 are the second finding: Run B is a correctly configured, fully conformant port on which preemption does nothing at all. Every frame is 64 octets, no frame has a legal cut point, and the usable fraction at a 62.5 µs cycle is Chapter 17.2's 0.49% rather than this chapter's 19.10% — with conformant high and no counter indicating anything. Row 15 is the remedy and it is a traffic-engineering change, not a configuration one.
21. Debugging Preemption
Five questions, and the first two explain most of the cases where preemption appears not to work.
Step 1 — did the handshake complete? preemption_enabled, c_verify_sent, c_verify_failed. A port that never enabled runs Chapter 17.2's full guard band, so a 12.5 µs window delivers 0.49% instead of 19.10% — a factor of 39 — with a healthy link and a valid schedule. This is checkable with no traffic.
Step 2 — is anything preemptible? c_preemptions against c_fragments, and the preemptable traffic's frame-size distribution. A link carrying 64-octet preemptable frames has no legal cut point anywhere — Section 11's first row — so preemption is enabled, correct, and inert. c_wanted_but_illegal rising with c_cut at zero is exactly this.
Step 3 — are fragments surviving? The far end's c_lost_fragment, c_interleaved, c_timed_out and its FCS error rate. A receiver discarding fragments produces an FCS error rate equal to the preemption rate, and Chapter 6.1's framing sends that investigation to the cable. Two causes — an incomplete handshake and a classifier with one residue — produce identical symptoms, and Step 1's counter separates them.
Step 4 — is the class split sensible? cfg_all_classes_express and cfg_no_class_express. Both extremes disable the mechanism and both look like complete configurations: all-express is a cautious operator and none-express is a port with nothing to preempt for.
Step 5 — is the guard band matched to the decider? The band's L_uninterruptible against the decider's MIN_FRAG_OCTETS, and the pipeline depth between an express frame becoming ready and cut_wanted asserting. A band computed from 64 octets protecting a decider that sees express traffic ten cycles late is a band that does not cover its own mechanism, and worst_illegal_wait_ns exceeding 0.512 µs is the evidence.
And the finding that ends an investigation: handshake complete, cuts happening, no lost fragments at the far end, a sensible class split, and worst_illegal_wait_ns at the minimum-fragment time. That is preemption delivering its 21.8× — and any remaining shortfall is Chapter 17.2's cycle or Chapter 16.5's clock.
22. Common Misconceptions
1 — "Preemption makes the network faster."
The wrong model: interrupting a frame reduces latency.
What it costs: a mechanism deployed for the wrong measurement. Preemption adds no guarantee at all — Chapter 17.1's bound and Chapter 17.2's schedule provide it. What it changes is the guard band: 12.192 µs to 0.560 at 1 Gb/s, which is bandwidth rather than latency.
The corrected model: preemption is a bandwidth-recovery feature for a determinism feature. It makes Chapter 17.2 §8's short cycles affordable — a 62.5 µs cycle goes from delivering 0.49% of the link to 19.10% — and an express frame's worst wait falls from 12.144 µs to 0.512, which is a latency gain for one class and not for the network.
2 — "Mark everything important as express."
The wrong model: express is a priority level.
What it costs: the mechanism, entirely. Express means uninterruptible, not fast — and there is one express MAC with no internal priority, so two express classes block each other for a full frame. All eight classes express means nothing is preemptable and preemption is disabled in effect.
The corrected model: one class express, everything else preemptable. The gain is that the one express class's worst wait falls 23.7×; adding a second express class restores a full-frame wait between them and removes most of the benefit.
3 — "A fragment is just a short frame."
The wrong model: the receiver will work it out.
What it costs: every fragment discarded. A fragment is a prefix of a frame, so its last four octets are ordinary content — Chapter 6.3's residue check fails exactly as it would on corruption. And a final fragment's FCS covers the whole frame, not the fragment, so it fails too.
The corrected model: a non-final fragment carries an mCRC — the ordinary CRC-32, complemented — so the residue is 0x38FB2284 instead of 0xC704DD7B, and a receiver with two constants and one comparator distinguishes a fragment from corruption to 7.3 × 10⁻¹².
4 — "Enable preemption on both ends and it works."
The wrong model: configuration is agreement.
What it costs: if one end does not implement it, every fragment is discarded and the FCS error rate equals the preemption rate — which Chapter 6.1's framing says is a physical-layer fault. The investigation goes to the cable, the optics and Chapter 3.7's FEC, and none of them is at fault.
The corrected model: preemption is verified before use — SMD-V until an SMD-R arrives, three attempts, and it fails closed. A partner that does not answer leaves preemption disabled and the guard band large, which is a performance loss rather than a traffic loss. And the handshake re-runs after every link event, because a flapped link may have a different partner.
5 — "Any frame can be preempted."
The wrong model: the cut point is free.
What it costs: a design whose guard band assumes a 64-octet floor and whose traffic never reaches it. Both sides of a cut must be at least 64 octets — Chapter 7.3's runt check applies to fragments — so a 64-octet frame has no legal cut point and a 128-octet one has exactly one.
The corrected model: preemptibility is a window that closes on short frames, and short frames are what a congested link produces. A link of 64-octet preemptable frames is fully conformant, correctly configured, and preemption does nothing on it — with c_wanted_but_illegal rising and c_cut at zero as the only evidence.
6 — "Fragments cost bandwidth."
The wrong model: extra framing means extra octets.
What it costs: a mechanism rejected for an overhead that is not there. The SMD occupies Chapter 5.2's SFD octet — a position that already existed and held a constant — so the only addition is one octet of fragment count per continuation.
The corrected model: a frame cut twice carries two extra octets against 1518 — 0.13%, invisible in Chapter 8.3's efficiency. The real costs are logic and memory: two CRC engines, and a maximum-frame buffer at each end — about 3 KiB per port, 0.024% of Chapter 14.1's pool.
23. Interview Reasoning
Q1 — What does preemption change, and what does it not?
It changes what a gate must reserve against, and nothing else. Chapter 17.2's guard band is L_uninterruptible / R + 2 × sync_error, and preemption takes L_uninterruptible from a maximum frame (1518 octets) to a minimum fragment (64) — 12.192 µs to 0.560 at 1 Gb/s, a 21.8× reduction. It adds no guarantee: Chapter 17.1's bound and Chapter 17.2's schedule provide those. What it buys is that Chapter 17.2 §8's collapse disappears — a 62.5 µs cycle goes from 0.49% of the link to 19.10%.
Q2 — Why does a fragment need a different CRC, and what is it?
Because a fragment is a prefix, so its last four octets are ordinary content and Chapter 6.3's residue check fails on it exactly as on corruption. The mCRC is the ordinary CRC-32 of the fragment, bitwise complemented — and since complementing the check value complements the residue, an mCRC-terminated unit gives 0x38FB2284 where an FCS gives 0xC704DD7B. The two are complements. Chapter 6.4's engine is unchanged; the checker gains one constant, one comparator and a third output.
Q3 — How does a receiver avoid mistaking corruption for a fragment?
By requiring both the residue and the delimiter to agree. A corrupt frame must produce the mCRC residue — 2⁻³² ≈ 2.33 × 10⁻¹⁰ — and its SMD must decode as SMD-S or SMD-C — eight codes of 256, so 1/32. The product is ≈7.3 × 10⁻¹², which is 32× rarer than the 2⁻³² of corrupt frames the ordinary FCS already admits. And the SMD codes are chosen for Hamming distance, so a single-bit error in 0xD5 does not produce a valid SMD-C — the common case is excluded by construction.
Q4 — Why is there a 64-octet floor on both sides of a cut?
Because Chapter 7.3's runt check applies to fragments as units on the wire. A fragment shorter than 64 octets is silently discarded, so its frame never reassembles — and the same applies to the remainder, which is itself a unit. So a 64-octet frame has no legal cut point and a 128-octet frame has exactly one. The consequence is that a link carrying short preemptable frames gains nothing, and short frames are what a congested link produces.
Q5 — Why must preemption be verified before use?
Because a receiver that does not implement it discards every fragment, and the symptom points at the physical layer. An unrecognised SMD or a failed residue increments the FCS error counter, so a link where 10% of frames are preempted shows a 10% CRC error rate — and Chapter 6.1's framing sends that investigation to the cable and the optics. The handshake — SMD-V until SMD-R, three attempts — fails closed: no response means preemption stays off, the guard band stays large, and whole frames always work. And it re-runs after every link event, because a flapped link may be a different partner.
Q6 — Why can't you assert that a frame's octets are contiguous on the wire?
Because the mechanism's entire purpose is to make that false. Every chapter before this one assumed it, usually without writing it down, and Chapter 12.6 §8's un-abortable frame is the fact three chapters relied on. The property is not merely false — it is false by design, with no window to except, because a preempted frame is never contiguous at any instant. The fix is to re-scope rather than to re-bound: assert contiguity of a fragment and of an express frame, both of which have it, and assert content preservation of the reassembled frame, which is the guarantee anything actually depended on.
24. Understanding Check
25. What's Next
Module 17 is complete, and its three chapters are one argument.
Chapter 17.1 established that five of a hop's seven latency terms are bounded and sum to 37.12 µs, and that two have no bound at all. Chapter 17.2 bounded them with a schedule and paid a guard band of 12.192 µs at 1 Gb/s — which at short cycles consumed the window entirely. And this chapter took the band to 0.560 µs by making Chapter 12.6 §8's un-abortable frame abortable.
The result is a network that can promise a deadline. Five hops at 1 Gb/s with a 31.25 µs cycle: a 210.6 µs worst case and 18.21% of the link delivered — against an unbounded worst case and no promise at all before Module 17.
And the cost, assembled across the three chapters: a synchronised clock (Chapter 16.5's 24.2 ns), a schedule installed atomically across every hop, a guard band, two MACs, two CRC engines and two maximum-frame buffers per port — and the requirement that every device in the path participates, which Chapter 17.1 §22's last callout identified as the condition six mechanisms in this track now share.
Module 18 — Ethernet in SoCs — changes the subject entirely.
Everything from Module 2 to here has been about what happens on the wire and inside a switch. Module 18 is about what happens where the MAC meets the rest of a chip: the register interface the CPU drives, the memory the MAC masters, the clock domains between them, and the descriptor rings and DMA engines that move frames into and out of host memory.
Chapter 18.1 — The Ethernet MAC as an SoC IP Block frames the four interfaces and derives the memory bandwidth a MAC must sustain at each line rate. Then Chapter 18.2 builds the descriptor ring and its ownership model — and finds that the ownership bit is a memory location written by two agents in different clock and coherency domains, so the order of the writes is the entire correctness argument.
One thread carries across, and it is this chapter's. Preemption's whole content was that a unit which looked atomic — a frame on the wire — is not, and every property attached to it had to be re-scoped. Module 18's descriptor is the same discovery in memory: a structure that looks like one object is a set of writes a memory system may reorder, and the barrier that fixes it is the guard band of that chapter.
Continue learning
Related tutorials
- Related topic
What Determinism Requires
Five of a hop's seven latency terms are bounded and sum to 37 microseconds. The other two have no bound at all, and no amount of engineering gives them one.
- Related topic
Time-Aware Shaping (802.1Qbv)
A schedule turns an unbounded interference term into a configuration parameter, and then spends up to 19.5% of the cycle on a guard band to stay safe.
- Related topic
The Shared-Medium Problem
Why several independent transmitters on one medium is a distributed timing problem, not a formatting problem. Propagation delay makes every station's view of the medium stale, so two locally correct decisions can still collide — and that is the constraint the Ethernet MAC was built around.
- Related topic
CSMA/CD, Collision Domains and Slot Time
Slot time is the parameter the whole half-duplex MAC hangs on: it bounds medium acquisition, bounds a collision fragment, and is the retransmission quantum. Deriving it from round-trip propagation plus jam is what fixes Ethernet's minimum frame size — a timing constant wearing a frame-format costume.
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.
