Skip to content
VLSI Mentor

Ethernet · Module 25

"CRC Provides Security"

CRC-32 is linear over GF(2), so a chosen change has a computable compensating change to the check sequence — a residual of 1 where noise sees 2⁻³², and 6.92x the gates to fix it.

Chapter 6.1 specified what the frame check sequence guarantees, with bounds attached to every clause. This chapter is about a guarantee it never made and is constantly credited with.

The myth, stated as the people who hold it would state it. Every frame carries a 32-bit check value computed over its contents. A frame whose contents were altered fails the check. So a frame that passes the check has not been altered, and the FCS is therefore an integrity mechanism.

The first two sentences are true. The third does not follow, and the reason is one algebraic property.

What is trueSection
CRC-32 is a linear function over GF(2)CRC(a ⊕ b) = CRC(a) ⊕ CRC(b) for the pure division2
so a chosen change has a computable compensationchange the payload, compute the delta, apply it to the FCS4
the residual for a RANDOM error is 2⁻³²one in 4 294 967 2966
the residual for a CHOSEN change is 1exactly one, not a smaller number8

Against noise the check fails once in four billion times. Against an adversary it fails every time, and the difference is not a matter of degree — it is the difference between a probability and a certainty.

And the substitute is not free. Section 10 derives what an actual integrity mechanism costs: 404 640 gate equivalents of AES-GCM at 400 Gb/s against a 49 142-GE CRC engine — 8.2× — plus 24 to 32 octets of frame overhead, which at minimum frame size is 28.57% to 38.10% more wire.


1. Scope — What a Check Detects and What It Resists

Two mechanisms that both produce a value appended to a frame. The cyclic redundancy check takes the frame and a generator polynomial that is printed in the standard, and produces a thirty two bit check value. Every input to it is public, so any party on the path has exactly the information the receiver has. Its guarantee is a set of theorems about the shape of a difference: all bursts up to thirty two bits, all patterns of weight three at any frame length, weight four up to about three hundred and seventy five octets, and a residual of about two to the minus thirty two for everything else. The integrity mechanism takes the frame and a key that is not published, and produces a one hundred and twenty eight bit integrity check value. Its guarantee is a forgery probability of about two to the minus one hundred and twenty eight under a stated attack model, and it holds because the adversary lacks the key rather than because the difference has a particular shape.A frameon the wireThe polynomialin the standard —publicA keynot published32-bit FCSall inputs public128-bit ICVone input secretBursts ≤ 32,weight ≤ 3residual 2⁻³²Forgery ≈ 2⁻¹²⁸under an attack modelAgainst a chosendifferenceresidual 1 against2⁻¹²⁸12
Figure 1 — a detector's guarantee rests on a structure and an integrity mechanism's on a secret, and the structure is published.

This chapter owns four derivations and one substitute, priced.

What is derived
Sections 2 to 5linearity from the polynomial, and the compensation construction it implies
Sections 6 to 9what CRC guarantees with bounds, and why the adversarial case is a certainty rather than a small probability
Sections 10 to 13MACsec's GCM-AES: gates, frame overhead, key state, and what it does to Chapter 25.1's parse window
Sections 14 to 17what an integrity claim must never do, and the two mechanisms side by side

What this chapter does not own. Chapter 6.2 owns the polynomial division and the four conventions wrapped around it; Chapter 6.3 owns the residue; Chapter 6.4 owns the parallel engine; Chapter 19.4 owns its integration and its area. This chapter uses all four and re-derives none of them.

And it is a defensive chapter. The mathematics below is the reason a design must not use a check sequence as an integrity mechanism, and the remedy is the mechanism that does the job. Nothing here is tooling for altering frames on a network; it is the arithmetic that says why the check cannot be relied on and what replaces it.


2. The Linearity, Derived from the Polynomial

Chapter 6.2 §3 established the arithmetic in one sentence: treat the message as a polynomial over the field with two elements, divide it by the generator, and keep the remainder. Linearity follows from that sentence and nothing else.

Write the pure division — no initial value, no reflection, no complement — as a function.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
R(m) = m(x) · x³² mod G(x)      over GF(2)
G(x) = x³² + x²⁶ + x²³ + x²² + x¹⁶ + x¹² + x¹¹ + x¹⁰ + x⁸ + x⁷ + x⁵ + x⁴ + x² + x + 1

That is 0x04C11DB7 with the x³² term implicit — Chapter 6.2 §4's taps, read off directly.

Now the property, in three lines.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
addition in GF(2) is XOR, and polynomial multiplication distributes over it:
  (a ⊕ b) · x³² = (a · x³²) ⊕ (b · x³²)

the remainder of a sum is the sum of the remainders:
  ((a ⊕ b) · x³²) mod G = ((a · x³²) mod G) ⊕ ((b · x³²) mod G)

therefore:
  R(a ⊕ b) = R(a) ⊕ R(b)

The check value of a sum is the sum of the check values, which is the definition of a linear function — and it is a consequence of the mechanism being polynomial division rather than an accident of this polynomial.

Three things follow immediately and each matters for the rest of the chapter.

One — the effect of a change depends only on the change. If a frame m becomes m ⊕ d, then R(m ⊕ d) = R(m) ⊕ R(d). The new check value is the old one XORed with the check value of the difference pattern, and the difference pattern is what the adversary chose. The original frame's contents do not enter.

Two — the conventions do not remove the property, they make it affine. Chapter 6.2 §5's four wrappers — a nonzero initial value, input and output reflection, and a final complement — change the value and not the structure. With them, the Ethernet FCS satisfies:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
FCS(m ⊕ d) = FCS(m) ⊕ R₀(d)

where R₀ is the pure division of d with a zero initial value and no complement. The initial value contributes a constant that cancels in the difference; the complement contributes a constant that cancels in the difference; and reflection is a relabelling of bit order that commutes with XOR. So the compensating delta is computable from the difference pattern alone.

Three — this is not a weakness in CRC-32 and a stronger polynomial does not fix it. The property holds for every cyclic redundancy check, because every one of them is polynomial division. A 64-bit CRC has the same property, and so would a 256-bit one.


3. RTL 1 — The Security Package and the CRC Difference Model

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// crcsec_pkg -- the constants an integrity argument needs, with the
// polynomial taken from Chapter 6.2 Section 4 and every derived figure
// computed here rather than written as a literal.
//
// Units: Chapter 23.3 Section 2's bitcell equivalent for STATE, and
// gate equivalents for LOGIC. Section 19 explains why this chapter
// needs both and the previous eight needed one.
//   1 BCE = 0.35 GE = one bit of usable on-die SRAM
//   1 flip-flop = 7 GE = 20 BCE
//   Chapter 19.7 Section 19's MAC receive datapath = 283 320 BCE
// ---------------------------------------------------------------------
package crcsec_pkg;

  localparam int unsigned DATAPATH_BCE = 283_320;
  localparam int unsigned BCE_PER_FLOP = 20;
  localparam int unsigned GE_PER_FLOP  = 7;
  localparam int unsigned GE_PER_XOR   = 3;      // 2-input XOR, rounded up

  // ---- the generator, Chapter 6.2 Section 4 ---------------------------
  // x^32 implicit; the stored value is the low 32 terms.
  localparam logic [31:0] CRC32_POLY = 32'h04C1_1DB7;
  localparam int unsigned CRC_WIDTH  = 32;

  // ---- Chapter 6.1's guarantees, with their bounds --------------------
  localparam int unsigned BURST_BOUND_BITS   = 32;
  localparam int unsigned WEIGHT3_ANY_LENGTH = 3;
  localparam int unsigned WEIGHT4_MAX_OCTETS = 375;

  // ---- MACsec, 802.1AE -------------------------------------------------
  localparam int unsigned SECTAG_NO_SCI   = 8;
  localparam int unsigned SECTAG_WITH_SCI = 16;
  localparam int unsigned ICV_OCTETS      = 16;
  localparam int unsigned AES_BLOCK_BITS  = 128;
  localparam int unsigned AES_ROUNDS      = 10;   // AES-128

  // ---- a gate model for the two mechanisms ----------------------------
  // Derived, not quoted: an S-box optimised for a pipelined datapath,
  // MixColumns as four column circuits, AddRoundKey as 128 XORs.
  localparam int unsigned SBOX_GE      = 400;
  localparam int unsigned MIXCOL_GE    = 800;     // 4 columns x 200
  localparam int unsigned GHASH_GE     = 17_000;  // Karatsuba + reduction
  // Chapter 19.4 Section 6's 512-bit engine, re-read: 19 461 XOR terms
  // across both cores and about 70 flops of state.
  localparam int unsigned CRC_XOR_TERMS = 19_461;
  localparam int unsigned CRC_FLOPS     = 70;

  typedef enum logic [1:0] {
    THREAT_NOISE     = 2'd0,   // Chapter 6.1's channel
    THREAT_FAULT     = 2'd1,   // a broken device, no intent
    THREAT_CHOSEN    = 2'd2    // a difference pattern somebody selected
  } threat_e;

  // ---- the polynomial division, one bit, no conventions ---------------
  function automatic logic [31:0] crc_step(logic [31:0] state, logic bit_in);
    logic fb;
    fb = state[31] ^ bit_in;
    return fb ? ((state << 1) ^ CRC32_POLY) : (state << 1);
  endfunction

  // R0(d): the pure remainder of a difference pattern, zero init, no
  // complement. Section 4 shows this IS the compensating delta.
  function automatic logic [31:0] r0(logic [1023:0] d, int unsigned nbits);
    logic [31:0] s;
    s = '0;
    for (int i = 0; i < nbits; i++) s = crc_step(s, d[i]);
    return s;
  endfunction

  // ---- derived gate counts ---------------------------------------------
  function automatic int unsigned aes_round_ge();
    return (16 * SBOX_GE) + MIXCOL_GE + (AES_BLOCK_BITS * GE_PER_XOR);
  endfunction

  function automatic int unsigned aes_pipeline_ge();
    return (AES_ROUNDS * aes_round_ge())
         + (AES_ROUNDS * AES_BLOCK_BITS * GE_PER_FLOP);
  endfunction

  function automatic int unsigned gcm_pipeline_ge();
    return aes_pipeline_ge() + GHASH_GE;
  endfunction

  function automatic int unsigned crc_engine_ge();
    return (CRC_XOR_TERMS * GE_PER_XOR) + (CRC_FLOPS * GE_PER_FLOP);
  endfunction

  function automatic int unsigned datapaths_milli(int unsigned bce);
    return (bce * 1000) / DATAPATH_BCE;
  endfunction

endpackage
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// crc_difference_model -- Section 2's linearity, measured.
//
// It computes the check value of a frame, of a difference pattern, and
// of their exclusive-or, and asserts that the third equals the first
// two combined. The output that matters is linearity_holds, which is
// always high and is the chapter's entire subject.
// ---------------------------------------------------------------------
module crc_difference_model
  import crcsec_pkg::*;
(
  input  logic          clk,
  input  logic          rst_n,

  input  logic          eval,
  input  logic [1023:0] frame_bits,
  input  logic [1023:0] diff_bits,
  input  logic [15:0]   nbits,

  output logic [31:0]   r_frame,
  output logic [31:0]   r_diff,
  output logic [31:0]   r_combined,
  output logic [31:0]   r_expected,
  output logic          linearity_holds,
  output logic          diff_is_nonzero,
  output logic [15:0]   diff_weight,
  output logic [15:0]   diff_span_bits,
  output logic          within_burst_bound,
  output logic [31:0]   c_evaluations
);
  logic [1023:0] combined;
  int unsigned   lo, hi;

  always_comb begin
    combined = frame_bits ^ diff_bits;

    r_frame    = r0(frame_bits, int'(nbits));
    r_diff     = r0(diff_bits,  int'(nbits));
    r_combined = r0(combined,   int'(nbits));

    // Section 2's identity, evaluated rather than asserted in prose.
    r_expected      = r_frame ^ r_diff;
    linearity_holds = (r_combined == r_expected);

    diff_is_nonzero = (diff_bits != '0);

    diff_weight = '0;
    for (int i = 0; i < 1024; i++)
      if (diff_bits[i]) diff_weight = diff_weight + 16'd1;

    // The span between the first and last set bit -- Chapter 6.1's
    // burst length, which is what the 32-bit bound is about.
    lo = 1024; hi = 0;
    for (int i = 0; i < 1024; i++) begin
      if (diff_bits[i]) begin
        if (i < lo) lo = i;
        if (i > hi) hi = i;
      end
    end
    diff_span_bits = diff_is_nonzero ? 16'(hi - lo + 1) : 16'd0;

    within_burst_bound = diff_is_nonzero
                      && (diff_span_bits <= 16'(BURST_BOUND_BITS));
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n)    c_evaluations <= '0;
    else if (eval) c_evaluations <= c_evaluations + 32'd1;
  end
endmodule

Classification: a measurement of an algebraic identity, whose output is a constant 1 that the chapter exists to explain.

What it teaches: that linearity_holds is high for every frame and every difference pattern, so Section 2's identity is not a special case or an approximation. The check value of a modified frame depends on the original's check value and on the difference — and on nothing else about the original. That is the sentence the rest of the chapter is built on.

And it teaches that diff_span_bits is the quantity Chapter 6.1 §5's burst bound is about. A difference confined to 32 bits or fewer is always detected; the model reports within_burst_bound so a scenario can be classified before its outcome is examined, and Section 9's adversary model exists precisely to produce differences that are not.

Deliberately simplified: r0 is a bit-serial loop, unsynthesisable at any useful width and written that way because Chapter 6.4 owns the parallel form and this module is about the algebra. The frame is capped at 1 024 bits where a real frame is up to 12 144; the identity does not depend on the length and the model does not need to demonstrate it at one. And the conventions are absent — no initial value, no reflection, no complement — which is deliberate: Section 4 shows they cancel in the difference, and showing that requires starting without them.

Production implication: the weight and span outputs are the two a verification environment should keep, and the reason is that they classify a failure before anybody looks at the data. A received frame that fails its FCS carries a difference pattern nobody can see — the original is not available — but a frame that fails in a testbench does, and recording the injected difference's weight and span turns a pass-fail result into evidence about which of Chapter 6.1's guarantees was exercised. Chapter 20.5 §20's class 92 is the warning that comes with it: the injector's model of an error and the design's classification of it must not be the same rules written twice, and the defence is that these two outputs describe the pattern rather than the verdict.


4. The Compensation Construction

A frame m carries a check value computed over it. A difference pattern d is applied to the payload. Because the check is linear over the field with two elements, the check value of the altered frame equals the original check value exclusive-ored with the pure remainder of the difference pattern. That remainder is computed from the difference alone: the original frame's contents do not enter the calculation. The four conventions wrapped around the division — a nonzero initial value, input and output reflection, and a final complement — all cancel in the difference, so the construction is unaffected by them. Every quantity required is public: the generator polynomial is in the standard, the frame is on the wire, and the original check value is its last four octets. The result is a frame whose payload differs and whose check value verifies.Frame mwith FCS(m)Difference dchosenR₀(d)depends on d aloneConventionscancelinit, reflect,complementOne XORFCS(m) ⊕ R₀(d)m ⊕ d with avalid FCSthe check passesAll inputs publicstandard, wire, last 4octetsBits of secret: 0the structural fact12
Figure 2 — the compensating value depends on the difference alone, and every quantity it needs is on the wire.

Section 2 gave the identity. This section states what it means operationally, which is the part the myth cannot survive.

The construction, in four lines.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
a frame m has check value FCS(m), and the receiver verifies it
somebody changes the payload:  m becomes m ⊕ d
by Section 2's identity:        FCS(m ⊕ d) = FCS(m) ⊕ R₀(d)
so the altered frame's check value is computable from d alone

The fourth line is the whole finding. The compensating value does not require knowing the original frame's contents, only the difference pattern and the original check value — and the original check value is in the frame, in plain sight, four octets from the end.

Work through what each party has.

The receiver hasAnybody on the path has
the polynomialyes — it is in the standardyes — same standard
the frameyesyes
the original FCSyesyes — it is the last four octets
a secretNO

Row four is empty on both sides and that is the mechanism's situation, not a flaw in its arithmetic. Every input the check uses is public, so the check distinguishes a party who followed the rules from a party who did not follow them — and an adversary follows them.

The conventions do not help. Chapter 6.2 §5's initial value, reflections and final complement change the check's value and not its structure: the initial value's contribution is identical for m and m ⊕ d and cancels in the difference; the complement is a constant XOR and cancels; reflection is a relabelling of bit positions that commutes with XOR. All four survive the subtraction.

And the padding and length rules do not help either. Chapter 5.6's minimum-length padding is part of the frame, so it is part of m and part of what the difference may touch.

Three defensive consequences follow, and they are the reason this section is in an engineering track rather than a security one.

One — a design must not treat a passing FCS as evidence about the sender. Chapter 5.8 §7 already said what a passing check IS evidence of: that the covered octets arrived as they left the last append point, which is a statement about a hop and about noise. It is not evidence about origin, intent, or authorship.

Two — end-to-end integrity cannot be built from hop-by-hop checks even in principle. Chapter 5.8 §8 established that the FCS is recomputed at every store-and-forward hop; so the check a receiver verifies was computed by the last switch, on whatever that switch had, and it certifies the last hop and nothing before it.

Three — the remedy is not a bigger check. A 64-bit CRC has the same linearity and therefore the same compensation; the remedy is a mechanism whose guarantee rests on a secret, which is Section 10.


5. RTL 2 — The Compensating-Delta Computer

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// compensating_delta -- Section 4's construction as a model, so a
// verification environment can demonstrate that a chosen difference
// leaves the check satisfied.
//
// It is an ELABORATION and TESTBENCH model. Its purpose is to make the
// design's integrity claim falsifiable: a design asserting that a
// passing FCS implies an unaltered frame can be shown wrong here, at
// no cost and on no real network.
//
// The output that matters is check_still_passes, which is hard-wired
// high for any nonzero difference. That constant IS the myth's refutation.
// ---------------------------------------------------------------------
module compensating_delta
  import crcsec_pkg::*;
(
  input  logic          clk,
  input  logic          rst_n,

  input  logic          eval,
  input  logic [31:0]   fcs_original,
  input  logic [1023:0] diff_bits,
  input  logic [15:0]   nbits,
  input  logic  [1:0]   threat,          // threat_e

  output logic [31:0]   delta,
  output logic [31:0]   fcs_compensated,
  output logic          check_still_passes,
  output logic          requires_a_secret,
  output logic          inputs_are_all_public,
  output logic [15:0]   bits_of_secret_available,
  output logic [31:0]   detection_prob_recip,   // 1 in N, for noise
  output logic [31:0]   c_compensations
);
  always_comb begin
    // Section 4 line 3: the compensating value is R0 of the difference.
    delta           = r0(diff_bits, int'(nbits));
    fcs_compensated = fcs_original ^ delta;

    // For a CHOSEN difference, the compensated frame verifies. For a
    // difference the channel produced, nobody computed the delta and
    // the check does its job.
    check_still_passes = (threat == 2'(THREAT_CHOSEN)) && (diff_bits != '0);

    // THE structural facts.
    requires_a_secret        = 1'b0;   // the CRC needs none
    inputs_are_all_public    = 1'b1;   // polynomial, frame, FCS
    bits_of_secret_available = 16'd0;

    // Chapter 6.1 Section 6: the residual for a pattern outside the
    // deterministic classes. Meaningful for noise, meaningless for a
    // chosen difference, and reported as such.
    detection_prob_recip = (threat == 2'(THREAT_CHOSEN))
                         ? 32'd1              // 1 in 1 -- it passes
                         : 32'hFFFF_FFFF;     // 1 in 4 294 967 295+
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n)    c_compensations <= '0;
    else if (eval) c_compensations <= c_compensations + 32'd1;
  end
endmodule

Classification: one XOR and three constants, of which the constants are the content.

What it teaches: that bits_of_secret_available is zero and that this is the entire difference between a check and a protection. The module's arithmetic is a single XOR — fcs_original ^ r0(diff)and the reason it works is not that the arithmetic is clever but that no input to it is withheld from anybody. Section 10's substitute changes exactly this field and nothing else about the shape of the problem.

And it teaches that detection_prob_recip reports two incomparable things under one name. For a noise difference it is about 4.29 × 10⁹Chapter 6.1 §6's 2⁻³² residual. For a chosen difference it is 1, meaning the frame passes. Those are not two points on a scale; the first is a probability over a distribution the design does not control and the second is a certainty, and Section 8 is about why writing them in the same column is the myth's root.

Deliberately simplified: threat is an input a testbench supplies, because no hardware can determine whether a difference was chosen — that is the property that makes the problem what it is, and the module makes it an explicit input rather than pretending otherwise. The delta is computed with the pure division and Section 4's argument about the conventions cancelling is taken as established rather than re-demonstrated. And the module does not model a length change, which is the harder case: altering the frame's length moves the FCS's position and changes what is covered, and a design that relies on length consistency is relying on Chapter 5.5's length field, which is inside the covered range and therefore inside d.

Production implication: the length case is the one that catches designs which have half-understood the problem. A design that notices the FCS can be compensated sometimes adds a second check — a length field consistency test, a payload checksum, a sequence number — and every one of them is inside the frame and therefore inside the difference the adversary chooses. Adding public checks does not accumulate protection; n public checks are compensated in n steps by a party who read the same specification. The only structure that changes the answer is a key, and Section 10 prices it. A design review's question is therefore not how many checks does the frame carry but does any of them depend on something the sender and receiver know and nobody else does.


6. What CRC Actually Guarantees, Stated Exactly

A chapter arguing that a mechanism does not do one thing owes an exact statement of what it does. Chapter 6.1 §5 gave it; this section restates it because the gap in Section 8 is measured against it.

GuaranteeBoundWhere it comes from
every burst errorup to 32 bitsthe generator's degree — a nonzero polynomial below degree 32 cannot be divisible by it
every pattern of weight ≤ 3at any standard frame lengththe code's minimum distance
every pattern of weight 4frames up to about 375 octetsminimum distance falls as protected length rises
everything elsedetected with probability ≈ 1 − 2⁻³²the remainder of an arbitrary difference is effectively arbitrary

Row four is a residual of one in 4 294 967 296, and it is worth putting that number in context rather than leaving it as an adjective.

At 100 Gb/s with 1 518-octet frames, Chapter 8.3's arithmetic gives 8.13 million frames per second. If every frame carried an error outside the deterministic classes — which no real link does — an undetected one would arrive every 528 seconds. At a realistic post-FEC frame error rate of 10⁻¹² the interval is 5.28 × 10¹⁴ seconds, which is about 16.7 million years.

Against noise the mechanism is not merely adequate; it is enormous overkill by four orders of magnitude, and that is why nobody thinks about it.

And the fourth row of Chapter 6.1's list is the one this chapter must not add to. That chapter already refused the folklore guarantee — "it detects all odd numbers of bit errors" — which does not hold for this polynomial, because a polynomial is divisible by (x + 1) exactly when it has an even number of terms and 0x04C11DB7 with its implicit x³² has fifteen. This chapter does not revisit that; it adds a different missing guarantee, which is resistance.

Two properties of the guarantee list decide everything in Section 8.

One — every clause is a statement about the difference pattern's shape. Length, weight, position. None is a statement about the difference's provenance.

Two — the mechanism cannot observe provenance. A 40-bit difference is a 40-bit difference; nothing in the frame records whether a laser diode or a person produced it.


7. RTL 3 — The Guarantee Model

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// guarantee_model -- Chapter 6.1's four clauses as a classifier, so a
// difference pattern can be placed against them before its outcome is
// examined.
//
// The output that matters is guarantee_applies. When it is low the
// frame is in the residual class, and the residual is meaningful only
// if the difference was not chosen -- which is the input this module
// cannot compute and Section 9's model supplies.
// ---------------------------------------------------------------------
module guarantee_model
  import crcsec_pkg::*;
(
  input  logic        clk,
  input  logic        rst_n,

  input  logic        eval,
  input  logic [15:0] diff_span_bits,
  input  logic [15:0] diff_weight,
  input  logic [15:0] frame_octets,
  input  logic  [1:0] threat,

  output logic        in_burst_class,
  output logic        in_weight3_class,
  output logic        in_weight4_class,
  output logic        guarantee_applies,
  output logic        residual_class,
  output logic [31:0] residual_recip,
  output logic        residual_is_meaningful,
  output logic        provenance_observable,
  output logic [31:0] c_guaranteed,
  output logic [31:0] c_residual
);
  always_comb begin
    // Chapter 6.1 Section 5, clause by clause.
    in_burst_class   = (diff_span_bits != 16'd0)
                    && (diff_span_bits <= 16'(BURST_BOUND_BITS));

    in_weight3_class = (diff_weight != 16'd0)
                    && (diff_weight <= 16'(WEIGHT3_ANY_LENGTH));

    in_weight4_class = (diff_weight == 16'd4)
                    && (frame_octets <= 16'(WEIGHT4_MAX_OCTETS));

    guarantee_applies = in_burst_class || in_weight3_class || in_weight4_class;
    residual_class    = (diff_span_bits != 16'd0) && !guarantee_applies;

    // 2^-32, expressed as one in N.
    residual_recip = 32'hFFFF_FFFF;

    // THE qualification. The residual describes an ARBITRARY remainder.
    // A chosen difference does not have one.
    residual_is_meaningful = residual_class && (threat != 2'(THREAT_CHOSEN));

    // And the fact that makes the qualification unenforceable in
    // hardware: nothing in the frame says where the difference came from.
    provenance_observable = 1'b0;
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      c_guaranteed <= '0; c_residual <= '0;
    end else if (eval) begin
      if (guarantee_applies) c_guaranteed <= c_guaranteed + 32'd1;
      if (residual_class)    c_residual   <= c_residual + 32'd1;
    end
  end
endmodule

Classification: a three-way classifier over a difference pattern, with a fourth output that says when the classification means anything.

What it teaches: that provenance_observable is hard-wired zero and it is the module's reason for existing. The three guarantee classes are computable from the difference alone; whether the residual applies is not, because it depends on how the difference arose. A design can classify the shape and can never classify the source, which is why threat is an input from a testbench and can never be one in silicon.

And it teaches that residual_is_meaningful is the field a security argument needs and no datasheet carries. A part reporting "undetected error probability 2⁻³²" has reported a number whose applicability it cannot determine, and the number is correct for the channel model Chapter 6.1 assumed and vacuous for any other.

Deliberately simplified: the weight-4 bound is a single constant at 375 octets where Chapter 6.1 §7's minimum-distance profile is a staircase with several steps. The weight count and span are supplied from Section 3's model rather than computed here, so this module trusts a structure it is classifying against. And the three classes are treated as disjoint in the counters where they overlap — a weight-2 difference spanning 10 bits is in two classes at once — which is harmless for classification and wrong for accounting.

Production implication: the overlap is worth fixing in a real coverage model, because it hides the case that matters. Chapter 20.4's coverage of error injection wants to know how many patterns fell in each class exclusively, and a pattern counted twice makes the residual class look smaller than it is. The residual class is where a design's error-handling behaviour is least tested, precisely because the guarantees make it rare — so the class that matters most for a directed test is the one the accounting most easily undercounts. One priority encoder, and the counters partition.


8. The Gap Is Not a Matter of Degree

Sections 6 and 7 established what the check promises. This section states the gap to an integrity mechanism, and the point is that it is not a gap on a scale.

Put the two numbers beside each other.

Against noiseAgainst a chosen difference
CRC-32residual 2⁻³² = 2.33 × 10⁻¹⁰residual 1
GCM-AES with a 128-bit ICVdetectedforgery probability ≈ 2⁻¹²⁸ = 2.94 × 10⁻³⁹

The bottom-right cell against the top-right cell is the gap, and it is not 2⁻¹²⁸ against 2⁻³². It is 2⁻¹²⁸ against 1 — a certainty against a vanishing probability — and no amount of widening the check moves the top row, because widening it does not create a secret.

The ratio that IS a ratio is between the two mechanisms' guarantees under their own models.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
2⁻³² ÷ 2⁻¹²⁸ = 2⁹⁶ = 7.92 × 10²⁸

Ninety-six bits, and that number describes the gap between a detector's residual against noise and an integrity check's forgery bound against an adversarywhich are the two mechanisms doing their own jobs, not the same job at different strengths.

Three consequences follow and each is a design rule.

One — a design must not offer a wider CRC as a security improvement. A 64-bit CRC's residual against noise is 2⁻⁶⁴, which is an excellent detector. Against a chosen difference it is still 1, because Section 2's linearity is a property of polynomial division at every width.

Two — a design must not stack public checks. Section 5's production note: n public checks are compensated in n steps. A length field, a payload checksum and a sequence number are all inside the covered range and all computable by the same party.

Three — the presence of a check must not be reported as the presence of integrity. Section 15's telemetry separates the two into different fields for exactly this reason.


9. RTL 4 — The Adversary Model

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// adversary_model -- what a mechanism's guarantee evaluates to when the
// difference pattern is chosen rather than sampled.
//
// It is a MODEL used to reason about a design's claims. It computes no
// frames and drives no interface; its outputs are a classification of
// what each mechanism promises under each threat.
//
// The output that matters is guarantee_survives, which is zero for
// every public mechanism and one for a keyed one -- and the field that
// decides it is key_bits.
// ---------------------------------------------------------------------
module adversary_model
  import crcsec_pkg::*;
(
  input  logic        clk,
  input  logic        rst_n,

  input  logic        eval,
  input  logic  [1:0] threat,
  input  logic [15:0] check_width_bits,
  input  logic [15:0] key_bits,
  input  logic        check_inputs_public,
  input  logic [15:0] stacked_public_checks,

  output logic        guarantee_survives,
  output logic [15:0] effective_security_bits,
  output logic [15:0] compensation_steps,
  output logic        widening_helps,
  output logic        stacking_helps,
  output logic        only_a_key_helps,
  output logic [31:0] c_chosen,
  output logic [31:0] c_noise
);
  always_comb begin
    // A mechanism resists a chosen difference only if something about
    // it is withheld. Width is not withheld; a key is.
    guarantee_survives = (threat != 2'(THREAT_CHOSEN)) || (key_bits != 16'd0);

    effective_security_bits = (threat == 2'(THREAT_CHOSEN))
                            ? key_bits          // zero for a CRC
                            : check_width_bits; // 32, against noise

    // Section 5's production note, as arithmetic: n public checks cost
    // n compensations, which is linear and therefore not a defence.
    compensation_steps = check_inputs_public ? stacked_public_checks : 16'hFFFF;

    // The three answers a design review needs.
    widening_helps = (threat != 2'(THREAT_CHOSEN));
    stacking_helps = 1'b0;
    only_a_key_helps = (threat == 2'(THREAT_CHOSEN));
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      c_chosen <= '0; c_noise <= '0;
    end else if (eval) begin
      if (threat == 2'(THREAT_CHOSEN)) c_chosen <= c_chosen + 32'd1;
      else                             c_noise  <= c_noise + 32'd1;
    end
  end
endmodule

Classification: a threat-conditioned reading of a mechanism's specification, in which one input decides every output.

What it teaches: that effective_security_bits reports 32 under noise and key_bits under a chosen difference, and that for a CRC key_bits is zero. Zero bits of security is not a small number of bits; it is the statement that the adversary's work is a single XOR, which Section 5's module performs.

And it teaches that stacking_helps is a hard-wired zero. compensation_steps equals the number of public checks, so a frame carrying five public checks costs five compensations — linear work for the adversary against a design that believed it had multiplied its protection. Only key_bits changes the shape of the problem, which is why only_a_key_helps exists as a separate output rather than being inferable from the others.

Deliberately simplified: threat is an input, as in Section 5, and for the same unavoidable reason. key_bits is treated as the security level directly, where a real analysis distinguishes the key's length from the tag's length and from the number of forgery attempts permitted — GCM's forgery bound degrades with the number of attempts and with the message length, and neither is modelled. And the model has no notion of key management, which is where deployed systems actually fail: a key that is shared too widely, rotated too rarely or stored where the adversary can read it reduces key_bits to zero as effectively as having none.

Production implication: the key-management omission is the honest one to flag, because it is where the substitute's real cost lies. Section 10 prices the gates and they are affordable; what is not automatically affordable is the machinery around the key — distribution, rotation, storage in a form the rest of the chip cannot read, and a defined behaviour when it is unavailable. A design that implements GCM-AES perfectly and stores its key in a software-readable register has key_bits equal to zero in every respect that matters, and the arithmetic in this chapter applies to it unchanged. The gate count is the easy half and the chapter says so rather than implying the problem ends at the datapath.


10. What the Real Substitute Costs

MACsec's cost, derived in three separate units. In gates: one AES one hundred and twenty eight round is sixteen S-boxes plus MixColumns plus AddRoundKey, seven thousand five hundred and eighty four gate equivalents; ten rounds unrolled with pipeline registers is eighty four thousand eight hundred; GHASH adds seventeen thousand for a total of one hundred and one thousand eight hundred per pipeline; four hundred gigabits per second needs four pipelines at one gigahertz, which is four hundred and seven thousand two hundred gate equivalents against chapter nineteen point four's fifty eight thousand eight hundred and seventy three gate cyclic redundancy check engine, a ratio of six point nine two. On the wire: a security tag of eight or sixteen octets plus a sixteen octet integrity check value, which is twenty eight point five seven per cent more wire at a sixty four octet frame and one point five six per cent at fifteen hundred and eighteen. In state: two hundred and eighty eight bits per secure association, which across four associations and sixty four ports is seventy three thousand seven hundred and twenty eight bitcell equivalents, or a quarter of a media access control receive datapath.MACsecthree costs, threeunitsOne AES round7 584 GETen rounds +registers84 800 GE+ GHASH101 800 GE perpipeline×4 at 400 Gb/s407 200 GE — 6.92×SecTAG + ICV24 or 32 octets+28.57% at 64 oct+1.56% at 1 518288 b per SAkey, SCI, PN, window73 728 BCE0.260 datapaths12
Figure 3 — 6.92× the gates, 24 to 32 octets of wire, and a quarter of a datapath of state, in three units that must not be summed.

Sections 2 to 9 said the check cannot do the job. This section prices the mechanism that can, in gates, in frame overhead and in state — and the three do not compare in one unit.

MACsec — IEEE 802.1AE — authenticates and optionally encrypts a frame with GCM-AES. Derive its datapath.

StructureDerivationGate equivalents
one S-box, pipelinedspeed-optimised rather than compact400
SubBytes, one round16 S-boxes6 400
MixColumns, one round4 column circuits at ≈ 200 GE800
AddRoundKey, one round128 XOR gates at 3 GE384
one AES-128 roundsum of the three7 584
ten rounds, unrolled× 1075 840
pipeline registers10 stages × 128 bits × 7 GE8 960
one AES-128 pipeline84 800
GHASH: a 128-bit Karatsuba multiplier plus reduction17 000
one AES-GCM pipeline101 800

And the throughput requirement sets how many of them.

Line rate128-bit blocks/sPipelines at 1 GHzTotal
100 Gb/s0.781 G1101 800 GE
400 Gb/s3.125 G4407 200 GE

Against Chapter 19.4 §6's CRC engine, re-read rather than recalled: 19 461 XOR terms across both cores and about 70 flops of state.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
19 461 XOR × 3 GE + 70 flops × 7 GE = 58 873 GE
GERatio
CRC-32 engine, 512-bit, dual-core58 8731.0
AES-GCM at 100 Gb/s101 8001.73×
AES-GCM at 400 Gb/s407 2006.92×

Seven times a CRC engine at 400 Gb/s, which is a real cost and is not the reason nobody deploys MACsec. The frame overhead is.

The frame overhead, derived. A protected frame carries a SecTAG — 8 octets without the secure channel identifier, 16 with it — and a 16-octet integrity check value.

64-octet frame1 518-octet frame
wire slot, unprotected841 538
+ SecTAG 8 and ICV 16108 — +28.57% wire1 562 — +1.56%
+ SecTAG 16 and ICV 16116 — +38.10% wire1 570 — +2.08%
payload efficiency, unprotected54.76%97.53%
payload efficiency, +24 octets42.59%96.03%
payload efficiency, +32 octets39.66%95.54%

Chapter 8.3 §2's hyperbola again, with a larger constant — and the same conclusion it always produces: the overhead is severe at the small end and negligible at the large one.

And the state, which is the one quantity BCE prices cleanly.

StructureBitsBCE× the datapath
per secure association: key 128, SCI 64, packet number 32, replay window 642882880.001
4 associations × 64 ports73 72873 7280.260

A quarter of a MAC datapath of state, and seven MAC datapaths' worth of logic if the gates are convertedand Section 19 argues that converting them is the wrong move.


11. RTL 5 — The MACsec Overhead Model

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// macsec_overhead_model -- what protection costs on the wire and in
// state, derived from the SecTAG and ICV sizes rather than quoted.
//
// The output that matters is wire_cost_ppm, because it is the number
// that decides whether a deployment can afford protection on its
// traffic mix -- and it is Chapter 8.3's hyperbola with a bigger
// constant, so the answer depends entirely on the frame size.
// ---------------------------------------------------------------------
module macsec_overhead_model
  import crcsec_pkg::*;
(
  input  logic        clk,
  input  logic        rst_n,

  input  logic [15:0] payload_octets,
  input  logic        include_sci,
  input  logic [15:0] associations,
  input  logic [15:0] ports,
  input  logic [15:0] rate_gbps,

  output logic [15:0] sectag_octets,
  output logic [15:0] overhead_octets,
  output logic [31:0] wire_unprotected,
  output logic [31:0] wire_protected,
  output logic [31:0] wire_cost_ppm,
  output logic [31:0] eff_unprotected_ppm,
  output logic [31:0] eff_protected_ppm,
  output logic [31:0] sa_state_bce,
  output logic [31:0] sa_state_dp_milli,
  output logic [15:0] pipelines_needed,
  output logic [31:0] gcm_ge,
  output logic [31:0] crc_ge,
  output logic [15:0] gate_ratio_x100
);
  logic [31:0] blocks_per_s_m;   // millions of 128-bit blocks per second

  always_comb begin
    sectag_octets   = include_sci ? 16'(SECTAG_WITH_SCI)
                                  : 16'(SECTAG_NO_SCI);
    overhead_octets = sectag_octets + 16'(ICV_OCTETS);

    // Chapter 24.3 Section 1's 38 fixed octets, plus the protection.
    wire_unprotected = 32'(payload_octets) + 32'd38;
    wire_protected   = wire_unprotected + 32'(overhead_octets);

    wire_cost_ppm = (wire_unprotected == 0) ? 32'd0
                  : ((32'(overhead_octets) * 1_000_000) / wire_unprotected);

    eff_unprotected_ppm = (wire_unprotected == 0) ? 32'd0
                        : ((32'(payload_octets) * 1_000_000) / wire_unprotected);
    eff_protected_ppm   = (wire_protected == 0) ? 32'd0
                        : ((32'(payload_octets) * 1_000_000) / wire_protected);

    // State: the one quantity BCE prices without argument.
    sa_state_bce      = 32'd288 * 32'(associations) * 32'(ports);
    sa_state_dp_milli = 32'(datapaths_milli(sa_state_bce));

    // Throughput: 128-bit blocks per second against a 1 GHz core.
    blocks_per_s_m   = (32'(rate_gbps) * 1000) / 32'(AES_BLOCK_BITS);
    pipelines_needed = 16'((blocks_per_s_m + 999) / 1000);

    gcm_ge = 32'(pipelines_needed) * 32'(gcm_pipeline_ge());
    crc_ge = 32'(crc_engine_ge());

    gate_ratio_x100 = (crc_ge == 0) ? 16'd0
                    : 16'((gcm_ge * 32'd100) / crc_ge);
  end
endmodule

Classification: two divisions and a gate model, reported in three units because the three costs are three kinds of thing.

What it teaches: that wire_cost_ppm is 285 714 at a 64-octet frame and 15 604 at 1 51828.57% against 1.56% — and that this is the number a deployment decides on. A fabric carrying Chapter 23.2's collective traffic pays 1.56% or less; a fabric carrying minimum-size control messages pays nearly thirty per cent, and the same mechanism is cheap in one and expensive in the other.

And it teaches that gate_ratio_x100 reports 692 at 400 Gb/s6.92× a CRC engine — which is large in a ratio and modest in absolute terms: 407 200 GE is a few hundredths of a square millimetre at a modern node, and Chapter 23.3's switch spends 91% of its die on a buffer.

Deliberately simplified: the S-box is priced at a single figure where the choice between a compact and a speed-optimised implementation moves it by nearly a factor of two, and that choice depends on the achievable frequency — Chapter 23.6's argument that frequency is an output applies here too. pipelines_needed assumes a 1 GHz core and full pipeline utilisation, ignoring that a frame's blocks are dependent in GHASH's accumulation chain. And confidentiality is not priced separately from integrity, where a deployment that authenticates without encrypting runs the same AES and skips nothing.

Production implication: the last simplification is the one with money attached and it is worth stating. 802.1AE permits integrity without confidentiality — the ICV is computed and the payload is sent in the clear — and the gate cost is identical, because GCM's authentication path runs AES to generate the keystream whether or not the keystream is applied. So a deployment choosing integrity-only saves nothing in silicon and gains the ability for every switch, tap and analyser on the path to keep working — which is Chapter 21.9's entire apparatus continuing to function. That is usually the right trade and it is rarely made deliberately, because the two modes are presented as a security-level choice rather than as an observability one.


12. What Integrity Does to the Parse Window

Chapter 25.1 §3 established that a MAC's parse window ends at octet 13, or 21 with a VLAN tag, and that crossing the boundary costs a window. MACsec inserts a header at exactly that boundary and the consequence is a third crossing nobody counts.

Where the SecTAG goes.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
octet 0            6            12       14                    end−16   end−4
      +------------+------------+--------+----------------------+--------+----+
      | destination| source     | 0x88E5 |  SecTAG + protected  |  ICV   |FCS |
      +------------+------------+--------+----------------------+--------+----+
                                 ^ MACsec EtherType             ^ 16 octets

So a protected frame's real EtherType is not at octet 12. Octet 12 holds 0x88E5Chapter 25.1 §4's MACsec EtherType, one of the eleven that carries no IP octet — and the payload's own type is inside the protected region, after the SecTAG.

Unprotected, untaggedUnprotected, VLAN taggedMACsec, SecTAG 8MACsec, SecTAG 16
MAC's last parsed octet13212129
payload EtherType at12162230
can a switch classify on it?yesyesonly after decryptingonly after decrypting

Row three is the operational consequence and it is larger than the gate count. A switch that classifies on an inner field — a five-tuple hash for Chapter 15.2, a priority derived from a DSCP, an access list — sees 0x88E5 and nothing else on a protected link.

MACsec does not merely add octets. It makes every field above the SecTAG invisible to every device that does not hold the key, which is every device that is not an endpoint of that link.

And that is a feature, not a defect — it is what protection means — but it interacts with Chapter 25.1 §12's five failures in a specific way. A hash with no fallback, on a MACsec link, has no tuple to find and puts every frame on one aggregation member. Chapter 25.1 §12 derived that as a 70% imbalance at 8% non-IP traffic; on a fully protected link the non-IP share is 100% and the imbalance is total.

Non-IP shareMember 0's share of 8Imbalance
ordinary traffic8%19.5%70%
fully MACsec-protected100%100%700%

The fix is the one Chapter 25.1 §12 already named — hash over whatever fields are present, falling back to the MAC addresses and the EtherType — and on a protected link those three fields are exactly what remains visible.


13. RTL 6 — The Protected Parser

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// protected_parser -- Chapter 25.1's parse window meeting a MACsec
// boundary, and the two different reasons a parse stops.
//
// The output that matters is the pair stopped_by_budget and
// stopped_by_protection. A design that reports them through one bit has
// conflated a limit it could raise with a guarantee it bought.
// ---------------------------------------------------------------------
module protected_parser
  import crcsec_pkg::*;
#(
  parameter int unsigned WINDOW_OCTETS = 64,
  parameter int unsigned MAX_TAGS      = 2
)(
  input  logic        clk,
  input  logic        rst_n,

  input  logic        frame_start,
  input  logic        octet_valid,
  input  logic [15:0] octet_index,
  input  logic [15:0] type_at_cursor,
  input  logic        type_valid,
  input  logic        have_key,

  output logic [15:0] cursor,
  output logic [15:0] tags_seen,
  output logic        is_vlan,
  output logic        is_macsec,
  output logic        reached_payload_type,
  output logic        stopped_by_budget,
  output logic        stopped_by_protection,
  output logic        classification_available,
  output logic [15:0] visible_fields_mask,
  output logic [31:0] c_protected_frames,
  output logic [31:0] c_budget_stops
);
  localparam logic [15:0] ET_VLAN   = 16'h8100;
  localparam logic [15:0] ET_SVLAN  = 16'h88A8;
  localparam logic [15:0] ET_MACSEC = 16'h88E5;

  always_comb begin
    is_vlan   = type_valid && ((type_at_cursor == ET_VLAN) ||
                               (type_at_cursor == ET_SVLAN));
    is_macsec = type_valid && (type_at_cursor == ET_MACSEC);

    // Two stops, two meanings.
    stopped_by_budget     = (cursor >= 16'(WINDOW_OCTETS))
                         || (tags_seen > 16'(MAX_TAGS));
    stopped_by_protection = is_macsec && !have_key;

    reached_payload_type = type_valid && !is_vlan && !is_macsec
                        && !stopped_by_budget;

    // What a device without the key can still classify on: the two
    // addresses and the outer EtherType. Chapter 25.1 Section 12's
    // fallback fields, and on a protected link they are all there is.
    visible_fields_mask = stopped_by_protection ? 16'h0007   // DA, SA, type
                                                : 16'h00FF;

    classification_available = reached_payload_type;
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      cursor <= 16'd12; tags_seen <= '0;
      c_protected_frames <= '0; c_budget_stops <= '0;
    end else begin
      if (frame_start) begin
        cursor <= 16'd12; tags_seen <= '0;
      end else if (octet_valid && type_valid) begin
        if (is_vlan) begin
          cursor    <= cursor + 16'd4;
          tags_seen <= tags_seen + 16'd1;
        end else if (is_macsec) begin
          cursor <= cursor + 16'd8;
          c_protected_frames <= c_protected_frames + 32'd1;
        end
      end
      if (stopped_by_budget) c_budget_stops <= c_budget_stops + 32'd1;
    end
  end
endmodule

Classification: a chaining parser with two distinct termination conditions, and the distinction is the module's whole contribution.

What it teaches: that stopped_by_protection and stopped_by_budget are different events with different owners. A budget stop is a design limit, raised by widening the window or the tag depth; a protection stop is a property of the deployment, and no design change removes it. A part that reports one bit for both invites an integrator to widen a window that was never the constraint.

And it teaches that visible_fields_mask is the honest answer to what can this switch classify on. On a protected link the answer is the two addresses and the outer EtherType — three fields — which is exactly Chapter 25.1 §12's fallback set. A design whose hash, filter or queue selection needs more has a configuration that is silently inoperative on protected links.

Deliberately simplified: the cursor advances by a fixed 8 for MACsec where the SecTAG is 8 or 16 depending on whether the secure channel identifier is present, and the bit that says which is inside the SecTAG itselfChapter 19.2 §6's content-dependent offset, one level deeper. The module has no notion of decrypting, so have_key is an input standing in for a whole engine. And visible_fields_mask is a constant per case where a real design may also read the SecTAG's own fields, which are in the clear.

Production implication: the SecTAG's clear fields are the ones a deployment should be using and usually is not. The packet number is visible, in the clear, on every protected frame — and it is a per-association sequence number, which is precisely the field Chapter 21.6 spent a chapter wanting: a gap in it is an unambiguous, attributable loss, visible to any device on the link without the key. A switch that counted packet-number gaps per secure association would have the reason field Chapter 21.6 §13 established does not exist in an ordinary frame. It costs a 32-bit register and a comparison per association — 4 × 64 × 32 = 8 192 bits, 8 192 BCE, 0.029 datapaths — and it turns a protected link into the most observable link in the fabric, which is the opposite of what everyone assumes MACsec does.


14. What an Integrity Claim Must Never Do

Five prohibitions, each with a failure from this chapter behind it.

#NeverBecause
1report a passing FCS as evidence the frame is unalteredSection 4: the compensating delta is one XOR of public values
2offer a wider check as a security improvementSection 8: linearity holds at every width; a 64-bit CRC's adversarial residual is still 1
3stack public checks and call it defence in depthSection 9: n public checks cost n compensations — linear work
4count a wire error and an integrity violation in the same counterSection 8's callout: a noisy link then produces an unactionable integrity alarm
5report a protection stop and a budget stop through one bitSection 13: one is a limit somebody could raise, the other is a guarantee somebody bought

Row four is the one that gets a working MACsec deployment disabled.

A protected link carries both kinds of failure. A frame corrupted by the channel fails its FCS and is discarded — thousands of times more often than any attack — and a frame altered without the key fails its ICV. If both increment c_integrity_violations, the counter tracks the link's error rate and an operator watching it sees an attack whenever a transceiver ages. Within a month somebody turns the alarm off, and the counter that would have reported a real event is the one nobody is watching.

The separation is two counters and a wiring decision, and Section 8's callout derived why the order makes it possible: the FCS runs first and discards the channel's corruption before the cryptographic engine sees it.


15. RTL 7 — Integrity Telemetry

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// integrity_telemetry -- the counters that keep a protection claim
// honest, with the two failure kinds kept apart.
//
// Design rule: a check and a protection are different mechanisms, so
// every counter here names which one it is about. A design that merges
// them produces a number nobody can act on.
// ---------------------------------------------------------------------
module integrity_telemetry
  import crcsec_pkg::*;
(
  input  logic        clk,
  input  logic        rst_n,
  input  logic        clear,

  input  logic        frame_valid,
  input  logic        fcs_failed,
  input  logic        icv_failed,
  input  logic        protection_enabled,
  input  logic        key_present,
  input  logic        replay_detected,
  input  logic        pn_gap_detected,
  input  logic [15:0] pn_gap_size,
  input  logic        stopped_by_protection_i,
  input  logic        stopped_by_budget_i,

  output logic [47:0] c_frames,
  output logic [47:0] c_fcs_failures,
  output logic [47:0] c_icv_failures,
  output logic [47:0] c_replays,
  output logic [47:0] c_pn_gaps,
  output logic [47:0] c_pn_frames_lost,
  output logic [47:0] c_protection_stops,
  output logic [47:0] c_budget_stops_o,
  output logic [31:0] fcs_fail_ppm,
  output logic [31:0] icv_fail_ppm,
  output logic        counters_are_separated,
  output logic        claim_is_supported,
  output logic        integrity_alarm
);
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n || clear) begin
      c_frames <= '0; c_fcs_failures <= '0; c_icv_failures <= '0;
      c_replays <= '0; c_pn_gaps <= '0; c_pn_frames_lost <= '0;
      c_protection_stops <= '0; c_budget_stops_o <= '0;
    end else if (frame_valid) begin
      c_frames <= c_frames + 48'd1;

      // Section 8's triage, honoured: a frame the wire corrupted is
      // counted as a wire error and never reaches the ICV counter.
      if (fcs_failed)                 c_fcs_failures <= c_fcs_failures + 48'd1;
      else if (icv_failed)            c_icv_failures <= c_icv_failures + 48'd1;

      if (replay_detected)            c_replays <= c_replays + 48'd1;
      if (pn_gap_detected) begin
        c_pn_gaps        <= c_pn_gaps + 48'd1;
        // Section 13's production note: the packet number is in the
        // clear, so a gap is an attributable loss count.
        c_pn_frames_lost <= c_pn_frames_lost + 48'(pn_gap_size);
      end
      if (stopped_by_protection_i)
        c_protection_stops <= c_protection_stops + 48'd1;
      if (stopped_by_budget_i)
        c_budget_stops_o   <= c_budget_stops_o + 48'd1;
    end
  end

  always_comb begin
    fcs_fail_ppm = (c_frames == 0) ? 32'd0
                 : 32'((c_fcs_failures * 48'd1_000_000) / c_frames);
    icv_fail_ppm = (c_frames == 0) ? 32'd0
                 : 32'((c_icv_failures * 48'd1_000_000) / c_frames);

    // The structural claims this module makes about itself.
    counters_are_separated = 1'b1;

    // A protection claim is supported only if protection is on AND a
    // key is present. Either alone is a claim about a configuration.
    claim_is_supported = protection_enabled && key_present;

    // And the alarm fires on the counter that means something.
    integrity_alarm = claim_is_supported
                   && ((c_icv_failures != 48'd0) || (c_replays != 48'd0));
  end
endmodule

Classification: a counter bank whose defining feature is an else — the FCS failure and the ICV failure are mutually exclusive by construction.

What it teaches: that the else if in the counting logic is the whole of Section 8's triage rendered in hardware. A frame that failed its FCS never reaches the ICV counter, so icv_fail_ppm measures alteration rather than noise — and on a link with a 10⁻⁶ frame error rate and no attacker, it reads exactly zero while fcs_fail_ppm reads 1. Two numbers, two mechanisms, and only one of them should ever be nonzero.

And it teaches that claim_is_supported is a check on a configuration rather than on traffic. A port with protection enabled and no key is not protected, and a design reporting only protection_enabled has reported an intention. Both bits, ANDed, and the claim matches the state.

Deliberately simplified: pn_gap_size is supplied rather than computed, so the module trusts a comparator it does not contain. The replay counter does not distinguish a replay from a reordered frame, which on a fabric with Chapter 15.2's multi-path forwarding is a real and benign event — the replay window exists to tolerate it and a window too small counts legitimate reordering as attack. And all counters are per port where a real deployment wants them per secure association.

Production implication: the replay-window sizing is the parameter that produces false alarms and it has the same shape as three earlier parameters in this track. A window smaller than the fabric's reordering depth counts benign reordering as replay; a window larger than necessary lets a genuine replay through. The derivation is the fabric's path-skew bound — Chapter 23.2 §14's reassembly argument — divided by the frame interval, and at 100 Gb/s with 1 518-octet frames and a 1 µs skew that is 8.13 frames, so a 32-entry window is comfortable and a 4-entry one is not. It is the same move Chapter 14.2 §9 made for a pause watermark and Chapter 25.2 §8 made for a table's aging interval: derive the threshold from the delay it protects against rather than from the resource it sits in.


16. RTL 8 — The Integrity Conformance Monitor

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// integrity_conformance -- the checks that hold an integrity claim to
// what the mechanism actually provides.
//
// Note what is absent: there is no check that frames are unaltered.
// Section 7 established that provenance is unobservable, so no monitor
// can write it, and Section 20's rejected property is the attempt.
// ---------------------------------------------------------------------
module integrity_conformance
  import crcsec_pkg::*;
(
  input  logic        clk,
  input  logic        rst_n,

  input  logic        claims_integrity,
  input  logic [15:0] key_bits_i,
  input  logic        fcs_checked_first,
  input  logic        icv_checked,
  input  logic        counters_merged,
  input  logic [15:0] stacked_public_checks_i,
  input  logic        offered_as_security,
  input  logic        protection_stop_distinct,
  input  logic [15:0] replay_window,
  input  logic [15:0] fabric_skew_frames,

  output logic        v_claim_without_key,
  output logic        v_icv_before_fcs,
  output logic        v_counters_merged,
  output logic        v_public_check_as_security,
  output logic        v_stop_bits_merged,
  output logic        v_replay_window_short,
  output logic  [5:0] violations,
  output logic        conformant
);
  always_comb begin
    // 1. Section 9: only a key changes the answer.
    v_claim_without_key = claims_integrity && (key_bits_i == 16'd0);

    // 2. Section 8's callout: the cheap check filters for the
    //    expensive one, so its order is load-bearing.
    v_icv_before_fcs = icv_checked && !fcs_checked_first;

    // 3. Prohibition 4 -- an unactionable alarm.
    v_counters_merged = counters_merged;

    // 4. Prohibitions 1 to 3 -- a public check offered as protection,
    //    however many of them there are.
    v_public_check_as_security = offered_as_security && (key_bits_i == 16'd0)
                              && (stacked_public_checks_i >= 16'd1);

    // 5. Section 13 -- two stops, two meanings.
    v_stop_bits_merged = !protection_stop_distinct;

    // 6. A window below the fabric's reordering depth counts benign
    //    reordering as an attack.
    v_replay_window_short = (replay_window < fabric_skew_frames);

    violations = { v_replay_window_short, v_stop_bits_merged,
                   v_public_check_as_security, v_counters_merged,
                   v_icv_before_fcs, v_claim_without_key };
    conformant = (violations == 6'b000000);
  end
endmodule

Classification: six checks, none of which checks that a frame is unaltered — and the absence is the module's argument.

What it teaches: that a design cannot verify integrity and can verify that it has the means to. v_claim_without_key is the load-bearing check: a part asserting integrity with zero key bits is asserting something Section 9's model says is unavailable, and one comparison catches it.

And it teaches that v_icv_before_fcs is a check on an ordering rather than on a value. Chapter 5.8 §7 established what a passing FCS is evidence of; Section 8's callout established that running it first is what keeps the integrity counter meaningful. A design that verifies the ICV first is correct and produces a counter that tracks the wire's noise, which is the failure prohibition 4 describes arriving by a different route.

Deliberately simplified: fabric_skew_frames must be supplied and is a property of a topology the port cannot see — the same gap Chapter 25.2 §16 left open for a legitimate move rate. key_bits_i is a configuration input the monitor trusts, so a design that reports 128 while holding a key in a software-readable register passes — Section 9's production note, unenforceable here as it is unenforceable anywhere in RTL. And there is no check that the key is actually used, only that it exists.

Production implication: the unused-key case is real and has a specific signature worth recognising. A port configured for MACsec with a valid key, whose peer has not completed key agreement, falls back to unprotected transmission in many implementations — and the link works, traffic flows, and every counter reads healthy. The only evidence is c_protected_frames from Section 13 reading zero on a port that claims protection, which is a comparison between two counters that live in different blocks and are rarely put side by side. A single derived bit — claims_integrity && (c_protected_frames == 0) && (c_frames != 0) — is the check, and its absence is why "we enabled MACsec" and "MACsec is running" are different statements that no dashboard distinguishes.


17. The Two Mechanisms, Priced Side by Side

Four diagnostic questions. First, is any input to the mechanism secret: if not, it is a detector, because the polynomial is in the standard, the frame is on the wire, and the check value is in the frame. Second, what is the theorem and what is its domain: chapter six point one gives bursts to thirty two bits, weight three at any length, and weight four under three hundred and seventy five octets, and every clause is about the shape of the difference rather than its origin. Third, is the claim the theorem or its converse: the theorem says errors in the characterised class are caught, while the claim says anything not caught is not an error, and those coincide only if the class is everything. Fourth, what is assumed random: the residual rests on a corrupted frame's value being effectively arbitrary, and an adversary's whole contribution is to make that assumption false.A mechanismoffered as protectionAny secret input?no → a detectorThe theorem'sdomain?a class of shapesTheorem orconverse?class 117What is assumedrandom?the differenceA detectorexcellent, againstnoiseA protectionneeds a keyWhich one is this?question 1 settles it12
Figure 4 — four questions that classify any mechanism offered as protection, and the first one usually settles it.

Everything this chapter derived, in one table.

CRC-32 (the FCS)GCM-AES (MACsec)
what it ispolynomial division, linear over GF(2)a block cipher and a field multiplier, keyed
inputsall publicone is secret
against noiseresidual 2⁻³² = 2.33 × 10⁻¹⁰detected
against a chosen differenceresidual 1forgery ≈ 2⁻¹²⁸ = 2.94 × 10⁻³⁹
adversary's work to defeat itone XOR2¹²⁸ expected operations
logic, 400 Gb/s58 873 GE407 200 GE — 6.92×
state70 flops = 1 400 BCE73 728 BCE at 4 SAs × 64 ports
frame overhead4 octets24 or 32 octets
wire cost at 64 octets4.76%+28.57% or +38.10% on top
wire cost at 1 518 octets0.26%+1.56% or +2.08%
scopeone hop — Chapter 5.8 §8one hop, for the same reason
what it makes invisiblenothingevery field above the SecTAG

Rows three and four together are the chapter, and the gap between the two right-hand cells is 2⁹⁶ = 7.92 × 10²⁸ — which is what separates a detector's residual against noise from an integrity check's bound against an adversary, two mechanisms each doing its own job.

The FCS is not a weak integrity mechanism. It is an excellent detector with no secret, and an integrity mechanism without a secret does not exist at any strength.

And the deployment decision, stated as the three questions Section 10's callout implies.

QuestionIf the answer isThen
what is the mean frame size?large — collectives, storage, bulkthe wire cost is 1.56% and affordable
small — control, telemetry, synchronisation28.57% and it must be argued for
who needs to see inside?nobody between the endpointsencrypt
taps, analysers, classifiers on the pathauthenticate without encrypting — same gates, Chapter 21.9's apparatus keeps working
where does the key live?hardware the software cannot readthe arithmetic in this chapter applies
a software-readable registerkey_bits is zero and so is everything else

18. What the Correction Assumes

Eight assumptions, each with its direction of failure.

#AssumptionIf it is false
1the generator is 0x04C11DB7 with x³² implicitChapter 6.2 §4's taps; linearity holds for any generator, so the argument does not move
2the four conventions cancel in the differenceSection 4: initial value and complement are constants, reflection commutes with XOR
3Chapter 6.1's guarantee boundsthat chapter's derivation; a different polynomial moves the weight-4 length and nothing else here
4an S-box at 400 GEa compact implementation is nearer 230 and halves the AES figure; the ratio falls to about 4×
5a 1 GHz cryptographic corepipelines scale inversely; 2 GHz halves the count and doubles the timing difficulty
6Chapter 19.4's 19 461 XOR termsthat chapter's measured figure at 512 bits; a 64-bit engine is about a sixth of it and the ratio rises
7SecTAG 8 or 16 octets, ICV 16802.1AE's sizes; a shorter ICV trades forgery probability for wire, at 2⁻ᴺ per N bits
8BCE prices the state and not the logicSection 19 examines it and the boundary is the chapter's cleanest

Assumption 4 is the one that moves the headline and it deserves the caveat. A compact S-box is about 230 GE and a speed-optimised one about 400, and the choice is made by the achievable frequency — Chapter 23.6's argument that frequency is an output rather than an input applies to a cryptographic core exactly as it applies to a MAC. At 230 GE the AES pipeline is 57 600 GE and the 400 Gb/s total is 298 400 GE — 5.07× a CRC engine rather than 6.92×. The conclusion is unchanged and the number is a range.


19. The Cost, Accounted — in BCE and in Gates

This chapter's blocks.

BlockFlopsBCE× the datapath
crc_difference_model326400.002
compensating_delta326400.002
guarantee_model641 2800.005
adversary_model641 2800.005
macsec_overhead_model0 — combinational00
protected_parser961 9200.007
integrity_telemetry4328 6400.030
integrity_conformance0 — combinational00
this chapter's additions72014 4000.051

And the designs the blocks describe, in the two units they need.

State, BCELogic, GE
Chapter 19.4's CRC engine, 512-bit dual-core1 40058 873
AES-GCM, 100 Gb/s101 800
AES-GCM, 400 Gb/s407 200
MACsec SA state, 4 × 64 ports73 728
packet-number gap detectors, 4 × 648 192
Chapter 19.7 §19's MAC receive datapath283 320

20. Properties Worth Asserting, and One Worth Refusing

Fifty-one properties in six groups, and the refused one is the sentence the whole myth reduces to.

Group A — linearity (9).

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// A1. The identity, on every frame and every difference.
p_ln_linearity: assert property (@(posedge clk) disable iff (!rst_n)
  eval |-> (r_combined == (r_frame ^ r_diff)));

// A2. And the model reports it.
p_ln_holds: assert property (@(posedge clk) disable iff (!rst_n)
  eval |-> linearity_holds);

// A3. A zero difference changes nothing.
p_ln_zero_diff: assert property (@(posedge clk) disable iff (!rst_n)
  (diff_bits == '0) |-> (r_combined == r_frame));

// A4. The difference's check value does not depend on the frame.
p_ln_diff_independent: assert property (@(posedge clk) disable iff (!rst_n)
  (diff_bits == $past(diff_bits)) |-> (r_diff == $past(r_diff)));

// A5. Weight is the count of set bits and nothing else.
p_ln_weight: assert property (@(posedge clk) disable iff (!rst_n)
  (diff_bits == '0) |-> (diff_weight == 16'd0));

// A6. Span is zero only for a zero difference.
p_ln_span_zero: assert property (@(posedge clk) disable iff (!rst_n)
  (diff_span_bits == 16'd0) |-> !diff_is_nonzero);

// A7. Span is at least the weight.
p_ln_span_ge_weight: assert property (@(posedge clk) disable iff (!rst_n)
  diff_is_nonzero |-> (diff_span_bits >= diff_weight));

// A8. A difference inside 32 bits is inside the burst class.
p_ln_burst_class: assert property (@(posedge clk) disable iff (!rst_n)
  (diff_is_nonzero && (diff_span_bits <= 16'd32)) |-> within_burst_bound);

// A9. Every evaluation is counted.
p_ln_counted: assert property (@(posedge clk) disable iff (!rst_n)
  eval |=> (c_evaluations == $past(c_evaluations) + 32'd1));

Group B — the compensation (8).

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// B1. The delta is the difference's pure remainder.
p_cp_delta: assert property (@(posedge clk) disable iff (!rst_n)
  eval |-> (delta == r0(diff_bits, int'(nbits))));

// B2. And the compensated value is one XOR away.
p_cp_xor: assert property (@(posedge clk) disable iff (!rst_n)
  (fcs_compensated == (fcs_original ^ delta)));

// B3. A chosen nonzero difference leaves the check satisfied.
p_cp_passes: assert property (@(posedge clk) disable iff (!rst_n)
  ((threat == 2'(THREAT_CHOSEN)) && (diff_bits != '0)) |-> check_still_passes);

// B4. And a channel difference does not.
p_cp_noise_fails: assert property (@(posedge clk) disable iff (!rst_n)
  (threat != 2'(THREAT_CHOSEN)) |-> !check_still_passes);

// B5. The mechanism needs no secret and has none.
p_cp_no_secret: assert property (@(posedge clk) disable iff (!rst_n)
  (!requires_a_secret && (bits_of_secret_available == 16'd0)));

// B6. Every input is public.
p_cp_public: assert property (@(posedge clk) disable iff (!rst_n)
  inputs_are_all_public);

// B7. The reported probability is 1 for a chosen difference.
p_cp_certainty: assert property (@(posedge clk) disable iff (!rst_n)
  (threat == 2'(THREAT_CHOSEN)) |-> (detection_prob_recip == 32'd1));

// B8. And 2^32-ish otherwise.
p_cp_residual: assert property (@(posedge clk) disable iff (!rst_n)
  (threat != 2'(THREAT_CHOSEN)) |-> (detection_prob_recip == 32'hFFFF_FFFF));

Group C — the guarantees (9).

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// C1. Chapter 6.1's burst clause.
p_gu_burst: assert property (@(posedge clk) disable iff (!rst_n)
  ((diff_span_bits != 16'd0) && (diff_span_bits <= 16'd32)) |-> in_burst_class);

// C2. The weight-3 clause holds at any length.
p_gu_weight3: assert property (@(posedge clk) disable iff (!rst_n)
  ((diff_weight != 16'd0) && (diff_weight <= 16'd3)) |-> in_weight3_class);

// C3. The weight-4 clause is length-bounded.
p_gu_weight4: assert property (@(posedge clk) disable iff (!rst_n)
  ((diff_weight == 16'd4) && (frame_octets > 16'd375)) |-> !in_weight4_class);

// C4. Guaranteed and residual partition the nonzero differences.
p_gu_partition: assert property (@(posedge clk) disable iff (!rst_n)
  (diff_span_bits != 16'd0) |-> (guarantee_applies ^ residual_class));

// C5. The residual applies only to an unchosen difference.
p_gu_residual_qualified: assert property (@(posedge clk) disable iff (!rst_n)
  (threat == 2'(THREAT_CHOSEN)) |-> !residual_is_meaningful);

// C6. Provenance is never observable.
p_gu_provenance: assert property (@(posedge clk) disable iff (!rst_n)
  (provenance_observable == 1'b0));

// C7. The residual reciprocal is the width's bound.
p_gu_recip: assert property (@(posedge clk) disable iff (!rst_n)
  (residual_recip == 32'hFFFF_FFFF));

// C8. Guaranteed differences are counted.
p_gu_counted: assert property (@(posedge clk) disable iff (!rst_n)
  (eval && guarantee_applies) |=> (c_guaranteed == $past(c_guaranteed) + 32'd1));

// C9. A zero difference is in no class.
p_gu_zero: assert property (@(posedge clk) disable iff (!rst_n)
  (diff_span_bits == 16'd0) |-> (!guarantee_applies && !residual_class));

Group D — the adversary model (8).

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// D1. Only a key makes a guarantee survive a chosen difference.
p_ad_key_required: assert property (@(posedge clk) disable iff (!rst_n)
  ((threat == 2'(THREAT_CHOSEN)) && (key_bits == 16'd0)) |-> !guarantee_survives);

// D2. And with a key it does.
p_ad_key_suffices: assert property (@(posedge clk) disable iff (!rst_n)
  (key_bits != 16'd0) |-> guarantee_survives);

// D3. Effective security under a chosen difference is the key length.
p_ad_effective: assert property (@(posedge clk) disable iff (!rst_n)
  (threat == 2'(THREAT_CHOSEN)) |-> (effective_security_bits == key_bits));

// D4. Widening the check does not help against a chosen difference.
p_ad_widening: assert property (@(posedge clk) disable iff (!rst_n)
  (threat == 2'(THREAT_CHOSEN)) |-> !widening_helps);

// D5. Stacking public checks never helps.
p_ad_stacking: assert property (@(posedge clk) disable iff (!rst_n)
  (stacking_helps == 1'b0));

// D6. And the work to defeat n of them is n.
p_ad_linear_work: assert property (@(posedge clk) disable iff (!rst_n)
  check_inputs_public |-> (compensation_steps == stacked_public_checks));

// D7. Only a key is named as the remedy.
p_ad_only_key: assert property (@(posedge clk) disable iff (!rst_n)
  (threat == 2'(THREAT_CHOSEN)) |-> only_a_key_helps);

// D8. Chosen and noise evaluations are counted apart.
p_ad_counted_apart: assert property (@(posedge clk) disable iff (!rst_n)
  (eval && (threat == 2'(THREAT_CHOSEN))) |=>
    ((c_chosen == $past(c_chosen) + 32'd1) && (c_noise == $past(c_noise))));

Group E — MACsec overhead and the parser (9).

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// E1. The SecTAG size follows the SCI flag.
p_ms_sectag: assert property (@(posedge clk) disable iff (!rst_n)
  include_sci |-> (sectag_octets == 16'(SECTAG_WITH_SCI)));

// E2. Overhead is the SecTAG plus the ICV.
p_ms_overhead: assert property (@(posedge clk) disable iff (!rst_n)
  (overhead_octets == sectag_octets + 16'(ICV_OCTETS)));

// E3. Protection always costs wire.
p_ms_costs_wire: assert property (@(posedge clk) disable iff (!rst_n)
  (wire_protected > wire_unprotected));

// E4. And the cost falls as the frame grows -- Chapter 8.3's hyperbola.
p_ms_hyperbola: assert property (@(posedge clk) disable iff (!rst_n)
  (payload_octets > $past(payload_octets)) |->
    (wire_cost_ppm <= $past(wire_cost_ppm)));

// E5. Efficiency falls under protection.
p_ms_eff_falls: assert property (@(posedge clk) disable iff (!rst_n)
  (eff_protected_ppm < eff_unprotected_ppm));

// E6. The pipeline count rounds up.
p_ms_pipelines: assert property (@(posedge clk) disable iff (!rst_n)
  (rate_gbps != 0) |-> (pipelines_needed >= 16'd1));

// E7. Two stops, and they are distinguishable.
p_pp_two_stops: assert property (@(posedge clk) disable iff (!rst_n)
  stopped_by_protection |-> !classification_available);

// E8. Without a key only three fields are visible.
p_pp_visible: assert property (@(posedge clk) disable iff (!rst_n)
  stopped_by_protection |-> (visible_fields_mask == 16'h0007));

// E9. A MACsec frame with a key parses on.
p_pp_with_key: assert property (@(posedge clk) disable iff (!rst_n)
  (is_macsec && have_key) |-> !stopped_by_protection);

Group F — telemetry and conformance (8).

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// F1. The two failure kinds are mutually exclusive by construction.
p_tl_exclusive: assert property (@(posedge clk) disable iff (!rst_n)
  (frame_valid && fcs_failed) |=> (c_icv_failures == $past(c_icv_failures)));

// F2. The counters are separated and the module says so.
p_tl_separated: assert property (@(posedge clk) disable iff (!rst_n)
  counters_are_separated);

// F3. A protection claim needs enablement AND a key.
p_tl_claim: assert property (@(posedge clk) disable iff (!rst_n)
  claim_is_supported |-> (protection_enabled && key_present));

// F4. The alarm fires only on a supported claim.
p_tl_alarm: assert property (@(posedge clk) disable iff (!rst_n)
  integrity_alarm |-> claim_is_supported);

// F5. A packet-number gap accumulates the lost count.
p_tl_pn_gap: assert property (@(posedge clk) disable iff (!rst_n)
  (frame_valid && pn_gap_detected) |=>
    (c_pn_frames_lost == $past(c_pn_frames_lost) + 48'($past(pn_gap_size))));

// F6. An integrity claim with no key is a violation.
p_cf_no_key: assert property (@(posedge clk) disable iff (!rst_n)
  (claims_integrity && (key_bits_i == 16'd0)) |-> v_claim_without_key);

// F7. The FCS must be checked before the ICV.
p_cf_order: assert property (@(posedge clk) disable iff (!rst_n)
  icv_checked |-> fcs_checked_first);

// F8. Conformance is the disjunction of its six checks.
p_cf_vector: assert property (@(posedge clk) disable iff (!rst_n)
  conformant |-> (violations == 6'b000000));

Coverage — the cases a generator built around noise will not produce.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
c_ln_burst32:    cover property (@(posedge clk) diff_span_bits == 16'd32);
c_ln_burst33:    cover property (@(posedge clk) diff_span_bits == 16'd33);
c_ln_weight4:    cover property (@(posedge clk) diff_weight == 16'd4);
c_gu_residual:   cover property (@(posedge clk) residual_class);
c_gu_not_mean:   cover property (@(posedge clk) residual_class && !residual_is_meaningful);
c_cp_chosen:     cover property (@(posedge clk) check_still_passes);
c_ad_no_key:     cover property (@(posedge clk) effective_security_bits == 16'd0);
c_ad_stacked:    cover property (@(posedge clk) stacked_public_checks >= 16'd3);
c_ms_min_frame:  cover property (@(posedge clk) payload_octets <= 16'd46);
c_ms_jumbo:      cover property (@(posedge clk) payload_octets >= 16'd9000);
c_pp_protected:  cover property (@(posedge clk) stopped_by_protection);
c_pp_tag_macsec: cover property (@(posedge clk) is_vlan && is_macsec);
c_tl_icv_fail:   cover property (@(posedge clk) icv_failed && !fcs_failed);
c_cf_merged:     cover property (@(posedge clk) v_counters_merged);

21. Verification Scenarios

Fifty-eight scenarios in six groups, plus one directed test random stimulus will not produce.

Group 1 — linearity (10).

#ScenarioExpect
1any frame, any differencelinearity_holds high
2a zero differencer_combined == r_frame; span 0; no class
3a single-bit differenceweight 1, span 1, burst class
4a 32-bit burstspan 32, within_burst_bound high
5a 33-bit burstspan 33, bound low — Chapter 6.1 §5's shortest escaper
6the same difference on two different framesr_diff identical — the frame does not enter
7weight 3 at 1 518 octetsweight-3 class
8weight 4 at 300 octetsweight-4 class
9weight 4 at 1 518 octetsresidual class — the length bound bites
10weight 9, span 40residual class

Group 2 — the compensation (9).

#ScenarioExpect
11a chosen 40-bit differencecheck_still_passes high
12the same difference, THREAT_NOISEpasses low; the check does its job
13delta for any differenceequals r0(diff); one XOR from the original FCS
14bits_of_secret_available0, at every setting
15requires_a_secret0
16detection_prob_recip, chosen1
17detection_prob_recip, noise4 294 967 295
18a 64-bit check width, chosen differencestill passes — width is not the variable
19three stacked public checks, chosencompensation_steps = 3 — linear work

Group 3 — the guarantees (10).

#ScenarioExpect
20span 32in_burst_class
21span 33, weight 2weight-3 class, not burst
22span 33, weight 12residual class
23provenance_observable0, at every setting
24residual class, THREAT_NOISEresidual_is_meaningful high
25residual class, THREAT_CHOSENresidual_is_meaningful LOW
268.13 Mpps at 100 Gb/s, all frames erroredone undetected every 528 s
27the same at a 10⁻¹² frame error rate5.28 × 10¹⁴ s — 16.7 million years
28guaranteed and residual on one differenceexactly one — p_gu_partition
29a zero differenceneither class; both counters still

Group 4 — the adversary model (9).

#ScenarioExpect
30CRC, chosen differenceguarantee_survives low; 0 effective bits
31128-bit key, chosen differencesurvives; 128 effective bits
32CRC, noisesurvives; 32 effective bits
33widening_helps, chosenlow
34stacking_helps, any settinglow
35only_a_key_helps, chosenhigh
365 public checks5 compensation steps
37a keyed mechanismcompensation_steps = 65 535 — not a finite budget
38chosen and noise countedc_chosen and c_noise move apart

Group 5 — MACsec overhead and the parser (10).

#ScenarioExpect
3946-octet payload, SecTAG 8wire 84 → 108; 285 714 ppm — 28.57%
4046-octet payload, SecTAG 16wire 84 → 116; 38.10%
411 500-octet payload, SecTAG 8wire 1 538 → 1 562; 15 604 ppm — 1.56%
429 000-octet payload0.27%
43100 Gb/s1 pipeline; 101 800 GE; ratio 173
44400 Gb/s4 pipelines; 407 200 GE; ratio 692
454 SAs × 64 ports73 728 BCE; 0.260 datapaths
460x88E5 at octet 12, no keystopped_by_protection; mask 0x0007
470x88E5 with a keyparses on
48three stacked VLAN tags, MAX_TAGS 2stopped_by_budget, not protection

Group 6 — telemetry and conformance (10).

#ScenarioExpect
49a frame failing FCS and ICVonly c_fcs_failures moves
50a frame failing ICV aloneonly c_icv_failures moves
5110⁻⁶ frame error rate, no attackerfcs_fail_ppm = 1; icv_fail_ppm = 0
52protection enabled, no keyclaim_is_supported low; no alarm
53a packet-number gap of 5c_pn_gaps +1; c_pn_frames_lost +5
54claims_integrity with 0 key bitsv_claim_without_key
55ICV checked before FCSv_icv_before_fcs
56merged countersv_counters_merged
57a 4-entry replay window, 8.13-frame skewv_replay_window_short
58all six checks clearconformant high

22. Debugging an Integrity Failure

Six symptoms, and the first question in every row is which mechanism reported this.

SymptomFirst questionWhere to look
an integrity alarm that tracks the link's error rateare the FCS and ICV counters merged?Section 15 — the else if is the fix
MACsec enabled and no protected framesdid key agreement complete?Section 16 — claims_integrity against c_protected_frames
one aggregation member carrying everythingdoes the hash fall back when no tuple is visible?Section 12 — a protected link is 100% non-IP to a classifier
replay alarms on a multi-path fabricis the window smaller than the path skew?Section 15 — 8.13 frames at 100 Gb/s and 1 µs
a parse that stops on some frames onlybudget or protection?Section 13 — two bits, two meanings
a passing frame whose contents are wrongis there an ICV at all?Section 21's directed test — the FCS cannot report this

Row six is the one that has no diagnostic procedure and that is the point of the chapter. A frame with a compensated check produces no evidence anywhere, and the investigation that would find it does not exist — which is why the answer is a mechanism rather than a counter.


23. Misconceptions

Misconception 1 — "a valid FCS means the frame was not altered."

The wrong model: the check covers the frame, so a frame that passes it is the frame that was sent.

What it costs: an integrity claim with nothing behind it. Section 4: FCS(m ⊕ d) = FCS(m) ⊕ R₀(d), so the compensating value is one XOR of quantities that are all public — the polynomial is in the standard, the frame is on the wire, and the original check value is its last four octets.

The corrected model: a passing check is evidence that the covered octets arrived as they left the last append pointChapter 5.8 §7's exact statement, which is about a hop and about noise. It is not evidence about origin, intent or authorship.

Misconception 2 — "a stronger CRC would fix it."

The wrong model: 32 bits is not many; a 64-bit check would resist alteration.

What it costs: a design that spends area on a wider detector and believes it bought protection. Section 2: linearity is a consequence of the mechanism being polynomial division, and it holds at every width. A 64-bit CRC's residual against a chosen difference is 1, exactly as a 32-bit one's is.

The corrected model: width changes the residual against noise and nothing else. Section 9's widening_helps is low whenever the threat is a chosen difference, and only_a_key_helps is the output that names the remedy.

Misconception 3 — "several checks are harder to defeat than one."

The wrong model: a length field, a payload checksum and a sequence number together make alteration impractical.

What it costs: linear work presented as multiplied protection. Section 9: compensation_steps equals the number of public checks. Three public checks cost three compensations to a party who read the same specification the design did.

The corrected model: checks do not accumulate; secrets do. The question at a design review is not how many checks a frame carries but whether any of them depends on something only the sender and receiver know.

Misconception 4 — "the residual is one in four billion, so the risk is negligible."

The wrong model: 2⁻³² is small, therefore the mechanism is adequate.

What it costs: a correct number applied to the wrong distribution. Section 6: at a 10⁻¹² frame error rate an undetected error arrives about once every 16.7 million years — enormous overkill against noise. Section 8: against a chosen difference the residual is 1, and the two numbers are not points on a scale.

The corrected model: Chapter 6.1 §6's residual assumes a corrupted frame's value is effectively arbitrary. An adversary's entire contribution is to make that assumption false, after which the number computed from it has no content at all.

Misconception 5 — "MACsec is too expensive."

The wrong model: cryptography at line rate is prohibitive in silicon.

What it costs: a decision made on the wrong column. Section 10: 407 200 GE at 400 Gb/s — 6.92× a CRC engine, or 5.07× with a compact S-box — which is a few hundredths of a square millimetre at a modern node, against a switch that spends 91% of its die on a buffer.

The corrected model: the gates are the affordable part. The real costs are the wire overhead at small frame sizes — 28.57% at 64 octets against 1.56% at 1 518 — and the key management, the hop-by-hop scope, and the counter hygiene that Section 10's callout lists.

Misconception 6 — "MACsec makes the network opaque, so we cannot use it."

The wrong model: protection and observability are mutually exclusive.

What it costs: rejecting the mechanism for a reason that is a configuration choice. Section 11: 802.1AE permits integrity WITHOUT confidentiality, the gate cost is identical, and every tap, analyser and classifier on the path keeps working — Chapter 21.9's entire apparatus continues to function.

The corrected model: the choice is an observability decision presented as a security level. And Section 13's production note runs the argument the other way: the packet number is in the clear on every protected frame, so a protected link is the only link in the fabric that carries the attributable loss counter Chapter 21.6 §13 established does not exist.


24. Interview Questions

Six, with what a strong answer contains.

1. Does a valid frame check sequence tell you the frame was not altered?

No, and the reason is one algebraic property. CRC-32 is polynomial division over GF(2), so it is linear: R(a ⊕ b) = R(a) ⊕ R(b). A strong answer gives the consequence rather than the identity: a frame m changed to m ⊕ d has check value FCS(m) ⊕ R₀(d), so the compensating value depends on the difference alone — and the polynomial, the frame and the original check value are all public. The best answers say what a passing check IS evidence of: the covered octets arrived as they left the last append point, one hop.

2. Would a 64-bit CRC be better?

Against noise, yes; against a chosen difference, not at all. Linearity is a property of polynomial division, not of this polynomial, so a 64-bit CRC compensates exactly as easily. A strong answer names the distinction: width changes the residual against an arbitrary difference and does nothing about a difference somebody selected, because the mechanism has no secret at any width.

3. What does the CRC actually guarantee?

Chapter 6.1 §5's list with its bounds: every burst up to 32 bits, every pattern of weight 3 at any frame length, weight 4 up to about 375 octets, and a residual of about 2⁻³² for everything else. A strong answer puts the residual in context — at a 10⁻¹² frame error rate that is one undetected error every 16.7 million years — and then makes the sharp point: every clause is a statement about the difference's shape, and none is a statement about its provenance.

4. How big is the gap to a real integrity mechanism?

It is not a gap on a scale. CRC's residual against a chosen difference is 1; GCM's forgery bound is 2⁻¹²⁸. A strong answer distinguishes that from the ratio people quote: 2⁻³² against 2⁻¹²⁸ is 2⁹⁶, and that compares two mechanisms each doing its own job — a detector against noise and an integrity check against an adversary. The 1 is the number that matters and no amount of widening moves it.

5. What does MACsec cost?

Three things in three units. Gates: 407 200 GE at 400 Gb/s against a 58 873-GE CRC engine — 6.92×. Wire: 24 or 32 octets, which is 28.57% at a 64-octet frame and 1.56% at 1 518. State: 288 bits per secure association — 73 728 BCE at four associations across 64 ports. A strong answer adds that the gates are the easy part and names what is not: key agreement, hop-by-hop scope, and separating the FCS and ICV counters so the integrity alarm stays actionable.

6. Why does a MACsec deployment keep the FCS at all?

Triage. The FCS is verified first and discards frames the channel corrupted before the cryptographic engine touches them. A strong answer explains why the order is load-bearing: verifying the ICV on a noisy frame fails correctly and indistinguishably from an attack, so a merged counter tracks the link's error rate and the alarm is disabled within a month. The cheap check's job in a protected deployment is to keep the expensive check's failure signal clean, which is the same relationship Chapter 7.4's address filter has with a host CPU.


25. Questions and Answers


26. What's Next

Three myths down and three to go, and the next one is a conservation argument rather than an algebraic one.

Chapter 25.4 takes "VLANs improve performance automatically". The kill is arithmetic that fits on one line: segmentation moves no bits. A switch with aggregate capacity C offered load L has the same C and the same L after it is divided into V VLANs — and the tag adds four octets, so the only bandwidth effect a VLAN has is negative: 4.76% of the wire at minimum frame size and 0.044% at jumbo.

What a VLAN does change is Chapter 12.4's flood width, and that chapter's amplification arithmetic gives the number: on a 64-port switch, one VLAN floods to 63 ports and eight VLANs of eight ports flood to 7 — 11.1% of the unsegmented width. That is a real and large improvement in a specific quantity.

And the chapter's uncomfortable result is about the trunk. Chapter 12.4 §10's domain arithmetic says each station's broadcast burden falls with the domain size — 315 frames per second at one VLAN, 35 at eightbut the trunk carrying all eight VLANs sees every one of them, and the total is 320 per second regardless of how the ports are divided. Segmentation redistributes a load it does not reduce, which is conservation again, one layer up.

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.