Skip to content
VLSI Mentor

Ethernet · Module 2

The MAC Layer

Framing, addressing, error detection, sizing, interframe gap and transmit access. Each exists because the medium is unreliable, shared, or both — and knowing which reason applies predicts exactly what full duplex deleted and what it left untouched.

Chapter 2.1 listed the MAC's duties in a table and moved on. Chapter 2.4 established what it refuses to do. Neither asked the question that organises the set.

Why does the MAC have exactly these responsibilities and not others?

There is a principle, and it is worth having because it predicts. Every MAC responsibility exists for one of two reasons — the medium is shared, or the medium is unreliable — and knowing which reason applies to each tells you what happened to it when full duplex removed the sharing, and what will happen when the next medium arrives.

1. The Gap the MAC Closes

Put the two sides next to each other and the MAC's job is the difference.

What the physical layer offers: a stream of bits, transmitted and received, with no structure. No indication of where anything begins or ends, no way to say who a bit is for, no confirmation that a received bit is the one that was sent, and — historically — no arbitration over who may transmit.

What a client needs: to hand over a block of octets, name a destination, and have exactly those octets arrive there intact, or be told they did not.

The gapThe responsibility that closes itBecause the medium is
bits have no boundariesframingunreliable
bits carry no destinationaddressingunreliable
bits may not be the ones senterror detectionunreliable
a short frame is indistinguishable from wreckagesizingshared, historically
a receiver needs recovery time between framesinterframe gapunreliable
several stations may transmit at oncetransmit accessshared

Read the right-hand column. Four "unreliable" and two "shared", and the two shared entries are exactly the two that Chapter 1.5 found to be affected by full duplex — one deleted outright, one surviving with its justification gone.

That is the predictive value of the split. A change to the medium's sharing touches the shared column. A change to its reliability touches the other. A medium that was perfectly reliable would need no FCS; one that was never shared needs no access control. Neither exists, so the MAC has both.

2. RTL 1 — The Responsibilities as Separate Blocks

The clearest statement of a set of responsibilities is a partition where each has its own module and its own port list.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE SKELETON. The MAC's transmit responsibilities as separate
// modules. Port lists are the content.
//
// NOT an implementation. Module 19 owns that; this owns the partition.
 
// ── FRAMING. Because a bit stream has no boundaries. ───────────────────
// Needs: the client's octets, and a way to mark the start.
// Does NOT need: any address, any check value, any medium state.
module mac_framing #(
  parameter int unsigned WIDTH = 8
) (
  input  logic clk, rst_n,
  input  logic             cli_valid,
  input  logic [WIDTH-1:0] cli_data,
  input  logic             cli_last,
  output logic             cli_ready,
  output logic             out_valid,
  output logic [WIDTH-1:0] out_data,
  output logic             out_sof,      // this octet begins a frame
  output logic             out_eof,
  input  logic             out_ready
);
endmodule
 
// ── ADDRESSING. Because a bit stream carries no destination. ───────────
// Needs: this station's address, and the frame's addresses.
// Does NOT need: the payload, the check value, or the medium.
module mac_addressing (
  input  logic clk, rst_n,
  input  logic        frame_start,
  input  logic [47:0] dest_addr,
  input  logic [47:0] src_addr,
  input  logic [47:0] my_addr,
  input  logic        promiscuous,
  output logic        accept,          // this frame is for us
  output logic        is_broadcast,
  output logic        is_multicast
);
endmodule
 
// ── ERROR DETECTION. Because the bits may not be the ones sent. ────────
// Needs: every octet of the frame, in order.
// Does NOT need: to know what any octet MEANS. Chapter 2.4's opacity,
// applied: the FCS covers the payload without interpreting it.
module mac_error_detect #(
  parameter int unsigned WIDTH = 8
) (
  input  logic clk, rst_n,
  input  logic             frame_start,
  input  logic             oct_valid,
  input  logic [WIDTH-1:0] oct_data,
  input  logic             oct_last,
  output logic [31:0]      fcs_value,   // to append on transmit
  output logic             fcs_ok       // the residue matched on receive
);
endmodule
 
// ── SIZING. Because a short frame cannot be told from collision wreckage.
// Needs: a count of octets and two limits.
// Does NOT need: the octets themselves, only that they occurred.
module mac_sizing #(
  parameter int unsigned MIN_OCTETS = 60,   // before the FCS; see Section 6
  parameter int unsigned MAX_OCTETS = 1500
) (
  input  logic clk, rst_n,
  input  logic frame_start,
  input  logic oct_valid,
  input  logic oct_last,
  output logic pad_required,
  output logic [10:0] pad_count,
  output logic too_long
);
endmodule
 
// ── INTERFRAME GAP. Because a receiver needs recovery time. ────────────
// Needs: a bit-time tick and the end of the previous frame.
// Does NOT need: anything about the frame that just ended.
module mac_ifg #(
  parameter int unsigned IFG_BITS = 96      // NORMATIVE; see Section 8
) (
  input  logic clk, rst_n,
  input  logic bit_tick,
  input  logic frame_end,
  output logic gap_active,
  output logic may_transmit
);
endmodule
 
// ── TRANSMIT ACCESS. Because the medium may be shared. ─────────────────
// Needs: medium observation, and a mode.
// THE ONLY responsibility whose port list changes with the medium's
// sharing — which is why Chapter 1.5 could delete it and touch nothing
// else. Under full duplex the two medium inputs become reports, not
// controls.
module mac_tx_access (
  input  logic clk, rst_n,
  input  logic full_duplex,
  input  logic tx_req,
  input  logic carrier_sense,
  input  logic collision_detect,
  output logic tx_enable,
  output logic defer_active,
  output logic mode_violation
);
endmodule

Classification: illustrative skeleton; elaborates, implements nothing.

What it teaches: read each module's needs comment against its port list. Five of the six take no medium input at all. Only mac_tx_access has carrier_sense and collision_detect, and that is the structural reason Chapter 1.5 could delete the access machinery without touching framing, addressing, error detection or sizing.

mac_error_detect has no port capable of expressing what an octet means, which is Chapter 2.4 §7's opacity applied to a specific block. The FCS covers the payload and the block cannot interpret it, and the proof is the port list rather than the body.

mac_sizing takes oct_valid but not oct_data. It counts occurrences and never sees values, because sizing is a container property. That absence is deliberate and is the kind of thing worth checking in review: a sizing block that took the data would have acquired the ability to make a content-dependent decision it has no business making.

Deliberately simplified: transmit-oriented; no receive-side partition, which is broadly symmetric; no statistics, which cut across all six; bodies omitted.

Production implication: a real MAC does not have six separate modules — the datapath is shared and the responsibilities are stages within it. The partition still matters, because it is how the verification is partitioned: each duty gets its own properties and its own scenarios, and a failure is attributed to a responsibility rather than to a monolith.

Six MAC responsibility blocks. Framing, addressing, error detection, sizing and interframe gap take no medium input. Only transmit access takes carrier sense and collision detect, which is why removing the shared medium affected only that block.framingunreliable: bits have noboundariesaddressingunreliable: bits carry nodestinationerror detectionunreliable: bits may not bethe ones sentsizingshared, historically:fragments must differinterframe gapunreliable: the receiverneeds recoverytransmit accessshared: the only block thatsees the medium12
Figure 1 — six responsibilities, and only one of them sees the medium.

3. Framing — Because Bits Have No Boundaries

The medium delivers a continuous stream. Nothing in it says where one unit of meaning ends and the next begins, and a receiver cannot invent that information.

So the transmitter marks it, and the mark has to be recognisable without already knowing where to look — which is the discovery problem Chapter 2.2 identified.

Ethernet uses a two-stage mark: a preamble that gives a receiver a pattern to lock onto, then a delimiter that says the frame begins on the next octet. Chapter 5.2 owns both. The structural point here is why two stages exist: one establishes alignment, the other establishes position, and neither can do the other's job.

And the end is not marked at all. Ethernet infers the end from the transmission stopping, which is why a truncated frame and a short frame look identical until the length is checked — the connection between framing, sizing and error detection that Section 6 develops.

4. RTL 2 — Framing, and Why the Mark Is Two Stages

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Prepends a two-stage mark: a repeating pattern for the
// receiver to lock onto, then a delimiter fixing the frame's position.
//
// NOT the real preamble/SFD values — those are parameters here and
// normative in Chapter 5.2.
module mac_frame_marker #(
  parameter int unsigned WIDTH      = 8,
  parameter int unsigned PRE_OCTETS = 7,           // ILLUSTRATIVE
  parameter logic [7:0]  PRE_PATTERN = 8'b10101010, // ILLUSTRATIVE
  parameter logic [7:0]  DELIMITER  = 8'b10101011   // ILLUSTRATIVE
) (
  input  logic clk, rst_n,
 
  input  logic             cli_valid,
  input  logic [WIDTH-1:0] cli_data,
  input  logic             cli_last,
  output logic             cli_ready,
 
  output logic             out_valid,
  output logic [WIDTH-1:0] out_data,
  output logic             out_last,
  input  logic             out_ready
);
 
  typedef enum logic [1:0] { M_IDLE, M_PRE, M_DELIM, M_BODY } m_state_e;
  m_state_e state_q, state_d;
  logic [3:0] pre_q;
 
  // The client cannot be accepted until the mark has been sent, because the
  // mark must precede the data on the wire. That is why cli_ready is low in
  // M_PRE and M_DELIM — a detail easy to get wrong, and the failure is a
  // frame whose first octets are lost.
  assign cli_ready = (state_q == M_BODY) && out_ready;
 
  always_comb begin
    state_d = state_q;
    case (state_q)
      M_IDLE:  if (cli_valid) state_d = M_PRE;
      // STAGE ONE: a repeating pattern with maximum transition density, so
      // the receiver's clock recovery and alignment logic has something to
      // work with. Its CONTENT does not matter; its regularity does.
      M_PRE:   if (out_ready && pre_q == 4'(PRE_OCTETS - 1)) state_d = M_DELIM;
      // STAGE TWO: a pattern that BREAKS the repetition. Alignment is now
      // established, so a receiver can recognise a single distinguished
      // octet — which it could not have done before, having no idea where
      // octet boundaries were.
      M_DELIM: if (out_ready) state_d = M_BODY;
      M_BODY:  if (out_ready && cli_valid && cli_last) state_d = M_IDLE;
      default: state_d = M_IDLE;
    endcase
  end
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      state_q <= M_IDLE; pre_q <= '0;
    end else begin
      state_q <= state_d;
      if (state_q != M_PRE)          pre_q <= '0;
      else if (out_ready)            pre_q <= pre_q + 1'b1;
    end
  end
 
  assign out_valid = (state_q == M_PRE) || (state_q == M_DELIM)
                     || (state_q == M_BODY && cli_valid);
  assign out_data  = (state_q == M_PRE)   ? PRE_PATTERN
                   : (state_q == M_DELIM) ? DELIMITER
                                          : cli_data;
  assign out_last  = (state_q == M_BODY) && cli_valid && cli_last;
 
endmodule
A four-state marker. IDLE waits for a client frame. PRE emits a repeating preamble pattern for the receiver to align to. DELIM emits the delimiter that breaks the pattern and fixes the frame's position. BODY emits the client octets and returns to IDLE on the last one.IDLEPREDELIMBODYclient has a frameclient has a framepattern establishedpattern establishedposition fixedpositionfixedlast octetlast octet
Figure 2 — the mark is two states because alignment and position are two jobs.

Classification: synthesizable.

What it teaches: why the mark cannot be one stage. The preamble's job is to give the receiver's alignment logic a regular pattern to lock onto; the delimiter's job is to be irregular, breaking the pattern at a known point. A single distinguished octet with no preamble would be unrecognisable, because a receiver that has not achieved octet alignment does not know where one octet ends and the next begins — it would see a shifted version of the pattern and match nothing.

cli_ready low during the mark is the implementation detail that bites. The client cannot be accepted until the mark is on the wire, and a design that accepted early would have octets queued with nowhere to put them. The symptom is a frame missing its first octets, which looks like a client bug.

Deliberately simplified: parameterised pattern and length rather than the normative values; no bit-level alignment, which is where a real receiver does the hard work; no handling of a client that withdraws mid-mark.

Production implication: a real transmitter emits the mark through the same path as data with the correct encoding for the operating mode, and a real receiver detects the delimiter at bit alignment and derives octet alignment from it — which is why Chapter 5.2 treats the two stages as one mechanism rather than two fields.

5. Addressing — Because Bits Carry No Destination

A shared medium delivers every transmission to every station. Without a destination, every station would have to hand every frame to its client and let software decide — which is exactly what promiscuous mode does, and why it is expensive.

The address makes filtering a hardware decision. A station compares and discards, and its client never learns the frame existed.

Three cases, and the third is the one that costs hardware.

Unicast. One address, one comparison. Cheap.

Broadcast. One reserved address that every station accepts. Also one comparison.

Multicast. A group, and a station may belong to many. This cannot be one comparison, and Chapter 7.4 develops the perfect-plus-hash filter that real hardware uses. The structural point here is that multicast is what makes address recognition a filter rather than a comparator.

And filtering is why addressing survived full duplex unchanged. On a point-to-point link every frame is for this station, so filtering appears redundant — and it is not, because a switch still floods, multicast still exists, and promiscuous mode is still a mode. The responsibility outlived the medium property that motivated it.

6. Sizing — The Responsibility With Two Reasons

Sizing is the interesting one, because its two ends have different causes.

The minimum exists because the medium was shared. Chapter 1.2 derived it: a valid frame must be longer than any fragment a collision can produce, and slot time bounds a fragment. The minimum equals slot time because they are the same constraint.

The maximum exists for a different reason entirely — a receiver must bound what it commits to buffering, and a station must not occupy the medium indefinitely. Neither is about collisions.

So full duplex removed the minimum's justification and not the maximum's, and neither value changed. Chapter 1.5 §5 called this out: a constant whose reason expired and whose requirement did not.

7. RTL 3 — Sizing, and the Order of Operations

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Counts a frame, inserts padding below the minimum, and
// flags a frame past the maximum.
//
// Limits are parameters; Chapter 1.2 quotes the normative values. Note the
// port list: oct_valid without oct_data. Sizing counts, it does not read.
module mac_frame_sizer #(
  parameter int unsigned MIN_OCTETS = 60,    // before the 4-octet FCS
  parameter int unsigned MAX_OCTETS = 1500,
  localparam int unsigned CNT_W     = $clog2(MAX_OCTETS + 2)
) (
  input  logic clk, rst_n,
 
  input  logic frame_start,
  input  logic oct_valid,      // an octet occurred — its VALUE is not an input
  input  logic oct_last,
 
  output logic pad_active,
  output logic [CNT_W-1:0] pad_remaining,
  output logic fcs_may_start,  // padding done; the check value may be computed
  output logic too_long,
  output logic [CNT_W-1:0] frame_octets
);
 
  typedef enum logic [1:0] { Z_IDLE, Z_COUNT, Z_PAD, Z_DONE } z_state_e;
  z_state_e         state_q, state_d;
  logic [CNT_W-1:0] cnt_q, pad_q;
  logic             over_q;
 
  wire short_frame = (cnt_q < CNT_W'(MIN_OCTETS));
 
  always_comb begin
    state_d = state_q;
    case (state_q)
      Z_IDLE:  if (frame_start) state_d = Z_COUNT;
      // The decision can only be made at the END, because the length is not
      // known until then. That is why padding cannot be streamed and why
      // the FCS waits.
      Z_COUNT: if (oct_valid && oct_last)
                 state_d = short_frame ? Z_PAD : Z_DONE;
      Z_PAD:   if (pad_q == '0) state_d = Z_DONE;
      Z_DONE:  state_d = Z_IDLE;
      default: state_d = Z_IDLE;
    endcase
  end
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      state_q <= Z_IDLE; cnt_q <= '0; pad_q <= '0; over_q <= 1'b0;
    end else begin
      state_q <= state_d;
      if (frame_start) begin
        cnt_q <= '0; over_q <= 1'b0; pad_q <= '0;
      end else if (state_q == Z_COUNT && oct_valid) begin
        if (cnt_q == CNT_W'(MAX_OCTETS)) over_q <= 1'b1;
        else                             cnt_q  <= cnt_q + 1'b1;
        // Latch the pad count at the transition, computed from the final
        // length. Computing it continuously would give a moving target.
        if (oct_last && short_frame)
          pad_q <= CNT_W'(MIN_OCTETS) - cnt_q - 1'b1;
      end else if (state_q == Z_PAD) begin
        if (pad_q != '0) pad_q <= pad_q - 1'b1;
        cnt_q <= cnt_q + 1'b1;
      end
    end
  end
 
  assign pad_active    = (state_q == Z_PAD);
  assign pad_remaining = pad_q;
  // THE ORDERING CONSTRAINT, as a signal. The check value may begin only
  // once padding is complete, because it must cover the pad. A design that
  // started the FCS at the first octet and streamed it would produce a
  // value covering the client data alone, and every receiver would reject
  // the frame.
  assign fcs_may_start = (state_q == Z_DONE);
  assign too_long      = over_q;
  assign frame_octets  = cnt_q;
 
endmodule

Classification: synthesizable.

What it teaches: that padding forces a sequencing constraint on the whole transmit path. The pad count is not known until the frame ends, and the FCS cannot start until the pad is placed, so the check value is inherently a late-stage operation on a frame whose length is already settled.

oct_valid without oct_data is the port-list discipline from Section 2, and it is checkable in review: this block counts occurrences and has no way to read a value, so it cannot make a content-dependent decision.

The MIN_OCTETS = 60 default is worth explaining. The normative minimum frame is 64 octets including the four-octet FCS, so the payload-and-header portion this block counts is 60. Getting that off by four is a classic bug: the frame is four octets short, every receiver discards it as a runt, and the transmitter reports success.

Deliberately simplified: octet-granular where a real datapath is wider and must handle a pad that straddles a word; no interaction with carrier extension; too_long latched without defining what the datapath does next.

Production implication: a real sizer works at the datapath width with byte enables on the final word, defines whether an over-long frame is truncated or aborted, and coordinates with the FCS engine so the check value covers exactly the octets transmitted — the boundary condition where the two blocks most often disagree.

8. The Interframe Gap — The Responsibility That Outlived Its Neighbours

The gap is the responsibility most often misfiled. It looks like part of the access method — it is idle time on a medium — and it is not.

It exists for the receiver. A receiver needs time between frames to complete its processing, reset per-frame state, and prepare for the next start. That need is independent of whether anything else could have transmitted during the gap.

The proof is in the parameter table. Chapter 1.2 quotes interFrameGap as 96 bits at every rate including 10 Gb/s, which has no half-duplex mode at all and lists slot time, attempt limit, backoff limit and jam size as not applicable. A parameter that survives at a rate with no contention was never about contention.

And it is a minimum, not a fixed value. The PCS may insert additional idle to absorb the frequency difference between two independent clocks — Chapter 4.4's elastic buffer — so a receiver sees at least the gap and often more. A design that assumed exactly the minimum would break on any link with a real clock offset.

9. RTL 4 — Address Recognition

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Decides whether a frame is for this station.
//
// NOT the full filter — Chapter 7.4 owns perfect/hash filtering and group
// management. This is the decision's shape and its three cases.
module mac_addr_recognise (
  input  logic clk, rst_n,
 
  input  logic        frame_start,
  input  logic [47:0] dest_addr,
 
  input  logic [47:0] my_addr,
  input  logic        promiscuous,
  input  logic        accept_all_multicast,
  // A small perfect-match table. Real hardware adds a hash for the groups
  // that do not fit — Chapter 7.4.
  input  logic [47:0] group_addr [4],
  input  logic [3:0]  group_valid,
 
  output logic        accept,
  output logic        is_unicast_mine,
  output logic        is_broadcast,
  output logic        is_multicast,
  output logic        accepted_promiscuously   // status, not a decision
);
 
  // The group bit. Chapter 5.3 owns the address structure; what matters
  // here is that ONE BIT separates the cheap case from the expensive one.
  // A clear group bit means a single comparison suffices. A set group bit
  // means the frame may belong to any of several groups this station has
  // joined, which is why recognition is a filter.
  wire group_bit = dest_addr[40];
 
  wire bcast = (dest_addr == 48'hFFFFFFFFFFFF);
  wire mine  = (dest_addr == my_addr);
 
  logic group_hit;
  always_comb begin
    group_hit = 1'b0;
    for (int unsigned g = 0; g < 4; g++)
      if (group_valid[g] && (group_addr[g] == dest_addr)) group_hit = 1'b1;
  end
 
  logic acc_q, promisc_q;
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      acc_q <= 1'b0; promisc_q <= 1'b0;
    end else if (frame_start) begin
      acc_q <= mine || bcast || group_hit
               || (group_bit && accept_all_multicast) || promiscuous;
      // Recorded SEPARATELY from the acceptance itself. A frame accepted
      // only because the station is promiscuous is a different fact from
      // one addressed to it, and a client that cannot tell them apart
      // cannot behave correctly — a monitor wants both, a normal client
      // wants only the second.
      promisc_q <= promiscuous && !(mine || bcast || group_hit);
    end
  end
 
  assign accept                 = acc_q;
  assign accepted_promiscuously = promisc_q;
  assign is_unicast_mine        = mine && !group_bit;
  assign is_broadcast           = bcast;
  assign is_multicast           = group_bit && !bcast;
 
endmodule

Classification: synthesizable.

What it teaches: that one bit in the address — the group bit — separates a single comparison from a set membership test, and that is what makes address recognition a filter. A design that treated every address as a comparison would work for unicast and broadcast and silently drop every multicast frame the station had joined.

accepted_promiscuously earns its output. A frame accepted only because the station is in promiscuous mode is a different fact from one addressed to it. A monitoring client wants both; a normal client wants only the second, and one that cannot distinguish them will act on traffic that was never for it.

Broadcast is checked before the group bit, because the broadcast address has its group bit set — it is a multicast address, the all-ones one. A filter that tested the group bit first and stopped would classify broadcast as multicast, and a station with accept_all_multicast disabled would drop broadcasts. That is a real and confusing bug: ARP stops working while unicast traffic is fine.

Deliberately simplified: four perfect-match entries and no hash, which does not scale to real group counts; no VLAN-aware filtering; the whole address compared at once rather than progressively as it arrives.

Production implication: a real filter compares progressively so a rejection can stop the receive datapath early, uses a hash for groups beyond the perfect table's capacity, and counts each acceptance reason separately — because "frames received" merged across unicast, broadcast, multicast and promiscuous is a number from which nothing can be concluded.

10. RTL 5 — Transmit Access, Parameterised by Mode

The one responsibility whose shape depends on the medium, tying Module 1 into this chapter's partition.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Transmit access as a single responsibility, in both modes.
//
// Chapter 1.5 owns the full treatment; this shows its PLACE in the set of
// six and how little of the MAC it actually is.
module mac_access_control (
  input  logic clk, rst_n,
  input  logic full_duplex,
  input  logic bit_tick,
 
  input  logic tx_req,
  input  logic gap_elapsed,        // from mac_ifg — a SEPARATE responsibility
  input  logic carrier_sense,
  input  logic collision_detect,
 
  output logic tx_enable,
  output logic defer_active,
  output logic retry_needed,
  output logic mode_violation
);
 
  typedef enum logic [1:0] { A_IDLE, A_DEFER, A_GAP, A_TX } a_state_e;
  a_state_e state_q, state_d;
 
  always_comb begin
    state_d = state_q;
    case (state_q)
      // THE ONE LINE THAT DEPENDS ON THE MEDIUM BEING SHARED. Everything
      // else in this module, and all five other responsibilities, is
      // unaffected by the mode.
      A_IDLE:  if (tx_req)
                 state_d = (!full_duplex && carrier_sense) ? A_DEFER : A_GAP;
      A_DEFER: if (!carrier_sense) state_d = A_GAP;
      A_GAP:   if (gap_elapsed)    state_d = A_TX;
      A_TX:    if (!full_duplex && collision_detect) state_d = A_IDLE;
               else if (!tx_req)                     state_d = A_IDLE;
      default: state_d = A_IDLE;
    endcase
  end
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) state_q <= A_IDLE;
    else        state_q <= state_d;
  end
 
  assign tx_enable    = (state_q == A_TX);
  assign defer_active = (state_q == A_DEFER);
  assign retry_needed = (state_q == A_TX) && !full_duplex && collision_detect;
 
  // In full duplex the medium inputs become EVIDENCE rather than controls —
  // Chapter 1.5's design rule, in one assignment.
  assign mode_violation = full_duplex
                          && (collision_detect || (carrier_sense && !tx_enable));
 
endmodule

Classification: synthesizable.

What it teaches: the proportion. This is one module of six, and the mode affects exactly two lines within it. Presented as "the MAC" — as CSMA/CD often is — access control looks like the whole layer; presented as one responsibility among six, it is the smallest and the only one the medium's sharing touches.

The gap is a separate input, not internal state. gap_elapsed arrives from mac_ifg, because the gap is its own responsibility with its own reason for existing. A design that folded the gap counter into the access controller would have coupled a receiver-recovery mechanism to a contention mechanism, and removing contention would then have removed the gap — Section 8's misfiling, in RTL.

Deliberately simplified: no backoff, jam or attempt limit, all of which Chapter 1.2 owns; tx_req doubles as a frame-in-progress indication.

Production implication: a real MAC gates this from a mode register written by auto-negotiation and held stable for the life of a frame — Chapter 1.5 §9 — and reports a mode violation as a distinct status rather than folding it into a collision counter.

11. Waveform — Six Responsibilities, One Frame

One short frame through the responsibilities

10 cycles
Ten clock cycles. A frame starts at cycle 1 and the marker emits a preamble and delimiter. Client octets flow from cycle 3 to cycle 5. The frame is below the minimum so padding is active at cycles 6 and 7. The check value may start at cycle 8, and the interframe gap runs from cycle 9.mark, then bodymark, then bodybelow minimum: paddingbelow minimum: paddingpadding done: FCS may startpadding done: FCS may startgap before the next framegap before the next frameclkframe_startmark_activecli_validoct_count0001234555pad_activefcs_startgap_activeaccepttx_enablet0t1t2t3t4t5t6t7t8t9
Figure 3 — a short frame: framed, sized, padded, checked, then gapped.

fcs_start at cycle 8, after pad_active falls. That is Section 6's ordering constraint observed: the check value cannot begin until the frame's final length is settled, because it must cover the padding.

oct_count stops advancing at 5 and padding runs for two more cycles. The count of client octets and the count of frame octets diverge, which is why a sizer must be explicit about which it holds — a confusion that produces frames four octets short.

gap_active follows the frame rather than the transmission decision. It is sequenced by the frame ending, not by anything about the medium, which is Section 8's argument as a trace.

12. Assertions

Invariants of these models. Only the interframe gap and the frame limits are normative, and those are quoted from Chapter 1.2.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SVA over the modules in this chapter.
 
// SAFETY — P1: the mark precedes every frame body. A body octet emitted
// before the delimiter is a frame no receiver will find.
property p_mark_precedes_body;
  @(posedge clk) disable iff (!rst_n)
  (state_q == M_BODY && out_valid) |-> $past(state_q == M_DELIM
                                             || state_q == M_BODY);
endproperty
a_mark_first : assert property (p_mark_precedes_body);
 
// SAFETY — P2: the client is not accepted while the mark is being sent.
// Catches the early-accept bug whose symptom is a frame missing its first
// octets and looks like a client fault.
property p_no_accept_during_mark;
  @(posedge clk) disable iff (!rst_n)
  (state_q == M_PRE || state_q == M_DELIM) |-> !cli_ready;
endproperty
a_no_early_accept : assert property (p_no_accept_during_mark);
 
// SAFETY — P3: the check value never starts before padding completes. The
// ordering constraint from Section 6; violating it produces a frame every
// receiver rejects.
property p_fcs_after_pad;
  @(posedge clk) disable iff (!rst_n)
  fcs_may_start |-> !pad_active;
endproperty
a_fcs_after_pad : assert property (p_fcs_after_pad);
 
// SAFETY — P4: a padded frame reaches exactly the minimum, never past it.
// Catches an off-by-one in the pad count, which produces a runt or wastes
// medium time on every short frame.
property p_pad_to_exact_minimum;
  @(posedge clk) disable iff (!rst_n)
  ($fell(pad_active)) |-> (frame_octets == MIN_OCTETS);
endproperty
a_pad_exact : assert property (p_pad_to_exact_minimum);
 
// SAFETY — P5: the sizer's count advances once per accepted octet and by
// nothing else. The nearest checkable form of "it counts, it does not
// read" — the structural guarantee is the PORT LIST, which has no
// oct_data, and this property catches a count driven from anywhere else.
property p_sizer_counts_octets_only;
  @(posedge clk) disable iff (!rst_n)
  (state_q == Z_COUNT) |=> (frame_octets == $past(frame_octets) + $past(oct_valid));
endproperty
a_sizer_counts_only : assert property (p_sizer_counts_octets_only);
 
// SAFETY — P6: broadcast is recognised as broadcast, not as multicast.
// Catches a filter testing the group bit first, whose symptom is that
// address resolution stops working while unicast traffic is fine.
property p_broadcast_before_multicast;
  @(posedge clk) disable iff (!rst_n)
  is_broadcast |-> !is_multicast;
endproperty
a_bcast_not_mcast : assert property (p_broadcast_before_multicast);
 
// SAFETY — P7: promiscuous acceptance is reported separately from addressed
// acceptance. A client that cannot tell them apart acts on traffic that was
// never for it.
property p_promiscuous_distinguished;
  @(posedge clk) disable iff (!rst_n)
  accepted_promiscuously |-> (accept && !is_unicast_mine && !is_broadcast);
endproperty
a_promisc_distinct : assert property (p_promiscuous_distinguished);
 
// SAFETY — P8: the gap is served before every frame, in both modes. Catches
// the gap being deleted along with the contention logic.
property p_gap_before_every_frame;
  @(posedge clk) disable iff (!rst_n)
  $rose(tx_enable) |-> $past(state_q == A_GAP);
endproperty
a_gap_always : assert property (p_gap_before_every_frame);
 
// CAUSATION — P9: only the access controller consults the medium. The
// chapter's central claim, as a property over the partition.
property p_only_access_sees_medium;
  @(posedge clk) disable iff (!rst_n)
  ($changed(carrier_sense) || $changed(collision_detect))
    |=> ($stable(pad_active) && $stable(fcs_may_start) && $stable(accept));
endproperty
a_medium_isolated : assert property (p_only_access_sees_medium);
 
// LIVENESS — P10: a requested frame is eventually transmitted. ASSUMPTION,
// stated: the medium eventually permits it and the gap eventually elapses.
assume property (@(posedge clk) s_eventually (!carrier_sense));
assume property (@(posedge clk) s_eventually (gap_elapsed));
property p_request_transmits;
  @(posedge clk) disable iff (!rst_n)
  (state_q == A_DEFER) |-> s_eventually (tx_enable);
endproperty
a_request_transmits : assert property (p_request_transmits);

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_every_received_frame_is_delivered;
//   @(posedge clk) disable iff (!rst_n)
//   frame_start |-> ##[1:$] accept;
// endproperty

It reads like the definition of a working receiver, and it contradicts the purpose of address recognition.

Most frames on a shared medium, and every flooded frame on a switched one, are addressed elsewhere. Filtering them out is the responsibility working — a station that accepted everything would hand every frame on the network to its client, which is what promiscuous mode is for and is not normal operation.

The correct property is P7's shape: acceptance happens for a stated reason, and the reason is reported. Not "every frame is accepted" but "every accepted frame was accepted because it matched, or was broadcast, or was a joined group, or the station is promiscuous — and which one is visible."

The wrong version fires constantly on healthy traffic, gets disabled, and takes P6 and P7 with it — which are the properties that catch the broadcast misclassification and the undistinguished promiscuous accept.

13. Verification

Monitors observe: the marker's state and output; the client handshake against the marker's readiness; the sizer's count, pad and FCS-start; every address recognition input and all five outputs; and the access controller's state against both medium inputs and the mode.

The scoreboard independently predicts the transmitted octet sequence including mark and padding, and the acceptance decision from the address and the station's configuration. It must compute the pad count itself from the normative minimum rather than reading pad_q — a checker reading the design's count agrees with it about the four-octet error.

Scenarios

  1. Frame well above the minimum. Verify the mark, no padding, and the check value starting immediately after the last octet.
  2. Frame one octet below the minimum. Verify exactly one pad octet (P4). The off-by-one that ships.
  3. Frame exactly at the minimum. Verify no padding at all.
  4. Empty client frame. Verify padding to the full minimum and that the state machine does not stall with a zero-length body.
  5. Frame at the maximum, and one octet beyond. Verify too_long asserts at the right octet and not one early.
  6. Client withdraws mid-frame. Verify the marker does not emit a body octet it does not have, and that the underrun is reportable.
  7. Unicast addressed to this station. Verify acceptance and is_unicast_mine.
  8. Unicast addressed elsewhere. Verify rejection — the responsibility working (the case the rejected property gets wrong).
  9. Broadcast. Verify acceptance, is_broadcast, and not is_multicast (P6).
  10. Multicast, group joined and not joined. Verify acceptance in the first case and rejection in the second, with is_multicast in both.
  11. Promiscuous mode, frame addressed elsewhere. Verify acceptance and that accepted_promiscuously distinguishes it (P7).
  12. Half duplex, medium busy. Verify deferral, and that no other responsibility's outputs move (P9).
  13. Full duplex, carrier asserted by a peer. Verify transmission proceeds and mode_violation is reported.
  14. Back-to-back frames. Verify a full gap between every pair (P8), in both modes.
  15. Reset in each state of each module. Verify no stale pad count, no stale acceptance, no partial mark.

Coverage

Cross frame length against the minimum and maximum boundaries: MIN-1, MIN, MIN+1, MAX-1, MAX, MAX+1. Cover every combination of address type against every filter configuration — unicast, broadcast, multicast joined, multicast not joined, each with promiscuous on and off. Cover the mode input against every access-controller state.

A directed stimulus for the broadcast classification

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// NON-SYNTHESIZABLE — directed stimulus. Checks broadcast acceptance with
// multicast acceptance DISABLED, which is the configuration that exposes a
// filter testing the group bit before the broadcast address.
task automatic broadcast_with_multicast_disabled();
  accept_all_multicast <= 1'b0;
  promiscuous          <= 1'b0;
  group_valid          <= 4'b0000;
  @(posedge clk);
 
  // Broadcast MUST be accepted regardless of multicast configuration.
  send_frame_addressed(48'hFFFFFFFFFFFF);
  @(posedge clk);
  assert (dut.accept)
    else $error("broadcast dropped with multicast disabled — the filter is testing the group bit before the broadcast address");
  assert (dut.is_broadcast && !dut.is_multicast)
    else $error("broadcast classified as multicast");
 
  // And a genuine multicast MUST still be rejected, or the fix went too far.
  send_frame_addressed(48'h0180C2000000);
  @(posedge clk);
  assert (!dut.accept)
    else $error("unjoined multicast accepted — broadcast handling was made too permissive");
endtask

Both halves are needed. A filter that accepts everything with the group bit set passes the first assertion and fails the second; one that tests the group bit first fails the first. Only the pair distinguishes a correct filter from either error, and the second half is the one an over-eager fix breaks.

14. Debugging — Attributing a Fault to a Responsibility

The partition's practical payoff is that a symptom points at one of six blocks rather than at "the MAC".

SymptomResponsibilityFirst check
Far end never sees the frame at allframingIs the mark emitted, and complete?
Frames rejected as runts, consistently four octets shortsizingWhether the minimum counts the FCS
Frames rejected as damagederror detectionWhether the FCS covers the padding
Unicast fine, address resolution brokenaddressingBroadcast classified as multicast — Section 13
Client sees traffic it did not ask foraddressingPromiscuous mode, and whether it is reported
Frames merge or the receiver misses the secondinterframe gapGap length, and whether it is served before every frame
Works alone, fails with another stationtransmit accessDuplex mode at both ends

Rows two and three are the pair worth memorising, because both present as "the far end rejects our frames" and they have different causes. A consistent four-octet shortfall is the minimum computed without accounting for the FCS. A damaged-frame report with correct length is the check value covering the wrong range — usually the padding excluded.

And the last row is the only one that needs the other station. Five of the six responsibilities can be diagnosed from this station alone; access control is the one that depends on what somebody else is doing, which is the shared-medium column of Section 1 reappearing as a debugging property.

15. Common Misconceptions

"The MAC is CSMA/CD."

The wrong model: the access method is the MAC layer.

What it costs: on a modern full-duplex link the access machinery is unreachable logic, so the MAC appears to do almost nothing — and framing, addressing, error detection, sizing and the gap all become invisible. Engineers then look for MAC faults in the wrong place, or conclude the MAC is trivial.

The corrected model: access control is one of six responsibilities and the only one that depends on the medium being shared. Section 10's module is small, and the mode affects two lines in it. The other five are unchanged by full duplex and are most of what a MAC actually does.

"The interframe gap is part of the access method."

The wrong model: it is idle time on the medium, so it belongs with contention.

What it costs: a design that deletes the gap along with the contention logic when moving to full duplex. Frames then run back to back, receivers miss the second one's start, and the symptom is intermittent lost frames at high rates with nothing reporting an error.

The corrected model: the gap exists for the receiver's recovery, not for contention. Chapter 1.2 quotes it as 96 bits at every rate including 10 Gb/s, which has no half-duplex mode at all — a parameter that survives where contention does not was never about contention.

"Padding is added after the check value, since it is just filler."

The wrong model: padding is not real data, so it need not be protected.

What it costs: a frame whose FCS covers only the client data. Every receiver computes the check over the whole frame including the pad, gets a different answer, and discards it. The symptom is that short frames always fail and long ones always work, which points at length handling rather than at ordering.

The corrected model: padding is part of the frame that travels, so the FCS must cover it — which means the check value cannot be computed until the final length is known. That is a sequencing constraint on the whole transmit path, and P3 asserts it.

"Address filtering is unnecessary on a switched link."

The wrong model: a point-to-point link delivers only frames meant for this station, so recognition is redundant.

What it costs: a filter that is under-tested or configured permissively, which then admits flooded traffic, unjoined multicast groups and anything a misconfigured switch sends — and the client is handed frames it was never meant to process.

The corrected model: a switch still floods unknown unicast, broadcast and multicast — Chapter 12.4 — so a station on a switched link still receives frames addressed elsewhere. Multicast group membership is a real filtering decision at every station regardless of topology, and promiscuous mode remains a mode. The responsibility outlived the medium property that motivated it, exactly as the minimum frame size did.

16. Interview Reasoning

Six responsibilities, and each exists for one of two reasons.

Because the medium is unreliable:

  • Framing — a bit stream has no boundaries, so the transmitter marks them. Two stages: a preamble giving the receiver a regular pattern to align to, then a delimiter breaking that pattern at a known point.
  • Addressing — bits carry no destination, so the frame names one and the receiver filters in hardware.
  • Error detection — the bits may not be the ones sent, so an FCS covers the frame and the receiver checks it.
  • Interframe gap — the receiver needs recovery time between frames.

Because the medium is shared:

  • Transmit access — several stations may want it at once.

And one with a foot in each: sizing. The minimum exists because a valid frame had to be distinguishable from collision wreckage; the maximum exists so a receiver can bound its buffering and no station can hold the medium indefinitely.

What separates a good answer from a complete one: using the split to predict. Full duplex removed the sharing, so it deleted access control and left the other five untouched — and it left the minimum frame size in place with its justification gone. The interframe gap survived because it was never about contention, which the parameter table proves: 96 bits at every rate including 10 Gb/s, a rate with no half-duplex mode at all.

The follow-up to be ready for: what ordering constraint does padding impose? The FCS must cover the padding, so it cannot be computed until the final length is known. That makes the check value a late-stage operation and is why the transmit path is a pipeline rather than a streaming computation.

17. Understanding Check

18. What's Next

The MAC's six responsibilities exist for two reasons: the medium delivers bits with no structure and no guarantee, and the medium may be shared. Four responsibilities answer the first, one answers the second, and sizing has a foot in each — which is why full duplex could delete one block and leave the rest untouched.

That accounts for everything above the interface. Chapter 2.6 — The PHY Layer takes the other half: the work that exists because the medium is analog. Coding, serialisation, clock recovery and line drive are not about frames at all, and the PHY's internal division into PCS, PMA and PMD follows the same pattern this chapter used — each sublayer exists for a reason, and knowing the reason predicts what changes when the medium does.

Chapter 2.7 then closes Module 2 with the devices these layers are built into: end systems, switches and routers, and the forwarding boundary between them.

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.