Ethernet · Module 25
"Ethernet Equals TCP/IP"
A receive MAC parses 18 octets of a 1 518-octet frame, Ethernet names 64 000 protocols of which IP is two, and 66.2% of a modern NIC is the cost of crossing the boundary.
A myth chapter is not a lighter chapter. This one meets the same floor as every chapter in Module 23 and does the same thing: it derives the number that kills the myth rather than asserting the correction.
The myth, stated as the people who hold it would state it. Ethernet is the network. A network carries IP packets, and on those packets TCP provides reliable connections. So an Ethernet interface is a TCP/IP interface and the terms are interchangeable in ordinary conversation.
Three numbers dispose of it and this chapter derives all three.
| The number | Section | |
|---|---|---|
| what a receive MAC actually parses | 18 octets of a 1 518-octet frame — 1.19% | 2 |
| how many protocol identifiers Ethernet can carry | 64 000, of which IPv4 and IPv6 are two | 4 |
| what crossing the boundary costs, in one real part | 66.2% of Chapter 23.4's NIC | 10 |
Row three is the one worth stating first, because it converts a definitional argument into a silicon one. Chapter 23.4 §9 derived that a high-rate NIC's connection context cache is 3.356 × 10⁷ BCE — 118 MAC receive datapaths, 66.2% of the whole part. Every bit of it is above the Ethernet boundary, and a pure MAC holds none of it.
The myth is not merely imprecise. It is the reason two thirds of a modern network interface card exists, and it costs 118 MAC datapaths to act on.
1. Scope — The Myth, and the Eighteen Octets That Kill It
This chapter owns four derivations and a boundary built in RTL.
| What is derived | |
|---|---|
| Sections 2 to 3 | the parse window: 18 octets, 1.19% of a maximum frame, and zero edges above offset 21 |
| Sections 4 to 7 | the EtherType space, and what a MAC structurally cannot see |
| Sections 8 to 9 | the one place the two layers genuinely touch — the MTU — and what the coupling costs |
| Sections 10 to 13 | what crossing the boundary costs: a 9.8× parser window and two thirds of a NIC |
What this chapter does not own. It is not a TCP/IP tutorial: congestion control, the three-way handshake, routing protocols and the socket API are all outside it. It uses exactly as much of the upper layers as the boundary argument needs.
And it does not repeat Chapter 2.4. That chapter established where Ethernet stops and built an opaque payload path whose port list makes interpretation impossible. This chapter starts from that boundary and asks what it costs to cross it, which is a silicon question that chapter did not ask.
2. What a MAC Actually Reads
Count the octets. A receive MAC touches eighteen of them and the count does not grow with the frame.
| Field | Offset | Octets | What the MAC does with it |
|---|---|---|---|
| destination address | 0–5 | 6 | Chapter 7.4's filter — accept or discard |
| source address | 6–11 | 6 | Chapter 12.2's learning, in a switch; nothing, in an end station |
| EtherType or length | 12–13 | 2 | Chapter 5.5 — decide which consumer gets the payload |
| payload | 14 … | 46–1 500 | NOTHING — it is moved, not read |
| frame check sequence | last 4 | 4 | Chapter 5.8 — verify and discard |
Eighteen octets, and every one of them is a property of the container.
| Frame | Octets parsed | Share of the frame | Share of the wire slot |
|---|---|---|---|
| 64 octets | 18 | 28.12% | 21.43% |
| 1 518 octets | 18 | 1.19% | 1.17% |
| 9 018 octets | 18 | 0.20% | 0.20% |
On a maximum-size frame a receive MAC reads 1.19% of what arrives, and the 98.81% it does not read is where TCP/IP lives.
Now locate the upper layers against those offsets.
octet 0 6 12 14 34
+-----------------+----------------+-------+-----------------+
| destination | source | type | IPv4 header ... | TCP ...
+-----------------+----------------+-------+-----------------+
|<------ the MAC's last parsed octet is 13 ------>|
|<-- 20 octets -->|<-- 20 -->|With Chapter 13.2's 802.1Q tag the MAC's window extends to octet 21 and the IPv4 header begins at 22. In neither case does it overlap.
| Untagged | Tagged | |
|---|---|---|
| MAC's last parsed octet | 13 | 21 |
| IPv4 header begins at | 14 | 22 |
| TCP header begins at | 34 | 42 |
| overlap | none | none |
And the forty octets of IPv4 and TCP minimum headers are forty octets the MAC's state machine has no state for. Chapter 19.2 §4's receive parser is a state machine over the offsets above; there is no transition in it conditioned on an octet above 21, and adding one would be the boundary violation Chapter 2.4 §7's opaque payload path was built to make structurally impossible.
3. RTL 1 — The Myth Package and the Parse-Window Model
// ---------------------------------------------------------------------
// layermyth_pkg -- the offsets and sizes a layer-boundary argument
// needs, every one of them derived rather than written as a literal.
//
// Unit: Chapter 23.3 Section 2's bitcell equivalent.
// 1 BCE = one bit of usable on-die SRAM = 0.35 GE
// 1 flip-flop = 20 BCE
// Chapter 19.7 Section 19's MAC receive datapath = 283 320 BCE
// Chapter 23.4's NIC = 5.07e7 BCE, of which 66.2% is context cache
// ---------------------------------------------------------------------
package layermyth_pkg;
localparam int unsigned DATAPATH_BCE = 283_320;
localparam int unsigned BCE_PER_FLOP = 20;
// ---- what a MAC reads -------------------------------------------------
localparam int unsigned OCT_DA = 6;
localparam int unsigned OCT_SA = 6;
localparam int unsigned OCT_TYPE = 2;
localparam int unsigned OCT_FCS = 4;
localparam int unsigned OCT_VLAN_TAG = 4; // Chapter 13.2
localparam int unsigned MAC_PARSED = OCT_DA + OCT_SA + OCT_TYPE
+ OCT_FCS; // 18
// The last octet offset a MAC's state machine may branch on.
localparam int unsigned MAC_LAST_OFF_UNTAGGED = 13;
localparam int unsigned MAC_LAST_OFF_TAGGED = 21;
// ---- what is above it --------------------------------------------------
localparam int unsigned OCT_IPV4_MIN = 20;
localparam int unsigned OCT_IPV4_MAX = 60; // with options
localparam int unsigned OCT_IPV6_FIX = 40;
localparam int unsigned OCT_TCP_MIN = 20;
localparam int unsigned OCT_TCP_MAX = 60; // with options
localparam int unsigned OCT_UDP = 8;
// ---- the EtherType space ----------------------------------------------
// Values at or below 1500 are a length (802.3); 1536 and above are a
// type. So the type space is 65536 - 1536.
localparam int unsigned TYPE_SPACE_LOW = 1536;
localparam int unsigned TYPE_SPACE_SIZE = 65_536 - TYPE_SPACE_LOW; // 64000
typedef enum logic [15:0] {
ET_IPV4 = 16'h0800,
ET_ARP = 16'h0806,
ET_VLAN = 16'h8100,
ET_IPV6 = 16'h86DD,
ET_LACP = 16'h8809,
ET_PPPOE_D = 16'h8863,
ET_PPPOE_S = 16'h8864,
ET_PROFINET = 16'h8892,
ET_ECAT = 16'h88A4,
ET_MACSEC = 16'h88E5,
ET_LLDP = 16'h88CC,
ET_PTP = 16'h88F7,
ET_FCOE = 16'h8906,
ET_ROCE = 16'h8915
} ethertype_e;
localparam int unsigned COMMON_TYPES = 14;
localparam int unsigned IP_TYPES = 2; // IPv4 and IPv6
// ---- derived: the parse window ----------------------------------------
function automatic int unsigned mac_window(bit tagged);
return tagged ? (MAC_LAST_OFF_TAGGED + 1)
: (MAC_LAST_OFF_UNTAGGED + 1);
endfunction
function automatic int unsigned offload_window(int unsigned tunnels);
// Ethernet header, then IPv4 with options and TCP with options,
// then one more (outer IP, UDP, inner Ethernet) per tunnel.
int unsigned w;
w = (OCT_DA + OCT_SA + OCT_TYPE) + OCT_IPV4_MAX + OCT_TCP_MAX;
for (int i = 0; i < tunnels; i++)
w = w + OCT_IPV4_MAX + OCT_UDP + (OCT_DA + OCT_SA + OCT_TYPE);
return w;
endfunction
function automatic int unsigned window_bce(int unsigned octets);
return octets * 8 * BCE_PER_FLOP; // held in registers
endfunction
// ---- derived: share of a frame ----------------------------------------
function automatic int unsigned parsed_ppm(int unsigned frame_octets);
return (MAC_PARSED * 1_000_000) / frame_octets;
endfunction
function automatic int unsigned datapaths_milli(int unsigned bce);
return (bce * 1000) / DATAPATH_BCE;
endfunction
endpackage// ---------------------------------------------------------------------
// parse_window_model -- how far into a frame a design must look, and
// what that costs, at each of three positions on the boundary.
//
// The output that matters is highest_offset_branched_on. For a MAC it
// is 13, or 21 with a tag. Anything above that is a design that has
// decided to cross the boundary, and Section 11 prices the decision.
// ---------------------------------------------------------------------
module parse_window_model
import layermyth_pkg::*;
(
input logic clk,
input logic rst_n,
input logic tagged,
input logic crosses_boundary,
input logic [15:0] tunnel_levels,
input logic [15:0] frame_octets,
output logic [15:0] window_octets,
output logic [15:0] highest_offset_branched_on,
output logic [31:0] window_bce_o,
output logic [31:0] window_dp_milli,
output logic [15:0] growth_x10,
output logic [31:0] parsed_ppm_o,
output logic mac_remains_opaque,
output logic payload_is_read,
output logic [31:0] c_frames_parsed
);
logic [15:0] base_window;
always_comb begin
base_window = 16'(mac_window(tagged));
window_octets = crosses_boundary
? 16'(offload_window(int'(tunnel_levels)))
: base_window;
// The one number that says which side of the boundary a design is on.
highest_offset_branched_on = window_octets - 16'd1;
window_bce_o = 32'(window_bce(int'(window_octets)));
window_dp_milli = 32'(datapaths_milli(window_bce_o));
growth_x10 = (base_window == 0) ? 16'd0
: (window_octets * 16'd10) / base_window;
parsed_ppm_o = (frame_octets == 0) ? 32'd0
: 32'(parsed_ppm(int'(frame_octets)));
// A MAC that has not crossed the boundary reads the container and
// moves the contents -- Chapter 2.4 Section 7's opaque path.
mac_remains_opaque = !crosses_boundary;
payload_is_read = crosses_boundary;
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) c_frames_parsed <= '0;
else c_frames_parsed <= c_frames_parsed + 32'd1;
end
endmoduleClassification: an offset model whose single output decides which layer a design belongs to.
What it teaches: that highest_offset_branched_on is the operational definition of the boundary, and it is 13 or 21. A design that branches on octet 22 of an untagged frame is reading an IP header, whatever its documentation says, and Chapter 2.4 §7's opaque payload path is the structure that makes the number unable to rise.
And it teaches that growth_x10 reports 61 and 98 for the two offload cases. A tagged MAC holds 22 octets — 176 bits, 3 520 BCE; a design that offloads TCP checksum over IPv4 with options holds 134 octets — 21 440 BCE, 6.1×; one that must see through a tunnel holds 216 octets — 34 560 BCE, 9.8×. Those are small absolute numbers and a large ratio, and the ratio is what the myth costs a parser.
Deliberately simplified: offload_window assumes worst-case IPv4 and TCP option lengths, where most traffic carries neither and a real parser is opportunistic — it holds what it has seen and stalls if a field turns out to be further in. IPv6 extension headers are not modelled and they are unbounded in principle, which is the case that breaks every fixed-window parser. And the window is charged as registers at 20 BCE per bit, where a design that buffers the header in a small SRAM pays 1 BCE per bit and a cycle of latency.
Production implication: the unbounded case is the one that ships as a vulnerability rather than as a bug. An IPv6 extension-header chain has no specified maximum length, so a parser with a fixed window must decide what to do when the chain runs past it: stop parsing and forward without offload, or drop. Designs that silently forward with an incorrect checksum are the common failure, and the symptom is corrupted data on one traffic class that no counter reports, because the design believes it computed a checksum. The correct behaviour is to expose a parse_incomplete bit per frame and let the software decide — one bit in the receive descriptor, and it turns a silent corruption into a handled case.
4. The EtherType Space, and How Much of It Is IP
Ethernet's payload-type field is sixteen bits. Count what that means and the myth's second half disposes of itself.
Chapter 5.5 established the encoding: a value at or below 1 500 is a length in the 802.3 sense, and 1 536 or above is a type. So:
type values = 65 536 - 1 536 = 64 000 distinct protocol identifiers
IPv4 (0x0800) and IPv6 (0x86DD) are two of them
2 / 64 000 = 0.00313%Ethernet can name sixty-four thousand payload protocols. IP is two of them, and the frame format has no opinion about which.
The counting argument is weak on its own — most of the space is unallocated — so count what an ordinary port actually sees.
| EtherType | Protocol | Carries an IP octet? |
|---|---|---|
| 0x0800 | IPv4 | yes |
| 0x0806 | ARP | no |
| 0x8100 | 802.1Q VLAN tag — Chapter 13.2 | it is a shim, not a payload |
| 0x86DD | IPv6 | yes |
| 0x8809 | LACP and other slow protocols — Chapter 15.3 | no |
| 0x8863 / 0x8864 | PPPoE discovery and session | encapsulates, does not contain |
| 0x8892 | PROFINET | no |
| 0x88A4 | EtherCAT | no |
| 0x88CC | LLDP | no |
| 0x88E5 | MACsec | no — it encrypts what follows |
| 0x88F7 | PTP — Chapter 16.2 | no, in its layer-2 mapping |
| 0x8906 | FCoE | no |
| 0x8915 | RoCEv1 — Chapter 24.2 §10 | no |
Two of thirteen carry IP — 15.4% — and the other eleven are protocols a real network carries daily.
Three of them are worth a sentence because they are this track's own subjects.
Chapter 16.2's precision time protocol has a layer-2 mapping — EtherType 0x88F7 — and it is the one deployments use when they care about Chapter 16.5's accuracy limits, because every IP hop adds variable delay that the servo must then estimate. So the highest-accuracy time distribution on Ethernet deliberately does not use IP.
Chapter 24.2 §10's RoCEv1 is an EtherType. An entire remote-DMA transport, moving the traffic Chapter 23.2's clusters run on, rides Ethernet with no IP header at all.
And MACsec encrypts everything above itself, including any IP header there may be. A switch that classifies on IP fields sees nothing on a MACsec-protected link, which is the point of MACsec and a surprise to designs that assumed the fields were there.
5. RTL 2 — The EtherType Demultiplexer, Counted
// ---------------------------------------------------------------------
// ethertype_demux -- Chapter 2.4 Section 3's demultiplexer, with the
// counters that make the myth falsifiable on a live port.
//
// The output that matters is ip_share_ppm. Run this on a real port for
// an hour and the myth is settled empirically rather than by argument.
// ---------------------------------------------------------------------
module ethertype_demux
import layermyth_pkg::*;
(
input logic clk,
input logic rst_n,
input logic clear,
input logic type_valid,
input logic [15:0] ethertype,
input logic [15:0] frame_octets,
output logic is_length_field,
output logic is_ip,
output logic is_known_non_ip,
output logic is_unknown,
output logic [3:0] consumer_id,
output logic [47:0] c_frames,
output logic [47:0] c_ip_frames,
output logic [47:0] c_non_ip_frames,
output logic [47:0] c_unknown_frames,
output logic [47:0] c_ip_octets,
output logic [47:0] c_non_ip_octets,
output logic [31:0] ip_share_ppm,
output logic [31:0] ip_octet_share_ppm,
output logic [31:0] type_space_size
);
always_comb begin
// Chapter 5.5's encoding: at or below 1500 it is a length.
is_length_field = (ethertype <= 16'd1500);
is_ip = type_valid && !is_length_field
&& ((ethertype == 16'(ET_IPV4)) || (ethertype == 16'(ET_IPV6)));
is_known_non_ip = type_valid && !is_length_field && !is_ip
&& ((ethertype == 16'(ET_ARP)) || (ethertype == 16'(ET_VLAN)) ||
(ethertype == 16'(ET_LACP)) || (ethertype == 16'(ET_PPPOE_D)) ||
(ethertype == 16'(ET_PPPOE_S)) || (ethertype == 16'(ET_PROFINET))||
(ethertype == 16'(ET_ECAT)) || (ethertype == 16'(ET_MACSEC)) ||
(ethertype == 16'(ET_LLDP)) || (ethertype == 16'(ET_PTP)) ||
(ethertype == 16'(ET_FCOE)) || (ethertype == 16'(ET_ROCE)));
is_unknown = type_valid && !is_length_field && !is_ip && !is_known_non_ip;
consumer_id = is_ip ? 4'd1
: is_known_non_ip ? 4'd2
: is_length_field ? 4'd3 // 802.3 LLC path
: 4'd0; // nobody -- discard
type_space_size = 32'(TYPE_SPACE_SIZE);
ip_share_ppm = (c_frames == 0) ? 32'd0
: 32'((c_ip_frames * 48'd1_000_000) / c_frames);
ip_octet_share_ppm =
((c_ip_octets + c_non_ip_octets) == 0) ? 32'd0
: 32'((c_ip_octets * 48'd1_000_000)
/ (c_ip_octets + c_non_ip_octets));
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n || clear) begin
c_frames <= '0; c_ip_frames <= '0; c_non_ip_frames <= '0;
c_unknown_frames <= '0; c_ip_octets <= '0; c_non_ip_octets <= '0;
end else if (type_valid) begin
c_frames <= c_frames + 48'd1;
if (is_ip) begin
c_ip_frames <= c_ip_frames + 48'd1;
c_ip_octets <= c_ip_octets + 48'(frame_octets);
end else begin
c_non_ip_frames <= c_non_ip_frames + 48'd1;
c_non_ip_octets <= c_non_ip_octets + 48'(frame_octets);
end
if (is_unknown) c_unknown_frames <= c_unknown_frames + 48'd1;
end
end
endmoduleClassification: a sixteen-way comparison and six counters, of which the counters are the point.
What it teaches: that the myth is falsifiable in an hour on any real port, and that ip_share_ppm and ip_octet_share_ppm will differ substantially. Non-IP frames are almost all small — LLDP, LACP, PTP and ARP are tens of octets — so a port carrying 8% non-IP frames may carry 0.3% non-IP octets, and which number a design should care about depends entirely on whether the cost is per frame or per octet.
And it teaches that consumer_id of zero is a real outcome that a design must handle. Chapter 2.4 §3's demultiplexer establishes that an EtherType with no registered consumer is discarded at the boundary, and c_unknown_frames counting is either a misconfiguration, a protocol the design does not implement, or traffic somebody added to the network without telling the hardware team.
Deliberately simplified: the comparison is flat where a real design uses a small CAM or a hash, because thirteen sixteen-bit comparators at line rate is a wide, slow structure at a 1 024-bit datapath. The VLAN case is not unwrapped — a frame with EtherType 0x8100 has its real type four octets later, and a design that counts 0x8100 as a protocol has counted a shim. And double tagging is not handled at all, which is Chapter 13.2 §6's problem and the reason a fixed-offset type field is a fiction.
Production implication: the VLAN simplification is the one that produces a wrong measurement rather than a wrong design. A port carrying tagged traffic reports 100% EtherType 0x8100 from this module, so ip_share_ppm reads zero and the myth appears refuted for entirely the wrong reason. The fix is to unwrap the tag before classifying — which means the classifier's input offset is no longer fixed, which is Chapter 19.2 §6's argument that a parser's cost is set by how far into the frame a field's position depends on the frame's own content. One shim costs one conditional; two costs a loop, and a loop at 595 Mpps is a pipeline.
6. What a MAC Cannot See, and What Follows
Section 2 said a MAC reads eighteen octets. This section says what that makes impossible, because the impossibilities are where the myth does damage.
| A MAC cannot tell | Because | What needs it |
|---|---|---|
| a retransmission from an original | sequence numbers are in TCP, at octet 38 | any duplicate suppression |
| a connection from a flow | ports are at octets 34 and 36 | Chapter 23.4's context cache |
| which frames belong together | fragmentation state is in the IP header | reassembly |
| whether delivery succeeded | acknowledgements are end to end | Chapter 24.1 §12's completion notion |
| who the sender is, end to end | the source MAC is this hop's, not the origin's | any end-to-end policy |
| how much to send | congestion windows are a transport concept | rate control |
Row five is the one that produces an operational surprise and it is worth a line of arithmetic. A frame arriving at a host from a station four routers away carries the source MAC of the last router, which means a MAC-address-based access control list on the receiving switch sees one address for every remote station in the world. Chapter 12.2's table on such a port holds one entry for the entire internet.
Row two is the expensive one and Section 10 prices it.
7. RTL 3 — The Opacity Ledger
// ---------------------------------------------------------------------
// opacity_ledger -- what a design knows about a frame, recorded by
// which layer the fact came from.
//
// Chapter 2.4 Section 7 made opacity structural by removing the ports
// through which a payload decision could leave. This does the
// complementary thing: it makes every fact carry its provenance, so a
// boundary violation is visible in a report rather than in a review.
// ---------------------------------------------------------------------
module opacity_ledger
import layermyth_pkg::*;
(
input logic clk,
input logic rst_n,
input logic clear,
input logic frame_valid,
input logic [15:0] frame_octets,
// facts a MAC may produce: all properties of the CONTAINER
input logic fact_length,
input logic fact_fcs_ok,
input logic fact_da_matched,
input logic fact_type,
// facts that require reading the CONTENTS
input logic fact_l3_protocol,
input logic fact_l4_ports,
input logic fact_tcp_flags,
input logic fact_payload_bytes,
output logic [3:0] container_facts,
output logic [3:0] content_facts,
output logic boundary_crossed,
output logic [15:0] deepest_layer,
output logic [47:0] c_container_only,
output logic [47:0] c_content_read,
output logic [31:0] crossing_ppm,
output logic ledger_is_complete
);
always_comb begin
container_facts = { fact_type, fact_da_matched, fact_fcs_ok, fact_length };
content_facts = { fact_payload_bytes, fact_tcp_flags,
fact_l4_ports, fact_l3_protocol };
// One bit, and it is the whole of Chapter 2.4's contract.
boundary_crossed = (content_facts != 4'b0000);
deepest_layer = fact_payload_bytes ? 16'd7
: fact_tcp_flags ? 16'd4
: fact_l4_ports ? 16'd4
: fact_l3_protocol ? 16'd3
: 16'd2;
crossing_ppm = ((c_container_only + c_content_read) == 0) ? 32'd0
: 32'((c_content_read * 48'd1_000_000)
/ (c_container_only + c_content_read));
// Every fact is accounted to a layer. A design that produces a
// fact this module has no input for has escaped the ledger.
ledger_is_complete = 1'b1;
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n || clear) begin
c_container_only <= '0; c_content_read <= '0;
end else if (frame_valid) begin
if (boundary_crossed) c_content_read <= c_content_read + 48'd1;
else c_container_only <= c_container_only + 48'd1;
end
end
endmoduleClassification: a provenance ledger, in which the design's own facts are classified by the layer they came from.
What it teaches: that deepest_layer is a reportable quantity and almost no design reports it. Chapter 2.4 §7 made opacity structural by removing the ports through which a payload decision could leave; that is the strongest form and it is only available when the block is designed that way. This is the retrofit: a design that has already crossed the boundary can at least say how far, and crossing_ppm says on what fraction of its traffic.
And it teaches that boundary_crossed is a single bit with a large consequence for verification. A testbench that asserts a property over content_facts has itself crossed the boundary, and Section 20's rejected property is about exactly that: the property can only be checked by an observer the product is forbidden to contain.
Deliberately simplified: the fact inputs are a fixed set, so a design producing a fact the ledger has no input for escapes it silently — ledger_is_complete is hard-wired true and is a claim rather than a check, which is the module's own instance of the problem it exists to catch. deepest_layer uses a priority encoder where a design reading both layer-3 and layer-7 facts should report both. And the counters do not distinguish reading a field from acting on it, which matters: a design that extracts a five-tuple for a hash has read it, and a design that drops on a TCP flag has acted on it, and only the second is a policy decision at the wrong layer.
Production implication: the distinction the counters miss is the one a security review cares about. Reading an IP field to compute Chapter 15.2's hash is a performance optimisation with no correctness consequence — a bad hash costs balance, not behaviour. Dropping a frame because of a TCP flag is a firewall, and a firewall in a MAC is a policy engine that the operating system does not know exists, cannot configure and cannot audit. The two are indistinguishable in this module and trivially distinguished by adding a fact_acted_on bit beside each fact_* input — four extra bits, and it turns a provenance ledger into an audit trail.
8. The One Place They Touch — the MTU
Six impossibilities and sixty-four thousand EtherTypes say the layers are separate. One number says they are coupled, and it is the only one.
1 500 is an Ethernet number. Chapter 5.7 §2 established where it came from — a bound on how long a station may hold the medium, chosen when the medium was shared — and it has no significance above layer 2 whatsoever.
And it is the number every IP stack in the world defaults to.
| Why 1 500 is there | |
|---|---|
| in Ethernet | a transmission-time bound on a shared medium, decided in 1980 |
| in IP | because the link below it is usually Ethernet |
| in TCP | because the maximum segment size is the IP MTU less 40 |
So the transport's segment size is an Ethernet decision, three layers down, made for a medium that no longer exists.
Quantify the coupling by breaking it. An IP datagram larger than the link's MTU must be fragmented:
a 9 000-octet IP datagram -- 20 octets of header and 8 980 of payload
over a 1 500-octet MTU, each fragment carries 1 480 octets of payload
fragments = ceil(8 980 / 1 480) = 7| Unfragmented, 9 018 MTU | Fragmented, 1 500 MTU | |
|---|---|---|
| frames on the wire | 1 | 7 |
| IP headers | 20 octets | 140 octets |
| Ethernet framing | 38 octets | 266 octets |
| total wire octets | 9 038 | 9 386 |
| overhead penalty | — | 3.85% |
| loss amplification | 1× | 7× — one lost fragment discards the datagram |
Row six is the cost and row five is the one people quote. A 3.85% efficiency penalty is unremarkable; a seven-fold loss amplification is not, and it is the same arithmetic Chapter 24.2 §10 found in RoCE's go-back-N for the same structural reason: a fixed quantity of data split into N pieces, all of which must arrive.
The layers are independent in every respect except one, and the exception costs a seven-fold loss amplification when it is got wrong.
9. RTL 4 — The MTU Coupling Model
// ---------------------------------------------------------------------
// mtu_coupling_model -- the one genuine dependency between the layers,
// and what breaking it costs.
//
// The output that matters is loss_amplification: fragmentation turns
// one datagram into N frames all of which must arrive, so the
// effective loss rate is multiplied rather than added to.
// ---------------------------------------------------------------------
module mtu_coupling_model
import layermyth_pkg::*;
(
input logic clk,
input logic rst_n,
input logic [15:0] link_mtu,
input logic [15:0] datagram_octets,
input logic dont_fragment,
input logic icmp_reaches_sender,
input logic [15:0] drop_rate_ppm,
output logic [15:0] fragment_payload,
output logic [15:0] fragments,
output logic [31:0] wire_octets_frag,
output logic [31:0] wire_octets_whole,
output logic [15:0] overhead_penalty_x100,
output logic [15:0] loss_amplification,
output logic [31:0] effective_loss_ppm,
output logic black_hole,
output logic connection_establishes,
output logic bulk_transfer_fails,
output logic [31:0] c_black_holes
);
always_comb begin
fragment_payload = (link_mtu > 16'(OCT_IPV4_MIN))
? (link_mtu - 16'(OCT_IPV4_MIN)) : 16'd1;
fragments = (datagram_octets <= fragment_payload)
? 16'd1
: ((datagram_octets + fragment_payload - 16'd1)
/ fragment_payload);
wire_octets_whole = 32'(datagram_octets) + 32'(OCT_IPV4_MIN) + 32'd38;
wire_octets_frag = 32'(datagram_octets)
+ (32'(fragments) * (32'(OCT_IPV4_MIN) + 32'd38));
overhead_penalty_x100 =
(wire_octets_whole == 0) ? 16'd0
: 16'(((wire_octets_frag - wire_octets_whole) * 32'd10_000)
/ wire_octets_whole);
// THE number. Every fragment must arrive or the datagram is lost.
loss_amplification = fragments;
effective_loss_ppm = 32'(drop_rate_ppm) * 32'(fragments);
// The failure that establishes and then hangs.
black_hole = dont_fragment && (datagram_octets > fragment_payload)
&& !icmp_reaches_sender;
connection_establishes = 1'b1; // the handshake is small
bulk_transfer_fails = black_hole;
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) c_black_holes <= '0;
else if (black_hole) c_black_holes <= c_black_holes + 32'd1;
end
endmoduleClassification: a division and a multiplication, of which the multiplication is the one nobody does.
What it teaches: that loss_amplification equals the fragment count and that this is a multiplication rather than an addition. A path with a 10⁻³ drop rate carrying 9 000-octet datagrams over a 1 500-octet MTU has an effective datagram loss rate of 7 × 10⁻³, because all seven fragments must arrive. The link's counters report 0.1% and the application experiences 0.7%, and neither number is wrong.
And it teaches that connection_establishes is hard-wired high, which is why the black hole is expensive. The handshake is small and always works; only bulk transfer fails, so the fault presents as an application problem on a path that passes every connectivity test — and c_black_holes is a counter no device keeps.
Deliberately simplified: the fragment arithmetic ignores that IPv4 fragment offsets are in units of eight octets, so real fragment payloads are rounded down to a multiple of eight — 1 480 happens to be one, which is why the example is clean and a 1 492-octet MTU on a PPPoE link is not. The model assumes IPv4; IPv6 does not permit routers to fragment at all, so the black hole is the only failure mode there. And drop_rate_ppm is applied independently per fragment, where losses are correlated: a congested queue drops bursts, and a burst is likely to take several fragments of one datagram together, which makes the real amplification lower than fragments and the variance higher.
Production implication: the correlation cuts the other way for the diagnosis. Independent losses would make fragmented traffic uniformly a little worse; correlated losses make it occasionally much worse, so the symptom is not a slightly elevated error rate — it is a transfer that is fine for minutes and then stalls. The defence in hardware is to make the coupling visible: a receive path that counts fragments separately from whole datagrams costs one comparison of the IP fragment-offset and more-fragments fields — which is a boundary crossing, and Section 7's ledger should record it as one. It is a legitimate crossing with a real diagnostic payoff, and the discipline is to declare it rather than to let it happen in a classifier nobody documented.
10. What Believing the Myth Costs in Silicon
The chapter's strongest argument is not that the layers are separate. It is that crossing the boundary is expensive, and that a real part exists whose cost is two thirds boundary crossing.
Chapter 23.4 §9 derived a high-rate NIC's area.
| Structure | BCE | Share | Which layer? |
|---|---|---|---|
| connection context cache, 16 384 entries | 3.356 × 10⁷ | 66.2% | 4 — TCP connections |
| everything else — MAC, DMA, descriptors, counters | 1.714 × 10⁷ | 33.8% | 2 and the host interface |
| total | 5.070 × 10⁷ | 100% | — |
Two thirds of a modern network interface card is state about transport connections, and a device that stopped at the Ethernet boundary would hold none of it.
And the reason it is there is worth stating precisely, because it is not a mistake. Chapter 23.4 §10 established the cliff: above a 39.4% context-cache miss rate the NIC's throughput collapses, and Chapter 23.4 §12 derived that Chapter 23.2's striping ×32 pushes the connection count to 32 768 against a 16 384-entry cache — a 50% miss rate and 35.1 Gb/s of a 100 Gb/s port.
So the cache is load-bearing. A NIC that offloads TCP must hold connection state, and holding it is 66.2% of the part.
Now price the parser, which is the other half of crossing.
| What the design parses | Window | Held in registers | × the datapath | Growth |
|---|---|---|---|---|
| Ethernet only, VLAN-tagged | 22 octets | 3 520 BCE | 0.012 | 1.0× |
| + IPv4 and TCP, with options | 134 octets | 21 440 BCE | 0.076 | 6.1× |
| + one level of tunnel | 216 octets | 34 560 BCE | 0.122 | 9.8× |
The absolute numbers are small and the ratio is not, and the ratio is what shows up as timing pressure: a 216-octet extraction window at Chapter 23.6 §4's 1 024-bit datapath is two beats of field extraction with content-dependent offsets, which is a much harder timing problem than a 22-octet window at a fixed offset.
Put the two together and the myth's silicon bill is complete.
| BCE | × the datapath | |
|---|---|---|
| connection context, 16 384 entries | 3.356 × 10⁷ | 118.5 |
| a tunnel-capable parser window | 34 560 | 0.122 |
| total, one NIC | 3.359 × 10⁷ | 118.6 |
| a pure MAC's equivalent | 3 520 | 0.012 |
| ratio | 9 545× | — |
Nine and a half thousand times, and every bit of the difference is above octet 21.
11. RTL 5 — The Offload Parser
// ---------------------------------------------------------------------
// offload_parser -- what a design must build when it decides to cross
// the boundary, and the one output that keeps the crossing honest.
//
// parse_incomplete is the whole point. A fixed window meets an
// unbounded header chain eventually, and the only safe behaviour is
// to say so rather than to compute a checksum over the wrong bytes.
// ---------------------------------------------------------------------
module offload_parser
import layermyth_pkg::*;
#(
parameter int unsigned WINDOW_OCTETS = 134
)(
input logic clk,
input logic rst_n,
input logic frame_start,
input logic octet_valid,
input logic [7:0] octet,
input logic [15:0] octet_index,
output logic [15:0] l3_offset,
output logic [15:0] l4_offset,
output logic l3_offset_valid,
output logic l4_offset_valid,
output logic is_ipv4,
output logic is_tcp,
output logic parse_incomplete,
output logic checksum_may_be_computed,
output logic [15:0] deepest_octet_read,
output logic [31:0] window_bce_o,
output logic [31:0] c_incomplete,
output logic [31:0] c_parsed_ok
);
logic [15:0] type_hi, type_lo;
logic [15:0] eth_type;
logic [3:0] ihl;
logic vlan_present;
always_comb begin
window_bce_o = 32'(window_bce(WINDOW_OCTETS));
eth_type = { type_hi[7:0], type_lo[7:0] };
vlan_present = (eth_type == 16'(ET_VLAN));
// Chapter 19.2 Section 6's problem: the offset depends on content.
l3_offset = vlan_present ? 16'd18 : 16'd14;
l3_offset_valid = (octet_index >= l3_offset);
is_ipv4 = l3_offset_valid && (eth_type == 16'(ET_IPV4));
// And again one layer up: the IPv4 header's length is IN the header.
l4_offset = l3_offset + (16'(ihl) * 16'd4);
l4_offset_valid = is_ipv4 && (octet_index >= l4_offset);
is_tcp = l4_offset_valid;
deepest_octet_read = octet_index;
// THE output. A window is finite and a header chain is not.
parse_incomplete = (l4_offset + 16'd20) > 16'(WINDOW_OCTETS);
// Never compute a checksum you could not locate the bounds of.
checksum_may_be_computed = l4_offset_valid && !parse_incomplete;
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
type_hi <= '0; type_lo <= '0; ihl <= '0;
c_incomplete <= '0; c_parsed_ok <= '0;
end else begin
if (frame_start) begin
type_hi <= '0; type_lo <= '0; ihl <= '0;
end
if (octet_valid) begin
if (octet_index == 16'd12) type_hi <= {8'd0, octet};
if (octet_index == 16'd13) type_lo <= {8'd0, octet};
if (octet_index == l3_offset) ihl <= octet[3:0];
if (octet_index == 16'(WINDOW_OCTETS) - 16'd1) begin
if (parse_incomplete) c_incomplete <= c_incomplete + 32'd1;
else c_parsed_ok <= c_parsed_ok + 32'd1;
end
end
end
end
endmoduleClassification: a parser with two content-dependent offsets and one honest failure output.
What it teaches: that the offsets depend on the content twice, at two different layers, and each dependency costs the same thing Chapter 19.2 §6 identified: a field whose position the frame's own content decides cannot be extracted by a fixed mux. The VLAN tag moves l3_offset by four; the IPv4 header's own ihl field moves l4_offset by up to forty — and ihl is inside the header whose length it describes.
And it teaches that parse_incomplete is the output a design must have and usually does not. A fixed window meets an unbounded chain — IPv4 options, IPv6 extension headers, a second tunnel — and the three possible behaviours are: drop, forward without offload, or compute a checksum over whatever happened to be in the window. The third is a silent corruption and it is the common one, because the parser does not know it failed.
Deliberately simplified: the module handles one VLAN tag, not Chapter 13.2 §6's stacked tags, and it handles IPv4 but not IPv6's extension-header chain, which is the case with no bound at all. ihl is captured in one cycle at a computed index, where a real design at a 1 024-bit datapath extracts it with a barrel shifter — Chapter 23.6 §4's aligner, which at 1 024 bits was 41 984 LUT6. And the checksum itself is not built: it is a 16-bit ones-complement adder tree over the datapath, 64 adders at 1 024 bits, whose cost is logic rather than state and therefore outside BCE.
Production implication: the three behaviours on window exhaustion should be a configuration field rather than a design decision, and almost no part exposes one. A switch wants "forward without offload"; a NIC wants "forward and set parse_incomplete in the descriptor"; a security appliance wants "drop" — and all three are correct for their role. A part that hard-codes one of them has made a policy decision for its integrator, and the integrator finds out when a traffic class with IPv6 extension headers either disappears or arrives corrupted. One two-bit configuration register, and one bit in the receive descriptor, is the whole cost of getting it right.
12. Where the Boundary Actually Fails
Sections 10 and 11 priced a deliberate crossing. This section is about the crossings nobody decided to make, and they are all the same shape: a field assumed present at an offset in traffic that has neither.
Five failures, each with a number.
| Failure | The assumption | What it costs |
|---|---|---|
| a hash that finds no IP header | every frame has a five-tuple | all non-IP traffic on one Chapter 15.2 member |
| a filter keyed on a TCP port | the port is at octet 34 | PTP, LLDP, FCoE and RoCEv1 misclassified |
| a checksum at a fixed offset | the IPv4 header is 20 octets | corrupted data whenever an option is present |
| a testbench that only makes IP | the traffic is IP | the non-IP path is never exercised |
| an assertion naming "the source address" | there is one | Section 2's ambiguity; the property means two things |
Row one has the largest number and it is worth deriving. Chapter 15.2 §5 established that a five-tuple hash distributes across an aggregation group's members. A frame with no IP header has no five-tuple, so a hash implemented as extract the tuple, hash it returns a constant on all such frames — and they all take member zero.
| Frames | Where they land | |
|---|---|---|
| IP traffic on an 8-member LAG | 92% | spread across 8 |
| non-IP traffic | 8% | all on member 0 |
| member 0's share | — | 11.5% + 8% = 19.5% |
| the other seven | — | 11.5% each |
A 70% imbalance on one member, and Chapter 23.2 §7 established that a collective runs at its slowest member, so the fabric's delivered bandwidth is set by the imbalanced link rather than by the mean.
And on a fabric carrying FCoE or RoCEv1 the arithmetic is much worse, because those are bulk protocols: a storage fabric where 40% of the octets are FCoE puts 40% of the load on one member of an eight-member group.
The fix is one line and it is in every good hash implementation: hash over whatever fields are actually present, falling back to the MAC addresses and the EtherType when there is no IP header. It costs a multiplexer and it converts a pathological case into a merely imperfect one.
13. RTL 6 — The Tunnel Depth Limiter
// ---------------------------------------------------------------------
// tunnel_depth_limiter -- the structure that makes Section 11's
// unbounded case bounded, and reports when it did.
//
// The output that matters is depth_exceeded, which is how a finite
// design meets an infinite specification honestly.
// ---------------------------------------------------------------------
module tunnel_depth_limiter
import layermyth_pkg::*;
#(
parameter int unsigned MAX_DEPTH = 2,
parameter int unsigned MAX_OCTETS = 216
)(
input logic clk,
input logic rst_n,
input logic frame_start,
input logic header_seen,
input logic [15:0] header_octets,
input logic header_is_encapsulation,
output logic [15:0] depth,
output logic [15:0] octets_consumed,
output logic depth_exceeded,
output logic octets_exceeded,
output logic stop_parsing,
output logic [1:0] action_on_limit, // configured, not hard-coded
output logic may_claim_verified,
output logic [31:0] c_depth_exceeded,
output logic [31:0] c_octets_exceeded
);
// 0 = forward without offload, 1 = forward and flag, 2 = drop.
// Section 11's production note: this is the integrator's choice.
localparam logic [1:0] ACT_NO_OFFLOAD = 2'd0;
always_comb begin
depth_exceeded = (depth > 16'(MAX_DEPTH));
octets_exceeded = (octets_consumed > 16'(MAX_OCTETS));
stop_parsing = depth_exceeded || octets_exceeded;
action_on_limit = ACT_NO_OFFLOAD;
// THE rule. A design that could not locate the bounds must not
// claim to have checked what is inside them.
may_claim_verified = !stop_parsing;
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
depth <= '0; octets_consumed <= '0;
c_depth_exceeded <= '0; c_octets_exceeded <= '0;
end else begin
if (frame_start) begin
depth <= '0; octets_consumed <= '0;
end else if (header_seen) begin
octets_consumed <= octets_consumed + header_octets;
if (header_is_encapsulation) depth <= depth + 16'd1;
if ((depth + 16'd1) > 16'(MAX_DEPTH))
c_depth_exceeded <= c_depth_exceeded + 32'd1;
if ((octets_consumed + header_octets) > 16'(MAX_OCTETS))
c_octets_exceeded <= c_octets_exceeded + 32'd1;
end
end
end
endmoduleClassification: two counters and a prohibition, which is how a finite parser meets an unbounded format.
What it teaches: that may_claim_verified is the one signal that makes the limiter safe rather than merely present. A parser that stops has not failed; a parser that stops and still reports a verified checksum has. The distinction is one AND gate and it separates a graceful degradation from a silent data corruption.
And it teaches that two limits are needed rather than one. MAX_DEPTH bounds encapsulation levels and MAX_OCTETS bounds total header bytes, and neither implies the other: a single IPv6 packet with a long extension-header chain has depth 1 and can exhaust the octet budget, while three trivially small tunnels exhaust the depth budget at a fraction of it. A design with only a depth limit is unbounded in octets, which is the case that actually ships.
Deliberately simplified: the limiter consumes headers one at a time where a real parser at a 1 024-bit datapath sees several per beat and must evaluate the limits combinationally across all of them. action_on_limit is a localparam rather than the configuration register Section 11's production note argued for, which is the simplification the note itself identifies as wrong. And header_is_encapsulation is supplied from outside, so the module does not decide what counts as a tunnel — which is the interesting question, since a VLAN tag, a PPPoE header and a VXLAN header are all encapsulations at different layers.
Production implication: the question the module does not answer is the one that decides whether a limit is a security control. A design counting only IP-in-IP tunnels toward MAX_DEPTH can be driven to arbitrary parse depth with stacked VLAN tags, each of which is four octets and none of which increments the counter. The octet budget catches it and the depth budget does not, which is the argument for having both: an attacker chooses the encapsulation the counter does not count, and only a limit on total work is immune to that choice. The general rule is worth carrying: bound the resource, not the construct — a limit on levels bounds a category and a limit on octets bounds the work, and the work is what runs out.
14. What a MAC Must Never Do
Five prohibitions, and each has a failure in this chapter behind it.
| # | Never | Because |
|---|---|---|
| 1 | branch on an octet above 13 — or 21 with a tag — without declaring it | Section 3: that offset is the operational definition of the boundary |
| 2 | claim a checksum was verified over a range you could not locate | Section 12: on receive it is a silent corruption with no counter |
| 3 | hash a five-tuple that is not there | Section 12: 70% imbalance on one Chapter 15.2 member |
| 4 | bound a parser by encapsulation levels alone | Section 13: stacked VLAN tags increment no tunnel counter |
| 5 | write a property naming "the source address" | Section 2: there are two, they differ after a router, and only one is checkable here |
Row one is the one to enforce structurally rather than by review, and Chapter 2.4 §7 showed how: remove the ports through which a payload decision could leave. A block whose port list cannot express a content-derived output cannot violate the boundary, and no review is required.
Row five is the one that reaches sign-off, and it is Section 20's refused property.
15. RTL 7 — Layer Telemetry
// ---------------------------------------------------------------------
// layer_telemetry -- the counters that make the myth falsifiable on a
// live port, and the boundary auditable on a live design.
//
// Design rule: every counter here answers a question whose answer the
// myth predicts wrongly.
// ---------------------------------------------------------------------
module layer_telemetry
import layermyth_pkg::*;
(
input logic clk,
input logic rst_n,
input logic clear,
input logic frame_valid,
input logic [15:0] frame_octets,
input logic is_ip_i,
input logic boundary_crossed_i,
input logic parse_incomplete_i,
input logic verified_claimed_i,
input logic hash_had_tuple_i,
input logic [15:0] deepest_offset_i,
input logic fragment_seen_i,
output logic [47:0] c_frames,
output logic [47:0] c_ip,
output logic [47:0] c_non_ip,
output logic [47:0] c_crossings,
output logic [47:0] c_parse_incomplete,
output logic [47:0] c_unverifiable_claims,
output logic [47:0] c_hash_no_tuple,
output logic [47:0] c_fragments,
output logic [15:0] deepest_offset_seen,
output logic [31:0] non_ip_ppm,
output logic [31:0] crossing_ppm_o,
output logic design_is_layer2_only
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n || clear) begin
c_frames <= '0; c_ip <= '0; c_non_ip <= '0; c_crossings <= '0;
c_parse_incomplete <= '0; c_unverifiable_claims <= '0;
c_hash_no_tuple <= '0; c_fragments <= '0; deepest_offset_seen <= '0;
end else if (frame_valid) begin
c_frames <= c_frames + 48'd1;
if (is_ip_i) c_ip <= c_ip + 48'd1;
else c_non_ip <= c_non_ip + 48'd1;
if (boundary_crossed_i) c_crossings <= c_crossings + 48'd1;
if (parse_incomplete_i) c_parse_incomplete <= c_parse_incomplete + 48'd1;
// THE counter that finds Section 12's silent corruption.
if (parse_incomplete_i && verified_claimed_i)
c_unverifiable_claims <= c_unverifiable_claims + 48'd1;
if (!hash_had_tuple_i) c_hash_no_tuple <= c_hash_no_tuple + 48'd1;
if (fragment_seen_i) c_fragments <= c_fragments + 48'd1;
if (deepest_offset_i > deepest_offset_seen)
deepest_offset_seen <= deepest_offset_i;
end
end
always_comb begin
non_ip_ppm = (c_frames == 0) ? 32'd0
: 32'((c_non_ip * 48'd1_000_000) / c_frames);
crossing_ppm_o = (c_frames == 0) ? 32'd0
: 32'((c_crossings * 48'd1_000_000) / c_frames);
// A design that has never branched above octet 21 is layer 2, and
// it can say so with evidence rather than with documentation.
design_is_layer2_only = (deepest_offset_seen <= 16'd21);
end
endmoduleClassification: a counter bank in which one counter — c_unverifiable_claims — should always read zero and is the only one worth an alarm.
What it teaches: that c_unverifiable_claims is the hardware form of Section 12's worst failure and it costs one AND gate. parse_incomplete AND verified_claimed is a design reporting a checksum it could not have computed, and it is the silent receive corruption that no other counter in any part reports. Any nonzero value is a bug, not a workload characteristic.
And it teaches that design_is_layer2_only converts a documentation claim into a measurement. A part whose datasheet says it is a layer-2 device and whose deepest_offset_seen reads 134 has crossed the boundary in the field, whatever its architecture document says, and the evidence is one 16-bit high-water register.
Deliberately simplified: deepest_offset_seen is a high-water mark that never decays, so one malformed frame during bring-up latches it forever — a real design should make it clear-on-read or window it. non_ip_ppm counts frames rather than octets, and Section 5 established the two differ substantially. And the module cannot see the difference between reading a field and acting on it, which is Section 7's ledger's limitation inherited.
Production implication: the high-water mark's stickiness is a feature during verification and a nuisance in the field, and the resolution is to have both. A latching high-water register answers "has this design ever crossed the boundary", which is the verification question; a windowed one answers "is it crossing now", which is the operational question. Two 16-bit registers — 640 BCE, 0.002 datapaths — and they answer two questions that a single register answers badly. The general habit is Chapter 19.7 §6's argument about clear-on-read against sticky semantics, arriving at a counter whose subject is the design's own behaviour rather than the traffic's.
16. RTL 8 — The Boundary Conformance Monitor
// ---------------------------------------------------------------------
// layer_conformance -- the checks that hold a design to the layer it
// claims to be.
//
// Every check is about the design rather than about the traffic, which
// is what distinguishes a boundary monitor from a protocol monitor.
// ---------------------------------------------------------------------
module layer_conformance
import layermyth_pkg::*;
(
input logic clk,
input logic rst_n,
input logic claims_layer2_only,
input logic [15:0] deepest_offset_i,
input logic tagged_i,
input logic parse_incomplete_i,
input logic verified_claimed_i,
input logic hash_had_tuple_i,
input logic hash_used_fallback_i,
input logic payload_octet_used_i,
input logic offload_declared_i,
output logic v_boundary_crossed_undeclared,
output logic v_unverifiable_claim,
output logic v_hash_without_fallback,
output logic v_payload_read_undeclared,
output logic v_claim_contradicted,
output logic [4:0] violations,
output logic conformant
);
logic [15:0] mac_limit;
always_comb begin
mac_limit = tagged_i ? 16'(MAC_LAST_OFF_TAGGED)
: 16'(MAC_LAST_OFF_UNTAGGED);
// 1. Prohibition 1 -- crossing is allowed and must be declared.
v_boundary_crossed_undeclared = (deepest_offset_i > mac_limit)
&& !offload_declared_i;
// 2. Prohibition 2 -- Section 12's silent receive corruption.
v_unverifiable_claim = parse_incomplete_i && verified_claimed_i;
// 3. Prohibition 3 -- a hash with no tuple must fall back.
v_hash_without_fallback = !hash_had_tuple_i && !hash_used_fallback_i;
// 4. Chapter 2.4 Section 7's contract: the payload is moved, not read.
v_payload_read_undeclared = payload_octet_used_i && !offload_declared_i;
// 5. And the claim must match the evidence.
v_claim_contradicted = claims_layer2_only
&& (deepest_offset_i > mac_limit);
violations = { v_claim_contradicted, v_payload_read_undeclared,
v_hash_without_fallback, v_unverifiable_claim,
v_boundary_crossed_undeclared };
conformant = (violations == 5'b00000);
end
endmoduleClassification: five checks whose subject is the design's own layering rather than the protocol's correctness.
What it teaches: that a boundary monitor is a different object from a protocol monitor, and that the difference is what it quantifies over. Chapter 19.1 §16's monitor checks a MAC against the frame format; this one checks a MAC against a claim about which layer it is. Every signal it consumes is a fact about the design, and none of them is in the frame.
And it teaches that v_boundary_crossed_undeclared makes crossing legal and undeclared crossing illegal, which is the correct policy. Chapter 2.4 §9 called offload the deliberate violation; the monitor enforces the word deliberate by requiring offload_declared_i, which is a configuration bit an architect sets rather than something the datapath can assert for itself.
Deliberately simplified: offload_declared_i is an input the monitor trusts, so a design that sets it unconditionally passes every check — which makes the monitor a documentation aid rather than a guarantee, and there is no way around that at this level. v_hash_without_fallback assumes one hash, where a real part has several for different purposes. And the monitor has no check for Section 13's stacked-tag depth attack, because depth is a property of a sequence and this is combinational.
Production implication: offload_declared_i being trusted is the reason this monitor belongs in a design review rather than only in a regression. Its value is that it forces somebody to set a bit, and setting a bit means a name is attached to a decision that otherwise happens implicitly in a classifier. Teams that run it find between one and three undeclared crossings in a mature design — almost always a hash, a filter or a statistics counter that reaches one field further than anybody remembered — and each one is either legitimate and should be declared, or a bug. The exercise costs an afternoon and is the cheapest thing in this chapter.
17. The Two Layers, Priced Side by Side
Everything this chapter derived, in one table.
| Quantity | A pure MAC | A design that crosses | Ratio |
|---|---|---|---|
| octets parsed, maximum frame | 18 — 1.19% | up to 216 — 14.2% | 12× |
| highest offset branched on | 13, or 21 tagged | 215 | 10× |
| parse window held | 3 520 BCE | 34 560 BCE | 9.8× |
| connection state | 0 | 3.356 × 10⁷ BCE — 118.5 datapaths | — |
| total | 3 520 BCE | 3.359 × 10⁷ BCE | 9 545× |
| share of Chapter 23.4's NIC | 0.007% | 66.2% | — |
| protocols it can carry | 64 000 | the ones it parses | — |
| failure when the assumption breaks | none — there is none | Section 12's five | — |
A pure MAC costs 3 520 bits of parse window and can carry sixty-four thousand protocols. A design that crosses the boundary costs nine and a half thousand times that and works for the ones it was taught.
And the myth's three numbers, restated.
| What the myth predicts | What is true | |
|---|---|---|
| what a MAC reads | the packet | 18 octets — 1.19% of a maximum frame |
| what Ethernet carries | IP | 64 000 protocol identifiers; IP is 2, and 11 of 13 common ones carry none |
| what a NIC is | an Ethernet interface | 66.2% transport connection state |
18. What the Correction Assumes
Eight assumptions, each with its direction of failure.
| # | Assumption | If it is false |
|---|---|---|
| 1 | a MAC parses 18 octets | a design that also parses the VLAN tag parses 22; the argument is unchanged |
| 2 | type values are 1 536 and above | Chapter 5.5's encoding; the 64 000 figure follows from it |
| 3 | thirteen common EtherTypes | a count from ordinary deployments; the ratio moves and the conclusion does not |
| 4 | IPv4 options are up to 40 octets | the offload window shrinks proportionally if a design refuses them |
| 5 | Chapter 23.4's NIC is 66.2% context cache | that chapter's figure; a part without TCP offload is 0% |
| 6 | a 16 384-entry context cache | linear — 4 096 entries is 8.39 × 10⁶ BCE and a lower cliff |
| 7 | 1 480 octets per IPv4 fragment | a 1 500 MTU less a 20-octet header; options reduce it |
| 8 | BCE applies | Section 19 examines it; it holds for the state and not for the checksum logic |
Assumption 5 is the one that carries the chapter's headline and it deserves the caveat. A NIC without TCP offload has no context cache at all, so its whole area is below the boundary and the 66.2% figure is zero. That is not a counterexample; it is the chapter's point made the other way — the 66.2% is what crossing costs, and a part that does not cross does not pay it.
19. The Cost, Accounted — in BCE
This chapter's blocks.
| Block | Flops | BCE | × the datapath |
|---|---|---|---|
parse_window_model | 32 | 640 | 0.002 |
ethertype_demux | 336 | 6 720 | 0.024 |
opacity_ledger | 112 | 2 240 | 0.008 |
mtu_coupling_model | 32 | 640 | 0.002 |
offload_parser, 134-octet window | 1 072 + 40 | 22 240 | 0.078 |
tunnel_depth_limiter | 96 | 1 920 | 0.007 |
layer_telemetry | 400 | 8 000 | 0.028 |
layer_conformance | 0 — combinational | 0 | 0 |
| this chapter's additions | 2 120 | 42 400 | 0.150 |
offload_parser is 52.5% of the total and it is the block that crosses the boundary, which is the chapter's structure showing through: the expensive thing about believing the myth is holding the octets you would not otherwise hold.
And the designs the blocks describe.
| BCE | × the datapath | |
|---|---|---|
| a pure MAC's parse window, tagged | 3 520 | 0.012 |
| an offload parser's, with a tunnel | 34 560 | 0.122 |
| Chapter 23.4's connection context cache | 3.356 × 10⁷ | 118.5 |
| Chapter 23.4's whole NIC | 5.070 × 10⁷ | 179 |
| Chapter 19.7 §19's MAC receive datapath | 283 320 | 1 |
20. Properties Worth Asserting, and One Worth Refusing
Fifty-one properties in six groups, and the refused one is the natural way to verify an offload.
Group A — the parse window (9).
// A1. A MAC's window is 14 octets, or 22 with a tag.
p_pw_mac_window: assert property (@(posedge clk) disable iff (!rst_n)
!crosses_boundary |-> (window_octets == 16'(mac_window(tagged))));
// A2. And its highest branch offset is 13 or 21.
p_pw_highest_offset: assert property (@(posedge clk) disable iff (!rst_n)
!crosses_boundary |->
(highest_offset_branched_on == (tagged ? 16'd21 : 16'd13)));
// A3. Crossing the boundary is the only way the window grows.
p_pw_growth_needs_crossing: assert property (@(posedge clk) disable iff (!rst_n)
(growth_x10 > 16'd10) |-> crosses_boundary);
// A4. Opacity and payload reading are exclusive.
p_pw_exclusive: assert property (@(posedge clk) disable iff (!rst_n)
(mac_remains_opaque != payload_is_read));
// A5. The window is charged as registers.
p_pw_registers: assert property (@(posedge clk) disable iff (!rst_n)
(window_bce_o == 32'(window_octets) * 32'd8 * 32'(BCE_PER_FLOP)));
// A6. The parsed share falls as the frame grows.
p_pw_share_falls: assert property (@(posedge clk) disable iff (!rst_n)
(frame_octets > $past(frame_octets)) |->
(parsed_ppm_o <= $past(parsed_ppm_o)));
// A7. Eighteen octets, whatever the frame size.
p_pw_eighteen: assert property (@(posedge clk) disable iff (!rst_n)
(MAC_PARSED == 18));
// A8. Each tunnel level adds a fixed amount.
p_pw_tunnel_linear: assert property (@(posedge clk) disable iff (!rst_n)
(crosses_boundary && (tunnel_levels == 16'd1)) |->
(window_octets == 16'(offload_window(0)) + 16'd82));
// A9. A pure MAC never reads a payload octet.
p_pw_no_payload: assert property (@(posedge clk) disable iff (!rst_n)
mac_remains_opaque |-> !payload_is_read);Group B — the EtherType space (9).
// B1. The type space is 64 000 values.
p_et_space: assert property (@(posedge clk) disable iff (!rst_n)
(type_space_size == 32'd64_000));
// B2. A value at or below 1500 is a length, not a type.
p_et_length_encoding: assert property (@(posedge clk) disable iff (!rst_n)
(ethertype <= 16'd1500) |-> is_length_field);
// B3. IP is exactly two of them.
p_et_ip_is_two: assert property (@(posedge clk) disable iff (!rst_n)
is_ip |-> ((ethertype == 16'(ET_IPV4)) || (ethertype == 16'(ET_IPV6))));
// B4. The four classifications are mutually exclusive.
p_et_exclusive: assert property (@(posedge clk) disable iff (!rst_n)
type_valid |-> ($onehot({is_length_field, is_ip,
is_known_non_ip, is_unknown})));
// B5. An unknown type goes to no consumer.
p_et_unknown_discarded: assert property (@(posedge clk) disable iff (!rst_n)
is_unknown |-> (consumer_id == 4'd0));
// B6. Frames counted equals IP plus non-IP.
p_et_counts_partition: assert property (@(posedge clk) disable iff (!rst_n)
(c_frames == c_ip_frames + c_non_ip_frames));
// B7. PTP is not IP.
p_et_ptp_not_ip: assert property (@(posedge clk) disable iff (!rst_n)
(type_valid && (ethertype == 16'(ET_PTP))) |-> !is_ip);
// B8. And neither is RoCEv1.
p_et_roce_not_ip: assert property (@(posedge clk) disable iff (!rst_n)
(type_valid && (ethertype == 16'(ET_ROCE))) |-> !is_ip);
// B9. The octet share and the frame share are independent.
p_et_shares_independent: assert property (@(posedge clk) disable iff (!rst_n)
(c_frames > 48'd1000) |-> (ip_share_ppm <= 32'd1_000_000) &&
(ip_octet_share_ppm <= 32'd1_000_000));Group C — opacity (8).
// C1. Any content fact means the boundary was crossed.
p_op_crossing_def: assert property (@(posedge clk) disable iff (!rst_n)
(content_facts != 4'b0000) |-> boundary_crossed);
// C2. Container facts alone do not cross it.
p_op_container_safe: assert property (@(posedge clk) disable iff (!rst_n)
((content_facts == 4'b0000) && (container_facts != 4'b0000))
|-> !boundary_crossed);
// C3. The deepest layer is 2 when nothing above was read.
p_op_layer2: assert property (@(posedge clk) disable iff (!rst_n)
!boundary_crossed |-> (deepest_layer == 16'd2));
// C4. A layer-4 fact implies layer 4 or above is reported.
p_op_layer4: assert property (@(posedge clk) disable iff (!rst_n)
(fact_l4_ports || fact_tcp_flags) |-> (deepest_layer >= 16'd4));
// C5. Every frame lands in exactly one counter.
p_op_one_counter: assert property (@(posedge clk) disable iff (!rst_n)
frame_valid |=> ((c_container_only + c_content_read) ==
($past(c_container_only) + $past(c_content_read) + 48'd1)));
// C6. Crossing share is bounded.
p_op_share_bounded: assert property (@(posedge clk) disable iff (!rst_n)
(crossing_ppm <= 32'd1_000_000));
// C7. A design declaring layer 2 must not report a content fact.
p_op_claim_consistent: assert property (@(posedge clk) disable iff (!rst_n)
claims_layer2_only |-> !v_claim_contradicted);
// C8. The ledger accounts for every fact it has an input for.
p_op_complete: assert property (@(posedge clk) disable iff (!rst_n)
(ledger_is_complete == 1'b1));Group D — the MTU coupling (8).
// D1. Fragments round up.
p_mt_round_up: assert property (@(posedge clk) disable iff (!rst_n)
((fragments * fragment_payload) >= datagram_octets));
// D2. A datagram within the MTU is one fragment.
p_mt_one_fragment: assert property (@(posedge clk) disable iff (!rst_n)
(datagram_octets <= fragment_payload) |-> (fragments == 16'd1));
// D3. Loss amplification equals the fragment count.
p_mt_amplification: assert property (@(posedge clk) disable iff (!rst_n)
(loss_amplification == fragments));
// D4. And it multiplies the drop rate.
p_mt_effective_loss: assert property (@(posedge clk) disable iff (!rst_n)
(effective_loss_ppm == 32'(drop_rate_ppm) * 32'(fragments)));
// D5. The black hole needs all three conditions.
p_mt_black_hole: assert property (@(posedge clk) disable iff (!rst_n)
black_hole |-> (dont_fragment && !icmp_reaches_sender &&
(datagram_octets > fragment_payload)));
// D6. The connection establishes regardless -- that is the trap.
p_mt_establishes: assert property (@(posedge clk) disable iff (!rst_n)
(connection_establishes == 1'b1));
// D7. Bulk transfer fails exactly when the black hole is present.
p_mt_bulk_fails: assert property (@(posedge clk) disable iff (!rst_n)
(bulk_transfer_fails == black_hole));
// D8. Fragmenting always costs wire octets.
p_mt_penalty_positive: assert property (@(posedge clk) disable iff (!rst_n)
(fragments > 16'd1) |-> (wire_octets_frag > wire_octets_whole));Group E — the offload parser and the limiter (9).
// E1. A VLAN tag moves the layer-3 offset by four.
p_of_vlan_offset: assert property (@(posedge clk) disable iff (!rst_n)
vlan_present |-> (l3_offset == 16'd18));
// E2. The layer-4 offset depends on a field inside the layer-3 header.
p_of_ihl_dependency: assert property (@(posedge clk) disable iff (!rst_n)
is_ipv4 |-> (l4_offset == l3_offset + (16'(ihl) * 16'd4)));
// E3. A checksum is computed only when its bounds were located.
p_of_checksum_gated: assert property (@(posedge clk) disable iff (!rst_n)
checksum_may_be_computed |-> (l4_offset_valid && !parse_incomplete));
// E4. And never when the parse was incomplete.
p_of_no_claim_incomplete: assert property (@(posedge clk) disable iff (!rst_n)
parse_incomplete |-> !checksum_may_be_computed);
// E5. The window is finite and the check says so.
p_of_window_finite: assert property (@(posedge clk) disable iff (!rst_n)
((l4_offset + 16'd20) > 16'(WINDOW_OCTETS)) |-> parse_incomplete);
// E6. Depth and octets are independent limits.
p_of_two_limits: assert property (@(posedge clk) disable iff (!rst_n)
stop_parsing |-> (depth_exceeded || octets_exceeded));
// E7. A design that stopped may not claim verification.
p_of_no_claim_stopped: assert property (@(posedge clk) disable iff (!rst_n)
stop_parsing |-> !may_claim_verified);
// E8. Depth resets at each frame.
p_of_depth_resets: assert property (@(posedge clk) disable iff (!rst_n)
frame_start |=> (depth == 16'd0));
// E9. Octets consumed never decrease within a frame.
p_of_octets_monotone: assert property (@(posedge clk) disable iff (!rst_n)
(!frame_start && header_seen) |=>
(octets_consumed >= $past(octets_consumed)));Group F — telemetry and conformance (8).
// F1. The counter that must always read zero.
p_tl_no_unverifiable: assert property (@(posedge clk) disable iff (!rst_n)
(c_unverifiable_claims == 48'd0));
// F2. A design that never branched above 21 is layer 2.
p_tl_layer2_evidence: assert property (@(posedge clk) disable iff (!rst_n)
(deepest_offset_seen <= 16'd21) |-> design_is_layer2_only);
// F3. The high-water mark never falls.
p_tl_high_water: assert property (@(posedge clk) disable iff (!rst_n)
(deepest_offset_seen >= $past(deepest_offset_seen)));
// F4. Frames partition into IP and non-IP.
p_tl_partition: assert property (@(posedge clk) disable iff (!rst_n)
(c_frames == c_ip + c_non_ip));
// F5. An undeclared crossing is a violation.
p_cf_undeclared: assert property (@(posedge clk) disable iff (!rst_n)
((deepest_offset_i > (tagged_i ? 16'd21 : 16'd13)) && !offload_declared_i)
|-> v_boundary_crossed_undeclared);
// F6. A hash with no tuple must fall back.
p_cf_hash_fallback: assert property (@(posedge clk) disable iff (!rst_n)
!hash_had_tuple_i |-> hash_used_fallback_i);
// F7. A payload octet used without declaration is a violation.
p_cf_payload_declared: assert property (@(posedge clk) disable iff (!rst_n)
payload_octet_used_i |-> offload_declared_i);
// F8. Conformance is the disjunction of its five checks.
p_cf_vector: assert property (@(posedge clk) disable iff (!rst_n)
conformant |-> (violations == 5'b00000));Coverage — the traffic a generator that believes the myth will never produce.
c_et_ptp: cover property (@(posedge clk) type_valid && (ethertype == 16'(ET_PTP)));
c_et_lldp: cover property (@(posedge clk) type_valid && (ethertype == 16'(ET_LLDP)));
c_et_macsec: cover property (@(posedge clk) type_valid && (ethertype == 16'(ET_MACSEC)));
c_et_roce: cover property (@(posedge clk) type_valid && (ethertype == 16'(ET_ROCE)));
c_et_length: cover property (@(posedge clk) type_valid && is_length_field);
c_et_unknown: cover property (@(posedge clk) is_unknown);
c_of_ipv4_options: cover property (@(posedge clk) is_ipv4 && (ihl > 4'd5));
c_of_incomplete: cover property (@(posedge clk) parse_incomplete);
c_of_tunnel: cover property (@(posedge clk) depth >= 16'd1);
c_of_depth_limit: cover property (@(posedge clk) depth_exceeded);
c_of_octet_limit: cover property (@(posedge clk) octets_exceeded);
c_mt_fragmented: cover property (@(posedge clk) fragments > 16'd1);
c_mt_black_hole: cover property (@(posedge clk) black_hole);
c_cf_undeclared: cover property (@(posedge clk) v_boundary_crossed_undeclared);21. Verification Scenarios
Fifty-eight scenarios in six groups, plus one directed test random stimulus will not produce.
Group 1 — the parse window (10).
| # | Scenario | Expect |
|---|---|---|
| 1 | a MAC, untagged | window 14; highest offset 13; 3 520 BCE at 22 octets tagged |
| 2 | a MAC, tagged | window 22; highest offset 21 |
| 3 | a 64-octet frame | 18 parsed — 281 250 ppm, 28.12% |
| 4 | a 1 518-octet frame | 11 857 ppm — 1.19% |
| 5 | a 9 018-octet frame | 1 995 ppm — 0.20% |
| 6 | crossing, no tunnel | 134 octets; 21 440 BCE; 6.1× growth |
| 7 | crossing, one tunnel | 216 octets; 34 560 BCE; 9.8× growth |
| 8 | crossing, two tunnels | 298 octets; 47 680 BCE |
| 9 | mac_remains_opaque and payload_is_read | always opposite — p_pw_exclusive |
| 10 | growth above 1.0× without crossing | impossible — p_pw_growth_needs_crossing |
Group 2 — the EtherType space (10).
| # | Scenario | Expect |
|---|---|---|
| 11 | type_space_size | 64 000 |
| 12 | EtherType 0x05DC — 1 500 | is_length_field; the 802.3 LLC path |
| 13 | 0x0800 | is_ip; consumer 1 |
| 14 | 0x86DD | is_ip |
| 15 | 0x88F7 — PTP | is_known_non_ip — p_et_ptp_not_ip |
| 16 | 0x8915 — RoCEv1 | is_known_non_ip |
| 17 | 0x88CC — LLDP | is_known_non_ip |
| 18 | 0x9000 — unregistered | is_unknown; consumer 0; discarded |
| 19 | a port carrying 92% IP frames | ip_share_ppm = 920 000 |
| 20 | the same port, by octets | a different number — small non-IP frames |
Group 3 — opacity (9).
| # | Scenario | Expect |
|---|---|---|
| 21 | length, FCS, DA match, type only | boundary_crossed low; deepest_layer 2 |
| 22 | plus an L3 protocol fact | crossed; layer 3 |
| 23 | plus L4 ports | layer 4 |
| 24 | plus a payload byte | layer 7 |
| 25 | a design claiming layer 2 with layer-4 facts | v_claim_contradicted |
| 26 | 1 000 frames, 100 crossings | crossing_ppm = 100 000 |
| 27 | a hash reading the five-tuple | crossed — and legitimate, if declared |
| 28 | a drop on a TCP flag | crossed — a policy engine at the wrong layer |
| 29 | scenarios 27 and 28 compared | identical in this module; a fact_acted_on bit separates them |
Group 4 — the MTU coupling (10).
| # | Scenario | Expect |
|---|---|---|
| 30 | a 9 000-octet datagram, 1 500 MTU | 7 fragments; 9 386 wire octets against 9 038 |
| 31 | the same, penalty | 3.85% |
| 32 | the same, loss amplification | 7× |
| 33 | 10⁻³ drop rate, 7 fragments | effective 7 × 10⁻³ |
| 34 | a 1 400-octet datagram, 1 500 MTU | 1 fragment; no penalty |
| 35 | a 9 000-octet datagram, 9 018 MTU | 1 fragment |
| 36 | DF set, oversized, ICMP reaches the sender | no black hole; the sender reduces its segment size |
| 37 | DF set, oversized, ICMP filtered | black_hole; c_black_holes increments |
| 38 | the same, connection establishment | succeeds — p_mt_establishes |
| 39 | the same, bulk transfer | hangs — bulk_transfer_fails |
Group 5 — the offload parser (9).
| # | Scenario | Expect |
|---|---|---|
| 40 | untagged IPv4, no options | l3 at 14; l4 at 34; complete |
| 41 | tagged IPv4, no options | l3 at 18; l4 at 38 |
| 42 | IPv4 with ihl = 15 | l4 at 74; within a 134-octet window |
| 43 | one VXLAN-style tunnel | 216 octets; within a 216-octet window |
| 44 | two tunnels, 134-octet window | parse_incomplete; no checksum claim |
| 45 | three stacked VLAN tags, depth limit 2 | octets_exceeded fires, depth_exceeded does not |
| 46 | parse_incomplete with a verified claim | v_unverifiable_claim; c_unverifiable_claims increments |
| 47 | may_claim_verified after stop_parsing | low — p_of_no_claim_stopped |
| 48 | frame start after a deep parse | depth and octets reset |
Group 6 — telemetry and conformance (10).
| # | Scenario | Expect |
|---|---|---|
| 49 | a design that never branches above 21 | design_is_layer2_only high |
| 50 | one frame parsed to octet 134 | high-water latches at 134 and never falls |
| 51 | c_unverifiable_claims at any point | must be zero — p_tl_no_unverifiable |
| 52 | a hash with no tuple and no fallback | v_hash_without_fallback |
| 53 | the same with a fallback to MAC and type | clean |
| 54 | a payload octet used, offload undeclared | v_payload_read_undeclared |
| 55 | crossing at offset 40, declared | clean — crossing is legal, undeclared crossing is not |
| 56 | crossing at offset 40, undeclared | v_boundary_crossed_undeclared |
| 57 | 8% non-IP frames on an 8-member LAG, no fallback | member 0 takes 19.5% against 11.5% — 70% imbalance |
| 58 | all five checks clear | conformant high |
22. Debugging Across the Boundary
Six symptoms, and the first question in every row is which layer is this fact from?
| Symptom | First question | Where to look |
|---|---|---|
| one link of a LAG carrying twice its share | does the hash fall back when there is no tuple? | Section 12 — 8% non-IP is 70% imbalance |
| an application that connects and then hangs | is DF set and is ICMP reaching the sender? | Section 9 — a path MTU black hole |
| corrupted data on one traffic class only | does that class carry IPv4 options or a second tag? | Section 12 — a checksum over the wrong range |
| PTP accuracy far worse than Chapter 16.5's limits | is PTP being classified as ordinary traffic? | Section 5 — EtherType 0x88F7, not IP |
| a "layer-2 device" that behaves differently on IPv6 | what does deepest_offset_seen read? | Section 15 — the claim is contradicted by the evidence |
| a regression that is green and a field failure that is not | what EtherTypes does the generator produce? | Section 21's directed test |
Row six is the one this chapter exists for, and the procedure is short: list the EtherTypes the generator can emit, list the EtherTypes the deployment carries, and compare the two lists. The difference is the design's untested surface, and on a typical programme it is eleven of thirteen.
23. Misconceptions
Misconception 1 — "an Ethernet interface is a TCP/IP interface."
The wrong model: the terms describe the same thing at different levels of formality.
What it costs: a design whose filters, hashes and offloads assume fields that are not present. Section 2: a receive MAC parses eighteen octets of a 1 518-octet frame — 1.19% — and reads zero octets of the IP and TCP headers. Section 4: Ethernet names 64 000 payload protocols and IP is two of them.
The corrected model: the MAC reads the container and moves the contents. Chapter 2.4 §7's opaque payload path is the structural form of that, and Section 10 prices what crossing it costs: 66.2% of a real NIC.
Misconception 2 — "non-IP traffic is a corner case."
The wrong model: ARP and a little management traffic, and otherwise it is all IP.
What it costs: eleven of thirteen common EtherTypes untested. Section 4: PTP, LLDP, MACsec, LACP, FCoE, RoCEv1, PROFINET and EtherCAT all carry no IP octet, and several are the protocols with the tightest requirements — PTP skips IP precisely because Chapter 16.4's servo must otherwise estimate an IP hop's variable delay.
The corrected model: the most demanding users of Ethernet deliberately chose not to use IP, and LLDP's correctness actively depends on not being routable.
Misconception 3 — "the source address identifies the sender."
The wrong model: there is one source address and it names who sent the frame.
What it costs: a property that means two different things, and an access list that sees one address for the entire internet. Section 2: there are two source addresses; the MAC one is rewritten at every router and the IP one is not. A host receiving traffic from four routers away sees one source MAC — the last router's.
The corrected model: ask whether the fact survives a router. MAC addresses do not and IP addresses do, and a property that does not distinguish them is ambiguous in a way that changes its truth value.
Misconception 4 — "the layers are completely independent."
The wrong model: having established that they are separate, conclude that nothing couples them.
What it costs: a fragmentation surprise. Section 8: 1 500 is an Ethernet number that became an IP number and then a TCP number, and breaking the coupling costs a seven-fold loss amplification for a 9 000-octet datagram over a 1 500-octet MTU — 3.85% of efficiency and 7× of loss.
The corrected model: exactly one thing couples them, and it is the MTU. The mechanism that hides the coupling is path MTU discovery, and it fails by hanging after a successful connection, which is the worst-behaved failure in networking.
Misconception 5 — "checksum offload is free."
The wrong model: the hardware computes a checksum instead of the CPU; nothing else changes.
What it costs: a 6.1× parser window untunnelled and 9.8× with one tunnel, plus an unbounded case with no correct fixed answer. Section 12: on receive, a design that reports "verified" over a range it could not locate hands the host corrupted data with a clean flag, and no counter anywhere reports it.
The corrected model: crossing the boundary is legitimate and must be declared, bounded and reported. parse_incomplete in the descriptor is one bit, and it is the difference between a graceful degradation and a silent corruption.
Misconception 6 — "our regression is green, so the offload is verified."
The wrong model: full coverage over the fields the generator produces is full coverage.
What it costs: Section 21's directed test. A generator written by somebody who holds the myth produces IP traffic; the coverage model has the same blind spot; and the design, the generator and the model agree with each other perfectly. One PTP announce frame with a VLAN tag distinguishes a correct design from a common one.
The corrected model: list the EtherTypes the generator can emit and the EtherTypes the deployment carries, and compare. The difference is the untested surface, and Section 20's class 115 says why the property that would have caught it cannot ship.
24. Interview Questions
Six, with what a strong answer contains.
1. How many octets of a frame does a receive MAC actually read?
Eighteen: six destination, six source, two type or length, and four of frame check sequence. A strong answer converts that to a share — 1.19% of a 1 518-octet frame, 28.12% of a minimum one — and observes that the count does not grow with the frame. The best answers name the offsets: the MAC's last parsed octet is 13, or 21 with a VLAN tag, and the IPv4 header begins at 14 or 22. There is no overlap.
2. What else does Ethernet carry?
Sixty-four thousand possible protocol identifiers — type values 1 536 through 65 535 — of which IPv4 and IPv6 are two. A strong answer names the ones a real port sees: ARP, VLAN, LACP, PPPoE, PROFINET, EtherCAT, LLDP, MACsec, PTP, FCoE, RoCEv1. The best answers make the sharp point: PTP's layer-2 mapping exists because an IP hop adds the variable delay Chapter 16.4's servo would otherwise have to estimate, and LLDP's correctness depends on not being routable.
3. Somebody writes assert property (frame_accepted |-> learned_port[source_address] == this_port). What is wrong with it?
source_address is ambiguous. With the layer-2 reading it is Chapter 12.2's learning property and correct; with the layer-3 reading it is false on any frame that has crossed a router, because the source IP names a station that is not on this link. A strong answer gives the diagnostic: ask whether the fact survives a router.
4. What does a NIC's TCP offload actually cost?
Two things: a parser window and connection state. The window grows from 22 octets to 134 untunnelled — 6.1× — and to 216 with one tunnel, 9.8×. The state is Chapter 23.4's context cache: 3.356 × 10⁷ BCE, 118.5 MAC datapaths, 66.2% of the part. A strong answer adds that it is worth it — the alternative is tens of host cores — and that what makes it safe is declaring it, because Chapter 23.4 §12's rule stripes ≤ cache entries ÷ participants is a layer-2 configuration constraint imposed by a layer-4 cache.
5. Where do the layers genuinely touch?
The MTU, and nowhere else. 1 500 is an Ethernet number — a transmission-time bound on a shared medium — and it became the IP MTU and then the TCP maximum segment size. A strong answer prices the coupling: a 9 000-octet datagram over a 1 500-octet MTU is seven fragments, 3.85% of efficiency and a seven-fold loss amplification, because every fragment must arrive. The best answers describe the black hole: DF set, ICMP filtered, the connection establishes and bulk transfer hangs forever.
6. Why can't you verify a checksum offload with a hardware monitor?
Because checking it requires parsing TCP, and the design is specified not to. A strong answer names the consequence rather than the fact: the check lives in a testbench permanently, so it can never become an emulation assertion, a bring-up check or a field diagnostic — and the fault class it targets appears in the field, when real traffic carries an IPv4 option or a second tag. The best answers give the repair: assert the design's honesty in hardware — a claim requires a located range — and check the value against the host's own software computation on a sample, which is a genuine second observer rather than a second copy of the design team's reading.
25. Questions and Answers
26. What's Next
This chapter took the largest of Module 25's myths and killed it with three numbers: eighteen octets, sixty-four thousand EtherTypes, and 66.2% of a NIC.
Chapter 25.2 takes the second — that MAC addresses are globally unique forever — and the correction is not the one most engineers expect. Chapter 5.3 §4 already did the birthday arithmetic over the 46 free bits of a locally administered address and found one collision in 140 deployments at a million addresses, which sounds like a settled question.
It is not, because the 46 bits are a fiction. A hypervisor's generator fixes the first octet and usually a vendor prefix too, leaving 24 free bits — and the birthday bound over 2²⁴ at ten thousand virtual interfaces is not one in a million, it is better than even. The chapter derives the factor between the arithmetic people quote and the arithmetic their software implements, prices the duplicate detection a switch would need against Chapter 23.3's MAC table, and finds that the evidence a duplicate leaves is destroyed by the structure that would have recorded it.
Continue learning
Related tutorials
- Related topic
Where Ethernet Stops
The payload is opaque to a MAC, and every capability that follows — one silicon design for every protocol above, including protocols invented after it shipped — depends on it staying opaque. Checksum offload is the deliberate exception, and it costs exactly what the boundary was buying.
- Related topic
Layering as an Engineering Contract
A layer boundary costs a register stage, a translation and a forgone optimisation, continuously. It buys a re-verification count of one instead of many — and because the cost is visible and the benefit is not, boundaries erode one reasonable local decision at a time.
- Related topic
EtherType and Length
The same two octets carry either a payload length or a protocol identifier, and nothing in the frame says which. The two meanings occupy disjoint numeric ranges so the resolution is exact — and the harder half is that a tag moves the field, and a length-form frame names its protocol somewhere else entirely.
- Related topic
MTU and Jumbo Frames
The minimum frame size was derived from physics; the maximum was chosen — so it can be raised by agreement, and the agreement's scope is a path enforced by devices whose scope is a link. A partial agreement produces a black hole rather than an error.
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.
