Skip to content
VLSI Mentor

Ethernet · Module 2

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.

Chapter 2.3 argued that a boundary is worth its cost when it separates things that change at different rates. The boundary at the top of Ethernet is the most valuable one in the stack, and it is the one designers are under the most pressure to violate.

Ethernet delivers a frame between two stations on the same network. That is the whole promise. Everything a working system needs beyond it — reaching a station on a different network, ensuring data arrived, retransmitting what did not, ordering what arrived out of order — is somebody else's problem, by design.

What does an Ethernet MAC deliberately not do, and what does the hardware gain from refusing?

The refusal is the feature. A MAC that treated its payload as anything other than opaque octets would be a MAC that had to be revised whenever the thing inside changed.

1. What Ethernet Promises, Exactly

The promise is narrower than most descriptions of it, and the narrowness is deliberate.

Ethernet delivers a frame from one station to another on the same network, and tells you whether what arrived was intact.

Four qualifications, each of which is a thing Ethernet does not do.

On the same network. A MAC address identifies a station on this network. It carries no information about how to reach a station elsewhere and no hierarchy that could be used to route toward one. Reaching a different network requires a device that terminates one and originates on another — a router — and that device is above Ethernet by definition.

A frame, not a stream. Each frame is independent. Nothing in the MAC relates one to the next: no sequence number, no session, no notion that two frames belong together.

Delivery is attempted, not guaranteed. Chapter 1.2 showed a transmitter giving up after sixteen attempts. A receiver discards anything that fails validation. Neither end retransmits, and neither tells anyone the frame is missing.

Intact, not correct. The FCS says the octets that arrived are the octets that were sent. It says nothing about whether they were the right octets, whether they arrived in the right order relative to other frames, or whether they mean anything.

Everything a real system needs beyond that is above Ethernet, and Section 6's table is what that costs the layers that provide it.

2. The EtherType — Where Ethernet Hands Off

One field in the frame names what the payload contains, and it is the exact point at which Ethernet stops caring.

What the MAC does with it: compares it against a set of values the local clients have registered, and delivers the payload to whichever client matches. Nothing more.

What the MAC does not do with it: anything that depends on the value's meaning. It does not know that one value implies a header with addresses in it, or that another implies an address-resolution exchange. Two clients registering two different values get their payloads and the MAC has no model of either.

3. RTL 1 — The Demultiplexer

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Routes a payload to a registered client by an opaque tag.
//
// NOT the full length/type semantics — Chapter 5.5 owns the disambiguation
// rule. This models the handoff and the opacity.
module ethertype_demux #(
  parameter int unsigned WIDTH   = 8,
  parameter int unsigned CLIENTS = 4
) (
  input  logic clk,
  input  logic rst_n,
 
  // Registration: a client claims a value. The MAC stores it and never
  // examines it — a claimed value is a key, not a meaning.
  input  logic                    reg_valid,
  input  logic [$clog2(CLIENTS)-1:0] reg_client,
  input  logic [15:0]             reg_type,
 
  // From the receive datapath.
  input  logic                    frame_start,
  input  logic [15:0]             frame_type,
  input  logic                    pay_valid,
  input  logic [WIDTH-1:0]        pay_data,
  input  logic                    pay_last,
 
  // To the clients. Exactly one may be selected per frame.
  output logic [CLIENTS-1:0]      cli_valid,
  output logic [WIDTH-1:0]        cli_data,
  output logic                    cli_last,
 
  output logic                    unclaimed   // no client wants this type
);
 
  logic [15:0]        claim_q [CLIENTS];
  logic [CLIENTS-1:0] claimed_q;
  logic [CLIENTS-1:0] sel_q;
  logic               matched_q;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      claimed_q <= '0;
      for (int unsigned c = 0; c < CLIENTS; c++) claim_q[c] <= '0;
    end else if (reg_valid) begin
      claim_q[reg_client]   <= reg_type;
      claimed_q[reg_client] <= 1'b1;
    end
  end
 
  // THE ENTIRE INTERPRETATION OF THE FIELD, and it is an equality test.
  // There is no case statement over known values, no decode, no per-value
  // behaviour. Add a protocol and this module does not change; a client
  // registers a value and the hardware carries it on day one.
  logic [CLIENTS-1:0] match;
  always_comb begin
    match = '0;
    for (int unsigned c = 0; c < CLIENTS; c++)
      match[c] = claimed_q[c] && (claim_q[c] == frame_type);
  end
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      sel_q <= '0; matched_q <= 1'b0;
    end else if (frame_start) begin
      // Latched at the START of the frame and held for its duration. A
      // selection recomputed per beat could change mid-frame if the type
      // field's storage were ever disturbed, splitting one payload across
      // two clients — and neither would know it had half a frame.
      sel_q     <= match;
      matched_q <= |match;
    end
  end
 
  assign cli_valid = pay_valid ? sel_q : '0;
  assign cli_data  = pay_data;
  assign cli_last  = pay_last;
 
  // A frame nobody claimed is DISCARDED and counted, not delivered to a
  // default client. Delivering it somewhere would mean guessing, and a
  // guess about a payload is exactly what this module exists not to make.
  assign unclaimed = frame_start && !(|match);
 
endmodule

Classification: synthesizable.

What it teaches: that the handoff is an equality test against a registered value, and that the absence of a case statement over known EtherType values is the design. Nothing here knows that one value means an internet protocol and another means address resolution. Add a protocol tomorrow and this module is unchanged.

Latching the selection at frame_start is the subtle correctness point. A selection recomputed each beat would be stable in practice and catastrophic if it ever were not — half a payload to one client, half to another, with neither able to detect that it received a fragment. Latch once, hold for the frame.

The unclaimed path matters more than it looks. A frame whose type nobody registered is discarded and counted. The tempting alternative — deliver it to a default client — means the MAC guessing what a payload is for, which is the one thing this module exists to avoid. And the counter is diagnostic: unclaimed frames rising means something upstream is sending a protocol nothing here handles.

Deliberately simplified: no length/type disambiguation; a linear match rather than a CAM, which does not scale past a handful of clients; one frame in flight; no priority when two clients claim the same value, which a real implementation must define.

Production implication: a real demultiplexer uses a small CAM or hash for the type lookup, defines what happens when two clients claim one value, handles nested tags where a VLAN tag sits before the type field — Chapter 13.2 — and counts unclaimed frames per type so an unexpected protocol is visible rather than silently dropped.

4. What a VLAN Tag Does to This Boundary

Section 3's demultiplexer reads the type field at a fixed offset. That is true of an untagged frame and false the moment a VLAN tag is present, and the complication is worth meeting here because it is the first real pressure on the boundary this chapter defends.

A VLAN tag is inserted between the addresses and the type field. So the octets that were the type field are now the tag, and the real type field has moved further into the frame. A demultiplexer that reads a fixed offset will match against a tag value and route every tagged frame to whoever registered that value — or, more likely, to nobody.

Chapter 13.2 owns the tag's contents and semantics. What belongs here is the structural consequence: the handoff point is no longer at a fixed position, and finding it requires looking at what is there and deciding whether to look further.

5. RTL 2 — Locating the Type Field When It Moves

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Finds the type field, which is at a fixed offset in an
// untagged frame and further in for each tag present.
//
// NOT 802.1Q. The TPID value is a parameter here; Chapter 13.2 owns the
// real tag format and its semantics.
module type_field_locator #(
  // ILLUSTRATIVE. The real value is normative and belongs to Chapter 13.2.
  parameter logic [15:0] TAG_TPID  = 16'h8100,
  // How many stacked tags this design will follow. A DECLARED bound, so a
  // frame with more is rejected rather than parsed indefinitely.
  parameter int unsigned MAX_TAGS  = 2
) (
  input  logic clk,
  input  logic rst_n,
 
  input  logic        frame_start,
  input  logic        word_valid,
  input  logic [15:0] word_data,     // successive 16-bit words after the addresses
 
  output logic        type_valid,
  output logic [15:0] type_value,
  output logic [1:0]  tags_seen,
  output logic        too_many_tags  // exceeded the declared depth
);
 
  typedef enum logic [1:0] { L_IDLE, L_LOOK, L_SKIP, L_DONE } l_state_e;
  l_state_e    state_q, state_d;
  logic [1:0]  tags_q;
  logic        over_q;
  logic [15:0] type_q;
 
  // Each tag is two words: the identifier, then the control information.
  // Having recognised an identifier, this skips the control word and looks
  // again — which is why a stack of tags works and why an UNBOUNDED stack
  // would be a parser that never terminates.
  wire is_tag = word_valid && (word_data == TAG_TPID);
 
  always_comb begin
    state_d = state_q;
    case (state_q)
      L_IDLE: if (frame_start) state_d = L_LOOK;
      L_LOOK: if (word_valid) begin
                if (is_tag)
                  state_d = (tags_q == 2'(MAX_TAGS)) ? L_DONE : L_SKIP;
                else
                  state_d = L_DONE;   // not a tag: this IS the type field
              end
      L_SKIP: if (word_valid) state_d = L_LOOK;   // skip the control word
      L_DONE: if (frame_start) state_d = L_LOOK;
      default: state_d = L_IDLE;
    endcase
  end
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      state_q <= L_IDLE; tags_q <= '0; type_q <= '0; over_q <= 1'b0;
    end else begin
      state_q <= state_d;
      if (frame_start) begin
        tags_q <= '0; over_q <= 1'b0;
      end else if (state_q == L_LOOK && word_valid) begin
        if (is_tag) begin
          if (tags_q == 2'(MAX_TAGS)) over_q <= 1'b1;
          else                        tags_q <= tags_q + 1'b1;
        end else begin
          type_q <= word_data;
        end
      end
    end
  end
 
  // A frame with more tags than declared is REJECTED, not parsed further.
  // An unbounded parser is a denial-of-service surface: a crafted frame
  // with a long tag stack would occupy the locator indefinitely.
  assign type_valid    = (state_q == L_DONE) && !over_q;
  assign type_value    = type_q;
  assign tags_seen     = tags_q;
  assign too_many_tags = over_q;
 
endmodule

Classification: synthesizable.

What it teaches: that a handoff point which moves needs a bounded search, and that the bound is a safety property rather than a convenience. MAX_TAGS is declared, exceeded frames are rejected, and the parser terminates on every input.

An unbounded version is a real hazard. A parser that followed tags until it found a non-tag would spend an entire frame on a crafted stack of them, and on a device processing at line rate that is a way to consume the parser with valid- looking traffic. Every parser reading attacker-influenced data needs a declared depth, and this is the smallest possible example of the rule.

The tag stack is why the state machine has a skip state. A tag occupies two words — an identifier and its control information — so recognising one means consuming both before looking again. A design that skipped only the identifier would read the control word as a candidate type field and match it against whatever a client registered, which is a bug that appears only on tagged frames.

Deliberately simplified: a parameterised identifier rather than the normative value; 16-bit words rather than an arbitrary datapath width; no distinction between tag types; no handling of a frame that ends mid-tag.

Production implication: a real locator handles the datapath width in use, so the type field may straddle a word boundary; recognises every tag identifier the device supports rather than one; and counts rejected over-deep frames separately, because a rising count means either a misconfiguration or something deliberate.

Two frame layouts. The untagged frame has destination address, source address, then the type field, then payload. The tagged frame has destination address, source address, a tag, then the type field, then payload, so the type field sits further into the frame.addressesuntagged frametype fieldthe handoff pointpayloadopaque from here onFCScovers everything before itaddressestagged frametaginserted here, shifting whatfollowstype fieldthe handoff point, movedpayloadstill opaque12
Figure 2 — the handoff point moves; a fixed offset finds a tag instead.

6. What Sits Above, and What It Forces on the Hardware

IP and TCP appear here only as context, and only in terms of what they force the hardware to do. Their internals belong to a networking curriculum, not to this one.

Above EthernetWhat it providesWhat it forces on Ethernet hardware
A network-layer protocoladdressing that spans networks, so a packet can be routednothing — the MAC carries it as opaque octets
Address resolutiona mapping from a network address to a MAC addressnothing — it is a client registering an EtherType like any other
A transport protocoldelivery guarantees, ordering, retransmissionnothing directly; offload is where hardware chooses to reach up, Section 9
Applicationseverything elsenothing

The right-hand column is the chapter. Three of four rows are "nothing", and that is the property being defended. The fourth is where a real design chooses to break the rule and is worth its own sections.

Two facts worth carrying, and no more than two.

A network-layer address is hierarchical and a MAC address is not. That is why routing is possible above and impossible below: a hierarchical address can be aggregated and directed toward a region of the network, and a flat 48-bit identifier cannot. Chapter 5.3 covers the MAC address's structure; the consequence — that Ethernet cannot route — is this chapter's.

Reliability lives above because Ethernet declined to provide it. Chapter 1.6 argued that best-effort was the decision that made Ethernet cheap enough to win. Something has to notice a missing frame and ask for it again, and that something is a transport protocol operating on round-trip timescales — far slower than link-level flow control, which is why Module 14 exists as well.

7. RTL 3 — Opacity, Enforced

Opacity is easy to claim and easy to erode. This is what it looks like enforced structurally rather than by convention.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Carries a payload with no access to its contents beyond
// what is needed to move and count it.
//
// The point is the port list: there is no output through which a decision
// about payload CONTENT could leave this module.
module opaque_payload_path #(
  parameter int unsigned WIDTH   = 8,
  parameter int unsigned MAX_LEN = 1500
) (
  input  logic clk,
  input  logic rst_n,
 
  input  logic             in_valid,
  input  logic [WIDTH-1:0] in_data,
  input  logic             in_last,
  output logic             in_ready,
 
  output logic             out_valid,
  output logic [WIDTH-1:0] out_data,
  output logic             out_last,
  input  logic             out_ready,
 
  // The ONLY facts this module may produce about a payload: how long it
  // was, and whether it fitted. Both are properties of the CONTAINER, not
  // of the contents.
  output logic [$clog2(MAX_LEN+1)-1:0] length,
  output logic                         length_valid,
  output logic                         too_long
);
 
  logic [$clog2(MAX_LEN+1)-1:0] cnt_q;
  logic                         over_q;
 
  assign in_ready  = out_ready;
  assign out_valid = in_valid;
  assign out_data  = in_data;     // straight through, untouched
  assign out_last  = in_last;
 
  wire beat = in_valid && in_ready;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      cnt_q <= '0; over_q <= 1'b0;
    end else if (beat) begin
      if (in_last)                         cnt_q <= '0;
      else if (cnt_q == MAX_LEN[$clog2(MAX_LEN+1)-1:0]) over_q <= 1'b1;
      else                                 cnt_q <= cnt_q + 1'b1;
    end
  end
 
  assign length       = cnt_q;
  assign length_valid = beat && in_last;
  assign too_long     = over_q;
 
  // WHAT IS ABSENT is the design. There is no output carrying a decoded
  // field, no comparator against a payload byte, no state that depends on
  // a value. A reviewer can establish opacity by reading the port list,
  // which is a much cheaper check than reading the body.
endmodule

Classification: synthesizable.

What it teaches: that opacity is checkable from the port list. This module produces exactly two facts about a payload — its length and whether it overflowed — and both are properties of the container rather than the contents. There is no output through which a content-dependent decision could escape.

That is a much stronger guarantee than a coding convention. "The MAC must not interpret the payload" is a rule someone can violate in a future edit. A module with no output capable of expressing an interpretation cannot violate it without a port-list change, which is visible in review.

Length and overflow are permitted because they are container properties. A MAC must count octets to pad, to enforce the maximum and to compute the FCS. None of that requires knowing what any octet means, and the distinction — container versus contents — is the line that stays defensible when someone proposes an addition.

Deliberately simplified: no FCS, which a real path computes over the payload without interpreting it — itself a good example of the distinction; no clock-domain crossing; one payload in flight.

Production implication: apply the same test to any block claiming not to interpret something: read its outputs and ask whether any of them could express an interpretation. If one could, the claim rests on the body rather than on the interface, and bodies change.

8. RTL 4 — The Broadcast Domain Edge

The clearest hardware statement of where Ethernet stops is the difference between a device that forwards a frame and one that terminates it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Models the one decision that distinguishes a device that
// FORWARDS a frame from one that TERMINATES it.
//
// NOT a switch and NOT a router. Module 12 owns switching; routing is
// outside this track. This is the boundary between them.
module l2_l3_edge #(
  parameter int unsigned WIDTH = 8
) (
  input  logic clk,
  input  logic rst_n,
 
  input  logic             frame_start,
  input  logic [47:0]      dest_mac,
  input  logic [15:0]      ether_type,
  input  logic             pay_valid,
  input  logic [WIDTH-1:0] pay_data,
  input  logic             pay_last,
 
  // This device's own address, and the type it terminates on.
  input  logic [47:0]      my_mac,
  input  logic [15:0]      routed_type,
 
  // FORWARD: the frame passes through, payload untouched, still Ethernet.
  output logic             fwd_valid,
  output logic [WIDTH-1:0] fwd_data,
  output logic             fwd_last,
 
  // TERMINATE: the frame ends here and the payload goes UP, where something
  // that understands it will decide what happens next.
  output logic             up_valid,
  output logic [WIDTH-1:0] up_data,
  output logic             up_last,
 
  output logic             is_broadcast,
  output logic             terminated
);
 
  logic term_q;
 
  // THE DECISION, and it is made on the ADDRESS, not on the payload.
  //
  // Addressed to this device -> the frame's Ethernet journey ends here and
  // its payload becomes somebody else's problem. Addressed elsewhere -> it
  // is still an Ethernet frame going somewhere on this network, and this
  // device carries it without looking inside.
  //
  // Ethernet's involvement ends at the FIRST case and continues in the
  // SECOND. That is the whole L2/L3 edge.
  wire for_me    = (dest_mac == my_mac);
  wire broadcast = (dest_mac == 48'hFFFFFFFFFFFF);
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n)          term_q <= 1'b0;
    else if (frame_start) term_q <= for_me;
  end
 
  assign fwd_valid = pay_valid && !term_q;
  assign fwd_data  = pay_data;
  assign fwd_last  = pay_last;
 
  assign up_valid  = pay_valid && term_q;
  assign up_data   = pay_data;
  assign up_last   = pay_last;
 
  assign is_broadcast = frame_start && broadcast;
  assign terminated   = term_q;
 
  // Note what is NOT here: `routed_type` is a port and is never read. It
  // is present to make the point that even at this boundary, the decision
  // does not depend on what the payload IS. A device that terminated based
  // on EtherType rather than address would be inspecting a frame's contents
  // to decide whether it was for it, which is a layer above's job.
endmodule

Classification: synthesizable.

What it teaches: that the L2/L3 edge is an address decision, not a payload decision. A frame addressed elsewhere is carried without inspection; a frame addressed here has its Ethernet journey completed, and what happens to the payload afterwards is a question this device does not answer.

routed_type is a deliberately unused port, and it is the most instructive line in the module. It represents the tempting design: terminate based on what the payload is rather than who the frame is for. That inverts the layering — it makes an Ethernet-level decision depend on an above-Ethernet concept — and leaving the port present and unread makes the temptation visible.

Broadcast is flagged and does not change the decision. A broadcast frame is delivered locally and is still Ethernet's business across the broadcast domain, which is why Chapter 12.4 treats flooding as a forwarding behaviour rather than a termination one. The distinction matters because it is where "the broadcast domain" gets its boundary.

Deliberately simplified: no multicast group matching; no VLAN awareness, which changes the domain's extent; no forwarding table, so "forward" here means "not for me" rather than a real decision; a single address rather than a filter.

Production implication: a real device combines this with the address filter of Chapter 7.4 — perfect and hash matching, promiscuous mode, multicast groups — and the termination decision becomes one output of that filter rather than a standalone comparison.

A frame arrives and its destination address is compared against this device's own. If it does not match, the frame is forwarded with its payload untouched and remains an Ethernet frame. If it matches, the frame is terminated and its payload is passed upward to whatever understands it.frame arrivesdestination address, thenopaque payloadaddress compareis this frame for me?forwardstill Ethernet; payload neverinspectedterminateEthernet's journey ends herehand upwardpayload becomes somebodyelse's problemno matchmatch12
Figure 3 — one address comparison decides whether Ethernet is finished with a frame.

9. Offload — The Deliberate Violation

Everything above says the MAC does not interpret payloads. Real MACs do, for one specific purpose, and it is worth being precise about what is happening.

Checksum offload means the hardware computes or verifies a checksum belonging to a protocol above Ethernet. To do it, the hardware must locate that protocol's header inside the payload, know which bytes the checksum covers, and know where the result goes.

That is interpretation. The MAC is parsing a payload it is supposed to treat as opaque.

Why it is done anyway. The alternative is the host computing the checksum over every byte of every packet, which is expensive at high rates and is the single largest saving hardware offload provides. The performance argument is real and large.

10. What Offload Actually Costs

Four costs, and the fourth is the one that surprises people.

The MAC is now coupled to a protocol format. A change to the header layout the offload engine parses requires a MAC change. The block that was independent of everything above it is now dependent on one specific thing.

Unrecognised traffic falls back silently. The engine parses what it knows. A tunnelled packet, a protocol variant, an unexpected option field — the parse fails, offload is skipped, and the host does the work. Throughput drops with no error, and the cause is invisible unless the hardware counts fallbacks.

Correctness now depends on a parse. If the engine locates the header wrongly, it computes a checksum over the wrong bytes. The result is a frame that is internally consistent and wrong — and the FCS will happily protect it, because the FCS covers what the MAC transmitted, including the incorrect checksum.

And the boundary is harder to defend afterwards. Once the MAC parses one thing above it, "the MAC does not interpret payloads" is no longer literally true, and every subsequent proposal starts from a weaker position. That is the cost Chapter 2.3 §2 describes as erosion, and offload is where it usually starts.

11. RTL 5 — Detecting a Boundary Violation

If offload is going to reach up, the reach should be observable. This block makes the violation visible rather than silent.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Observes how deeply an offload engine parses into a
// payload, and reports the two facts that make the exception manageable:
// how far it reached, and how often it failed to.
//
// NOT a checksum engine. It computes nothing about the payload's value.
module offload_boundary_monitor #(
  parameter int unsigned WIDTH = 8,
  // How many payload octets the offload engine is DECLARED to inspect.
  // A declared depth is what makes "bounded" checkable rather than a claim.
  parameter int unsigned DECLARED_DEPTH = 40
) (
  input  logic clk,
  input  logic rst_n,
 
  input  logic             frame_start,
  input  logic             pay_valid,
  input  logic             pay_last,
 
  // From the offload engine: it inspected this octet.
  input  logic             offload_reads_payload,
  input  logic             offload_applied,     // it produced a result
  input  logic             offload_fellback,    // it could not parse
 
  output logic [$clog2(DECLARED_DEPTH+2)-1:0] max_depth_reached,
  output logic             depth_exceeded,      // reached past its declaration
  output logic [15:0]      applied_cnt,
  output logic [15:0]      fallback_cnt,
  output logic [15:0]      exceeded_cnt
);
 
  logic [$clog2(DECLARED_DEPTH+2)-1:0] depth_q, max_q;
  logic [15:0] app_q, fb_q, exc_q;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      depth_q <= '0; max_q <= '0; app_q <= '0; fb_q <= '0; exc_q <= '0;
    end else begin
      if (frame_start) depth_q <= '0;
      else if (pay_valid && offload_reads_payload
               && depth_q != {($clog2(DECLARED_DEPTH+2)){1'b1}})
        depth_q <= depth_q + 1'b1;
 
      if (depth_q > max_q) max_q <= depth_q;
 
      // THE COUNTER THAT MATTERS MOST. A fallback is not an error — the
      // host does the work and the frame is fine. It is a PERFORMANCE
      // event with no error signature, so without this counter a system
      // whose offload has silently stopped working looks identical to one
      // where it never worked, and both look healthy.
      if (offload_fellback && fb_q  != 16'hFFFF) fb_q  <= fb_q + 1'b1;
      if (offload_applied  && app_q != 16'hFFFF) app_q <= app_q + 1'b1;
 
      // Reaching past the declared depth means the exception has grown
      // beyond what was reviewed — Chapter 2.3's "bounded" test failing,
      // detected at run time.
      if (pay_valid && offload_reads_payload
          && depth_q >= DECLARED_DEPTH[$clog2(DECLARED_DEPTH+2)-1:0]
          && exc_q != 16'hFFFF)
        exc_q <= exc_q + 1'b1;
    end
  end
 
  assign max_depth_reached = max_q;
  assign depth_exceeded    = (max_q >= DECLARED_DEPTH[$clog2(DECLARED_DEPTH+2)-1:0]);
  assign applied_cnt       = app_q;
  assign fallback_cnt      = fb_q;
  assign exceeded_cnt      = exc_q;
 
endmodule

Classification: synthesizable monitor.

What it teaches: that a deliberate boundary violation should be instrumented, and that the instrument turns Chapter 2.3's "bounded" test from a review-time claim into a run-time measurement. A declared depth that is exceeded is the exception growing past what was agreed, and it is detectable.

The fallback counter is the most valuable output. A fallback is not an error — the host computes the checksum, the frame is correct, nothing fails. It is a performance event with no error signature, so a system whose offload has quietly stopped applying looks exactly like one where it never applied, and both look healthy. Without this counter, the symptom is "throughput is lower than expected" with no starting point.

The applied and fallback counters must be read together as a ratio. A hundred fallbacks is meaningless without knowing whether there were a thousand frames or a million. The ratio is the diagnostic; either count alone is not.

Deliberately simplified: the offload engine's parse depth is an input rather than derived; no per-protocol breakdown of fallbacks, which a real design needs because different causes have different fixes; no distinction between a fallback on transmit and on receive.

Production implication: count fallbacks per reason — unknown protocol, unexpected option, tunnelled packet, fragmented packet — because those have completely different responses. And expose the declared parse depth in a status register so software can check the hardware's boundary against the driver's assumption, which is where the two most often disagree.

12. Waveform — Forwarded Against Terminated

One address comparison, two outcomes

10 cycles
Ten clock cycles. A first frame arrives at cycle 1 addressed elsewhere and its payload is forwarded from cycle 2 to cycle 4 with nothing passed upward. A second frame arrives at cycle 6 addressed to this device, is terminated, and its payload is passed upward from cycle 7 to cycle 9 with nothing forwarded.addressed elsewhere: forwardaddressed elsewhere:forwardaddressed here: terminateaddressed here: terminateclkfrm_startdest_mac--BB--------AA------for_mepay_validfwd_validup_validterminatedether_type--0800--------0800------t0t1t2t3t4t5t6t7t8t9
Figure 4 — the same payload, two destinations, and one comparison deciding its fate.

ether_type is the same value on both frames and changes nothing. The two outcomes differ entirely on dest_mac. That is the chapter's central claim as a trace: Ethernet's decision is about who a frame is for, never about what is inside it.

fwd_valid and up_valid are never both high. A frame is either still Ethernet's business or it is not, and the exclusivity is asserted as P3.

terminated is latched at frame_start and held. Recomputing it per beat would let a disturbed address register split one payload between the forward path and the upward path, and neither destination would know it had a fragment.

13. Assertions

Invariants of these models. None is an IEEE requirement.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SVA over the modules in this chapter.
 
// SAFETY — P1: the demultiplexer selects at most one client. Two clients
// receiving one payload means both act on it, which is worse than neither.
property p_at_most_one_client;
  @(posedge clk) disable iff (!rst_n)
  $onehot0(cli_valid);
endproperty
a_one_client : assert property (p_at_most_one_client);
 
// SAFETY — P2: the selection is stable for a whole frame. Catches a
// selection recomputed per beat, which can split a payload between two
// clients with neither detecting a fragment.
property p_selection_stable;
  @(posedge clk) disable iff (!rst_n)
  (|sel_q && !pay_last) |=> $stable(sel_q);
endproperty
a_selection_stable : assert property (p_selection_stable);
 
// SAFETY — P3: a frame is forwarded or terminated, never both. The L2/L3
// edge is a decision, and a frame in both paths has been duplicated.
property p_forward_xor_terminate;
  @(posedge clk) disable iff (!rst_n)
  !(fwd_valid && up_valid);
endproperty
a_fwd_xor_term : assert property (p_forward_xor_terminate);
 
// SAFETY — P4: the termination decision depends on the address alone. The
// chapter's central claim, as a property: changing the EtherType must not
// change whether a frame is terminated.
property p_termination_from_address;
  @(posedge clk) disable iff (!rst_n)
  frame_start |=> (terminated == $past(dest_mac == my_mac));
endproperty
a_term_from_addr : assert property (p_termination_from_address);
 
// SAFETY — P5: the payload path alters nothing. Opacity as a check on the
// data itself, not only on the port list.
property p_payload_unaltered;
  @(posedge clk) disable iff (!rst_n)
  (in_valid && in_ready) |-> (out_data == in_data && out_last == in_last);
endproperty
a_payload_unaltered : assert property (p_payload_unaltered);
 
// SAFETY — P6: an unclaimed frame reaches no client. Delivering it to a
// default would be the MAC guessing what a payload is for.
property p_unclaimed_not_delivered;
  @(posedge clk) disable iff (!rst_n)
  unclaimed |=> (cli_valid == '0);
endproperty
a_unclaimed_dropped : assert property (p_unclaimed_not_delivered);
 
// CAUSATION — P7: offload never reads past its declared depth without the
// excess being counted. Chapter 2.3's bounded test, checked at run time.
property p_offload_depth_counted;
  @(posedge clk) disable iff (!rst_n)
  (offload_reads_payload && depth_q >= DECLARED_DEPTH)
    |=> (exceeded_cnt > $past(exceeded_cnt) || exceeded_cnt == 16'hFFFF);
endproperty
a_offload_counted : assert property (p_offload_depth_counted);
 
// SAFETY — P8: a fallback and an application are mutually exclusive per
// frame. Counting both for one frame makes the ratio meaningless.
property p_offload_exclusive;
  @(posedge clk) disable iff (!rst_n)
  !(offload_applied && offload_fellback);
endproperty
a_offload_exclusive : assert property (p_offload_exclusive);
 
// LIVENESS — P9: a payload offered to a client is eventually delivered.
// ASSUMPTION, stated: the client eventually accepts.
assume property (@(posedge clk) s_eventually (out_ready));
property p_payload_progresses;
  @(posedge clk) disable iff (!rst_n)
  (in_valid && in_ready) |-> s_eventually (out_valid && out_ready);
endproperty
a_payload_progresses : assert property (p_payload_progresses);

The property that must not be written

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// FALSE for a correct design. Included as a warning, not as a check.
// property p_mac_never_reads_the_payload;
//   @(posedge clk) disable iff (!rst_n)
//   !offload_reads_payload;
// endproperty

It reads like the layering rule this chapter has spent nine sections defending, and it forbids a capability every production MAC has.

Offload reads the payload. That is what it is. Asserting it never happens produces a check that fires on the design's intended behaviour, gets waived immediately, and takes P7 with it — which is the property that actually matters, because it is the one that catches offload reaching past its declared depth.

The correct form is not a prohibition, it is a bound: the MAC may read the payload to a declared depth for a declared purpose, and any read past that depth is counted and visible. That is Chapter 2.3 §8's three tests expressed in SVA — the exception is permitted, and it is measured.

The general lesson is the same one that chapter reached: an absolute rule with real exceptions is unenforceable, and an unenforceable rule is ignored entirely, taking the enforceable part with it.

14. Verification

Monitors observe: the demultiplexer's registrations, matches and selection stability; the payload path's input against its output, octet by octet; the edge's address comparison against its forward and terminate outputs; and the offload monitor's depth, applied and fallback counts.

The scoreboard independently predicts which client should receive each frame from the registration table it maintains itself, and the exact octet sequence out of the payload path from the sequence in. It must not read sel_q — a checker reading the design's selection agrees with it about every match bug.

Scenarios

  1. One client registered, matching frame. Verify delivery to that client and no others (P1).
  2. No client registered for a type. Verify unclaimed, no delivery (P6), and that the counter increments.
  3. Two clients registered for different types. Verify each frame reaches exactly the right one.
  4. Two clients registered for the same type. Verify the design's stated priority is applied deterministically — an unspecified case is a contract defect, not a bug.
  5. Registration changed mid-frame. Verify the in-flight frame's selection is unaffected (P2).
  6. Payload containing bytes that resemble an EtherType. Verify no re-selection — the demultiplexer looks at the type field once, not at payload contents.
  7. Frame addressed elsewhere. Verify forward, nothing upward, and the payload unaltered (P5).
  8. Frame addressed here. Verify terminate, nothing forwarded (P3).
  9. Same EtherType, both addresses. Run scenarios 7 and 8 with an identical type value. Verify the outcomes differ. The chapter's central claim, tested (P4).
  10. Broadcast frame. Verify it is flagged and that the forwarding decision follows the design's stated rule rather than being incidental.
  11. Offload applies. A parseable packet. Verify the applied counter, and that the depth reached is within the declaration.
  12. Offload falls back. An unparseable shape. Verify the fallback counter, that no result is produced, and that the frame is otherwise unaffected — a fallback must not damage anything.
  13. Offload reads past the declared depth. Force a deeper read. Verify exceeded_cnt and depth_exceeded (P7).
  14. Maximum-length payload. Verify the length count is right and too_long does not assert one octet early.
  15. Reset mid-frame in each module. Verify no stale selection, no stale termination decision, no partial payload delivered.

Coverage

Cross registered client count against matching and non-matching types. Cover the same type claimed by zero, one and two clients. Cover the address comparison matching and not matching for each EtherType value used. Cover offload applied, fallback and exceeded, and the cross of each against payload length.

A directed stimulus for the address-decides claim

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// NON-SYNTHESIZABLE — directed stimulus. Holds the EtherType CONSTANT and
// varies only the address, then does the reverse. The pair is the proof.
task automatic address_decides_not_type();
  localparam logic [15:0] FIXED_TYPE = 16'h0800;
 
  // Part 1: same type, two addresses. Outcomes must DIFFER.
  send_frame(.dest(48'hBBBBBBBBBBBB), .etype(FIXED_TYPE));
  @(posedge clk);
  assert (dut.fwd_valid && !dut.up_valid)
    else $error("frame addressed elsewhere was not forwarded");
 
  send_frame(.dest(MY_MAC), .etype(FIXED_TYPE));
  @(posedge clk);
  assert (dut.up_valid && !dut.fwd_valid)
    else $error("frame addressed here was not terminated");
 
  // Part 2: same address, two types. Outcomes must be IDENTICAL. This is
  // the half that catches a device deciding on the payload's type.
  send_frame(.dest(MY_MAC), .etype(16'h0800));
  @(posedge clk);
  assert (dut.terminated) else $error("termination varied with EtherType");
 
  send_frame(.dest(MY_MAC), .etype(16'h86DD));
  @(posedge clk);
  assert (dut.terminated)
    else $error("termination varied with EtherType — the device is deciding on the payload");
endtask

Part 2 is the half that finds the bug. Part 1 alone passes on a device that decides on either field, because in normal traffic the address and the type vary together. Holding the address fixed and varying the type is the only way to prove the type is not being consulted.

15. Debugging — Faults That Cross the Boundary

The boundary is also a diagnostic line: which side a symptom appears on narrows the cause sharply.

SymptomSideFirst check
Frames arrive intact, application sees nothingabove EthernetIs the EtherType registered? unclaimed counter
Some protocols work, one does notabove EthernetThat protocol's registration, and whether a tag shifts the type field
Throughput lower than expected, no errorsoffloadFallback counter against applied counter — a ratio, not a count
Checksums wrong on the far end, FCS goodoffload parseThe engine located the header wrongly; the FCS cannot catch it
Frames damaged in transitEthernetFCS errors — Chapter 21.3
Reachable locally, unreachable elsewhereabove EthernetRouting, not Ethernet. A MAC address does not span networks

The fourth row is the one worth memorising. A checksum computed over the wrong bytes produces a frame that is internally consistent — the FCS covers the whole frame including the wrong checksum, so it passes every Ethernet-level check and fails at the layer above. The FCS being good is not evidence that offload was right, because the FCS was computed after offload wrote its result.

And the last row is the one people get wrong most often. A station reachable on the local network and unreachable elsewhere is not an Ethernet problem in any respect. MAC addresses do not span networks, so nothing in the MAC could explain it — the investigation belongs entirely above.

16. Common Misconceptions

"Ethernet and TCP/IP are the same thing, or part of the same thing."

The wrong model: one networking stack, with Ethernet as its lower portion.

What it costs: every boundary in this chapter becomes invisible. Engineers expect the MAC to know about addresses that span networks, to guarantee delivery, or to be involved when routing fails — and debugging goes to the wrong layer routinely.

The corrected model: Ethernet delivers a frame between two stations on one network and reports whether it arrived intact. Reaching a different network, guaranteeing delivery, ordering and retransmitting are all above it and independent of it. Ethernet carries them as opaque octets and would carry a completely different protocol identically.

"The MAC needs to understand the payload to do its job."

The wrong model: the MAC is a networking device, so it processes network data.

What it costs: every proposal to add payload interpretation looks reasonable, because the MAC is already assumed to be doing it. The boundary erodes with no single decision that looks wrong.

The corrected model: the MAC needs the payload's length and nothing else. Length is a container property: it is needed to pad, to enforce the maximum, and to compute the FCS, and none of that requires knowing what an octet means. Section 7's module makes the distinction structural — its port list can express a length and cannot express an interpretation.

"Checksum offload proves the MAC can interpret payloads."

The wrong model: offload exists, therefore the layering rule is not real, therefore other interpretations are equally fine.

What it costs: the exception becomes precedent for a general licence, which is exactly the erosion Chapter 2.3 §2 describes — and each new interpretation is as defensible as offload was.

The corrected model: offload is a bounded, documented exception with a stated cost, and it passes Chapter 2.3's three tests. It knows specific fields of specific protocols and nothing more. A new proposal must pass the same three tests on its own merits; the existence of one exception is not an argument for a second.

"If the FCS is good, the frame is correct."

The wrong model: the check value validates the frame's contents.

What it costs: an offload parse error produces a frame with a wrong higher-layer checksum and a perfectly good FCS. The investigation trusts the FCS, concludes Ethernet is fine, and looks everywhere except at the engine that wrote the wrong value.

The corrected model: the FCS says the octets that arrived are the octets that were transmitted. If the transmitter wrote a wrong value, the FCS protects it faithfully. Offload runs before the FCS is computed, so anything offload got wrong is inside what the FCS covers — and only a check at the layer that owns the checksum will find it.

17. Interview Reasoning

Ethernet delivers a frame between two stations on the same network and reports whether it arrived intact. Everything else is above it.

The chain a strong answer walks:

  • Same network only. A MAC address is flat, so it cannot be aggregated or routed toward. Reaching another network needs a device that terminates Ethernet and originates on the other side.
  • Frames, not streams. Nothing in the MAC relates one frame to the next.
  • Best-effort. A transmitter gives up after sixteen attempts; a receiver discards what fails validation; neither retransmits.
  • Intact, not correct. The FCS says the octets that arrived are the octets sent — not that they were the right ones.
  • And the payload is opaque. One field, the EtherType, names what is inside, and the MAC's only use for it is an equality test against values clients registered. It never looks past it.

Why it matters to hardware: opacity is what lets one MAC design serve every protocol above it, including protocols invented after the silicon shipped. The moment a MAC interprets a payload it is coupled to that payload's format, and a format change becomes a silicon change.

The follow-up to be ready for: but checksum offload interprets the payload. Yes — it is a deliberate, documented, bounded exception, and it costs four things: the MAC becomes coupled to a protocol format, unrecognised traffic falls back silently with no error signature, correctness now depends on a parse locating the right bytes, and the boundary is harder to defend against the next proposal. The FCS cannot catch a bad parse, because offload writes its result before the FCS is computed over it.

18. Understanding Check

19. What's Next

Ethernet stops at the frame's payload. It carries opaque octets between two stations on one network and reports whether they arrived intact, and every capability that follows from the stack's longevity — one MAC for every protocol above, silicon that carries traffic invented after it shipped — rests on that opacity.

Offload is the one place hardware reaches up, and it is acceptable because it is written down, bounded and priced rather than because interpretation is fine.

The next two chapters take the two halves of what Ethernet does do. Chapter 2.5 — The MAC Layer covers the work that exists because the medium is shared and unreliable: framing, addressing, error detection and access control. Chapter 2.6 — The PHY Layer covers the work that exists because the medium is analog: coding, serialisation, clock recovery and line drive.

The full path is on the Ethernet curriculum index.

Continue learning

Standards & specifications

Governing standard
IEEE Std 802.3 (Ethernet)(opens IEEE in a new tab)

Defines the Ethernet MAC, the media-independent interfaces and the physical-layer sublayers, including framing, access control, auto-negotiation and per-rate PHY specifications. VLAN tagging, priority and time-sensitive shaping are defined by IEEE 802.1, not by 802.3.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the Ethernet curriculum.