Skip to content
VLSI Mentor

Ethernet · Module 1

CSMA/CD, Collision Domains and Slot Time

Slot time is the parameter the whole half-duplex MAC hangs on: it bounds medium acquisition, bounds a collision fragment, and is the retransmission quantum. Deriving it from round-trip propagation plus jam is what fixes Ethernet's minimum frame size — a timing constant wearing a frame-format costume.

Chapter 1.1 ended on a constraint rather than an answer. Carrier sense narrows the window in which a collision can start to one round-trip propagation time and cannot close it, so detection during transmission is mandatory rather than optional. It also established that a transmission must still be in progress when a collision comes back, or the collision is undetectable.

That is a lower bound on how long a station must transmit, and nothing so far has turned it into a number.

What does a station actually do about the collisions carrier sense cannot prevent, and how long must a frame be for that recovery to work at all?

Both halves of that question resolve to one parameter. This chapter derives it, builds it in RTL, and shows what breaks on either side of it.

1. The Algorithm, End to End

CSMA/CD names three mechanisms and hides a fourth. Carrier Sense: listen before starting. Multiple Access: several stations share the medium. Collision Detection: keep listening while transmitting. The fourth, unnamed and where most of the engineering is, is what to do afterwards.

Eight steps, each answering a problem Chapter 1.1 established.

An eight-step flow: sense the medium, defer while busy, transmit, monitor for collisions while transmitting, jam on collision, classify the collision as early or late, back off a random number of slot times, then retry or abandon after the attempt limit.The half-duplex transmit algorithm1Senseis the medium busy here, now?2Deferwait while it is; recheck3Transmitcommit, on stale information4Monitorkeep listening while driving5Jammake it unambiguous to everyone6Classifyearly, or after slot time?7Back offa random number of slot times8Retryor abandon at the attempt limit
Figure 1 — CSMA/CD as eight steps, each answering one problem from Chapter 1.1.

Steps 1 and 2 are the deferral logic 1.1 built. Steps 3 and 4 are the commitment and the monitor that exists because the commitment was made on stale information. Steps 5 through 8 are this chapter, and every one of them is measured in slot times.

Two properties of the algorithm are worth naming before the mechanism, because they explain design decisions that otherwise look arbitrary.

It is entirely distributed. No station is told what to do by another. Every station runs the same procedure on its own local observations, and the statistics of the outcome are what make the medium usable. Nothing coordinates the participants, which is why step 7 has to be random.

It is best-effort at the MAC layer. A frame can be discarded after enough failed attempts, and the MAC reports that to its client rather than guaranteeing delivery. Ethernet has never promised delivery; it promises that a delivered frame is intact, which is the FCS's job rather than the access method's.

2. Slot Time — What the Standard Actually Requires

The standard is unusually explicit about why this parameter exists. Slot time is described as serving three functions:

  1. It is an upper bound on the acquisition time of the medium — how long it can take a station to be sure the medium is its own.
  2. It is an upper bound on the length of a frame fragment generated by a collision — nothing shorter than a valid frame can survive an aborted transmission.
  3. It is the scheduling quantum for retransmission — backoff delays are counted in slot times, not seconds.

And then the requirement that ties them together:

To serve all three functions, the slot time must be larger than the sum of the physical layer round-trip propagation time and the MAC's maximum jam time.

That sentence is the entire derivation, and it is worth reading as three separate claims.

The round-trip term is Chapter 1.1 §4's result: a station cannot know its transmission was unopposed until a signal launched at the far end at the worst possible instant has travelled back. One-way is not enough.

The jam term exists because a station that detects a collision does not stop instantly and silently — it keeps driving for a defined interval so that every other station in the domain sees the collision too. That interval is part of the window a transmitter must still be transmitting through, so it is inside the bound.

"Larger than", not "equal to". The bound is a floor. The chosen value has margin over it, which is what makes the parameter a round number in bit times rather than an awkward function of cable length.

3. Deriving Slot Time from a Domain

Run the bound with illustrative numbers. Every value in this table is illustrative except slotTime and jamSize.

TermIllustrative valueWhere it comes from
Domain span, worst-case station pair500 mtopology
Signal velocity in the mediumapproximately 2 × 10⁸ m/sroughly two-thirds of the speed of light in copper
One-way propagation2.5 µs500 m ÷ 2 × 10⁸ m/s
Round-trip propagation5.0 µstwice the one-way
Repeaters in the path4topology
Delay per repeater, one direction0.4 µsillustrative device figure
Repeater contribution, round trip3.2 µs4 × 0.4 µs × 2 directions
Collision-detect and response latency0.6 µsillustrative PHY plus MAC figure
Round-trip total8.8 µssum of the above
Jam time (jamSize = 32 bits, normative)3.2 µs at 10 Mb/s32 bit times × 100 ns
Required floor12.0 µsround trip + jam
slotTime at 10 Mb/s (normative)51.2 µs512 bit times × 100 ns

The illustrative domain needs 12.0 µs and the standard provides 51.2 µs, so this topology fits with substantial margin. That margin is the point: the parameter was fixed once, generously, for the worst topology the standard intended to support, and every smaller topology inherits it.

Reading the table in the other direction is the more useful skill. Everything in the round-trip column competes for one fixed budget. Longer cable, more repeaters, slower collision detection — each spends from the same 51.2 µs, and when the sum exceeds it the network does not degrade gracefully. It produces late collisions, which Section 6 shows are not collisions in the ordinary sense at all.

4. RTL 1 — The Slot-Time Counter

Every mechanism after this one keys off a single question: has slot time elapsed since this transmission started? That deserves its own block.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Counts bit times from the start of a transmission and
// reports whether slot time has elapsed.
//
// NOT an 802.3 MAC. The parameter value is normative; the surrounding model
// is not. `bit_tick` abstracts a rate-dependent enable that a real MAC
// derives from its PHY interface.
module slot_time_counter #(
  // NORMATIVE (IEEE 802.3 Clause 4 parameter table): 512 bit times at
  // 10 Mb/s, 1BASE5 and 100 Mb/s; 4096 bit times at 1 Gb/s. See Section 19
  // for why the gigabit value differs.
  parameter int unsigned SLOT_TIME_BITS = 512,
  // Declared HERE, not in the body: it is used in the port list below, and a
  // localparam must be declared before its first use. This is the legal
  // SystemVerilog placement for a width derived from a parameter.
  localparam int unsigned SLOT_W = $clog2(SLOT_TIME_BITS + 1)
) (
  input  logic clk,
  input  logic rst_n,
 
  // One pulse per bit time at the operating rate. THIS is the only thing
  // that changes between 10 Mb/s and 100 Mb/s — the counter does not.
  input  logic bit_tick,
 
  // High while this station is driving the medium: frame bits, and at
  // 1 Gb/s also carrier-extension bits (Section 19).
  input  logic tx_active,
 
  output logic within_slot_time,  // still inside the collision window
  output logic slot_elapsed,      // slot time reached; the medium is acquired
  output logic [SLOT_W-1:0] bits_sent
);
 
  logic [SLOT_W-1:0] cnt_q;
  logic              tx_active_q;
 
  // Rising edge of tx_active = the start of an attempt. Detected from a
  // registered copy rather than trusting the caller to send a start pulse,
  // so the block cannot be desynchronised by a missed pulse.
  wire tx_start = tx_active && !tx_active_q;
 
  // SATURATING, not wrapping. A wrapping counter would report
  // within_slot_time again on a long frame, and the collision classifier
  // downstream would call a genuine late collision an early one — the exact
  // misclassification Section 6 says must never happen.
  wire at_max = (cnt_q == SLOT_W'(SLOT_TIME_BITS));
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      cnt_q       <= '0;
      tx_active_q <= 1'b0;
    end else begin
      tx_active_q <= tx_active;
      if (tx_start)                          cnt_q <= '0;
      else if (tx_active && bit_tick && !at_max) cnt_q <= cnt_q + 1'b1;
      else if (!tx_active)                   cnt_q <= '0;
    end
  end
 
  assign bits_sent        = cnt_q;
  assign within_slot_time = tx_active && !at_max;
  assign slot_elapsed     = tx_active &&  at_max;
 
endmodule

Classification: synthesizable.

What it teaches: that the MAC's fundamental time unit is the bit time and not the clock, so one design covers several rates; and that a counter feeding a classification decision must saturate rather than wrap, because a wrap silently converts one fault class into another.

A subtlety in the reset arm. cnt_q clears both on tx_start and on !tx_active. Either alone would work in the nominal sequence; both together mean the counter is clean whether the previous attempt ended by completion, by abort, or by the medium input glitching. A counter that can carry a stale value into the next attempt is the defect Section 17's scenario 5 exists to find.

Deliberately simplified: no interframe gap; bit_tick assumed glitch-free and synchronous; no separate accounting for preamble against frame bits; tx_active assumed to cover extension bits without saying how.

Production implication: a real implementation derives bit_tick from the PHY interface with a documented relationship to the transmit clock, counts from the first bit of the preamble as the standard's timing reference requires, and carries the counter's rate configuration from the same register field that selects the PHY rate — so the two can never disagree.

5. Why the Minimum Frame Is a Timing Constant

This is the chapter's central result, and it follows from Section 2's function (2).

Slot time is an upper bound on the length of a fragment a collision can produce. For that bound to be useful, a valid frame must be longer than any fragment — otherwise a receiver has no way to distinguish a complete short frame from the wreckage of an aborted long one, and would have to accept both or reject both.

So the minimum valid frame is set equal to slot time. minFrameSize is 512 bits, and slotTime at 10 and 100 Mb/s is 512 bit times. They are the same number because they are the same constraint stated twice.

Three consequences follow, and the third is the one engineers meet.

A transmitter is still transmitting when a collision can still arrive. If a frame were shorter than slot time, a station could finish, release the medium, and report success while its transmission was being destroyed at the far end. The collision would arrive at a station that was no longer listening for one. Making the minimum frame equal to slot time is what guarantees the detector is still armed.

A receiver can discard fragments on length alone. Anything shorter than minFrameSize is presumed to be collision wreckage and is dropped. The standard is explicit that discarding such a fragment is not reported as an error — it is the mechanism working. This matters for counter design: a runt counter that increments on every collision fragment on a healthy loaded segment is measuring load, not faults.

Short frames get padded, and the padding is inside the FCS. When the client's data would produce a frame below minFrameSize, the MAC appends pad octets after the client data and before computing and appending the FCS. The check value therefore covers the padding, and a receiver validating the frame validates the pad along with everything else.

6. Late Collisions

A collision that arrives after slot time has elapsed is not a louder version of an ordinary collision. It is a different fault class with a different cause, a different response, and a different meaning in a counter.

The standard's own framing makes the distinction sharp: slot time is the late collision threshold, and a collision after it is reported separately from an ordinary one.

Why it cannot be a normal collision. Section 2 function (1) says slot time is an upper bound on medium acquisition. Once a station has transmitted for slot time without a collision, the medium is acquired — every other station in the domain has had time to hear it and defer. A collision after that point means the guarantee was violated, and a guarantee is only violated by something outside the model:

  • The domain is physically too large. The round trip exceeds the budget, so a distant station's signal is still in flight when the acquisition bound expires. Section 3's table, overspent.
  • A duplex mismatch. One end is running full duplex and has no reason to defer at all, so it transmits whenever it likes, into the other end's frames. This is the most common cause in practice and Chapter 11.4 owns its diagnosis.
  • A faulty station or cable. Something is producing energy that a correct participant would not.

Why the MAC does not retry it. Backoff exists to resolve contention — two stations that both wanted the medium and can be separated in time. A late collision is not contention; retrying it changes nothing, because whatever caused it will still be there on the next attempt. The standard reflects this: an ordinary excessive-collision failure and a late-collision failure are separate transmit status codes, and a late collision aborts the frame rather than feeding the backoff engine.

Why the counter is diagnostic. Ordinary collisions rise with load and are normal. A late collision is never normal. A non-zero late-collision counter is one of the highest-signal readings in Ethernet debugging, because it points at exactly three causes and none of them is traffic.

7. RTL 2 — The Collision Classifier

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Splits a collision indication into the two fault classes
// the MAC must treat differently.
//
// NOT an 802.3 MAC. Status encoding, sticky behaviour and pulse widths are
// this model's; only the slot-time threshold is normative.
module collision_classifier (
  input  logic clk,
  input  logic rst_n,
 
  input  logic collision_detect,   // synchronised PHY indication
  input  logic within_slot_time,   // from slot_time_counter
  input  logic slot_elapsed,       // from slot_time_counter
  input  logic attempt_start,      // one pulse: a new attempt begins
 
  output logic early_collision,    // contention — retry is meaningful
  output logic late_collision,     // fault — retry is NOT meaningful
  output logic retryable,          // the single signal the backoff engine sees
  output logic late_sticky         // latched for a status register
);
 
  // A collision is EARLY only while the acquisition bound is still open, and
  // LATE only after it has closed. The two terms are decoded from disjoint
  // conditions on the same counter, so no input combination can assert both
  // — which is stronger than asserting an invariant about them afterwards.
  assign early_collision = collision_detect && within_slot_time;
  assign late_collision  = collision_detect && slot_elapsed;
 
  // The backoff engine must never see a late collision. Wiring `retryable`
  // rather than raw `collision_detect` into it makes that structural: there
  // is no path by which a late collision can reach the attempt counter.
  assign retryable = early_collision;
 
  // Sticky for software. Cleared only by the start of a new attempt, so a
  // one-cycle event survives long enough to be read, and a read that lands
  // between attempts still sees the last attempt's outcome.
  logic late_q;
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n)             late_q <= 1'b0;
    else if (attempt_start) late_q <= 1'b0;
    else if (late_collision) late_q <= 1'b1;
  end
  assign late_sticky = late_q;
 
endmodule

Classification: synthesizable.

What it teaches: that a fault taxonomy belongs in hardware, not in the software reading the counters. The MAC has to act differently on the two classes — one goes to backoff, one aborts — so the split has to exist before either response is chosen.

Why retryable exists as a separate wire. It would be possible to hand collision_detect to the backoff engine and have that engine consult slot_elapsed itself. That version has a path from a late collision to the attempt counter, and correctness then depends on the engine checking a condition. This version has no such path. Structural impossibility beats a checked invariant wherever the structure is available, and here it costs one wire.

Deliberately simplified: no distinction between a collision during the preamble and one during the frame; no counter saturation; no separate first-attempt versus retry classification; attempt_start assumed to be exactly one cycle.

Production implication: a real design counts early and late collisions in separate saturating counters with defined clear-on-read behaviour, records the attempt number at which each collision occurred so a histogram can be built, and reports the two through distinct status paths — because merging them into one "collision error" bit discards precisely the information Section 6 says is diagnostic.

8. Jamming

When a station detects a collision it does not stop immediately. It transmits a jam — a defined-length sequence whose purpose is to make the collision unmistakable to every other station in the domain.

The reason is a consequence of propagation delay, and it is easy to state wrongly. The jam is not to tell the other transmitter that a collision happened; that station is in the same situation and will detect it independently. The jam is for every station in the domain, including ones that are not transmitting, so that the event is long enough and unambiguous enough that no receiver mistakes a truncated fragment for a real frame it should try to interpret.

jamSize is 32 bits. Two properties of that number matter more than its value.

It is short. The jam is inside the slot-time budget, so every bit of it is a bit the round-trip term cannot use. A longer jam would be more emphatic and would shrink the maximum collision domain.

It is fixed. The jam runs for a defined number of bit times regardless of what the collision indication does after it starts. This is the property Chapter 1.1 built into its controller and it is worth restating in the terms of this chapter: a jam whose length depended on collision_detect staying asserted would be shortened by any detector that deasserted early, and the collision would then be less observable exactly when the analog conditions were marginal.

9. RTL 3 — The Jam Generator

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Emits a fixed-length jam after a collision.
//
// NOT an 802.3 MAC. Length is normative; content, the start handshake and
// the done pulse are this model's.
module jam_generator #(
  // NORMATIVE (IEEE 802.3 Clause 4 parameter table): jamSize = 32 bits at
  // 10 Mb/s, 1BASE5, 100 Mb/s and 1 Gb/s.
  parameter int unsigned JAM_BITS = 32
) (
  input  logic clk,
  input  logic rst_n,
  input  logic bit_tick,
  input  logic jam_start,     // one pulse: a collision was detected
 
  output logic jam_active,    // drive the jam onto the medium
  output logic jam_done       // one pulse: the jam is complete
);
 
  localparam int unsigned JAM_W = $clog2(JAM_BITS + 1);
 
  logic [JAM_W-1:0] cnt_q;
  logic             active_q;
 
  wire last_bit = active_q && bit_tick && (cnt_q == JAM_W'(JAM_BITS - 1));
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      active_q <= 1'b0;
      cnt_q    <= '0;
    end else if (jam_start && !active_q) begin
      // Arm. The counter is cleared HERE and never again until the next
      // start, so nothing that happens during the jam can restart it.
      active_q <= 1'b1;
      cnt_q    <= '0;
    end else if (last_bit) begin
      active_q <= 1'b0;
      cnt_q    <= '0;
    end else if (active_q && bit_tick) begin
      cnt_q <= cnt_q + 1'b1;
    end
  end
 
  // Note what is ABSENT from this block: collision_detect. Once armed, the
  // jam runs from its own state. A detector that deasserts early — which is
  // exactly what a marginal analog condition produces — cannot shorten it.
  assign jam_active = active_q;
  assign jam_done   = last_bit;
 
endmodule

Classification: synthesizable.

What it teaches: that a block whose entire purpose is to guarantee a minimum observable duration must not take its duration from the signal that triggered it. The absent input is the design.

The jam_start && !active_q guard is not defensive clutter. A second collision indication arriving mid-jam is entirely plausible — other stations are still transmitting into the same medium — and without the guard it would restart the counter and extend the jam past its budgeted length, spending slot time that Section 3 has already allocated.

Deliberately simplified: no jam content; no interaction with carrier extension, where the standard uses a distinct extension-error encoding to jam (Section 19); no handling of a collision detected during the jam itself beyond ignoring it.

Production implication: a real MAC emits the jam through the same transmit path as frame data with the correct encoding for the operating mode, accounts for the jam in the transmitted-octet statistics separately from frame octets, and defines whether a jam that is truncated by a reset is reported.

10. Truncated Binary Exponential Backoff

Two stations have collided. Both have jammed. Both now want to retransmit. If both retry immediately, they collide again — deterministically, because the situation that produced the first collision is reproduced exactly.

So the retry has to be spread out, and the standard's mechanism has four properties, each fixing a specific failure of the simpler design before it.

Random, because deterministic is worse than useless. A fixed retry delay makes the second collision certain: both stations wait the same time and start together. Even different fixed delays only work for two stations that happen to have been assigned different constants. Randomness is what decorrelates two stations that are otherwise running identical logic.

Counted in slot times, because that is the resolution that matters. Backoff delays are integer multiples of slot time. Anything finer would be spurious precision — two stations separated by less than a slot time have not been separated at all, because slot time is precisely the interval within which two decisions can still conflict.

Exponential, because the right spread depends on the load. The delay range doubles with each successive failed attempt. On the first collision the range is small, so a lightly loaded network recovers fast. Repeated collisions are evidence that more stations are contending than the current range can separate, so the range grows to match. The algorithm estimates the contention level from its own failure history — no station is told how many others there are.

Truncated, because unbounded doubling is not a delay policy. The exponent stops growing at backoffLimit. Formally, the number of slot times before the nth retransmission attempt is a uniformly distributed random integer r in the range 0 ≤ r < 2^k where k = min(n, 10). So the range doubles for the first ten attempts and then holds at 0..1023 slot times. Without the cap, attempt 16 would draw from 0..65535 slot times — a delay so large the frame is stale and the buffer occupied for no useful purpose.

And it gives up. After attemptLimit = 16 failed attempts the frame is discarded and the failure reported as an excessive-collision error. The MAC does not retry forever, because a medium that has defeated sixteen attempts is not congested; it is broken.

11. RTL 4 — The Backoff Engine

This is the block Chapter 1.1 deliberately left external. Its interface is the one 1.1's controller already exposes: retry_req in, retry_grant and retry_abandon out.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Truncated binary exponential backoff.
//
// NOT an 802.3 MAC. ATTEMPT_LIMIT, BACKOFF_LIMIT and the 0 <= r < 2^k draw
// are normative; the LFSR, its seeding and the slot-tick interface are not.
module backoff_engine #(
  parameter int unsigned ATTEMPT_LIMIT = 16,  // NORMATIVE: attemptLimit
  parameter int unsigned BACKOFF_LIMIT = 10,  // NORMATIVE: backoffLimit
  parameter logic [15:0] LFSR_SEED     = 16'hACE1  // per-station; see below
) (
  input  logic clk,
  input  logic rst_n,
 
  // One pulse per slot time, from the same bit-time source that drives
  // slot_time_counter. Backoff is counted in slot times, never in cycles.
  input  logic slot_tick,
 
  input  logic retry_req,       // from shared_medium_tx_ctrl: attempt failed
  input  logic frame_done,      // a frame completed or was abandoned
  input  logic late_collision,  // abort path — must NOT enter backoff
 
  output logic retry_grant,     // to shared_medium_tx_ctrl: attempt again now
  output logic retry_abandon,   // to shared_medium_tx_ctrl: discard the frame
  output logic [4:0] attempt_count,
  output logic [3:0] backoff_k,
  output logic [9:0] slots_remaining
);
 
  localparam int unsigned ATT_W = $clog2(ATTEMPT_LIMIT + 1);
 
  typedef enum logic [1:0] {
    B_IDLE  = 2'd0,  // no attempt in progress
    B_WAIT  = 2'd1,  // counting down slot times
    B_GRANT = 2'd2,  // one-cycle grant pulse
    B_GIVE  = 2'd3   // one-cycle abandon pulse
  } b_state_e;
 
  b_state_e         state_q, state_d;
  logic [ATT_W-1:0] attempt_q;
  logic [9:0]       slots_q;
  logic [15:0]      lfsr_q;
 
  // ── Pseudo-random source ────────────────────────────────────────────────
  // 16-bit Fibonacci LFSR, taps 16,14,13,11 (x^16 + x^14 + x^13 + x^11 + 1),
  // maximal length 65535. It free-runs on every clock, NOT only when a draw
  // is needed: a generator clocked only on demand would produce the same
  // sequence in two stations that collided together, which is exactly the
  // correlation the standard warns against.
  wire lfsr_fb = lfsr_q[15] ^ lfsr_q[13] ^ lfsr_q[12] ^ lfsr_q[10];
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) lfsr_q <= LFSR_SEED;             // never all-zero
    else        lfsr_q <= {lfsr_q[14:0], lfsr_fb};
  end
 
  // ── The draw ────────────────────────────────────────────────────────────
  // k = min(n, BACKOFF_LIMIT), and r is the low k bits of the LFSR.
  //
  // MASKING, not modulo. 2^k is a power of two, so taking k bits is an
  // EXACTLY uniform draw over 0..2^k-1. A `% (2^k)` would be identical here
  // and a `% m` for non-power-of-two m would be biased toward small values
  // — the standard specifies a power-of-two range precisely so that the
  // cheapest hardware draw is also the correct one.
  wire [3:0] k_next   = (attempt_q >= ATT_W'(BACKOFF_LIMIT))
                          ? 4'(BACKOFF_LIMIT) : 4'(attempt_q + 1'b1);
  wire [9:0] k_mask   = (10'd1 << k_next) - 10'd1;
  wire [9:0] draw     = lfsr_q[9:0] & k_mask;
 
  wire attempts_exhausted = (attempt_q >= ATT_W'(ATTEMPT_LIMIT - 1));
 
  // ── Next state ──────────────────────────────────────────────────────────
  always_comb begin
    state_d = state_q;
    case (state_q)
      // A late collision never reaches here: the classifier gates it out.
      // The port exists so this block can be RESET by one, not driven by one.
      B_IDLE:  if (retry_req) state_d = attempts_exhausted ? B_GIVE : B_WAIT;
      // r = 0 is legal and must resolve immediately, not after one slot.
      B_WAIT:  if (slots_q == 10'd0)        state_d = B_GRANT;
               else if (slot_tick && slots_q == 10'd1) state_d = B_GRANT;
      B_GRANT: state_d = B_IDLE;
      B_GIVE:  state_d = B_IDLE;
      default: state_d = B_IDLE;
    endcase
  end
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      state_q   <= B_IDLE;
      attempt_q <= '0;
      slots_q   <= '0;
    end else begin
      state_q <= state_d;
 
      // Attempt accounting. A completed or abandoned frame resets the
      // counter — the exponent is per contention event, not per station,
      // which is the capture effect's structural cause (Section 10).
      if (frame_done || late_collision)      attempt_q <= '0;
      else if (state_q == B_IDLE && retry_req) attempt_q <= attempt_q + 1'b1;
 
      if (state_q == B_IDLE && retry_req && !attempts_exhausted)
        slots_q <= draw;
      else if (state_q == B_WAIT && slot_tick && slots_q != 10'd0)
        slots_q <= slots_q - 10'd1;
    end
  end
 
  assign retry_grant     = (state_q == B_GRANT);
  assign retry_abandon   = (state_q == B_GIVE);
  assign attempt_count   = 5'(attempt_q);
  assign backoff_k       = k_next;
  assign slots_remaining = slots_q;
 
endmodule
A four-state machine. IDLE is the start state. On a retry request with attempts remaining, IDLE goes to WAIT; on a retry request at the attempt limit, IDLE goes to GIVE. WAIT goes to GRANT when the slot countdown reaches zero. GRANT returns to IDLE after pulsing the grant, and GIVE returns to IDLE after pulsing the abandon.IDLEWAITGRANTGIVEretry_req · attempts leftretry_req · attempts leftretry_req ·attempts…slots remaining = 0slots remaining = 0grant pulsedgrant pulsedretry_req · at limitretry_req · at limitabandon pulsedabandonpulsed
Figure 2 — the backoff engine's four states; the two exits from IDLE are the whole policy.

The two arrows leaving IDLE are the entire retransmission policy: contention that is still worth resolving goes to WAIT, and a frame that has exhausted attemptLimit goes to GIVE. Nothing else decides whether a frame lives.

Classification: synthesizable.

What it teaches: four things that are each a separate bug if missed — that the draw must be masked rather than reduced, that r = 0 must grant immediately rather than after a slot, that the generator must free-run so two stations decorrelate, and that the attempt counter resets per frame rather than per station.

The r = 0 arm deserves the extra state-machine line it costs. Section 10 established that a zero draw is legal and is half of all first-retry outcomes. A B_WAIT state that only left on slot_tick would silently convert every zero draw into a one-slot delay, halving the low-load recovery rate. The symptom would be a network that works and is inexplicably slower than the model predicts — the hardest kind of defect to find, because nothing fails.

Why the LFSR free-runs. Two stations that collide are, by definition, doing the same thing at the same time. A generator advanced only when a draw is needed would advance in lockstep in both, and two identically-seeded stations would draw identically forever. Free-running plus a per-station seed is the cheap way to satisfy the standard's requirement that two stations' numbers be uncorrelated. In production the seed must come from something genuinely per-device — the station's own MAC address is the usual choice — not from a synthesis-time constant that every board in the fleet shares.

Deliberately simplified: no separate treatment of the first attempt against retries in the attempt counter's encoding; slot_tick assumed exactly one cycle wide and aligned to slot boundaries; no ageing of a frame that has been in backoff a long time; a 16-bit LFSR where a real design would justify its width against how many draws occur between correlated events.

Production implication: seed from the device's MAC address or a fuse, size and tap the generator against a stated decorrelation requirement, expose the attempt count and the drawn value in a debug register so a collision histogram can be built, and define what happens to a frame whose backoff outlives the client's timeout.

12. RTL 5 — The Integrated CSMA/CD Transmit MAC

The four blocks above plus Chapter 1.1's controller are a working half-duplex transmit MAC. The integration is where the composition becomes visible — and where the late-collision path, which touches three of the five blocks, has to be got right.

A block diagram of the integrated CSMA/CD transmit MAC. The slot-time counter feeds the collision classifier. The classifier produces a retryable signal that feeds the backoff engine and a late-collision signal that goes to the abort path instead. The backoff engine returns retry grant and retry abandon to the Chapter 1.1 transmit controller, which drives transmit enable and the jam generator.slot_time_counterbit times since the attemptbegancollision_classifierearly against lateabort + reportlate collisions end hereshared_medium_tx_ctrlChapter 1.1, unchangedjam_generatorfixed 32 bit timesbackoff_enginegrant, or abandon at 16tx_enabledrive the mediumslot_elapsedlateretryablejam_enableretry_req12
Figure 3 — the five blocks and the one signal that must not reach the backoff engine.

The edge that is not drawn is the point of the figure: nothing connects late to backoff_engine. That absence is Section 7's structural guarantee, and it survives integration only if the integration respects it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Half-duplex CSMA/CD transmit MAC — Chapter 1.1's access
// controller plus this chapter's slot timing, classification, jam and backoff.
//
// NOT an 802.3 MAC. Transmit path only: no receive, no FCS, no interframe
// gap, no address filtering, no PHY interface.
module csma_cd_tx_mac #(
  parameter int unsigned SLOT_TIME_BITS = 512,   // NORMATIVE (10/100 Mb/s)
  parameter int unsigned JAM_BITS       = 32,    // NORMATIVE
  parameter int unsigned ATTEMPT_LIMIT  = 16,    // NORMATIVE
  parameter int unsigned BACKOFF_LIMIT  = 10,    // NORMATIVE
  parameter logic [15:0] LFSR_SEED      = 16'hACE1
) (
  input  logic clk,
  input  logic rst_n,
  input  logic bit_tick,
  input  logic slot_tick,
 
  input  logic tx_req,
  input  logic tx_frame_done,
  input  logic carrier_sense,
  input  logic collision_detect,
 
  output logic tx_enable,
  output logic jam_active,
  output logic tx_ok,              // frame transmitted successfully
  output logic excessive_collision, // attemptLimit reached
  output logic late_collision_err   // aborted: fault, not contention
);
 
  logic within_slot_time, slot_elapsed;
  logic early_coll, late_coll, retryable, late_sticky;
  logic ctrl_jam_enable, retry_req, retry_grant, retry_abandon;
  logic ctrl_tx_enable, tx_abort, tx_error;
  logic jam_done;
  logic [9:0] slots_remaining;
  logic [4:0] attempt_count;
  logic [3:0] backoff_k;
 
  // The slot-time counter must run across BOTH frame bits and jam bits: the
  // acquisition bound covers everything this station drives, and the jam is
  // inside the budget Section 3 allocated. Feeding it ctrl_tx_enable alone
  // would stop the clock during the jam and make a genuinely late collision
  // arriving during a jam look early.
  wire tx_active = ctrl_tx_enable || jam_active;
 
  logic tx_enable_q;
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) tx_enable_q <= 1'b0;
    else        tx_enable_q <= ctrl_tx_enable;
  end
  wire attempt_start = ctrl_tx_enable && !tx_enable_q;
 
  slot_time_counter #(.SLOT_TIME_BITS(SLOT_TIME_BITS)) u_slot (
    .clk, .rst_n, .bit_tick,
    .tx_active        (tx_active),
    .within_slot_time (within_slot_time),
    .slot_elapsed     (slot_elapsed),
    .bits_sent        (/* unused here; exposed for debug in production */)
  );
 
  collision_classifier u_cls (
    .clk, .rst_n,
    .collision_detect (collision_detect),
    .within_slot_time (within_slot_time),
    .slot_elapsed     (slot_elapsed),
    .attempt_start    (attempt_start),
    .early_collision  (early_coll),
    .late_collision   (late_coll),
    .retryable        (retryable),
    .late_sticky      (late_sticky)
  );
 
  // Chapter 1.1's controller, INSTANTIATED UNCHANGED. It sees `retryable`
  // where it previously saw a raw collision indication — the only change
  // this chapter makes to it, and it is made at the boundary rather than
  // inside the module.
  shared_medium_tx_ctrl #(.JAM_CYCLES(1)) u_ctrl (
    .clk, .rst_n,
    .tx_req           (tx_req && !late_coll),
    .tx_frame_done    (tx_frame_done),
    .tx_enable        (ctrl_tx_enable),
    .carrier_sense    (carrier_sense),
    .collision_detect (retryable),
    .jam_enable       (ctrl_jam_enable),
    .retry_req        (retry_req),
    .retry_grant      (retry_grant),
    .retry_abandon    (retry_abandon || late_coll),
    .tx_abort         (tx_abort),
    .tx_error         (tx_error)
  );
 
  // JAM_CYCLES is set to 1 above because the real jam length is now owned by
  // jam_generator in bit times rather than clock cycles. The controller's
  // jam state becomes a one-cycle handoff into the block that does the work.
  jam_generator #(.JAM_BITS(JAM_BITS)) u_jam (
    .clk, .rst_n, .bit_tick,
    .jam_start  (ctrl_jam_enable),
    .jam_active (jam_active),
    .jam_done   (jam_done)
  );
 
  backoff_engine #(
    .ATTEMPT_LIMIT (ATTEMPT_LIMIT),
    .BACKOFF_LIMIT (BACKOFF_LIMIT),
    .LFSR_SEED     (LFSR_SEED)
  ) u_back (
    .clk, .rst_n, .slot_tick,
    .retry_req       (retry_req && jam_done),
    .frame_done      (tx_ok),
    .late_collision  (late_coll),
    .retry_grant     (retry_grant),
    .retry_abandon   (retry_abandon),
    .attempt_count   (attempt_count),
    .backoff_k       (backoff_k),
    .slots_remaining (slots_remaining)
  );
 
  assign tx_enable           = ctrl_tx_enable;
  assign tx_ok               = ctrl_tx_enable && tx_frame_done && !collision_detect;
  assign excessive_collision = retry_abandon;
  assign late_collision_err  = late_coll;
 
endmodule

Classification: synthesizable.

What it teaches: that composing verified blocks is itself a design activity with its own bug class. Three integration decisions here are each a defect if made the other way.

tx_active = ctrl_tx_enable || jam_active. The acquisition bound covers everything this station puts on the medium, and the jam is part of that. Wiring the slot counter to the frame enable alone would freeze it during the jam, and a collision arriving during a jam — genuinely late — would be classified early and fed to backoff.

collision_detect becomes retryable at the boundary. Chapter 1.1's controller is instantiated unchanged. The behavioural change is made in the wiring, which means 1.1's module and its assertions remain valid, and the new policy is visible at exactly one line rather than buried in a modified copy.

retry_req && jam_done. The backoff draw must happen after the jam completes, not when the collision is detected. Drawing early would start the countdown while this station is still driving the medium, and the granted retry could land before the jam it is retrying after had finished.

Deliberately simplified: no interframe gap between a grant and the next attempt; tx_ok is a crude decode; no receive path, so no way to observe the outcome from the medium; bit_tick and slot_tick assumed consistent with each other rather than derived from one source.

Production implication: derive slot_tick from bit_tick inside the design so the two cannot be configured inconsistently, enforce the interframe gap between attempts, hold the frame intact in a buffer across all sixteen attempts, and define the client-visible status precisely — a discarded frame after excessive collisions and a discarded frame after a late collision are different events and a client that cannot distinguish them cannot act correctly on either.

13. The Collision Domain as a Budget

Section 3 ran the arithmetic once. The reason it is worth building into hardware is that the arithmetic is a budget with several independent spenders, and nothing in a running network reports that it has been overspent — except late collisions, after deployment.

Four spenders, all drawing on the same slot time:

SpenderWhat increases itWhat it costs
Physical spanlonger cable between the worst-case pairround trip, twice the one-way delay
Repeatersmore devices in the patha fixed store-and-repeat delay each, in both directions
PHY latencyslower encoding or detectionadded to the round trip at each end
Jamfixed by the standard at 32 bit timesa constant subtracted from the round-trip allowance

Two consequences are worth stating explicitly because they are where intuition fails.

A faster network is a smaller network. Slot time is fixed in bit times, so at ten times the rate a bit time is one tenth as long and the same 512 bit times buys one tenth the physical budget. Moving a shared segment from 10 to 100 Mb/s does not merely fail to help a marginal topology — it shrinks the budget that topology was already straining.

The failure is silent until it is not. A domain that is 5% over budget does not run 5% slower. It works correctly for every frame whose collisions happen to occur early, and produces a late collision only when two stations at the extremes of the domain collide in the narrow window the overspend created. The symptom is intermittent, load-dependent, and points at everything except cable length.

14. RTL 6 — Checking the Budget at Elaboration

The best place to catch an overspent budget is before the design exists. This is a parameterised check that fails at elaboration, not at simulation time and not in the lab.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ELABORATION-TIME CHECK. Not a datapath — this module contains no logic.
// It fails synthesis and simulation build if the configured collision domain
// cannot be covered by slot time.
//
// The delay figures are the INTEGRATOR'S; the standard's per-media path-delay
// tables are the real source for them. What is normative here is only the
// comparison: round trip + jam must fit inside slot time.
module collision_domain_budget #(
  parameter int unsigned SLOT_TIME_BITS   = 512,   // NORMATIVE (10/100 Mb/s)
  parameter int unsigned JAM_BITS         = 32,    // NORMATIVE
  parameter int unsigned BIT_TIME_PS      = 100_000, // 100 ns at 10 Mb/s
  parameter int unsigned SPAN_M           = 500,
  parameter int unsigned VELOCITY_M_PER_US = 200,  // ~2e8 m/s
  parameter int unsigned REPEATERS        = 4,
  parameter int unsigned REPEATER_DELAY_NS = 400,
  parameter int unsigned DETECT_LATENCY_NS = 600
) ();
 
  // All arithmetic in picoseconds, integer only — no reals, so the result is
  // identical across every tool that elaborates this.
  localparam int unsigned ONE_WAY_PS   = (SPAN_M * 1_000_000) / VELOCITY_M_PER_US;
  localparam int unsigned CABLE_RT_PS  = 2 * ONE_WAY_PS;
  localparam int unsigned REPEAT_RT_PS = 2 * REPEATERS * REPEATER_DELAY_NS * 1000;
  localparam int unsigned DETECT_PS    = DETECT_LATENCY_NS * 1000;
  localparam int unsigned JAM_PS       = JAM_BITS * BIT_TIME_PS;
 
  localparam int unsigned REQUIRED_PS  = CABLE_RT_PS + REPEAT_RT_PS + DETECT_PS + JAM_PS;
  localparam int unsigned BUDGET_PS    = SLOT_TIME_BITS * BIT_TIME_PS;
 
  // Reported as a percentage so the message is actionable rather than a pair
  // of raw picosecond figures nobody can size at a glance.
  localparam int unsigned USED_PCT     = (REQUIRED_PS * 100) / BUDGET_PS;
 
  // A bare `if` in the generate region is evaluated at ELABORATION, so this
  // fails the build rather than the first simulation that happens to produce
  // a worst-case collision.
  if (REQUIRED_PS > BUDGET_PS) begin : g_over_budget
    $error("collision domain over budget: needs %0d ps, slot time provides %0d ps (%0d%%). Shorten the span, remove repeaters, or stop using a shared medium.",
           REQUIRED_PS, BUDGET_PS, USED_PCT);
  end
 
  // A domain that fits but only barely is a maintenance hazard: one more
  // repeater or one longer patch lead pushes it over, and the symptom will
  // be intermittent late collisions rather than an obvious failure.
  if (REQUIRED_PS <= BUDGET_PS && USED_PCT > 80) begin : g_tight
    $warning("collision domain uses %0d%% of slot time — little margin for topology change.",
             USED_PCT);
  end
 
endmodule

Classification: elaboration-time check; contains no synthesizable logic and produces no hardware.

What it teaches: that a timing budget expressed in a specification can be expressed in a build, and that the cheapest place to catch a topology error is where it is written down rather than where it manifests. It is also the answer to "why does my design need to know the cable length" — it does not, but the configuration does, and encoding the relationship makes the dependency explicit.

Integer picoseconds, deliberately. Real arithmetic in elaboration-time expressions is a portability hazard: tools differ in constant folding and rounding, and a check that fires on one tool and not another is worse than no check. Integer picoseconds keep every value exact across the ranges involved.

The 80% warning is the more useful of the two checks. A design that is over budget will be found. A design at 95% will pass, ship, and fail in the field when someone adds a patch lead — and the fault will present as intermittent late collisions with no obvious cause, which Section 18 shows is one of the harder signatures to trace back.

Deliberately simplified: one worst-case path rather than a per-segment model; no per-media differentiation; no account of the receive-side latency asymmetry between two different station types.

Production implication: a real check is driven from the standard's path-delay values per media type, evaluates every station pair rather than an assumed worst case, and is generated from the same topology description that produces the cabling documentation — so the two cannot drift apart.

15. Waveform — Backoff After a Collision

The trace below is one attempt failing and the retry being scheduled. Propagation and bit-time relationships are compressed so the sequence fits in ten cycles; the ordering is what to read, not the durations.

One failed attempt and its scheduled retry

10 cycles
Ten clock cycles. Transmit enable is high from cycle 1. Collision detect asserts at cycle 3 while within slot time is still high, so the collision is classified early. Transmit enable falls and jam active rises at cycle 4. Jam done pulses at cycle 6, retry request pulses at cycle 6, and the attempt count increments to 1. Slots remaining loads to 1 at cycle 7 and decrements to 0 at cycle 8 on the slot tick, and retry grant pulses at cycle 9.collision, inside slot timecollision, inside slot timejam beginsjam beginsjam done, r drawnjam done, r drawnretry grantedretry grantedclktx_enablewithin_slotcoll_detjam_activeretry_reqattempt0000001111slots_rem0000000100slot_tickretry_grantt0t1t2t3t4t5t6t7t8t9
Figure 4 — collision, jam, draw, countdown, grant: the retry path in order.

Three orderings in that trace are the ones an implementation gets wrong.

The collision at cycle 3 is classified early only because within_slot was still high. Had the same coll_det pulse landed after within_slot fell — at cycle 7, say — the classifier would have produced late_coll and none of what follows would happen: no jam-to-backoff handoff, no attempt increment, no grant. One counter's state changes the entire response.

retry_req at cycle 6 coincides with jam_done, not with coll_det at cycle 3. The draw happens after the jam, for the reason Section 12 gives.

slots_rem loads 1 and the grant arrives after one slot_tick. Had the draw been 0, the grant would have arrived at cycle 7 with no tick at all — the path Section 11 says must exist and is easy to omit.

16. Assertions

Invariants of this model. The threshold semantics are normative; the pulse widths and the one-cycle response times are this design's own contract.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SVA over csma_cd_tx_mac and its submodules.
 
// SAFETY — P1: a late collision never reaches the backoff engine. This is
// the property the whole classifier exists to make structural; asserting it
// catches an integration that rewires around the classifier.
property p_late_never_retried;
  @(posedge clk) disable iff (!rst_n)
  u_cls.late_collision |-> !u_back.retry_req;
endproperty
a_late_never_retried : assert property (p_late_never_retried);
 
// SAFETY — P2: early and late are mutually exclusive. They are decoded from
// disjoint counter conditions, so a failure here means the counter itself is
// reporting both within_slot_time and slot_elapsed — a wrap, not a decode bug.
property p_class_exclusive;
  @(posedge clk) disable iff (!rst_n)
  !(u_cls.early_collision && u_cls.late_collision);
endproperty
a_class_exclusive : assert property (p_class_exclusive);
 
// SAFETY — P3: the slot counter saturates. Catches the wrapping counter that
// silently reclassifies late collisions as early ones on a long frame.
property p_slot_saturates;
  @(posedge clk) disable iff (!rst_n)
  (u_slot.cnt_q == SLOT_TIME_BITS) && u_slot.tx_active && u_slot.bit_tick
    |=> (u_slot.cnt_q == SLOT_TIME_BITS);
endproperty
a_slot_saturates : assert property (p_slot_saturates);
 
// SAFETY — P4: the jam runs its full normative length once started. The
// antecedent excludes reset deliberately; jam behaviour under reset is
// scenario 12's subject, not this property's.
property p_jam_full_length;
  @(posedge clk) disable iff (!rst_n)
  $rose(u_jam.jam_active) |-> ##[1:$] (u_jam.jam_done && u_jam.cnt_q == JAM_BITS - 1);
endproperty
a_jam_full_length : assert property (p_jam_full_length);
 
// CAUSATION — P5: a backoff draw never exceeds 2^k - 1 for the current k.
// This is the normative range, and it is the property a masking bug breaks
// silently — a wrong mask produces a legal-looking but wrongly-distributed
// delay that no functional test detects.
property p_draw_in_range;
  @(posedge clk) disable iff (!rst_n)
  (u_back.state_q == u_back.B_IDLE && u_back.retry_req)
    |=> (u_back.slots_q < (10'd1 << $past(u_back.k_next)));
endproperty
a_draw_in_range : assert property (p_draw_in_range);
 
// CAUSATION — P6: k is truncated at backoffLimit. Catches the missing
// min() that lets the range keep doubling past attempt 10.
property p_k_truncated;
  @(posedge clk) disable iff (!rst_n)
  u_back.k_next <= BACKOFF_LIMIT;
endproperty
a_k_truncated : assert property (p_k_truncated);
 
// CAUSATION — P7: abandon happens at attemptLimit and not before. Catches
// both an off-by-one that gives up early and one that retries forever.
property p_abandon_at_limit;
  @(posedge clk) disable iff (!rst_n)
  u_back.retry_abandon |-> (u_back.attempt_q >= ATTEMPT_LIMIT - 1);
endproperty
a_abandon_at_limit : assert property (p_abandon_at_limit);
 
// SAFETY — P8: a zero draw grants without waiting for a slot tick. The
// low-load performance property from Section 11, stated so a "helpful"
// minimum delay cannot be added without failing a test.
property p_zero_draw_grants_immediately;
  @(posedge clk) disable iff (!rst_n)
  (u_back.state_q == u_back.B_WAIT && u_back.slots_q == 10'd0)
    |=> u_back.retry_grant;
endproperty
a_zero_draw_immediate : assert property (p_zero_draw_grants_immediately);
 
// SAFETY — P9: two drive sources are never active together. tx_enable and
// jam_active both drive the medium; asserting both is the fault the whole
// access method exists to prevent.
property p_single_driver;
  @(posedge clk) disable iff (!rst_n)
  !(tx_enable && jam_active);
endproperty
a_single_driver : assert property (p_single_driver);
 
// LIVENESS — P10: a frame in backoff is eventually resolved, one way or the
// other. ASSUMPTIONS, stated: slot ticks keep arriving, and the medium is
// not permanently busy. Without both this is false for a correct design.
assume property (@(posedge clk) s_eventually (slot_tick));
assume property (@(posedge clk) s_eventually (!carrier_sense));
property p_backoff_resolves;
  @(posedge clk) disable iff (!rst_n)
  (u_back.state_q == u_back.B_WAIT)
    |-> s_eventually (u_back.retry_grant || u_back.retry_abandon);
endproperty
a_backoff_resolves : assert property (p_backoff_resolves);

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

It reads like the definition of collision recovery, and it is wrong twice over.

A late collision is not retried at all, by design — Section 6. This property fires on every late collision, which is precisely the case the design is handling correctly.

And the sixteenth collision is not retried either. attemptLimit is a real limit; the frame is abandoned. A liveness property over "every collision" has to be scoped to retryable && !attempts_exhausted before it is true of a conforming MAC.

The failure mode this creates in a project is familiar: the assertion fires during the first duplex-mismatch test, someone concludes the property is too strict rather than wrong, and it is waived. The waiver then also covers P1 in spirit — and P1 is the property that actually matters, because it is the one that catches a late collision reaching the backoff engine.

Getting an assertion's scope right is the work. Writing it is not.

17. Verification

Monitors observe: the client handshake, both medium inputs, tx_enable and jam_active, the classifier outputs, and the backoff engine's attempt_count, backoff_k and slots_remaining.

The scoreboard independently predicts the attempt sequence, the drawn value's legal range for each attempt, and the resolution. It must model the LFSR independently from its own specification rather than sampling the design's lfsr_q — a checker reading the design's generator agrees with the design about every seeding and tap bug in it.

Scenarios

  1. First-attempt success. Idle medium, no collision. Verify no attempt increment, no draw, tx_ok.
  2. Collision on the first bit. collision_detect in the same cycle tx_enable first rises. Verify the jam still runs to full length and the attempt increments — the counter has had almost no time to advance and must still classify early.
  3. Collision on the last bit inside slot time. bits_sent == SLOT_TIME_BITS - 1. Must classify early. The tightest boundary in the design.
  4. Collision on the first bit after slot time. bits_sent == SLOT_TIME_BITS. Must classify late, must not retry, must report late_collision_err. Together with 3 this is the off-by-one that a coarse test never separates.
  5. Long frame, no collision. Transmit far beyond slot time. Verify the counter saturates and within_slot_time never re-asserts — the wrapping bug P3 targets.
  6. Zero draw. Force the LFSR to a state producing r = 0. Verify the grant arrives with no slot_tick.
  7. Maximum draw at k = 1. r = 1. Verify exactly one slot_tick of delay, not zero and not two.
  8. Exponent growth. Collide repeatedly and check backoff_k follows 1, 2, 3 … 10, then holds at 10 for attempts 11 through 16.
  9. Attempt-limit exhaustion. Sixteen collisions. Verify excessive_collision, the frame discarded, the attempt counter reset, and that a new frame starts again at k = 1.
  10. Late collision mid-backoff. A late collision while a retry is already scheduled. Verify the abort wins and the attempt counter clears.
  11. Reset mid-backoff. Verify no stale slots_remaining, no spurious grant afterwards, and a clean first attempt on release.
  12. Reset mid-jam. Verify the medium is released, the jam does not resume its count on release, and no retry_req is emitted for the interrupted jam.

Coverage

Cross attempt_count against backoff_k — every legal pair, including the truncated region where k holds at 10 across six different attempt numbers. Cross collision timing against slot time in three bins: well inside, the boundary cycle either side, and well after. Cover draws of 0, 1, 2^k - 1 and an interior value at each k. Cover reset asserted in each of the backoff engine's four states.

A directed stimulus for the boundary that matters most

Scenarios 3 and 4 are one cycle apart and separate two entirely different behaviours. Randomisation reaches the boundary rarely and does not reliably hit both sides of it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// NON-SYNTHESIZABLE — directed stimulus. Walks the early/late boundary,
// one bit time either side, and checks that the two sides do opposite things.
task automatic walk_late_collision_boundary(input int unsigned offset_bits);
  // offset_bits = SLOT_TIME_BITS-1 → the last EARLY cycle
  // offset_bits = SLOT_TIME_BITS   → the first LATE cycle
  int unsigned n;
 
  tx_req <= 1'b1; carrier_sense <= 1'b0; collision_detect <= 1'b0;
  @(posedge clk);
  wait (tx_enable);
 
  // Advance exactly offset_bits bit times into the transmission.
  for (n = 0; n < offset_bits; n++) begin
    @(posedge clk iff bit_tick);
  end
 
  collision_detect <= 1'b1;
  @(posedge clk);
  collision_detect <= 1'b0;
 
  if (offset_bits < SLOT_TIME_BITS) begin
    assert (dut.u_cls.early_collision === 1'b0 || $past(dut.u_cls.early_collision))
      else $error("boundary-1: expected an EARLY classification");
    // The retry path must engage: jam to completion, then exactly one draw.
    wait (dut.jam_active); wait (!dut.jam_active);
    assert (dut.u_back.attempt_count == 1)
      else $error("boundary-1: early collision did not increment the attempt count");
  end else begin
    assert (dut.late_collision_err)
      else $error("boundary+0: expected a LATE classification");
    // The retry path must NOT engage. This is the assertion that matters.
    repeat (32) @(posedge clk);
    assert (dut.u_back.attempt_count == 0)
      else $error("boundary+0: a late collision entered the backoff engine");
  end
endtask

Call it twice, with SLOT_TIME_BITS-1 and SLOT_TIME_BITS. A design with an off-by-one in the saturation comparison passes one call and fails the other, and the failure message names which side of the boundary broke. The second call's attempt_count == 0 check is the executable form of property P1 — the one integration mistake that costs the most and shows the least.

18. Debugging — Reading the Collision Counters

Half-duplex Ethernet is unusual in that its counters are genuinely diagnostic, if the classes are kept apart. Four readings, four different conclusions.

ReadingConclusionNext step
Collisions present, rising with load, zero lateNormal. The access method is working.Look at domain size and offered load, not at faults.
Zero collisions, zero successful framesThe station is not reaching the medium at all.PHY, cabling, connector. Nothing in the MAC will explain it.
Any late collisionsA fault. Never normal.Duplex mismatch first, then domain size, then a faulty station.
Excessive-collision errors with few ordinary collisionsContradictory — sixteen failures need many collisions.Suspect the attempt counter or the classifier, not the network.

Why the third row deserves its priority. Late collisions have exactly three causes, and one of them — a duplex mismatch — is both the most common and the most confusing, because the full-duplex end reports nothing wrong at all. It has no reason to defer, no collision logic engaged, and no counter that will move. The fault is visible from one end only, which is why a one-sided investigation of a duplex mismatch so often concludes that the half-duplex station is broken.

Why the fourth row is worth checking. Sixteen consecutive failed attempts require at least sixteen collisions on that frame alone. Excessive-collision errors without a proportionate ordinary-collision count is an internal inconsistency, and the likely causes are inside the design: an attempt counter that is not reset on a successful frame, or a classifier feeding the backoff engine something it should not — which is P1 failing in silicon.

19. Gigabit Half Duplex — Carrier Extension and Bursting

Section 2's callout left a question: the budget scales with the bit time, so at 1 Gb/s a 512-bit-time slot would buy one hundredth of the 10 Mb/s physical extent. The standard's answer is to change the slot time rather than accept a domain measured in metres.

At 1 Gb/s, slotTime is 4096 bit times while minFrameSize stays at 512 bits. Those two no longer coincide, and the gap is where the mechanism lives.

Carrier extension. A frame shorter than slot time is followed by extension bits so that the total transmission occupies at least a slot time. The maximum extension is exactly slotTime − minFrameSize:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Derived from the NORMATIVE Clause 4 parameter values.
localparam int unsigned SLOT_TIME_BITS_1G = 4096;  // NORMATIVE, 1 Gb/s
localparam int unsigned MIN_FRAME_BITS    = 512;   // NORMATIVE, all rates
 
// The standard defines a boolean `extend`, true when this quantity is
// positive — which is exactly the condition "slot time exceeds the minimum
// frame", and it is false at 10 and 100 Mb/s where the two are equal.
localparam int unsigned MAX_EXTENSION_BITS = SLOT_TIME_BITS_1G - MIN_FRAME_BITS; // 3584
localparam bit          EXTEND             = (MAX_EXTENSION_BITS > 0);           // 1'b1
 
// So a minimum-length frame occupies 512 bits of frame plus up to 3584 bits
// of extension: 4096 bits, or 512 octets on the wire for 64 octets of frame.

Four consequences, and the fourth is why nobody uses this.

The extension is not frame data. It is carried as a distinct non-data encoding, and a separate error encoding is used to jam during an extension. The FCS covers the frame, not the extension.

Collision monitoring continues through the extension. The MAC keeps watching while it transmits extension bits, and — the detail that ties this section back to Section 6 — any collision after the slot-time threshold is treated as a late collision, extension or not. The threshold is the threshold.

Efficiency for small frames collapses. A 64-octet frame occupies 512 octets of medium time: one eighth of the wire's capacity does useful work. Frame bursting exists to soften this — after the first frame has met the slot-time requirement, a station may send further frames back to back within a burst limit of 65 536 bits, with only the first paying the extension cost.

And it is essentially unused. Gigabit Ethernet arrived after switching was standard. Half-duplex gigabit is specified and can be configured; it is not what anyone deploys. The reason to understand it is that it makes the underlying relationship explicit: when the standard needed the collision domain to stay a useful size at ten times the rate, it raised the slot time — because slot time was always the parameter that set the domain.

20. What Full Duplex Retires

Everything in this chapter exists because two stations can drive one medium. Remove that and the entire mechanism has nothing to do — the Clause 4 parameter table says so directly by listing slotTime, attemptLimit, backoffLimit and jamSize as not applicable at 10 Gb/s, a rate that has no half-duplex mode at all.

MechanismOn a shared mediumFull duplex
Slot-time counterBounds acquisition and fragment lengthNothing to bound
Collision classifierSplits contention from faultNo collisions to classify
Jam generatorMakes a collision unambiguousNothing to make unambiguous
Backoff engineSeparates two stations in timeNothing to separate
Attempt limitGives up on a broken mediumNo contention-driven failure
Minimum frame sizeGuarantees detection during transmissionThe timing reason is gone; the rule remains

Of the six blocks built in this chapter, five become dead logic. interFrameGap survives — it is 96 bits at every rate including 10 Gb/s, because it exists for receiver recovery rather than for contention.

A late collision changes meaning rather than disappearing. On a full-duplex link there should be no collisions at all, so a non-zero collision counter of any kind is a configuration fault — most often the duplex mismatch of Section 18, where one end is still running everything in this chapter and the other is not running any of it.

Chapter 1.5 develops what full duplex removed and what it created in its place.

21. Common Misconceptions

"Backoff is a fixed delay after a collision."

The wrong model: the MAC waits some interval and tries again, and the interval is a design constant.

What it costs: an implementation with a fixed delay collides again deterministically with the station it just collided with, because both wait identically and restart together. The symptom is a link that works with one active station and collapses with two — and because both stations are individually correct, the search goes to the PHY. The same model produces the "helpful" minimum-delay optimisation that breaks P8.

The corrected model: the delay is a uniformly distributed random integer number of slot times in the range 0 ≤ r < 2^k, where k = min(n, 10) grows with the attempt count. Randomness decorrelates two stations running identical logic; the exponential growth estimates contention from failure history; the truncation stops the range at 1023 slots; and r = 0 is legal, common, and the reason low-load recovery is fast.

"A late collision is just an ordinary collision that happened later."

The wrong model: collisions are collisions; the timing is incidental; retry it like any other.

What it costs: two failures at once. The MAC retries something a retry cannot fix, wasting sixteen attempts on a condition that is still present — and the late-collision counter, the single highest-signal reading in half-duplex Ethernet, is either not consulted or is merged into a total that hides it. Investigations then chase load and traffic patterns when the actual cause is a duplex mismatch or an over-length domain.

The corrected model: slot time is an upper bound on medium acquisition, so a collision after it means that bound was violated — by an over-budget domain, a duplex mismatch, or a faulty station. None of those is contention, so backoff cannot help, and the standard reports it as a distinct status. Retrying is not conservative; it is wrong.

"The 64-octet minimum frame size is an arbitrary format choice."

The wrong model: someone picked a round number for the smallest frame, the way a protocol designer picks a header length.

What it costs: the reasoning chain from cable length to frame format is invisible, so the connected facts — that a faster shared network must be a smaller one, that a fragment can be identified by length alone, that padding sits inside the FCS — all look like unrelated trivia to be memorised. It also makes carrier extension incomprehensible, since nothing explains why gigabit needed it.

The corrected model: the minimum frame equals slot time because a valid frame must be longer than any fragment a collision can produce, and slot time must exceed the round-trip propagation plus jam. 512 bits is a cable length in disguise. Everything else in the chapter follows from that one equality.

"CSMA/CD still runs on modern Ethernet links."

The wrong model: CSMA/CD is what Ethernet is, so every Ethernet MAC runs this algorithm.

What it costs: confident wrong reasoning about live systems — expecting collisions on a healthy switched port, attributing latency variation on a full-duplex link to contention, and, most damagingly, reading a non-zero collision counter on a full-duplex port as normal when it is the signature of a duplex mismatch that is silently destroying frames.

The corrected model: a full-duplex switched link has one station per end and a separate path per direction. There is no shared medium and the standard marks slot time, attempt limit, backoff limit and jam size not applicable at 10 Gb/s. This chapter explains where the MAC's shape, its vocabulary and its minimum frame size came from; it does not describe how a modern port operates.

22. Interview Reasoning

Because a valid frame has to be distinguishable from the fragment an aborted transmission leaves behind, and because a transmitter has to still be transmitting when a collision can still reach it.

The chain a strong answer walks:

  • Slot time must exceed the round-trip propagation time of the collision domain plus the maximum jam time.
  • Slot time is therefore an upper bound on how long acquiring the medium can take, and on the length of a fragment a collision can produce.
  • Making the minimum valid frame equal to slot time means anything shorter is unambiguously wreckage, and means the transmitter is still driving — and still monitoring — throughout the window in which a collision can arrive.
  • At 10 and 100 Mb/s, slot time is 512 bit times, so the minimum frame is 512 bits: 64 octets.

What separates a good answer from a complete one: naming that 64 octets is a timing constant expressed as a format rule, and that it survives on full-duplex links where every part of its justification is gone.

The follow-up to be ready for: why is gigabit's slot time different? Because the budget scales with the bit time, so keeping 512 would have shrunk the domain by a factor of ten. The standard raised slot time to 4096 bit times and added carrier extension to pad short frames up to it — which is also why a 64-octet frame occupies 512 octets of gigabit half-duplex medium time.

23. Understanding Check

24. What's Next

Two chapters have now built the half-duplex MAC from its constraint: a shared medium makes access a distributed timing problem, and slot time is the parameter that makes the problem solvable. The minimum frame size, the maximum domain size, the backoff resolution and the late-collision threshold are all that one parameter, seen from different directions.

What the algorithm does not do is scale. Section 13 showed the budget shrinking with every rate increase, and Section 10 showed the fairness degrading with every additional station. Both point the same way: the mechanism is an arbitration scheme for a resource that should not have been shared.

Chapter 1.3 — Packet Switching takes up the alternative that made sharing unnecessary — why a network moves discrete packets rather than allocating a circuit, and what that choice buys hardware. Chapter 1.4 then traces the physical evolution from coax through repeaters to switches, and Chapter 1.5 covers full duplex, which retires five of the six blocks built here and creates the need for flow control in their place.

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.