Skip to content
VLSI Mentor

Ethernet · Module 5

The 48-Bit MAC Address — OUI, I/G and U/L

The individual/group flag is the first bit of the frame body on the wire and the universal/local flag the second, so a receiver can select a matching pipeline 47 bit times before the address completes — and the address's global uniqueness is an administrative claim nothing enforces.

Chapter 5.1 placed the destination address second in the frame because it is the earliest point a station can give up. Chapter 2.7 §4 built the filter that acts there, branching on a bit in the first octet to separate individual addresses from group ones.

Neither said why that bit is in that position, and the answer turns Chapter 5.1's argument into something sharper.

The individual/group flag is the least significant bit of the first octet. Chapter 5.2 established that Ethernet transmits each octet least-significant-bit-first. Put those two facts together:

The individual/group bit is the very first bit of the frame body to arrive on the wire.

Not the first octet — the first bit. A receiver knows whether a frame is addressed to a group or to an individual station one bit time after the delimiter, before the first octet has finished arriving, and 47 bit times before the address is complete.

The universal/local flag is the second bit. And the organisationally assigned prefix occupies the first three octets, so it is complete at octet three while the device identifier runs to octet six.

Why is the address ordered this way, and what does its structure actually guarantee?

1. Scope — What This Chapter Owns

This chapter owns: why the two flag bits occupy the two lowest bit positions of the first octet and what that buys; the address as a structure ordered by decision urgency; the organisationally assigned prefix and the administrative model behind it; locally administered addressing and its collision reasoning; why a 48-bit comparison is not one comparison; and the difference between an administrative claim of uniqueness and an enforced one.

This chapter does not own:

  • What each address type means to a receiver or a switch — Chapter 5.4 owns that, and this chapter stops at what the bits are rather than what they imply.
  • The forwarding dispositions and the five accept reasons — Chapter 2.7 §4 and §7 own those, and this chapter explains the bit positions its filter branches on rather than repeating the filter.
  • The bit-ordering convention itself — Chapter 5.2 §2 owns it, and this chapter applies it.
  • Address learning and table management, which belong to the switching module.

The question this chapter answers that its neighbours do not: why is the address laid out this way, and what does its structure actually guarantee?

2. The Flag Bits Are the First Bits on the Wire

Chapter 5.2 §2 established the convention and showed why it makes hex values look wrong. Applied to the first octet of an address, it produces something more useful than a curiosity.

Take a first octet and transmit it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
first octet, as a value:   b7 b6 b5 b4 b3 b2 b1 b0
transmitted LSB first:     b0 b1 b2 b3 b4 b5 b6 b7
                           ^^ ^^
                           |  +-- U/L: second bit on the wire
                           +----- I/G: FIRST bit on the wire

The individual/group bit is bit 0 of the first octet, so it is the first bit of the frame body to arrive. The universal/local bit is bit 1, so it is the second.

Conceptual — the first octet of the address, LSB first

10 cycles
A conceptual bit-level view of the first octet of a destination address arriving after the start frame delimiter. The first bit carries the individual or group flag, the second carries the universal or local flag, and the remaining six carry the top of the assigned prefix. A receive path can select between an exact-match and a hash-lookup pipeline after the first bit.individual or group — one bit inindividual or group — onebit inuniversal or localuniversal or localstill 40 bits of address to comestill 40 bits of address tocomethe pipeline was chosen long agothe pipeline was chosenlong agobit_clkwireSFDI/GU/Lb2b3b4b5b6b7OCT2can_branchaddr_done0000000000t0t1t2t3t4t5t6t7t8t9
Figure 1 — conceptual: the coarsest classification arrives one bit after the delimiter.

This figure is conceptual and labelled so. It shows the ordering and the branch point exactly; it compresses the remaining five octets so the relationship is visible.

3. The Layout, Ordered by Decision Urgency

The address is 48 bits — six octets — and its structure is a sequence of progressively finer questions.

OctetsFieldWidthThe question it answers
1 (bit 0)I/G1 bitone station, or many?
1 (bit 1)U/L1 bitglobally assigned, or locally chosen?
1–3assigned prefix24 bitswhich organisation assigned it?
4–6device identifier24 bitswhich of their devices?
The 48-bit address decomposes into an assigned prefix of three octets and a device identifier of three octets. Inside the first octet of the prefix, the lowest bit is the individual or group flag and the second lowest is the universal or local flag. The two flags together define four disjoint address classes.48 bitssix octets, transmitted inorderAssigned prefixoctets 1 to 3, includesboth flagsDevice identifieroctets 4 to 6, theassignee's to useUniquenessclaimed by a process, notenforcedI/G — bit 0one station, or manyU/L — bit 1assigned, or locallychosenFour disjoint classeswhich is why they nevercollide12
Figure 2 — two flags inside the prefix, and the four classes they keep disjoint.

The two flag bits are inside the first octet of the prefix, not separate from it. That is worth stating plainly because it is a common misreading: the prefix is 24 bits including the two flags, so an organisation receiving a prefix receives 22 freely-assignable bits in that first octet's worth of space, with the low two constrained.

And that constraint is what makes the flags work. A globally assigned individual address has both flags clear, so every address an organisation assigns from a standard prefix begins with the same two bits. The flags are not carved out of the address space arbitrarily — they are the reason the assignment scheme can guarantee that a globally assigned unicast address never collides with a group address or a locally chosen one.

4. Locally Administered Addresses

Setting the U/L bit declares an address locally administered: chosen by whoever operates the network rather than assigned by an authority.

The mechanism is simply that bit. No registry is consulted and none exists — that is the point. A locally administered address is any address with bit 1 of its first octet set, and the space is large: 46 free bits per combination of the two flags.

Why it exists, in three genuine cases:

  • Virtualisation. A hypervisor creating hundreds of virtual interfaces has no assigned prefix to draw from and no reason to obtain one.
  • Address mobility. A service address that must move between physical machines on failover cannot be a manufacturer-assigned address, because those are bound to hardware.
  • Deliberate structure. An operator may want addresses that encode something — a rack, a role, a tenant — which an assigned prefix cannot express.

And what it gives up is the only thing the assigned prefix was providing: the administrative uniqueness claim.

5. RTL 1 — The Decomposer, With Early Outputs

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Address decomposition, streaming.
//
// A decomposer that takes a completed 48-bit address is easy to write and
// throws away this chapter's entire point. The flags are in the first two
// bit positions SO THAT A RECEIVER CAN BRANCH BEFORE THE ADDRESS ARRIVES --
// and a module that requires the whole address first cannot express that.
//
// So this consumes the address octet by octet and produces:
//   flags_valid after the FIRST octet   (both flags are inside it)
//   oui_valid   after the THIRD octet
//   addr_valid  after the SIXTH
//
// Each output is available at the earliest moment it can be, which is what
// lets the receive path select a pipeline early.
package macaddr_pkg;
  // Bit positions within the first octet, as published. Under LSB-first
  // transmission these are the first and second bits on the wire.
  localparam int unsigned BIT_IG = 0;
  localparam int unsigned BIT_UL = 1;
 
  localparam logic [47:0] ADDR_BROADCAST = 48'hFFFF_FFFF_FFFF;
 
  typedef enum logic [1:0] {
    ADDR_UNICAST_GLOBAL,   // I/G = 0, U/L = 0
    ADDR_UNICAST_LOCAL,    // I/G = 0, U/L = 1
    ADDR_GROUP_GLOBAL,     // I/G = 1, U/L = 0
    ADDR_GROUP_LOCAL       // I/G = 1, U/L = 1
  } addr_class_e;
endpackage
 
module mac_address_decomposer
  import macaddr_pkg::*;
(
  input  logic       clk,
  input  logic       rst_n,
 
  input  logic       octet_valid,
  input  logic [7:0] octet_in,
  input  logic       addr_start,     // first octet of an address
 
  // ── Available after ONE octet. The reason the flags are where they are.
  output logic        flags_valid,
  output logic        is_group,
  output logic        is_local,
  output addr_class_e addr_class,
 
  // ── Available after THREE octets.
  output logic        oui_valid,
  output logic [23:0] oui,
 
  // ── Available after SIX.
  output logic        addr_valid,
  output logic [47:0] address,
  output logic        is_broadcast,
 
  // How many octets into the address we are. Exposed so a consumer can
  // reason about what is and is not yet known.
  output logic [2:0]  octet_count
);
 
  logic [47:0] shift_q;
  logic [2:0]  count_q;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      shift_q      <= '0;
      count_q      <= '0;
      flags_valid  <= 1'b0;
      is_group     <= 1'b0;
      is_local     <= 1'b0;
      oui_valid    <= 1'b0;
      addr_valid   <= 1'b0;
      is_broadcast <= 1'b0;
    end else begin
      flags_valid <= 1'b0;
      oui_valid   <= 1'b0;
      addr_valid  <= 1'b0;
 
      if (octet_valid) begin
        shift_q <= {shift_q[39:0], octet_in};
 
        if (addr_start) begin
          count_q <= 3'd1;
          // THE EARLY BRANCH. Both flags are inside this octet, so the
          // coarse classification is complete here -- five octets before
          // the address is.
          flags_valid <= 1'b1;
          is_group    <= octet_in[BIT_IG];
          is_local    <= octet_in[BIT_UL];
        end else begin
          count_q <= count_q + 3'd1;
 
          if (count_q == 3'd2) oui_valid  <= 1'b1;   // third octet complete
          if (count_q == 3'd5) addr_valid <= 1'b1;   // sixth octet complete
        end
      end
    end
  end
 
  always_comb begin
    unique case ({is_group, is_local})
      2'b00:   addr_class = ADDR_UNICAST_GLOBAL;
      2'b01:   addr_class = ADDR_UNICAST_LOCAL;
      2'b10:   addr_class = ADDR_GROUP_GLOBAL;
      default: addr_class = ADDR_GROUP_LOCAL;
    endcase
  end
 
  assign oui         = shift_q[23:0];
  assign address     = shift_q;
  assign octet_count = count_q;
  // Broadcast is a group address with every bit set -- so it satisfies the
  // group test and is ALSO an exact value. Chapter 5.4 owns why that
  // double membership matters; here it is one comparison.
  assign is_broadcast = (shift_q == ADDR_BROADCAST);
 
endmodule

Classification: synthesizable.

What it teaches: that a decomposer must be streaming to express what the layout is for. The flags are in the first two bit positions so that a receiver can branch before the address arrives, and a module requiring a completed 48-bit value has discarded that property while producing identical outputs.

Deliberately simplified: it decomposes only. It does not compare against a station address or a subscription set — those are Chapter 2.7 §4's filter and Chapter 5.4's matcher respectively.

Production implication: octet_count being exposed is what lets a consumer reason about what is not yet known. A pipeline stage that acts on is_group at octet one must not also assume the prefix is available, and a design where downstream logic cannot tell how much has arrived will eventually read a stale or partial value. Making the progress explicit is cheaper than a convention nobody documents.

Later ownership: the four address classes are named here and Chapter 5.4 owns what each one means to a receiver and to a switch.

6. RTL 2 — A 48-Bit Compare Is Not One Comparison

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Staged address comparison with early rejection.
//
// `a == b` on 48 bits is a wide AND reduction over 48 XNORs. A tool will
// balance it into a tree, and at line rate the tree depth is a real path.
//
// But a STREAMING comparison can do something the operator cannot: reject
// as soon as any octet differs, which on unrelated traffic is almost always
// the first one. The average comparison then costs one octet rather than
// six, and the worst case is unchanged.
//
// That matters because a receive path compares every arriving frame against
// its own address, and the overwhelming majority do not match.
module mac_address_compare #(
  parameter int unsigned CNT_W = 24
) (
  input  logic        clk,
  input  logic        rst_n,
  input  logic        clear,
 
  input  logic        octet_valid,
  input  logic [7:0]  octet_in,
  input  logic        addr_start,
  input  logic [47:0] local_address,
 
  // Rejected as soon as an octet differs. On a mismatching address this
  // fires early, and everything downstream can stop.
  output logic       mismatch,
  output logic [2:0] mismatch_octet,
 
  // Confirmed only when all six have matched.
  output logic       match,
 
  // Distribution of how many octets were needed to reject. On unrelated
  // traffic this is heavily weighted to one, and a shift toward six means
  // the addresses on this segment share prefixes -- which is a real
  // property of a deployment and changes the timing argument.
  output logic [CNT_W-1:0] reject_at_octet [6],
  output logic [CNT_W-1:0] c_match
);
 
  logic [2:0] idx_q;
  logic       failed_q;
  logic       active_q;
 
  function automatic logic [CNT_W-1:0] bump(input logic [CNT_W-1:0] v,
                                            input logic             en);
    bump = (en && !(&v)) ? (v + 1'b1) : v;   // saturating
  endfunction
 
  // The octet of the local address to compare against, most significant
  // first -- addresses are transmitted in that octet order.
  logic [7:0] local_octet_c;
  always_comb begin
    unique case (idx_q)
      3'd0:    local_octet_c = local_address[47:40];
      3'd1:    local_octet_c = local_address[39:32];
      3'd2:    local_octet_c = local_address[31:24];
      3'd3:    local_octet_c = local_address[23:16];
      3'd4:    local_octet_c = local_address[15:8];
      default: local_octet_c = local_address[7:0];
    endcase
  end
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      idx_q          <= '0;
      failed_q       <= 1'b0;
      active_q       <= 1'b0;
      mismatch       <= 1'b0;
      match          <= 1'b0;
      mismatch_octet <= '0;
      for (int unsigned i = 0; i < 6; i++) reject_at_octet[i] <= '0;
      c_match <= '0;
    end else begin
      mismatch <= 1'b0;
      match    <= 1'b0;
 
      if (clear) begin
        for (int unsigned i = 0; i < 6; i++) reject_at_octet[i] <= '0;
        c_match <= '0;
      end
 
      if (octet_valid && addr_start) begin
        idx_q    <= 3'd1;
        active_q <= 1'b1;
        if (octet_in != local_address[47:40]) begin
          failed_q           <= 1'b1;
          mismatch           <= 1'b1;
          mismatch_octet     <= 3'd0;
          reject_at_octet[0] <= bump(reject_at_octet[0], 1'b1);
        end else begin
          failed_q <= 1'b0;
        end
      end else if (octet_valid && active_q) begin
        if (!failed_q && (octet_in != local_octet_c)) begin
          failed_q               <= 1'b1;
          mismatch               <= 1'b1;
          mismatch_octet         <= idx_q;
          reject_at_octet[idx_q] <= bump(reject_at_octet[idx_q], 1'b1);
        end
 
        if (idx_q == 3'd5) begin
          active_q <= 1'b0;
          if (!failed_q && (octet_in == local_octet_c)) begin
            match   <= 1'b1;
            c_match <= bump(c_match, 1'b1);
          end
        end else begin
          idx_q <= idx_q + 3'd1;
        end
      end
    end
  end
 
endmodule

Classification: synthesizable.

What it teaches: that a streaming comparison can reject early, which a 48-bit equality cannot. On traffic whose addresses are unrelated, the first octet differs almost always — so the average comparison costs one octet while the worst case is unchanged at six.

Deliberately simplified: one local address. A real receive path compares against several — its own, plus any additional unicast addresses it has been configured with — and the comparisons run in parallel.

Production implication: reject_at_octet is a deployment measurement disguised as a performance counter. Heavily weighted toward octet zero means the addresses on this segment are unrelated and early rejection is working. A shift toward octets three and later means the addresses share prefixes — which happens when a segment is dominated by one manufacturer, or by virtual interfaces from one hypervisor's locally administered range — and it changes both the timing argument and the effectiveness of any prefix-based hashing.

Later ownership: group-address matching is a different problem with a different structure, and Chapter 5.4 owns it.

7. RTL 3 — Generating a Locally Administered Address

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Locally administered address generation, auditable.
//
// Setting the U/L bit is trivial. The module exists because Section 4
// showed the real collision risk is a BAD SEED, not the birthday bound --
// and a generator that does not report its entropy source cannot be
// checked by anyone.
//
// So it takes a seed and a declaration of what that seed IS, and exposes
// both. An operator can then tell whether this deployment's addresses are
// derived from something genuinely unique or from a boot counter that every
// machine restarts at the same value.
package laa_pkg;
  typedef enum logic [2:0] {
    SEED_UNIQUE_HW,      // a device-unique value: a fuse, a serial number
    SEED_ASSIGNED_ADDR,  // derived from an existing globally assigned one
    SEED_CENTRAL,        // allocated by an authority for this deployment
    SEED_RANDOM,         // a hardware random source
    SEED_COUNTER,        // a boot-time counter -- NOT UNIQUE ACROSS MACHINES
    SEED_CONSTANT        // a fixed default -- guaranteed collision
  } seed_kind_e;
endpackage
 
module laa_generator
  import macaddr_pkg::*;
  import laa_pkg::*;
(
  input  logic        clk,
  input  logic        rst_n,
 
  input  logic        generate_req,
  input  logic [45:0] seed,           // 46 free bits: 48 less the two flags
  input  seed_kind_e  seed_kind,
  input  logic        make_group,     // a locally administered GROUP address
 
  output logic        addr_valid,
  output logic [47:0] address,
 
  // The audit outputs. A deployment can read these and know whether its
  // addresses are safe or merely untested.
  output seed_kind_e  address_seed_kind,
  output logic        seed_is_trustworthy,
  output logic        seed_guarantees_collision
);
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      addr_valid        <= 1'b0;
      address           <= '0;
      address_seed_kind <= SEED_CONSTANT;
    end else begin
      addr_valid <= 1'b0;
 
      if (generate_req) begin
        addr_valid        <= 1'b1;
        address_seed_kind <= seed_kind;
 
        // The two flags occupy the low bits of the FIRST octet, which is
        // address[47:40]. Everything else comes from the seed.
        address <= {
          seed[45:40],            // top six bits of the first octet
          make_group,             // I/G, at bit position 0 of that octet
          1'b1,                   // U/L set: this is locally administered
          seed[39:0]              // the remaining five octets
        };
      end
    end
  end
 
  always_comb begin
    // A seed that is unique per machine gives an address that is too.
    seed_is_trustworthy = (address_seed_kind == SEED_UNIQUE_HW)
                       || (address_seed_kind == SEED_ASSIGNED_ADDR)
                       || (address_seed_kind == SEED_CENTRAL)
                       || (address_seed_kind == SEED_RANDOM);
 
    // A constant seed is not a risk -- it is a certainty. Every machine
    // built from this image has the same address, and the fault appears
    // the moment two of them share a segment.
    seed_guarantees_collision = (address_seed_kind == SEED_CONSTANT);
  end
 
endmodule

Classification: synthesizable.

What it teaches: that a generator's entropy source is the design decision, and the arithmetic of Section 4 is not. A random generator drawing from 46 bits has a negligible collision probability for any realistic deployment; a generator seeded from a boot counter has a probability near one, and no amount of address space helps.

Deliberately simplified: the seed is an input and its quality is declared rather than measured. A production design derives it from a hardware unique value and has no configurable option to do otherwise.

Production implication: seed_guarantees_collision exists as its own output because SEED_CONSTANT is not a risk, it is a certainty — every device built from the same image has the same address, and the fault appears the instant two of them share a segment. A design where that case is indistinguishable from a merely weak seed will treat a guaranteed failure as a probabilistic one.

And seed_is_trustworthy is what makes a fleet auditable. An operator can read it across a deployment and know whether its locally administered addresses are safe by construction or safe by luck so far.

8. RTL 4 — Prefix Telemetry, and Why It Is Worth Silicon

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE INSTRUMENTATION.
//
// Recording which assigned prefixes are seen sounds like inventory. It is
// diagnostic, for three reasons this chapter has already established:
//
//   Section 6 -- a segment dominated by one prefix defeats early rejection,
//                because the first octets match on most frames.
//   Section 4 -- locally administered addresses have NO prefix guarantee,
//                so their share of traffic is a risk measurement.
//   Section 9 -- a duplicate address is far more likely WITHIN one prefix
//                than across two, so the mix informs the search.
//
// It is a small associative structure, not a full table -- the goal is the
// SHAPE of the distribution, not an exhaustive inventory.
module prefix_telemetry
  import macaddr_pkg::*;
#(
  parameter int unsigned SLOTS = 8,
  parameter int unsigned CNT_W = 24
) (
  input  logic        clk,
  input  logic        rst_n,
  input  logic        clear,
 
  input  logic        oui_valid,
  input  logic [23:0] oui,
  input  logic        is_local,
  input  logic        is_group,
 
  output logic [23:0]      slot_oui   [SLOTS],
  output logic [CNT_W-1:0] slot_count [SLOTS],
  output logic [SLOTS-1:0] slot_used,
 
  // Traffic whose prefix did not fit in the tracked set. A large residue
  // means the segment is diverse; a small one means a few prefixes
  // dominate, which is what Section 6's early rejection cares about.
  output logic [CNT_W-1:0] c_untracked,
 
  // Locally administered share. Rising means more virtual interfaces or
  // more mobility, and Section 4 showed those carry no uniqueness
  // guarantee -- so this is a risk measurement, not an inventory line.
  output logic [CNT_W-1:0] c_local_admin,
  output logic [CNT_W-1:0] c_global_admin,
 
  // Distinct prefixes seen. One dominant prefix defeats early rejection;
  // this is the number that says whether that is happening.
  output logic [$clog2(SLOTS+1)-1:0] distinct_prefixes
);
 
  function automatic logic [CNT_W-1:0] bump(input logic [CNT_W-1:0] v,
                                            input logic             en);
    bump = (en && !(&v)) ? (v + 1'b1) : v;
  endfunction
 
  logic                     hit_c;
  logic [$clog2(SLOTS)-1:0] hit_slot_c;
  logic                     free_c;
  logic [$clog2(SLOTS)-1:0] free_slot_c;
 
  always_comb begin
    hit_c       = 1'b0;
    hit_slot_c  = '0;
    free_c      = 1'b0;
    free_slot_c = '0;
    for (int unsigned i = 0; i < SLOTS; i++) begin
      if (slot_used[i] && (slot_oui[i] == oui)) begin
        hit_c      = 1'b1;
        hit_slot_c = ($clog2(SLOTS))'(i);
      end
      if (!slot_used[i] && !free_c) begin
        free_c      = 1'b1;
        free_slot_c = ($clog2(SLOTS))'(i);
      end
    end
  end
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      for (int unsigned i = 0; i < SLOTS; i++) begin
        slot_oui[i]   <= '0;
        slot_count[i] <= '0;
      end
      slot_used      <= '0;
      c_untracked    <= '0;
      c_local_admin  <= '0;
      c_global_admin <= '0;
    end else begin
      if (clear) begin
        for (int unsigned i = 0; i < SLOTS; i++) slot_count[i] <= '0;
        c_untracked    <= '0;
        c_local_admin  <= '0;
        c_global_admin <= '0;
        // slot_used and slot_oui deliberately survive: which prefixes are
        // present is a property of the segment, not of a measurement window.
      end else if (oui_valid) begin
        // Group addresses are excluded: their leading octets are not a
        // manufacturer identifier, and counting them would corrupt the
        // distribution with values that mean something else entirely.
        if (!is_group) begin
          if (is_local) c_local_admin  <= bump(c_local_admin,  1'b1);
          else          c_global_admin <= bump(c_global_admin, 1'b1);
 
          if (hit_c) begin
            slot_count[hit_slot_c] <= bump(slot_count[hit_slot_c], 1'b1);
          end else if (free_c) begin
            slot_used[free_slot_c]  <= 1'b1;
            slot_oui[free_slot_c]   <= oui;
            slot_count[free_slot_c] <= bump(slot_count[free_slot_c], 1'b1);
          end else begin
            c_untracked <= bump(c_untracked, 1'b1);
          end
        end
      end
    end
  end
 
  always_comb begin
    distinct_prefixes = '0;
    for (int unsigned i = 0; i < SLOTS; i++)
      if (slot_used[i]) distinct_prefixes = distinct_prefixes + 1'b1;
  end
 
endmodule

Classification: synthesizable instrumentation.

What it teaches: that a prefix distribution is three different measurements at once — a timing input for Section 6's early rejection, a risk measurement for locally administered traffic, and a search hint for Section 9's duplicate detection. Building it once serves all three.

Deliberately simplified: a small fixed slot set rather than a full table, and a linear search. The goal is distribution shape, and a design needing an exhaustive inventory should collect it in software from the frames themselves.

Production implication: excluding group addresses is not tidiness. A group address's leading octets are not a manufacturer identifierChapter 5.4 develops what they are — so counting them as prefixes corrupts the distribution, and a segment with heavy multicast would appear to have far more manufacturers than it does.

And c_local_admin rising against c_global_admin is a genuine risk indicator. Locally administered addresses carry no uniqueness guarantee, so a segment whose share of them is growing is a segment whose collision exposure is growing — and Section 4 showed that exposure depends entirely on generators nobody at this end can inspect.

9. RTL 5 — Detecting a Duplicate Address

Section 4 established that uniqueness is administrative and unenforced. This finds the case where it failed.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE INSTRUMENTATION.
//
// A duplicate address shows as one source address arriving on two ports.
// SO DOES A LEGITIMATE MOVE -- a service address failing over between
// machines does exactly this, deliberately, and must not be flagged.
//
// The distinguishing feature is RATE, not occurrence:
//
//   a MOVE      -- the address appears on the new port and stays there.
//                  One transition, then stability.
//   a DUPLICATE -- the address alternates between ports continuously,
//                  because both stations keep transmitting. The learning
//                  table flaps at the traffic rate.
//
// So the module counts TRANSITIONS over a window rather than reporting the
// first one, and Section 11's rejected property is the assertion that
// treats a single occurrence as the fault.
module duplicate_address_detector
  import macaddr_pkg::*;
#(
  parameter int unsigned ENTRIES = 16,
  parameter int unsigned PORT_W  = 4,
  parameter int unsigned CNT_W   = 16,
  // Port changes within a window above which it is a duplicate rather than
  // a move. A failover moves once; a duplicate moves continuously.
  parameter int unsigned FLAP_THRESHOLD = 8,
  parameter int unsigned WINDOW = 1_000_000,
  parameter int unsigned WIN_W  = $clog2(WINDOW + 1)
) (
  input  logic              clk,
  input  logic              rst_n,
  input  logic              clear,
 
  input  logic              sa_valid,
  input  logic [47:0]       source_address,
  input  logic [PORT_W-1:0] ingress_port,
 
  // A move: one transition, then stable. Normal, and counted not flagged.
  output logic        move_observed,
  output logic [47:0] moved_address,
 
  // A duplicate: sustained flapping. This is the fault.
  output logic              duplicate_suspected,
  output logic [47:0]       duplicate_address,
  output logic [PORT_W-1:0] duplicate_port_a,
  output logic [PORT_W-1:0] duplicate_port_b,
 
  output logic [CNT_W-1:0] c_moves,
  output logic [CNT_W-1:0] c_duplicates,
 
  // Whether the duplicated address is locally administered. Section 4
  // showed those carry no uniqueness guarantee, so a duplicate among them
  // points at a generator rather than at a manufacturer -- a completely
  // different investigation.
  output logic duplicate_is_local_admin
);
 
  logic [47:0]        addr_q  [ENTRIES];
  logic [PORT_W-1:0]  port_q  [ENTRIES];
  logic [CNT_W-1:0]   flaps_q [ENTRIES];
  logic [ENTRIES-1:0] used_q;
  logic [WIN_W-1:0]   win_q;
 
  function automatic logic [CNT_W-1:0] bump(input logic [CNT_W-1:0] v,
                                            input logic             en);
    bump = (en && !(&v)) ? (v + 1'b1) : v;
  endfunction
 
  logic                       hit_c;
  logic [$clog2(ENTRIES)-1:0] hit_idx_c;
  logic                       free_c;
  logic [$clog2(ENTRIES)-1:0] free_idx_c;
 
  always_comb begin
    hit_c      = 1'b0; hit_idx_c  = '0;
    free_c     = 1'b0; free_idx_c = '0;
    for (int unsigned i = 0; i < ENTRIES; i++) begin
      if (used_q[i] && (addr_q[i] == source_address)) begin
        hit_c     = 1'b1;
        hit_idx_c = ($clog2(ENTRIES))'(i);
      end
      if (!used_q[i] && !free_c) begin
        free_c     = 1'b1;
        free_idx_c = ($clog2(ENTRIES))'(i);
      end
    end
  end
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      for (int unsigned i = 0; i < ENTRIES; i++) begin
        addr_q[i]  <= '0;
        port_q[i]  <= '0;
        flaps_q[i] <= '0;
      end
      used_q                   <= '0;
      win_q                    <= '0;
      move_observed            <= 1'b0;
      duplicate_suspected      <= 1'b0;
      c_moves                  <= '0;
      c_duplicates             <= '0;
      duplicate_is_local_admin <= 1'b0;
    end else begin
      move_observed       <= 1'b0;
      duplicate_suspected <= 1'b0;
 
      if (clear) begin
        c_moves      <= '0;
        c_duplicates <= '0;
      end
 
      // The window bounds the flap count, which is what makes it a RATE.
      if (win_q == WIN_W'(WINDOW - 1)) begin
        win_q <= '0;
        for (int unsigned i = 0; i < ENTRIES; i++) flaps_q[i] <= '0;
      end else begin
        win_q <= win_q + 1'b1;
      end
 
      if (sa_valid) begin
        if (hit_c) begin
          if (port_q[hit_idx_c] != ingress_port) begin
            port_q[hit_idx_c]  <= ingress_port;
            flaps_q[hit_idx_c] <= bump(flaps_q[hit_idx_c], 1'b1);
 
            if (flaps_q[hit_idx_c] + 1'b1 >= CNT_W'(FLAP_THRESHOLD)) begin
              duplicate_suspected <= 1'b1;
              duplicate_address   <= source_address;
              duplicate_port_a    <= port_q[hit_idx_c];
              duplicate_port_b    <= ingress_port;
              // Section 4: a local-admin duplicate points at a generator,
              // a global one points at a manufacturer. Different searches.
              duplicate_is_local_admin <= source_address[41];
              c_duplicates             <= bump(c_duplicates, 1'b1);
            end else begin
              move_observed <= 1'b1;
              moved_address <= source_address;
              c_moves       <= bump(c_moves, 1'b1);
            end
          end
        end else if (free_c) begin
          used_q[free_idx_c]  <= 1'b1;
          addr_q[free_idx_c]  <= source_address;
          port_q[free_idx_c]  <= ingress_port;
          flaps_q[free_idx_c] <= '0;
        end
      end
    end
  end
 
endmodule

Classification: synthesizable instrumentation.

What it teaches: that the same observation has two causes, and rate distinguishes them. An address appearing on a new port is a move — which is legitimate, deliberate, and part of how failover works. An address alternating between ports is a duplicate, because both stations keep transmitting and neither yields.

Deliberately simplified: a small table with a linear search, and a fixed threshold. A production detector integrates with the switch's own learning table rather than duplicating it, since the learning table already holds address-to-port state.

Production implication: duplicate_is_local_admin sends the investigation to two different places. A duplicate among globally assigned addresses points at a manufacturer — a reused value, a cloned image, a production fault — and is somebody else's defect. A duplicate among locally administered ones points at a generator, which is this deployment's own, and Section 7's seed_kind says which machines are at risk.

And distinguishing a move from a duplicate is not optional. A detector that flags the first port change fires on every legitimate failover, which is common enough that the alarm gets disabled — taking the genuine duplicate detection with it. That is the same disable-the-noisy-check failure Chapter 3.1 §13's rejected property produced, arriving here through a different door.

10. Uniqueness Is a Claim About a Process

Section 4 stated it; here is what follows for a design.

No mechanism in any Ethernet device verifies that an address is unique. A frame carries an address; a receiver compares it; a bridge learns it. Nothing anywhere checks that only one station is using it.

The guarantee comes from a chain of administrative steps, and each link can fail independently:

StepWho guarantees itHow it fails
prefixes are distinctthe registration authorityit does not — this link holds
identifiers within a prefix are used oncethe assigneefirmware defect, production error, cloned image
the address is programmed correctlythe manufacturera default left in place, a fuse unblown
it is not overriddenthe operatora deliberately configured duplicate
locally administered ones are uniquethe operatora poor generator — Section 4's real risk

Read the third column. Only the first row is enforced by anything. Every other link is a process that can be got wrong, and the failure is invisible until two of the affected devices meet on one segment.

11. Assertions

Some properties below rest on published bit positions — the individual/group flag at the least significant bit of the first octet and the universal/local flag at the second are defined by IEEE 802.3. The streaming structure, the thresholds and the telemetry are implementation choices, and each property says which it is.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─── ADDRESS PROPERTY: the flags are in the first octet's low two bits ─────
// Published positions. Catches a decomposer reading them from the wrong end
// of the octet, which classifies every address wrongly while producing
// plausible-looking output.
property p_flag_bit_positions;
  @(posedge clk) disable iff (!rst_n)
  (flags_valid) |-> ((is_group == $past(octet_in)[0])
                  && (is_local == $past(octet_in)[1]));
endproperty
 
// ─── Causation: the flags are available after ONE octet ────────────────────
// The chapter's central structural claim, asserted. Catches a decomposer
// that waits for the whole address, which discards the early branch the
// layout exists to enable.
property p_flags_after_one_octet;
  @(posedge clk) disable iff (!rst_n)
  flags_valid |-> ($past(addr_start) && $past(octet_valid));
endproperty
 
// ─── Ordering: validity signals fire in increasing octet order ─────────────
// Catches a decomposer that can report the prefix before the flags, which
// means its counter has a path the address does not.
property p_validity_order;
  @(posedge clk) disable iff (!rst_n)
  oui_valid |-> (octet_count >= 3);
endproperty
 
// ─── Conservation: the four address classes are exhaustive ─────────────────
// Two flags, four classes, no fifth. Catches an enum gaining a state that
// no combination of bits produces.
property p_address_class_exhaustive;
  @(posedge clk) disable iff (!rst_n)
  flags_valid |-> ((addr_class == ADDR_UNICAST_GLOBAL)
                || (addr_class == ADDR_UNICAST_LOCAL)
                || (addr_class == ADDR_GROUP_GLOBAL)
                || (addr_class == ADDR_GROUP_LOCAL));
endproperty
 
// ─── ADDRESS PROPERTY: broadcast is also a group address ───────────────────
// All ones sets the I/G bit, so broadcast satisfies the group test AND is an
// exact value. Catches logic treating them as disjoint, which loses
// broadcast frames on a receiver that checks group membership first.
property p_broadcast_is_group;
  @(posedge clk) disable iff (!rst_n)
  (addr_valid && is_broadcast) |-> is_group;
endproperty
 
// ─── Safety: comparison rejects at the first differing octet ───────────────
// Catches a comparison that continues after a mismatch, which wastes the
// early-out the streaming structure exists for.
property p_reject_is_early;
  @(posedge clk) disable iff (!rst_n)
  mismatch |-> !$past(failed_q);
endproperty
 
// ─── Conservation: a match requires all six octets ─────────────────────────
// Catches a match declared on a prefix alone, which accepts every address
// from the same manufacturer.
property p_match_requires_all_octets;
  @(posedge clk) disable iff (!rst_n)
  match |-> (idx_q == 3'd5);
endproperty
 
// ─── Mutual exclusion: a comparison matches or rejects, never both ─────────
property p_match_xor_mismatch;
  @(posedge clk) disable iff (!rst_n)
  !(match && mismatch);
endproperty
 
// ─── ADDRESS PROPERTY: a generated LAA has the U/L bit set ─────────────────
// Catches the bit placed at the wrong end of the octet -- which produces an
// address that is NOT locally administered, carries someone else's prefix,
// and may collide with a real device.
property p_generated_address_is_local;
  @(posedge clk) disable iff (!rst_n)
  addr_valid |-> address[41];
endproperty
 
// ─── Causation: the group flag follows the request ─────────────────────────
// Catches the two flag positions transposed, which produces group addresses
// when unicast was asked for.
property p_generated_group_flag_follows_request;
  @(posedge clk) disable iff (!rst_n)
  addr_valid |-> (address[40] == $past(make_group));
endproperty
 
// ─── Safety: a constant seed is reported as certain, not risky ─────────────
// Catches SEED_CONSTANT being treated as merely weak, which turns a
// guaranteed collision into a probabilistic-looking one.
property p_constant_seed_is_certain;
  @(posedge clk) disable iff (!rst_n)
  (address_seed_kind == SEED_CONSTANT) |-> seed_guarantees_collision;
endproperty
 
// ─── Causation: group addresses are excluded from prefix telemetry ─────────
// Catches them being counted, which corrupts the distribution with values
// that are not manufacturer identifiers at all.
property p_group_excluded_from_prefix_stats;
  @(posedge clk) disable iff (!rst_n)
  (oui_valid && is_group) |=> ((c_local_admin == $past(c_local_admin))
                            && (c_global_admin == $past(c_global_admin)));
endproperty
 
// ─── Ordering: a duplicate requires sustained flapping, not one move ───────
// THE property of Section 9. Catches a detector that flags the first port
// change, which fires on every legitimate failover and gets disabled --
// taking the genuine detection with it.
property p_duplicate_requires_flapping;
  @(posedge clk) disable iff (!rst_n)
  duplicate_suspected |-> ($past(flaps_q[hit_idx_c]) + 1 >= FLAP_THRESHOLD);
endproperty
 
// ─── Mutual exclusion: a move and a duplicate are not both reported ────────
property p_move_xor_duplicate;
  @(posedge clk) disable iff (!rst_n)
  !(move_observed && duplicate_suspected);
endproperty
 
// ─── Stability: the prefix set survives a clear ────────────────────────────
// Catches slot_used folded into the clear branch, destroying the record of
// which prefixes this segment carries.
property p_prefix_set_survives_clear;
  @(posedge clk) disable iff (!rst_n)
  clear |=> $stable(slot_used);
endproperty

12. Verification

Five ordered questions the address answers as its octets arrive. After one bit, whether it addresses one station or many. After two bits, whether it is globally assigned or locally chosen. After three octets, which organisation assigned the prefix. After six octets, which device. And separately, whether the value is the all-ones broadcast address.What is known, and when1One bit inone station, or many?2Two bits inglobally assigned, or locally chosen?3Three octets inwhich organisation assigned it?4Six octets inwhich of their devices?5And separatelyis it the all-ones broadcast value?
Figure 3 — the address answers progressively finer questions as it arrives.

Scenarios

  1. A globally assigned unicast address. Verify flags_valid after one octet with both flags clear, addr_class reading unicast-global, and the prefix valid at octet three.
  2. A locally administered unicast address. Verify the U/L flag set and the class changing, with the I/G flag still clear.
  3. A globally assigned group address. Verify the I/G flag set after one bit's worth of octet, before any other field is known.
  4. A locally administered group address. The fourth class. Verify all four are reachable — a decomposer tested on two will pass with the flags transposed.
  5. The broadcast address. Verify is_broadcast and is_group both assert. They are not disjoint, and a receiver checking group membership first must still recognise it.
  6. The flags read from the wrong bit positions. Inject a decomposer using bits 7 and 6 and verify the classification is wrong for every address — the fault that produces plausible-looking output.
  7. A comparison that mismatches at each octet position. Six runs. Verify mismatch_octet names the right one and reject_at_octet bins it.
  8. A comparison that matches fully. Verify match fires only after the sixth octet, never on a prefix alone.
  9. Two addresses sharing five octets and differing in the last. Verify rejection at octet five, not earlier — the worst case for early rejection.
  10. Traffic dominated by one prefix. Verify reject_at_octet shifts toward the later octets and distinct_prefixes reads low. This is a deployment property, not a fault.
  11. An LAA generated from each seed kind. Six runs. Verify seed_is_trustworthy for the four safe kinds and seed_guarantees_collision only for the constant.
  12. A generated LAA's bit placement. Verify the U/L bit lands at address[41] and the I/G bit at address[40] — and that a generator placing U/L at address[47] produces an address that is not locally administered.
  13. A generated group LAA. Verify both flags set and that the address is not mistaken for a globally assigned one.
  14. Prefix telemetry with group addresses in the stream. Verify they are excluded and neither administration counter moves for them.
  15. Prefix telemetry overflow. Present more distinct prefixes than slots and verify c_untracked advances rather than an existing slot being evicted.
  16. A single port change for a known address. Verify move_observed and not duplicate_suspected — this is failover, and flagging it is the failure Section 9 exists to avoid.
  17. Sustained flapping for a known address. Verify duplicate_suspected after the threshold, with both ports named.
  18. A duplicate among locally administered addresses. Verify duplicate_is_local_admin sets, which sends the investigation to the generator rather than to a manufacturer.
  19. clear during operation. Verify counters zero while slot_used and slot_oui survive.

What the checker must own

  • A generator that can be given each seed kind, including the unsafe ones. Scenario 20 cannot be built from a testbench whose address generator is always sound.
  • An independent bit-position model written from the published positions rather than from Section 5's parameters. A scoreboard sharing BIT_IG and BIT_UL verifies only that the design agrees with itself, and Scenario 6's fault is exactly a wrong constant.
  • A multi-port stimulus with configurable port assignment per address, since Scenarios 16 to 18 all turn on which port a given address arrives from.
  • Coverage crosses of address class against comparison outcome. All four classes must be populated — a suite exercising two will pass a design with the flags transposed — and (broadcast, not classified as group) must be unreachable.

13. Debugging — Class, Then Prefix, Then Rate

The symptom: a station is not receiving frames it should, or is receiving frames it should not.

Step 1 — read addr_class on the frames in question. The four classes send the investigation to four different places:

ClassWhat to check
unicast, globalthe exact compare — Section 6, and reject_at_octet
unicast, localwhether the address is unique — Section 4's generator question
group, globalthe subscription set — Chapter 5.4's matcher
group, locala locally defined group, and whether both ends agree on it

Step 2 — if the class itself looks wrong, suspect the bit positions. A decomposer reading the flags from the wrong end of the first octet classifies every address wrongly while producing entirely plausible output — group addresses appearing as unicast and the reverse. The signature is that the classification is wrong consistently, not intermittently.

Step 3 — if unicast frames are being missed, read reject_at_octet. Heavily weighted to octet zero means early rejection is working and the address genuinely does not match. A distribution weighted to the later octets means the segment's addresses share prefixes, which is not a fault but does mean a prefix-based hash will be performing badly.

Step 4 — if traffic for one address is erratic, read the duplicate detector before anything else. move_observed without duplicate_suspected is a failover and is working correctly. Sustained flapping is a duplicate, and duplicate_is_local_admin then decides the owner: local means this deployment's generator, global means a manufacturer's assignment.

Step 5 — if a duplicate is confirmed among locally administered addresses, go to the generators, not the network. Section 7's seed_kind is readable per machine and identifies which are at risk before they collide. A fleet reporting SEED_COUNTER will produce more duplicates; one reporting SEED_UNIQUE_HW will not, and the difference is auditable without waiting for a failure.

The method stated once: the class names the subsystem, the rejection distribution distinguishes a genuine mismatch from a prefix-heavy segment, and the flap rate separates a legitimate move from a duplicate — because at a single instant those two are the same observation.

14. Common Misconceptions

"A MAC address is a serial number."

The wrong model: an opaque unique identifier burned into a device.

What it costs: you cannot explain why a receiver branches on it before it has arrived, why some addresses are chosen rather than assigned, or why two devices can share one. You treat every address as equivalent when four structurally different classes exist.

The corrected model: it is a structured value ordered by decision urgency — one bit for individual against group, one for globally assigned against locally chosen, then an organisational prefix, then a device identifier. Each field answers a finer question than the one before it, and each becomes available later.

"The flag bits are somewhere in the first byte."

The wrong model: the position is a detail you can look up when you need it.

What it costs: a decomposer reading them from the wrong end classifies every address wrongly while producing plausible output — group addresses appearing as unicast and the reverse. And a generator placing the U/L bit at the wrong end produces an address that is not locally administered, carries a real organisation's prefix, and may collide with real equipment months later.

The corrected model: they are the two lowest bits of the first octet, which under least-significant-bit-first transmission makes them the first two bits on the wire. That position is the entire reason a receive path can select between an exact-compare and a hash-lookup pipeline 47 bit times before the address is complete.

"MAC addresses are unique."

The wrong model: a guarantee provided by the addressing scheme.

What it costs: you build a learning table on an invariant nothing enforces, and Section 11's rejected property follows. When a duplicate does appear, the design has no way to report it and may "harden" itself in a way that breaks legitimate address mobility.

The corrected model: uniqueness is a claim about a process with five links, of which only the first — that no two organisations receive the same prefix — is enforced by anything. Manufacturers reuse values, images get cloned, and locally administered addresses have no authority at all. A design's job is to detect and attribute, never to assume.

"A locally administered address is safe because the space is huge."

The wrong model: 46 free bits and a birthday calculation.

What it costs: false confidence from arithmetic that describes the case which does not fail. The real failure mode — identical machines from one image, generating from a boot counter, booting together — produces collisions at a rate no space calculation predicts.

The corrected model: the risk is the generator, not the space. Derive the address from something already unique to the machine, or allocate centrally — and expose which of those was done, so a fleet can be audited before a collision rather than after.

"An address arriving on a new port means something is wrong."

The wrong model: a learned address should stay where it was learned.

What it costs: you flag every legitimate failover. That is common enough that the alarm gets disabled, and the genuine duplicate detection goes with it — the same disable-the-noisy-check failure the track has met before.

The corrected model: a move and a duplicate are the same observation at a single instant, and only rate separates them. One transition then stability is a move, which should be learned. Sustained flapping is a duplicate, which should be reported and left for an operator, because a receiver cannot tell which of the two stations is legitimate.

15. Interview Reasoning

"Why is the individual/group bit where it is?"

The weak answer is "in the first byte". The answer that ends the topic combines two facts: it is the least significant bit of the first octet, and Ethernet transmits least-significant-bit-first — so it is the first bit of the frame body on the wire. Then the payoff: an individual address needs an exact compare and a group address needs a hash lookup, those are different pipelines, and the selection between them is free because it is known 47 bit times before either could start.

"Are MAC addresses unique?"

The expected answer is yes; the correct one is that uniqueness is an administrative claim with five links in its chain, only the first of which any mechanism enforces. Naming the concrete failures — a manufacturer reusing a value, a cloned image, a locally administered generator seeded from a boot counter — shows it is understood as a process rather than a property. The strong follow-up is what a design should do, and the answer is detect, attribute and report, never assume.

"A switch sees one address alternating between two ports. What is it?"

The trap is to answer immediately. It is either a failover in progress or a duplicate address, and at a single instant those are indistinguishable — only the rate separates them, because a move transitions once and a duplicate flaps continuously. Adding that the response differs completely — learn the move, report the duplicate — and that a detector firing on the first transition breaks every legitimate failover, is what distinguishes a complete answer.

16. Understanding Check

So that a receiver can branch before the address has arrived.

Two facts combine. The individual/group flag is the least significant bit of the first octet, and Chapter 5.2 established that Ethernet transmits each octet least-significant-bit-first. Together those make it the first bit of the frame body on the wire — and the universal/local flag the second.

What that buys is a free pipeline selection. An individual address is matched by an exact 48-bit comparison; a group address is matched against a subscription set, which at scale is a hash lookup. Those are different pipelines with different latencies, and the choice between them is known 47 bit times before either could start.

Had the flag been in the last octet, a receiver would have to run both speculatively and discard one — wasting power on every frame — or wait for the whole address, delaying the abandon decision that Chapter 5.1 §2 showed the field order exists to make early.

The follow-up to be ready for: why is the second bit there too? Universal against local does not change the pipeline, but it changes what a mismatch means — a locally administered address has no uniqueness guarantee — so having it early lets that interpretation attach to the result rather than be looked up afterwards.

17. What's Next

The claim this chapter defended: the address is ordered by decision urgency, exactly as the frame is — and its uniqueness is an administrative claim rather than an enforced property.

The individual/group flag is the first bit of the frame body on the wire and the universal/local flag the second, which lets a receive path select between an exact-compare and a hash-lookup pipeline 47 bit times before the address is complete. The prefix follows at three octets and the device identifier at six: each field a finer question than the one before, each answerable later.

And nothing anywhere checks that an address is used once. The registration authority guarantees prefixes are distinct and that is the only enforced link in a five-link chain — which is why a design's contribution is to detect a duplicate, distinguish it from a legitimate move by rate, and name whether the fault belongs to a manufacturer or to this deployment's own generator.

Chapter 5.4 — Unicast, Multicast and Broadcast takes the classification this chapter produced and asks what each class means. Chapter 2.7 §7 answered that for a forwarding device — flood a group address, forward a known individual one — and left the receiver's side open.

5.4 owns it, and the substantial piece is the one this chapter kept pointing at: group matching is a hash lookup, not an exact comparison. Chapter 2.7 §4 used exact match and said production designs hash; 5.4 delivers that, with the false-positive path that hashing necessarily creates and that software must then filter.

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.