Skip to content
VLSI Mentor

Ethernet · Module 19

Integrating the CRC Engine

The 512-bit CRC matrix is 8 512 XOR terms rather than the dense bound's 16 384, and a beat that holds two frames needs two accumulators — which linearity caps at two, not three.

Chapter 6.4 built a parallel CRC-32 engine and settled its structure. This chapter integrates one at 512 bits into a MAC whose beats do not respect frame boundaries, and the integration breaks three of that chapter's assumptions.

Chapter 19.1 §9 named this block THE critical path of the MAC — the deepest logic between registers at every datapath width above about 64 bits. Chapter 19.1 §3 then made its input harder: above a 128-bit datapath a beat routinely carries the end of one frame and the start of the next, so the engine's input is not "the next 64 octets of a frame" but "64 octets that may belong to two frames."

Chapter 6.4's enginewhat this chapter needs
datapath64 bits512
partial-word residues864
frames per beatoneone or two
terminating check valuethe FCSFCS or Chapter 17.3's mCRC
equivalence referencea bit-serial enginethe same, and the trap is unchanged

Row three is the structural one and rows two and four are the expensive ones.

And there is a fourth thing this chapter owes. Chapter 19.1 §18 estimated the MAC's CRC logic at 32 768 XOR terms — two dense 32 × 512 matrices, one per direction — and Chapter 19.3 §18 promised this chapter would confirm or correct it. Section 2 generates the matrix and counts it; Section 19 assembles the total. The estimate is wrong three times: each matrix is 8 512 terms rather than 16 384, each direction needs two of them rather than one, and the corrections that make a partial final word work were not counted at all. The first two nearly cancel — 34 048 against 32 768, within 4%. The third does not: the real total is 39 445, 20.4% above the estimate, and the path to it is the chapter.


1. Scope, and Three Assumptions That Break at 512 Bits

Chapter 6.4 owns the transformation from the serial definition to a wide next-state function. Nothing in this chapter changes that: the matrix is still generated from the polynomial by running the serial step on unit vectors, the RTL is still a fixed exclusive-or network, and the equivalence check is still against an independently written bit-serial reference.

What changes is everything around the matrix.

AssumptionTrue at 64 bitsAt 512 bits
a beat belongs to one frameyes below 128 bitsNO — Chapter 19.1 §3
the partial word has 8 casesyes64
a directed length sweep is cheap8 residues64, and round numbers cover 3
the engine finishes a frame per invocationyesone invocation may finish one and start another

Row one is the chapter's spine. A beat carrying the end of frame n and the start of frame n+1 must advance frame n's accumulator over the octets that belong to it and initialise frame n+1's over the octets that belong to thatin the same cycle, because Chapter 19.1 §4 gives the MAC 1.312 cycles per minimum-size frame and there is no second cycle to borrow.

Row two is the chapter's largest single cost and it has a surprising answer. Sixty-four residues suggests sixty-four matrices; Section 6 shows that sixty-four matrices cost 26.5 times what the right structure costs, and the right structure is one full-width engine and a six-stage correction barrel.

Row three is the chapter's verification finding. Chapter 6.4 §12 already argued that a length sweep must be constructed rather than sampled. At 512 bits the argument gets eight times stronger: a test list of 64, 128, 256, 512, 1 024, 1 500 and 1 518 octets covers three of the sixty-four residue classes, which is 4.7%.

What this chapter owns: the actual term count at 512 bits, the partial-word structure and why the correction goes after rather than before, the two-accumulator split and the linearity result that caps it at two, the residue classifier with Chapter 17.3's second constant, the equivalence check and its unchanged trap, and the timing at 195.3125 MHz.

What it does not own: the polynomial, the reflection conventions and the residue's value — Chapter 6.2 and Chapter 6.3and the decision to preempt, which is Chapter 17.3's and arrives here as an input.

What this chapter does not build: the FIFOs are Chapter 19.5, the memory interface is Chapter 19.6, and the statistics counters are Chapter 19.7.


2. The 512-Bit Matrix, Actually Counted

The parallel cyclic redundancy check engine's next state is the state response matrix F applied to the current state, exclusive-ored with the data response matrix H applied to the beat. F is thirty two by thirty two at every datapath width and H is thirty two by the width. At five hundred and twelve bits the two together occupy seventeen thousand four hundred and eight cells, of which the sixteen thousand three hundred and eighty four belonging to H are what Chapter 19.1 quoted as a term count. A cell is a term only when it holds a one, and over the field of two elements the generated matrix is about half ones: eight thousand five hundred and twelve terms, which is fifty one point nine five percent of the dense bound. The consequence is not only area. The worst output bit is the parity of two hundred and eighty eight inputs rather than five hundred and twelve, so a balanced exclusive-or tree is nine levels deep rather than ten, and at one hundred and ninety five point three one two five megahertz one level is five hundred and sixty nine picoseconds of a five point one two nanosecond period. The way to obtain the real figure is to run the serial step on thirty two state unit vectors and five hundred and twelve data unit vectors and count the ones, which takes seconds.The polynomial0x04C11DB7Run the serial stepon 32 + 512 unit vectorsDense bound32 x 512 = 16 384 cellsGenerated andcounted8 512 ones — 51.95%Worst bit 512depth 10Worst bit 288depth 9One level is 569 ps11.1% of the periodF is 32 columns523 terms at any width12
Figure 1 — the matrix has 16 384 cells and 8 512 of them are ones, and the difference is one level of logic.

Chapter 19.1 §9's table gave the matrix size, not its term count, and the distinction is the whole of this section.

The next-state function is Chapter 6.4 §2's:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
next_state = F · current_state  XOR  H · input_data

F is 32 × 32 and H is 32 × 512, so the dense bound is 32 × 32 plus 32 × 512 — 17 408 cells, of which Chapter 19.1 §9 quoted the 16 384 belonging to H. A cell is a term only if it is a one, and over GF(2) the matrices are roughly half zeros.

Generated from the polynomial and counted:

WidthTotal XOR termsWorst output bitBalanced tree depthTerms per input bit
825214431.5
1644620527.9
3290434628.3
641 42252622.2
1282 55089719.9
2564 456157817.4
5128 512288916.6

The first four rows are Chapter 6.4 §5's table and they match exactly, which is the check that the generator used here is the same generator.

Three readings.

The dense bound overstates by 92.5%. The matrix has 8 512 nonzero cells out of 16 38451.95%so Chapter 19.1 §9's figure is an upper bound quoted as a count, and a floorplan built from it reserves nearly twice the area the engine needs.

The depth is nine, not ten. The worst output bit is the parity of 288 inputs, and ceil(log2(288)) is 9. Chapter 19.1 §9's "~10 levels" came from the dense bound's 512 terms, and one level of XOR at 195.3125 MHz is 569 ps of budget — a level saved is not nothing.

And terms per input bit keeps falling. 22.2 at 64 bits, 16.6 at 512Chapter 6.4 §5's amortisation of F continuing past the width that chapter stopped at. The 512-bit engine is 5.99 times the 64-bit engine's area for eight times the throughput.

64-bit engine512-bit engineRatio
terms1 4228 5125.99×
throughput
depth69+3 levels
area per Gb/s1.000.75−25%

Row four is the result that makes wide datapaths worth building and it is the opposite of the usual intuition about parallelism. The engine gets cheaper per bit as it gets wider, because F is paid once at every width.

The cost is the three levels of depth in row three, and Section 17 is whether nine levels fit.


3. RTL 1 — The Matrix Core

The core is Chapter 6.4 §6's engine parameterised to 512 bits, with one addition: it consumes a count of valid octets rather than a full beat.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// crcint_pkg -- shared types for the MAC's CRC integration. Section 3.
//
// The engine itself is Chapter 6.4's. What this package adds is the
// vocabulary the MAC needs around it: a beat that may hold two frames,
// a valid-octet count with 64 cases rather than 8, and a terminating
// check value that may be an FCS or Chapter 17.3's mCRC.
// ---------------------------------------------------------------------
package crcint_pkg;

  localparam int DATA_B      = 64;              // octets per beat
  localparam int DATA_W      = DATA_B * 8;      // 512
  localparam int CRC_W       = 32;

  localparam logic [31:0] CRC_POLY   = 32'h04C1_1DB7;
  localparam logic [31:0] CRC_INIT   = 32'hFFFF_FFFF;
  localparam logic [31:0] CRC_XOROUT = 32'hFFFF_FFFF;

  // Chapter 6.3's residue, and Chapter 17.3 Section 8's second one. A
  // fragment terminated by an mCRC -- the bitwise complement of an FCS
  // -- yields the complement of the residue, which is why one engine
  // distinguishes three cases and not two.
  localparam logic [31:0] FCS_RESIDUE  = 32'hC704_DD7B;
  localparam logic [31:0] MCRC_RESIDUE = 32'h38FB_2284;

  // Which accumulator a beat's octets belong to. Chapter 19.1 Section 3:
  // above a 128-bit datapath a beat can hold the tail of one frame and
  // the head of the next, and both must advance in the same cycle.
  typedef struct packed {
    logic [DATA_W-1:0]        data;
    logic [$clog2(DATA_B+1)-1:0] head_octets;  // belong to the OLD frame
    logic [$clog2(DATA_B+1)-1:0] tail_octets;  // belong to the NEW frame
    logic                     ends_frame;      // an EOF is in this beat
    logic                     starts_frame;    // an SOF is in this beat
    logic                     is_fragment;     // terminate with an mCRC
  } crc_beat_t;

  typedef enum logic [1:0] {
    RES_FCS_OK   = 2'd0,   // C704DD7B -- a complete, valid frame
    RES_MCRC_OK  = 2'd1,   // 38FB2284 -- a valid preemption fragment
    RES_BAD      = 2'd2    // neither -- corruption
  } residue_class_e;

endpackage
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// crc_matrix_core -- one 512-bit CRC-32 step. Section 3.
//
// Chapter 6.4 Section 5's engine, widened. The matrices are elaboration-time
// functions rather than checked-in data, so there is nothing for a merge
// to corrupt and no generated file to keep in step with the polynomial.
//
// The engine consumes VALID_B octets, where VALID_B is a runtime input
// with 64 legal values. It does this WITHOUT 64 matrices -- see
// Section 5 for the masker and Section 6 for why the correction goes
// after the network rather than before it.
// ---------------------------------------------------------------------
module crc_matrix_core
  import crcint_pkg::*;
#(
  parameter int W = DATA_W                      // 512
) (
  input  logic                 clk,
  input  logic                 rst_n,

  input  logic                 step_en,
  input  logic [W-1:0]         data_in,         // already masked
  input  logic [CRC_W-1:0]     state_in,
  output logic [CRC_W-1:0]     state_out,

  // Observability. Section 15 reads these.
  output logic [31:0]          c_steps
);

  // -------------------------------------------------------------------
  // The matrices, by superposition on unit vectors. This IS Chapter 6.4
  // Section 4's construction; nothing here is specific to 512 bits except
  // the loop bound, which is the point of generating rather than copying.
  // -------------------------------------------------------------------
  function automatic logic [CRC_W-1:0] serial_step
    (input logic [CRC_W-1:0] s, input logic b);
    logic top;
    begin
      top = s[CRC_W-1] ^ b;
      serial_step = {s[CRC_W-2:0], 1'b0} ^ (top ? CRC_POLY : '0);
    end
  endfunction

  function automatic logic [CRC_W-1:0] advance
    (input logic [CRC_W-1:0] s, input logic [W-1:0] d);
    logic [CRC_W-1:0] acc;
    begin
      acc = s;
      for (int i = W-1; i >= 0; i--) acc = serial_step(acc, d[i]);
      advance = acc;
    end
  endfunction

  // F: response to each state bit with zero data. 32 columns at every
  // width, which is why terms per input bit FALLS as W rises -- 22.2 at
  // 64 bits, 16.6 at 512. Chapter 6.4 Section 8.
  function automatic logic [CRC_W-1:0] F_col(input int i);
    F_col = advance(CRC_W'(1) << i, '0);
  endfunction

  // H: response to each data bit with zero state. W columns.
  function automatic logic [CRC_W-1:0] H_col(input int i);
    H_col = advance('0, W'(1) << i);
  endfunction

  // The whole next state, by superposition. Constant-folded at
  // elaboration into a fixed XOR network: 8 512 terms at W = 512, worst
  // output bit 288 terms, balanced tree depth 9. Section 2.
  function automatic logic [CRC_W-1:0] next_state
    (input logic [CRC_W-1:0] s, input logic [W-1:0] d);
    logic [CRC_W-1:0] acc;
    begin
      acc = '0;
      for (int i = 0; i < CRC_W; i++) if (s[i]) acc ^= F_col(i);
      for (int i = 0; i < W;     i++) if (d[i]) acc ^= H_col(i);
      next_state = acc;
    end
  endfunction

  assign state_out = step_en ? next_state(state_in, data_in) : state_in;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n)        c_steps <= '0;
    else if (step_en)  c_steps <= c_steps + 1;
  end

  // The network is combinational and unregistered on purpose: Section 8's
  // accumulator owns the register, because a dual-frame beat needs TWO
  // registers fed from two instances of this network.
  `ifdef FORMAL
  // A zero beat from a zero state stays zero -- the linearity check that
  // catches a mis-specified XOR_OUT folded into the wrong place.
  a_linear_zero: assert property (@(posedge clk) disable iff (!rst_n)
    (step_en && data_in == '0 && state_in == '0) |-> state_out == '0);
  `endif

endmodule

Classification: the chapter's datapath primitive, and the only block in it that is pure combinational logic.

What it teaches: that widening Chapter 6.4's engine costs a loop bound and nothing else. The functions are unchanged; W goes from 64 to 512; the synthesiser constant-folds 8 512 terms instead of 1 422. That is the payoff of generating the matrix rather than checking in a table — a 512-bit table would be 16 384 entries somebody has to trust.

And it teaches that the register belongs outside. Chapter 6.4 §5's engine registered its own state because it served one frame. A dual-frame beat needs two states advanced in the same cycle from two masked views of the same data, so this core is a function and Section 8 owns the flops. A design that registers inside the core needs two cores to hold two states and cannot share anything between them — which is the structure Section 9 shows is forced anyway, but for a different reason.

Deliberately simplified: data_in is assumed already masked, so the core has no notion of validity and a caller that forgets the mask gets a silently wrong answer rather than an error. The advance function is written as a bit loop for readability and a production version uses the packed-matrix form to keep elaboration time sane at W = 512 — 32 + 512 evaluations of a 512-iteration loop is 278 528 serial steps at elaboration, which is seconds rather than milliseconds. And c_steps counts beats, not frames, which Section 15 has to divide.

Production implication: the FORMAL property is the cheapest sanity check in the chapter and it catches a real class of bug. A zero beat from a zero state must stay zero because the function is linear over GF(2) with no affine term; an engine that folds CRC_INIT or CRC_XOROUT into the network rather than into the accumulator's reset and output breaks it, and that bug otherwise shows up as an off-by-one-frame check value that only the equivalence checker finds. Chapter 6.2 §8's convention errors are the same family, and this is the one-line version.


4. The Final Partial Word at Sixty-Four Residues

A frame's covered range — Chapter 5.8's everything-except-the-FCS — is rarely a multiple of the datapath width.

Chapter 6.4 §8 handled this at 64 bits with eight cases. At 512 bits there are sixty-four, and the jump is not merely a bigger mux.

DatapathResidue classesA minimum frame's residueA maximum frame's
64 bits860 mod 8 = 41 514 mod 8 = 2
512 bits6460 mod 64 = 601 514 mod 64 = 42

The naive structure is sixty-four engines, one per valid-octet count, selected by a mux. Generate them and count the terms:

StructureXOR terms
64 width-specific matrices, widths 8 to 512289 731
one 512-bit engine8 512
one 512-bit engine plus a correction barrel10 949

The naive structure is 26.5 times the right one, and Section 6 derives the right one. This section is why the residue matters at all, which is a different question and it is about verification rather than area.

Every one of the sixty-four classes is a distinct sub-network in the naive structure and a distinct correction in the right one. A bug in one class affects one residue and is invisible at the other sixty-three.

So the question is what a test suite covers, and the answer is embarrassing.

Frame size on the wireCovered rangeResidue mod 64
646060
12812460
25625260
51250860
1 0241 02060
1 5001 49624
1 5181 51442

Five of the seven land on the same residue, because every power of two above 64 is congruent to 0 mod 64 and the four-octet FCS subtracts the same 4 from all of them. The seven classic sizes cover three classes out of sixty-four — 4.7%.

Chapter 6.4 §12 made this argument at 64 bits, where the same seven sizes land on residues 4, 4, 4, 4, 4, 0 and 2 — three classes of eight. The coverage fraction therefore falls from 37.5% to 4.7% while the number of classes actually reached stays at three, which is the sharper way to say it: widening the datapath multiplied the classes by eight and covered not one more of them.

64-bit datapath512-bit datapath
classes864
covered by the classic seven3 — 37.5%3 — 4.7%
what fixes ita few extra sizesa constructed sweep — Section 21

Row two is the reason Section 14's equivalence checker reports residues_covered as sixty-four bits and Section 16 fails a run that has not filled it.


5. RTL 2 — The Partial-Word Masker

The masker turns "consume j octets" into "consume 64 octets, then undo 64 − j of them."

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// partial_word_masker -- consume j of 64 octets with ONE matrix.
// Sections 4 and 6.
//
// The beat is masked so the octets that do not belong to this frame are
// zero, and the full-width network of Section 3 runs over all 512 bits.
// The trailing zero octets advance the state by 8*(64-j) bit-steps that
// should not have happened, so the state is multiplied by the INVERSE
// of F raised to that power. The inverse exists because F is invertible
// over GF(2) -- the serial step is a bijection on 32-bit states.
//
// The correction is a six-stage barrel, one stage per bit of (64-j),
// each stage a 32x32 GF(2) matrix with a bypass. Total 2 437 XOR terms
// against the 289 731 that 64 width-specific matrices would cost.
//
// The barrel is REGISTERED between stages. It runs once per FRAME, not
// once per beat, so its latency is free and its depth never reaches the
// critical path -- which is the opposite of Chapter 19.3 Section 8's
// deficit, where the recurrence forbade exactly this.
// ---------------------------------------------------------------------
module partial_word_masker
  import crcint_pkg::*;
#(
  parameter int STAGES = 6                        // log2(DATA_B)
) (
  input  logic                    clk,
  input  logic                    rst_n,

  input  logic                    in_valid,
  input  logic [DATA_W-1:0]       beat_data,
  input  logic [$clog2(DATA_B+1)-1:0] valid_octets,   // j, 1..64
  input  logic [CRC_W-1:0]        state_in,

  output logic [DATA_W-1:0]       masked_data,        // to the core
  input  logic [CRC_W-1:0]        core_state,         // from the core

  output logic                    out_valid,
  output logic [CRC_W-1:0]        state_out,          // corrected

  // Observability. Sections 15 and 16.
  output logic [63:0]             residues_seen,
  output logic [31:0]             c_partial_words,
  output logic                    illegal_count
);

  // -------------------------------------------------------------------
  // The mask. Octet 0 of the frame sits in the most significant octet of
  // the beat, which is Chapter 6.2's MSB-first convention arriving as a
  // bit-ordering constraint on the datapath.
  // -------------------------------------------------------------------
  always_comb begin
    masked_data = '0;
    for (int b = 0; b < DATA_B; b++)
      if (b < int'(valid_octets))
        masked_data[DATA_W-1 - b*8 -: 8] = beat_data[DATA_W-1 - b*8 -: 8];
  end

  // -------------------------------------------------------------------
  // The correction barrel. Stage s undoes 2^s octets of zero advance
  // when bit s of (DATA_B - j) is set.
  // -------------------------------------------------------------------
  function automatic logic [CRC_W-1:0] serial_step
    (input logic [CRC_W-1:0] s, input logic b);
    logic top;
    begin
      top = s[CRC_W-1] ^ b;
      serial_step = {s[CRC_W-2:0], 1'b0} ^ (top ? CRC_POLY : '0);
    end
  endfunction

  // F raised to 8*m, as columns. Elaboration-time, like Section 3's.
  function automatic logic [CRC_W-1:0] Fpow_col(input int m, input int i);
    logic [CRC_W-1:0] acc;
    begin
      acc = CRC_W'(1) << i;
      for (int k = 0; k < 8*m; k++) acc = serial_step(acc, 1'b0);
      Fpow_col = acc;
    end
  endfunction

  // Its inverse, by Gauss-Jordan at elaboration. 160, 274, 487, 516,
  // 507 and 493 terms for m = 1, 2, 4, 8, 16 and 32.
  function automatic logic [CRC_W-1:0] Finv_col(input int m, input int i);
    logic [CRC_W-1:0] M [CRC_W];
    logic [CRC_W-1:0] row [CRC_W];
    logic [CRC_W-1:0] aug [CRC_W];
    int p;
    begin
      for (int c = 0; c < CRC_W; c++) M[c] = Fpow_col(m, c);
      for (int r = 0; r < CRC_W; r++) begin
        row[r] = '0; aug[r] = CRC_W'(1) << r;
        for (int c = 0; c < CRC_W; c++) if (M[c][r]) row[r] |= (CRC_W'(1) << c);
      end
      for (int c = 0; c < CRC_W; c++) begin
        p = c;
        for (int r = c; r < CRC_W; r++) if (row[r][c]) begin p = r; break; end
        {row[c], row[p]} = {row[p], row[c]};
        {aug[c], aug[p]} = {aug[p], aug[c]};
        for (int r = 0; r < CRC_W; r++)
          if (r != c && row[r][c]) begin row[r] ^= row[c]; aug[r] ^= aug[c]; end
      end
      Finv_col = '0;
      for (int r = 0; r < CRC_W; r++) if (aug[r][i]) Finv_col |= (CRC_W'(1) << r);
    end
  endfunction

  function automatic logic [CRC_W-1:0] apply_inv
    (input int m, input logic [CRC_W-1:0] s);
    logic [CRC_W-1:0] acc;
    begin
      acc = '0;
      for (int i = 0; i < CRC_W; i++) if (s[i]) acc ^= Finv_col(m, i);
      apply_inv = acc;
    end
  endfunction

  logic [CRC_W-1:0] pipe   [STAGES+1];
  logic [STAGES:0]  vpipe;
  logic [STAGES-1:0] sel_q [STAGES+1];
  logic [$clog2(DATA_B+1)-1:0] shortfall;

  assign shortfall = DATA_B[$clog2(DATA_B+1)-1:0] - valid_octets;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      for (int s = 0; s <= STAGES; s++) begin pipe[s] <= '0; sel_q[s] <= '0; end
      vpipe <= '0;
      residues_seen <= '0;
      c_partial_words <= '0;
      illegal_count <= 1'b0;
    end else begin
      pipe[0]  <= core_state;
      sel_q[0] <= shortfall[STAGES-1:0];
      vpipe[0] <= in_valid;

      for (int s = 0; s < STAGES; s++) begin
        pipe[s+1]  <= sel_q[s][s] ? apply_inv(1 << s, pipe[s]) : pipe[s];
        sel_q[s+1] <= sel_q[s];
        vpipe[s+1] <= vpipe[s];
      end

      if (in_valid) begin
        residues_seen[valid_octets % DATA_B] <= 1'b1;
        if (valid_octets != DATA_B[$clog2(DATA_B+1)-1:0])
          c_partial_words <= c_partial_words + 1;
        // j = 0 is not a partial word, it is a bug: a beat that belongs
        // to no frame should not reach the engine at all.
        if (valid_octets == '0) illegal_count <= 1'b1;
      end
    end
  end

  assign state_out = pipe[STAGES];
  assign out_valid = vpipe[STAGES];

endmodule

Classification: a datapath transform that trades six cycles of latency for a factor of 26.5 in area.

What it teaches: that the inverse exists, and that this is not an accident. The serial CRC step is a bijection on 32-bit states — it is a shift with a conditional XOR, and it can be run backwards — so F is invertible over GF(2) and so is every power of it. A design that believes the correction must go before the network (pre-multiplying state_in by the inverse) is computing the same thing, and it is worse, because Section 6's placement argument depends on where the latency can hide.

And it teaches the contrast with Chapter 19.3 §8 directly. That chapter had a three-bit accumulator that could not be pipelined at any cost, because frame n+1 depended on frame n. This barrel is thirty-four levels of logic and pipelines freely, because a frame's correction depends on nothing but that frame. Depth is not what decides pipelinability; the dependency graph isand a designer who sorts blocks by depth gets this pair exactly backwards.

Deliberately simplified: Finv_col runs Gauss-Jordan inside a function called from elaboration, which is legal and slow; production code computes the six inverses once into localparams. residues_seen indexes by valid_octets % DATA_B, so a full beat lands in bucket 0 and shares it with the illegal j = 0 — acceptable only because illegal_count separates them. And the pipeline has no backpressure: it assumes one correction per frame and 1.312 cycles per frame, which Section 18 states as an assumption and Section 16 checks.

Production implication: six cycles of latency at 1.312 cycles per minimum-size frame means up to 4.57 frames' corrections in flight, so the barrel needs Chapter 19.1 §6's tag travelling alongside — vpipe here is a placeholder for it. A design that assumes the correction returns before the next frame's does works in simulation at 1 Gb/s and reorders check values at 100. Chapter 19.2 §11 met the same requirement in the parser; this is the second block in Module 19 that needs it, which makes it a datapath-wide property rather than a parser quirk.


6. Correct Afterwards, Rather Than Choosing a Matrix

A frame's covered range is rarely a multiple of sixty four octets, so the engine's last beat carries j valid octets where j runs from one to sixty four. Three structures handle it. The first generates one network per octet count and selects among them with a sixty four way multiplexer: two hundred and eighty nine thousand seven hundred and thirty one exclusive-or terms, no extra cycles, and all of it on the per-beat critical path plus six more levels for the multiplexer. The second masks the invalid octets to zero, runs the full five hundred and twelve bit network, and afterwards multiplies the state by the inverse of F raised to eight times the shortfall: eight thousand five hundred and twelve terms for the network plus two thousand four hundred and thirty seven for a six stage correction barrel, and the barrel is pipelined and runs once per frame rather than once per beat, so its thirty four levels never appear against the beat clock. The third runs an eight bit engine repeatedly, which is only two hundred and fifty two terms but needs up to sixty three extra cycles, and Chapter 19.1 gives the media access controller one point three one two cycles per minimum size frame. The cycle budget therefore eliminates the cheapest option in gates, and the area comparison eliminates the fastest, leaving the middle structure at a factor of twenty six point five below the first.Consume j of 6464 legal values of j64 matrices289 731 termsMask and correct8 512 + 2 4378-bit engine,repeated252 termsPlus a 64-way muxon the critical pathBarrel runs perFRAME6 stages, off the beatpathUp to 63 extracyclesbudget is 1.31226.5x cheaper thanoption 1and feasible, unlikeoption 312
Figure 2 — three ways to consume j of 64 octets, and the cheapest one in gates is the one the cycle budget forbids.

Three structures consume j valid octets, and the difference between them is a factor of twenty-six.

Option 1 — sixty-four matrices. One generated network per octet count, selected by a 64-way mux on the 32-bit result.

Option 2 — one full-width network and a correction. Mask, run all 512 bits, undo the zero octets.

Option 3 — an 8-bit engine run repeatedly. Chapter 6.4 §4's 252-term network, up to 63 times.

Option 1Option 2Option 3
XOR terms289 73110 949252
extra cycles per frame06, pipelinedup to 63
on the critical path?yes — plus a 64-way muxNOyes
cycles per frame available1.3121.3121.312

Row four kills option 3 immediately and it is worth stating why it is tempting anyway: the 8-bit engine is the cheapest CRC network in this chapter by a factor of 34, and at 1 Gb/s with 13.1 cycles per minimum-size frame it fits. Chapter 19.1 §4's cycle budget is what forbids it, and the budget is a property of the line rate rather than of the design.

Row one kills option 1 on area and row three kills it again on timing. Sixty-four networks is 289 731 terms — 26.5 times option 2 — and the 64-way mux that selects among them is six more levels on the block Chapter 19.1 §9 already called the MAC's critical path.

Option 2's advantage is not its term count. It is where the terms sit.

Option 1Option 2
terms in the per-beat path289 7318 512
terms in the per-frame path02 437
depth in the per-beat path9 plus a 64-way mux9
depth in the per-frame path34, over 6 registers

Row two and row four are the structure. The correction runs once per frame, not once per beat — at minimum size that is one invocation per 1.312 beats and at maximum size one per 24so its thirty-four levels are spread over six cycles and never appear in a timing report against the beat clock.

And this is the general habit worth taking from the section. A transform that applies once per object can be pipelined to any depth the latency budget allows; a transform that applies once per beat cannot. Sorting the engine's work into those two bins is what makes a 512-bit CRC close timing at all:

WorkRateDepth budget
the matrix stepevery beat9 levels, 5.12 ns
the correctionevery frame34 levels, 6 cycles
the residue compareevery frameSection 10 — 2 levels
the mCRC selectevery frameSection 12 — 1 level

Three of the four rows are per-frame work, and all three would have been impossible inside the beat. The engine is fast because only one thing in it runs at beat rate.


7. The Dual-Frame Beat, Counted

Chapter 19.1 §3 established that a beat can hold two frames and counted it with c_dual_frame_beats. This section puts a number on how often, because the number decides whether the second accumulator is a corner case or the common case.

A frame occupies 8 + L + gap octets of wire timeChapter 19.3 §6's mean gap of 12 and Chapter 5.1's preamble of 8 — and a beat is 64. A beat is a dual-frame beat when it contains both an end and a start.

Frame sizeWire periodDual-frame beatsAs a fraction
6484 octets52.38%11 / 21
12814827.03%10 / 37
25627614.49%10 / 69
5125327.52%10 / 133
1 5181 5382.73%21 / 769

Row one is the case the MAC is specified against, and it says that more than half of all beats carry two frames at the size Chapter 19.1 §4's cycle budget was derived from. The second accumulator is not a corner case. It is the majority case.

Row five is why the bug survives to silicon. At 1 518 octets the dual-frame beat happens on 2.73% of beats, so a design with a broken second accumulator passes a jumbo-frame soak test and fails on the first minute of small-packet traffic. Chapter 19.1 §14's dual_frame_untested verdict exists for this, and this is the second block in Module 19 that needs it.

And there is a second question the table does not answer: can a beat hold two ends?

No, and the reason is a bound rather than a convention. Two ends in one beat requires a frame's entire wire period to fit inside 64 octets, and the minimum period is 84Chapter 5.9's floor of 9 plus the preamble's 8 plus the 64-octet minimum frame gives 81 at the absolute floor.

Octets
minimum frame64
preamble and SFD8
minimum legal gap9
total, absolute floor81
a beat64

Eighty-one exceeds sixty-four, so two ends in one beat is impossibleand that is what makes two accumulators sufficient rather than merely convenient. A design that provisions three has not done this arithmetic; one that provisions two has, and Section 20's properties can assert the bound because it is a consequence of Chapter 5.9's floor and Chapter 1.2's 64-octet minimum rather than an assumption about traffic.


8. RTL 3 — The Dual Accumulator

Two states, two masked views of one beat, two instances of Section 3's network — and a linearity result that says two is also the maximum.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// dual_accumulator -- advance two frames' CRC state from one beat.
// Sections 7, 8 and 9.
//
// A dual-frame beat holds the tail of the OLD frame in its leading
// head_octets and the head of the NEW frame in the remaining
// tail_octets, with head_octets + tail_octets == DATA_B. Section 7:
// this is 52.38% of beats at minimum frame size, so it is the common
// case and not a corner.
//
// Two masked full-width networks. Section 9 proves that two is also
// the CEILING: any two of {head CRC, tail CRC, whole-beat CRC} give
// the third by XOR, because the masks are complementary and the
// network is linear over GF(2).
// ---------------------------------------------------------------------
module dual_accumulator
  import crcint_pkg::*;
(
  input  logic              clk,
  input  logic              rst_n,

  input  logic              beat_valid,
  input  crc_beat_t         beat,

  // The frame currently being finished, and the one being started.
  output logic [CRC_W-1:0]  state_old,
  output logic [CRC_W-1:0]  state_new,
  output logic              old_complete,      // state_old is final
  output logic              new_started,

  // Observability. Sections 15 and 16.
  output logic [31:0]       c_beats,
  output logic [31:0]       c_dual_beats,
  output logic [31:0]       c_frames,
  output logic              two_ends_in_a_beat // must never fire -- Section 7
);

  logic [DATA_W-1:0] head_masked, tail_masked;
  logic [CRC_W-1:0]  head_next,   tail_next;
  logic [CRC_W-1:0]  acc_old_q,   acc_new_q;

  // The two masks. head_octets belong to the frame already in flight;
  // tail_octets to the frame that starts in this beat.
  always_comb begin
    head_masked = '0;
    tail_masked = '0;
    for (int b = 0; b < DATA_B; b++) begin
      if (b < int'(beat.head_octets))
        head_masked[DATA_W-1 - b*8 -: 8] = beat.data[DATA_W-1 - b*8 -: 8];
      else if (b < int'(beat.head_octets) + int'(beat.tail_octets))
        tail_masked[DATA_W-1 - b*8 -: 8] = beat.data[DATA_W-1 - b*8 -: 8];
    end
  end

  crc_matrix_core #(.W(DATA_W)) u_old (
    .clk(clk), .rst_n(rst_n),
    .step_en(beat_valid && beat.head_octets != '0),
    .data_in(head_masked),
    .state_in(acc_old_q),
    .state_out(head_next),
    .c_steps()
  );

  crc_matrix_core #(.W(DATA_W)) u_new (
    .clk(clk), .rst_n(rst_n),
    .step_en(beat_valid && beat.tail_octets != '0),
    // A frame that STARTS in this beat begins from CRC_INIT; one that
    // merely continues carries its own state. Getting this wrong makes
    // every frame after the first wrong and the first one right, which
    // is the signature in Section 22's complaint 2.
    .data_in(tail_masked),
    .state_in(beat.starts_frame ? CRC_INIT : acc_new_q),
    .state_out(tail_next),
    .c_steps()
  );

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      acc_old_q <= CRC_INIT;
      acc_new_q <= CRC_INIT;
      old_complete <= 1'b0;
      new_started  <= 1'b0;
      c_beats <= '0; c_dual_beats <= '0; c_frames <= '0;
      two_ends_in_a_beat <= 1'b0;
    end else begin
      old_complete <= 1'b0;
      new_started  <= 1'b0;

      if (beat_valid) begin
        c_beats <= c_beats + 1;

        if (beat.head_octets != '0) acc_old_q <= head_next;
        if (beat.tail_octets != '0) acc_new_q <= tail_next;

        if (beat.ends_frame) begin
          old_complete <= 1'b1;
          c_frames     <= c_frames + 1;
        end

        if (beat.starts_frame) new_started <= 1'b1;

        if (beat.ends_frame && beat.starts_frame)
          c_dual_beats <= c_dual_beats + 1;

        // Section 7: the absolute floor on a frame's wire period is 81
        // octets and a beat is 64, so two ends cannot share a beat. If
        // the upstream framing says otherwise, the framing is broken and
        // this engine's results are meaningless from here on.
        if (beat.ends_frame && beat.head_octets == '0)
          two_ends_in_a_beat <= 1'b1;
      end
    end
  end

  assign state_old = head_next;
  assign state_new = tail_next;

endmodule

Classification: the chapter's structural block — the one that exists because of Chapter 19.1 §3 rather than because of CRC.

What it teaches: that the second accumulator's cost is a second network and nothing else. No arbitration, no stalling, no ordering logic — the two frames' octets are disjoint by construction, so the two computations are independent and run in the same cycle. The block is 17 024 XOR terms and about 70 flops, and the flops are the cheap part.

And it teaches where the initial state comes from, which is the bug this block is actually for. A frame that starts in a beat begins from CRC_INIT; one that merely continues carries its own state. A design that always carries gets the first frame right and every subsequent frame wrong, because frame n+1 inherits frame n's final state. That failure is 100% at minimum size and 2.73% at maximum, which is Section 7's table read as a bug-exposure rate.

Deliberately simplified: head_octets + tail_octets is assumed to equal DATA_B on a dual-frame beat, so the MAC's internal beat carries frame octets only and not the gap — a real datapath either does this or carries a per-octet valid mask, which costs 64 bits per beat instead of two counts. two_ends_in_a_beat is sticky and has no clear, because the condition is a framing failure and a design that recovers from it silently has hidden it. And the outputs are the combinational head_next and tail_next rather than the registered accumulators, so a consumer must sample them in the same cycle as old_complete.

Production implication: the two cores are the largest single item in the chapter's area budget — 17 024 of the engine's 19 461 XOR terms, 87.5%and Section 9 is the argument that they cannot be reduced to one. A design that tries to time-multiplex one core over two cycles has 1.312 cycles per frame and needs two, which is Chapter 19.1 §4's budget forbidding an optimisation for the second time in this chapter. The budget is doing more design work than the polynomial is.


9. Splitting One Beat Between Two Frames

Above a one hundred and twenty eight bit datapath a beat routinely carries the end of one frame and the start of the next, which at minimum frame size is fifty two point three eight percent of beats and at maximum frame size two point seven three percent. The beat is split by two complementary masks: the leading head octets belong to the frame already in flight and the remaining tail octets to the frame starting here, and together they cover all sixty four octets. Each mask feeds its own full width network, because a network evaluates one input and the two inputs differ. That makes two networks the floor. It is also the ceiling, because the network is linear over the field of two elements and the masks are complementary, so the exclusive-or of the head result and the tail result is the whole beat result. A design that also wants the whole beat check value for a datapath integrity monitor therefore needs thirty two exclusive-or gates rather than a third network, which saves eight thousand four hundred and eighty terms or thirty three point two percent of a three network structure. The three quantities span a two dimensional space, so knowing any two gives the third and knowing one gives nothing.One 64-octet beat52.38% dual at minsizeHead maskthe old frame's octetsTail maskthe new frame's octetsNetwork 18 512 termsNetwork 28 512 terms32 XOR gateshead XOR tailWhole-beat valuefree, by linearityA third network8 480 terms not builtTwo is the floorthe inputs differ12
Figure 3 — two accumulators is the floor because the octets differ, and the ceiling because the third value is their exclusive-or.

Section 8 built two networks. This section is the argument that two is both the floor and the ceiling, and the ceiling half is the interesting one.

Write D(d) for the data response of Section 3's network — the H · d half of Chapter 6.4 §3's next-state function. D is linear over GF(2), which is the property that made the matrix exist at all, and linearity has a consequence for masked inputs that is easy to miss.

On a dual-frame beat the two masks are complementary: d_head holds the old frame's octets and zeros elsewhere, d_tail holds the new frame's octets and zeros elsewhere, and d_head XOR d_tail is the whole beat — because the masks are disjoint and together cover all 64 octets.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
d = d_head XOR d_tail          (disjoint, complementary)
D(d) = D(d_head) XOR D(d_tail) (linearity)

So any two of the three determine the third by a 32-bit XOR.

WantNetworks neededPlus
head and tail2
head and whole232 XOR gates for the tail
tail and whole232 XOR gates for the head
all three232 XOR gates

Row four is the result. A design that wants the whole-beat check value as well — for a datapath integrity monitor, which is a reasonable thing to wantdoes not build a third network.

StructureXOR terms
three independent networks25 536
two networks and a 32-bit XOR17 056
saving8 480 terms — 33.2%

And the same argument says two cannot become one. The three quantities live in a two-dimensional space over GF(2): knowing one of them tells you nothing about the others, because d_head and d_tail are independent inputs. Two networks is the floor because the beat carries two independent frames' data, and the ceiling because the third quantity is their XOR.

This is worth separating from the intuition it contradicts. Linearity is usually reached for as an optimisation — "compute one thing, derive the rest" — and here it caps the structure rather than shrinking it. The chapter's two most expensive decisions both go the same way:

DecisionNaiveActualWhy
partial word64 networks1 plus a barrelSection 6 — 26.5×
dual frame1, time-shared21.312 cycles — no second cycle
whole-beat monitor3rd network32 XOR gatesthis section — linearity

Row two is the one that costs area and it is the one that cannot be argued away. Rows one and three both shrink; row two is forced up by Chapter 19.1 §4's cycle budget, which is the same constraint that killed Section 6's option 3.


10. RTL 4 — The Residue Classifier

Chapter 6.3 checks a frame by running the CRC over the data and its trailing check value and comparing against a constant. Chapter 17.3 §8 gave that check a second constant.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// residue_classifier -- three answers from one engine. Section 10.
//
// Chapter 6.3: running the CRC over the data AND its trailing four-octet
// check value yields C704DD7B for a correct frame. Chapter 17.3
// Section 8: an mCRC is the bitwise complement of an FCS, and
// complementing the check value complements the residue, so a valid
// preemption fragment yields 38FB2284.
//
// The cost of the second case is ONE comparator. Chapter 17.3 Section 6
// priced the alternative -- a second receive CRC engine -- at a 32 x 512
// matrix, which Section 2 now puts at 8 512 XOR terms.
// ---------------------------------------------------------------------
module residue_classifier
  import crcint_pkg::*;
(
  input  logic                clk,
  input  logic                rst_n,

  input  logic                in_valid,
  input  logic [CRC_W-1:0]    residue,
  input  logic                expect_fragment,  // Chapter 17.3's SMD said so

  output logic                out_valid,
  output residue_class_e      classification,
  output logic                mismatch_kind,    // fragment/frame disagreement

  // Observability. Sections 15 and 16.
  output logic [31:0]         c_fcs_ok,
  output logic [31:0]         c_mcrc_ok,
  output logic [31:0]         c_bad,
  output logic [31:0]         c_kind_mismatch
);

  logic is_fcs, is_mcrc;

  // Two comparators, two levels of logic, once per frame. Section 6's
  // table row three.
  assign is_fcs  = (residue == FCS_RESIDUE);
  assign is_mcrc = (residue == MCRC_RESIDUE);

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      out_valid      <= 1'b0;
      classification <= RES_BAD;
      mismatch_kind  <= 1'b0;
      c_fcs_ok <= '0; c_mcrc_ok <= '0; c_bad <= '0; c_kind_mismatch <= '0;
    end else begin
      out_valid     <= in_valid;
      mismatch_kind <= 1'b0;

      if (in_valid) begin
        if (is_fcs) begin
          classification <= RES_FCS_OK;
          c_fcs_ok <= c_fcs_ok + 1;
        end else if (is_mcrc) begin
          classification <= RES_MCRC_OK;
          c_mcrc_ok <= c_mcrc_ok + 1;
        end else begin
          classification <= RES_BAD;
          c_bad <= c_bad + 1;
        end

        // A unit whose framing said "fragment" and whose residue says
        // "complete frame" is NOT a pass. Both halves are checkable and
        // a design that reports OK on either alone has lost Chapter
        // 17.3's ability to distinguish a fragment from corruption.
        if (in_valid && (is_fcs || is_mcrc) && (is_mcrc != expect_fragment)) begin
          mismatch_kind   <= 1'b1;
          c_kind_mismatch <= c_kind_mismatch + 1;
        end
      end
    end
  end

endmodule

Classification: a decision block whose whole content is two constants and one cross-check.

What it teaches: that Chapter 17.3's complement trick costs one comparator at every datapath width. The alternative — a second engine computing fragment check values — is now priceable: 8 512 XOR terms at 512 bits, which is 87.5% of this chapter's per-beat logic for a feature that a comparator delivers. Chapter 17.3 §6 made this argument qualitatively; Section 2's generator makes it a number.

And it teaches that the two halves of the check must agree. The residue says what terminated the unit; Chapter 17.3 §5's start-of-mData delimiter says what the transmitter intended. A design that checks only the residue accepts a corrupted frame whose corruption happened to produce the mCRC residue, and one that checks only the delimiter accepts a fragment whose data is wrong. mismatch_kind is the conjunction, and it is the block's only non-obvious output.

Deliberately simplified: expect_fragment arrives as a single bit with no timing relationship stated, and a real design has to align it with the residue's arrival through Section 5's six-stage barrel — which is the tag requirement again. The classifier does not distinguish the two ways a residue can be wrong — a data error and a check-value error produce the same RES_BADbecause Chapter 6.3 §6 established that the residue cannot tell them apart. And c_kind_mismatch counts an event that should be impossible on a conformant link, so a non-zero value is a bug report rather than a statistic.

Production implication: the three counters are the ones a field engineer reads first, and their ratio is the diagnosis. c_bad rising with c_mcrc_ok at zero on a link that negotiated preemption means the receiver is not recognising fragmentsChapter 17.3 §12's reassembly is misconfigured, not the CRC. c_bad rising with both others healthy is a link problem. And c_kind_mismatch non-zero is neither: it is a framing bug inside this MAC, and Section 22's complaint 3 is that reading.


11. The mCRC, and What It Costs This Engine

Chapter 17.3 §6 established that a fragment carries an mCRC — the bitwise complement of an ordinary FCS — and Section 10 showed the receive side costs one comparator. The transmit side is not one inverter, and the reason is a second accumulator.

A preempted frame's check values are not the same check value at different points.

CoversEmitted
a fragment's mCRCthat fragment onlyat every cut
the frame's FCSthe whole frame, all fragmentsonce, at the end

So a preemptable frame in flight has two running accumulators: a frame-level one that never resets until the frame ends, and a fragment-level one that resets at every resumption. Chapter 17.3 §3 stated this as a requirement on the paused MAC's saved state; this section prices it.

The naive price is a second network — 8 512 XOR terms. The real price is 523.

The two accumulators consume the same octets. Every beat of the preemptable frame advances both. From Chapter 6.4 §2's decomposition:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
frame_next    = F · frame_state    XOR  H · d
fragment_next = F · fragment_state XOR  H · d

The H · d term is identical, so it is computed once. Only F · S is duplicated, and F is 32 × 32 regardless of the datapath width — Section 2's amortisation argument arriving as a structural saving.

XOR terms
H at 512 bits7 989
F523
one accumulator8 512
two accumulators on the same data9 035
the second accumulator's marginal cost523 — 6.1%

Now put that beside Section 8's dual-frame accumulator, which is the same phrase describing a different thing.

ConsumeSecond accumulator costs
Section 8 — two frames in a beatdifferent octets8 512 terms — 100%
this section — frame and fragmentthe same octets523 terms — 6.1%

The ratio is sixteen and the whole difference is whether the data term can be shared. That is the structural rule worth taking out of the chapter: duplicating an accumulator over the same input is nearly free; duplicating it over different inputs costs a whole network.

And the rest of the mCRC's cost is small enough to enumerate.

ItemCost
the second F network523 XOR terms
the held fragment state32 flops
the complement32 inverters and a 2-way mux
the receive comparatorSection 10 — one 32-bit compare
the correction barrel at a cut pointalready built — Section 5
total against the engine's 19 461 terms2.7%

Row five is the one that could have been expensive and is not. A cut can happen at any octet boundary, so the fragment's check value needs Section 5's partial-word correction at an arbitrary point rather than only at frame end — and the barrel is already there, already pipelined, and already invoked once per terminating unit. A fragment is a terminating unit. Chapter 17.3 §11's cut-legality rule — at least 64 octets sent and at least 64 remaining — bounds how often, and at minimum size it cannot happen at all.


12. RTL 5 — The mCRC Selector

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// mcrc_selector -- the transmit side of Chapter 17.3's complement.
// Sections 11 and 12.
//
// Two accumulators over the SAME octets, so the H term is shared and
// only F is duplicated: 523 XOR terms rather than 8 512. Section 11.
//
// The terminating value is the fragment accumulator complemented at a
// cut and the frame accumulator plain at the end. Getting the SELECT
// right matters more than getting the complement right, because a
// wrong complement fails every fragment and a wrong select fails only
// the fragments -- which on a link with little express traffic is rare
// enough to reach production.
// ---------------------------------------------------------------------
module mcrc_selector
  import crcint_pkg::*;
(
  input  logic              clk,
  input  logic              rst_n,

  input  logic              beat_valid,
  input  logic [CRC_W-1:0]  data_term,      // H * d, computed once
  input  logic              frame_start,
  input  logic              fragment_start, // a resumption
  input  logic              cut_now,        // Chapter 17.3's decision
  input  logic              frame_end,

  output logic [CRC_W-1:0]  terminating_value,
  output logic              terminate_valid,
  output logic              is_mcrc,

  // Observability. Sections 15 and 16.
  output logic [31:0]       c_fragments,
  output logic [31:0]       c_frames,
  output logic              cut_without_resume
);

  logic [CRC_W-1:0] frame_acc, frag_acc;
  logic [CRC_W-1:0] frame_F,   frag_F;
  logic             fragment_open;

  // F applied to each accumulator. 523 terms each; the data term above
  // is the 7 989 that is NOT duplicated.
  function automatic logic [CRC_W-1:0] serial_step
    (input logic [CRC_W-1:0] s, input logic b);
    logic top;
    begin
      top = s[CRC_W-1] ^ b;
      serial_step = {s[CRC_W-2:0], 1'b0} ^ (top ? CRC_POLY : '0);
    end
  endfunction

  function automatic logic [CRC_W-1:0] F_apply(input logic [CRC_W-1:0] s);
    logic [CRC_W-1:0] acc;
    begin
      acc = s;
      for (int k = 0; k < DATA_W; k++) acc = serial_step(acc, 1'b0);
      F_apply = acc;
    end
  endfunction

  assign frame_F = F_apply(frame_acc);
  assign frag_F  = F_apply(frag_acc);

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      frame_acc <= CRC_INIT;
      frag_acc  <= CRC_INIT;
      fragment_open <= 1'b0;
      terminating_value <= '0;
      terminate_valid   <= 1'b0;
      is_mcrc <= 1'b0;
      c_fragments <= '0; c_frames <= '0;
      cut_without_resume <= 1'b0;
    end else begin
      terminate_valid <= 1'b0;

      if (beat_valid) begin
        frame_acc <= (frame_start    ? CRC_INIT : frame_F) ^ data_term;
        frag_acc  <= (fragment_start || frame_start
                                     ? CRC_INIT : frag_F)  ^ data_term;
        if (fragment_start) fragment_open <= 1'b1;
      end

      // A cut terminates the FRAGMENT. The frame accumulator is
      // untouched and survives the express MAC's turn on the wire --
      // Chapter 17.3 Section 3's saved state, which is these 32 flops.
      if (cut_now) begin
        terminating_value <= ~frag_acc;         // the complement
        terminate_valid   <= 1'b1;
        is_mcrc           <= 1'b1;
        c_fragments       <= c_fragments + 1;
        fragment_open     <= 1'b0;
      end else if (frame_end) begin
        terminating_value <= frame_acc ^ CRC_XOROUT;
        terminate_valid   <= 1'b1;
        is_mcrc           <= 1'b0;
        c_frames          <= c_frames + 1;
        fragment_open     <= 1'b0;
        // A frame that ended while a fragment was still open means a cut
        // was emitted and never resumed. The far end is holding a
        // partial reassembly that will time out.
        if (fragment_open) cut_without_resume <= 1'b1;
      end
    end
  end

endmodule

Classification: a selector whose difficulty is entirely in which accumulator, not in the arithmetic.

What it teaches: that the complement is the trivial half. Thirty-two inverters implement Chapter 17.3 §6's entire mCRC definition. The work is maintaining two accumulators with different reset points over one data stream, and Section 11's arithmetic is why that is affordable.

And it teaches which bug is dangerous. A wrong complement fails every fragment and is found in the first minute of preemption testing. A wrong select — emitting the frame accumulator at a cut — fails only fragments, and on a link where express traffic is 1% of frames that is a 1% error rate that looks like a marginal cable. Chapter 7.3 §7's point about error rates that look like physical-layer problems, arriving in a block that has nothing to do with the physical layer.

Deliberately simplified: F_apply is written as a 512-iteration loop for symmetry with Section 3 and elaborates to the same 523-term network; production code shares one constant matrix between both instances. CRC_XOROUT is applied only on the frame path, which is correct for Chapter 6.2's convention and is exactly the sort of asymmetry that gets mirrored into the fragment path by mistake — the mCRC is the complement of the final FCS value, so the complement and the XOR-out compose and a design applying both to the fragment path emits the plain accumulator. And cut_without_resume is sticky, because a single occurrence is a protocol failure rather than a rate.

Production implication: c_fragments against c_frames is the link's preemption rate and it is a capacity-planning number as much as a debugging one. Chapter 17.3 put the guard band's shrink at 21.8× at 1 Gb/s, and the shrink is only realised on frames that are actually preemptable — so a port reporting c_fragments near zero on a link that negotiated preemption is paying the negotiation's complexity and getting none of the latency benefit, which is usually a traffic-class configuration error upstream rather than anything in this block.


13. What the Engine Must Never Do

Five prohibitions, and the first two are the ones a working design gets wrong.

#Must neverBecauseSymptom
1carry a frame's final state into the next frameChapter 6.2's init is per framefirst frame right, all others wrong
2advance one accumulator over another frame's octetsSection 8's masksfails on 52.38% of beats at 64 octets
3report a residue before the correction barrel drainsSection 5 — 6 cyclescheck values attributed to the wrong frame
4treat an unknown residue as an mCRCSection 10corrupt frames accepted as fragments
5stallChapter 19.1 §11the receive path has no backpressure

Row one has the cleanest signature in the chapter and the worst reputation for wasting a week. A design that forgets to reload CRC_INIT produces one correct frame after every reset — so a directed test that resets between frames passes completely, and a soak test fails 100% from the second frame. Chapter 6.2 §8's convention bugs fail the first frame too; this one does not, which is why it is diagnosed last.

Row two is the dual-frame case and its exposure rate is Section 7's table. The important property of that table is not that the rate is high at minimum size. It is that the rate is 2.73% at maximum size — high enough to see, low enough to attribute to the cable.

Row three is the pipelining tax. Section 5's barrel takes six cycles and at 1.312 cycles per frame that is 4.57 frames in flight. A design that reports "the current frame's residue" has, on a small-packet stream, reported a residue belonging to a frame that finished four frames ago. That is Chapter 19.1 §6's tagging requirement, and Section 20's rejected property is its verification twin.

Row four is worth stating as a prohibition rather than as a check because the tempting implementation is a two-way decision — FCS or not — with "not" handled as "must be a fragment" on a preemption-enabled port. Chapter 17.3's whole point is that a fragment is distinguishable, and a design that infers rather than compares has thrown away the distinguishability it paid a comparator for.

Row five is inherited and it constrains everything above. The engine cannot ask for time. The barrel can be six stages deep only because it is pipelined rather than iterative; a design that loops the correction in place has a variable-latency block on a path with no backpressure, which Chapter 19.1 §11 forbids before this chapter begins.


14. RTL 6 — The Equivalence Checker

Chapter 6.4 §9 established that the only correct sign-off for a generated engine is equivalence against an independently written bit-serial reference — and that generating the reference from the same matrix makes the comparison worthless. Nothing about 512 bits changes that. What changes is the coverage the checker has to report.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// crcint_equivalence_checker -- sign off the 512-bit engine against a
// bit-serial reference. Sections 14 and 21.
//
// THE REFERENCE IS NOT GENERATED FROM THE MATRIX. It is Chapter 6.2's
// serial engine, written from the polynomial one bit at a time, and the
// whole value of this block is that fact. A reference derived from the
// same F and H compares a design against itself -- Section 20's
// rejected property class 84.
//
// What 512 bits changes is residues_covered: 64 classes rather than
// Chapter 6.4's 8, and the seven classic frame sizes reach three of
// them. Section 4.
// ---------------------------------------------------------------------
module crcint_equivalence_checker
  import crcint_pkg::*;
(
  input  logic              clk,
  input  logic              rst_n,

  input  logic              frame_start,
  input  logic              octet_valid,
  input  logic [7:0]        octet,           // the frame, octet by octet
  input  logic              frame_end,

  input  logic              dut_valid,
  input  logic [CRC_W-1:0]  dut_value,       // the parallel engine's answer

  output logic              mismatch,
  output logic [15:0]       failing_length,
  output logic [63:0]       residues_covered,
  output logic [31:0]       c_frames_checked,
  output logic [31:0]       c_mismatches,
  output logic              reference_is_independent  // tied, see below
);

  logic [CRC_W-1:0] ref_state;
  logic [15:0]      octet_count;

  // Chapter 6.2's bit-serial step. Written from the polynomial. This is
  // the ENTIRE reason the block exists; if this were a call into the
  // design's matrix functions the block would be a tautology generator.
  function automatic logic [CRC_W-1:0] ref_step
    (input logic [CRC_W-1:0] s, input logic [7:0] b);
    logic [CRC_W-1:0] acc;
    logic             top;
    begin
      acc = s;
      for (int i = 7; i >= 0; i--) begin
        top = acc[CRC_W-1] ^ b[i];
        acc = {acc[CRC_W-2:0], 1'b0} ^ (top ? CRC_POLY : '0);
      end
      ref_step = acc;
    end
  endfunction

  // A constant a reviewer can grep for. It does not make the reference
  // independent; it records that somebody asserted it was, and the
  // review that checks it is a human one.
  assign reference_is_independent = 1'b1;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      ref_state        <= CRC_INIT;
      octet_count      <= '0;
      mismatch         <= 1'b0;
      failing_length   <= '0;
      residues_covered <= '0;
      c_frames_checked <= '0;
      c_mismatches     <= '0;
    end else begin
      mismatch <= 1'b0;

      if (frame_start) begin
        ref_state   <= CRC_INIT;
        octet_count <= '0;
      end else if (octet_valid) begin
        ref_state   <= ref_step(ref_state, octet);
        octet_count <= octet_count + 1;
      end

      if (frame_end) begin
        // Which of the 64 partial-word classes this length exercised.
        // Section 4: a sweep that never fills this has not tested the
        // masker, and nothing else in the environment says so.
        residues_covered[octet_count[5:0]] <= 1'b1;
        c_frames_checked <= c_frames_checked + 1;
      end

      if (dut_valid && frame_end) begin
        if (dut_value != (ref_state ^ CRC_XOROUT)) begin
          mismatch       <= 1'b1;
          failing_length <= octet_count;
          c_mismatches   <= c_mismatches + 1;
        end
      end
    end
  end

endmodule

Classification: a verification component whose entire value is the provenance of one function.

What it teaches: that ref_step must be written, not called. Chapter 6.4 §9 made this point at 64 bits; at 512 bits the temptation is stronger, because the matrix functions are right there, parameterised, and W = 8 gives a bit-serial reference in one line. That reference passes on a design with a wrong polynomial, because both sides use the wrong polynomial. The independence is a property of the authoring, and no signal in the RTL can carry it — which is why reference_is_independent is tied high and documented as a grep target rather than pretending to be a check.

And it teaches that residues_covered is now the block's most useful output. Chapter 6.4's version had eight bits and a run would fill most of them by accident. Sixty-four bits do not fill by accident: Section 4's classic sizes reach three, so a regression reporting equivalence over a million frames with 3 of 64 bits set has not tested the masker at all and has tested Section 5's barrel on exactly one shortfall value.

Deliberately simplified: the reference consumes one octet per cycle, so the checker runs at 1/64th of the design's rate and is a simulation-only block — which is correct and worth saying, because a team that tries to synthesise it discovers the rate problem late. failing_length holds only the first failure. octet_count[5:0] assumes the covered range's residue is what matters, which is true for the masker and not for the dual-accumulator path — Section 16's monitor carries that coverage separately. And the comparison is only at frame_end, so an engine that is wrong mid-frame and right at the end passes, which is the correct behaviour: the check value is the only observable.

Production implication: failing_length is the diagnosis, as it was at 64 bits, and the mapping has become finer. Failures at lengths ≡ 0 mod 64 point at the full-width path; failures at every length except those point at the masker; failures at exactly one residue point at one stage of Section 5's barrel — and because the barrel is a binary decomposition, the set of failing shortfalls names the stage directly. A design failing at shortfalls 1, 3, 5, 7 and no others has a broken m = 1 stage; one failing at 32 through 63 has a broken m = 32 stage. That is a diagnosis no amount of waveform staring produces faster.


15. RTL 7 — Engine Telemetry

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// crcint_telemetry -- what an operator and a verification lead each need
// from a CRC engine, which are different things. Section 15.
//
// Three groups. Operator counters are about the LINK: how many frames
// failed and how many fragments arrived. Structural counters are about
// this DESIGN: how often the second accumulator ran and which of the 64
// residue classes the traffic reached. Ratio counters are the ones that
// read back a fact about the network the port is attached to.
// ---------------------------------------------------------------------
module crcint_telemetry
  import crcint_pkg::*;
(
  input  logic              clk,
  input  logic              rst_n,

  input  logic              beat_valid,
  input  logic              dual_beat,
  input  logic              frame_done,
  input  residue_class_e    classification,
  input  logic [$clog2(DATA_B+1)-1:0] valid_octets,
  input  logic              is_fragment,

  // Operator view.
  output logic [31:0]       c_frames,
  output logic [31:0]       c_fcs_errors,
  output logic [31:0]       c_fragments,

  // Structural view.
  output logic [31:0]       c_beats,
  output logic [31:0]       c_dual_beats,
  output logic [63:0]       residue_classes_seen,
  output logic [31:0]       c_full_beats,

  // Derived. Section 22 reads these before any of the above.
  output logic [15:0]       dual_beat_pct_x100,
  output logic [7:0]        residue_classes_count
);

  logic [63:0] seen_q;
  int unsigned popcnt;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      c_frames <= '0; c_fcs_errors <= '0; c_fragments <= '0;
      c_beats  <= '0; c_dual_beats <= '0; c_full_beats <= '0;
      seen_q   <= '0;
    end else begin
      if (beat_valid) begin
        c_beats <= c_beats + 1;
        if (dual_beat) c_dual_beats <= c_dual_beats + 1;
        if (valid_octets == DATA_B[$clog2(DATA_B+1)-1:0])
          c_full_beats <= c_full_beats + 1;
      end

      if (frame_done) begin
        c_frames <= c_frames + 1;
        seen_q[valid_octets % DATA_B] <= 1'b1;
        if (classification == RES_BAD)     c_fcs_errors <= c_fcs_errors + 1;
        if (classification == RES_MCRC_OK) c_fragments  <= c_fragments + 1;
        if (is_fragment && classification == RES_FCS_OK)
          c_fcs_errors <= c_fcs_errors + 1;   // Section 10's kind mismatch
      end
    end
  end

  assign residue_classes_seen = seen_q;

  always_comb begin
    popcnt = 0;
    for (int i = 0; i < 64; i++) if (seen_q[i]) popcnt++;
    residue_classes_count = 8'(popcnt);
  end

  // Section 7's table, measured rather than assumed. A port carrying
  // minimum-size frames should report about 5238; one carrying jumbo
  // frames about 273. The number reads back the traffic's SIZE
  // distribution, which is why it belongs in telemetry and not only in
  // a coverage database.
  assign dual_beat_pct_x100 =
    (c_beats == 0) ? 16'd0 : 16'((c_dual_beats * 10000) / c_beats);

endmodule

Classification: an observability block whose two most useful outputs are derived rather than counted.

What it teaches: that dual_beat_pct_x100 measures the network, not the design. Section 7 predicted 52.38% at 64 octets and 2.73% at 1 518; a port reporting 5 238 is carrying minimum-size traffic and a port reporting 273 is carrying jumbo frames, and neither number says anything about whether the engine is correct. What it does say is which of the engine's paths the deployed traffic is exercising, which is the question a verification lead has after a field failure and nobody has before one.

And it teaches that residue_classes_count is the coverage number that survives to production. Chapter 6.4's residues_covered lived in a testbench. This one is in silicon, and it answers a question a simulation cannot: which of the sixty-four partial-word classes the real network produces. A deployment reporting 7 of 64 after a week has an environment where 57 of the masker's classes never run — which is a legitimate reason to weight a regression differently, and an illegitimate reason to stop testing them.

Deliberately simplified: popcnt is a combinational loop over 64 bits and is fine at this rate because nothing reads it per beat; a design that exposes it on a fast register interface needs it pipelined. c_fcs_errors folds in Section 10's kind mismatch, which conflates two different faults in one counter — acceptable only because c_kind_mismatch exists separately and a design that drops that one has genuinely lost information. And dual_beat_pct_x100 divides every cycle, which is a synthesis hazard; production computes it on read.

Production implication: the three-group split is the one Chapter 18.1 §14 introduced and this is Module 19's third instance. The operator counters go to an RMON block and a network management system; the structural counters go to a design's own bring-up dashboard and nowhere else. Mixing them produces the failure Chapter 18.7 §13 described — an operator escalating c_dual_beats as a fault — and the fix is that the two groups have different consumers and should sit behind different register pages.


16. RTL 8 — The Engine Conformance Monitor

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// crcint_conformance_monitor -- verdicts, not counters. Section 16.
//
// Five verdicts. Two are about correctness, two are about coverage, and
// one is about a standards claim -- the same split Chapter 19.3
// Section 15 arrived at, which suggests it is the right split rather
// than a coincidence.
// ---------------------------------------------------------------------
module crcint_conformance_monitor
  import crcint_pkg::*;
#(
  parameter int MIN_RESIDUE_CLASSES = 64,
  parameter int MIN_DUAL_BEATS      = 1000
) (
  input  logic              clk,
  input  logic              rst_n,

  input  logic [31:0]       c_frames,
  input  logic [31:0]       c_mismatches,
  input  logic [31:0]       c_dual_beats,
  input  logic [31:0]       c_kind_mismatch,
  input  logic [7:0]        residue_classes_count,
  input  logic              two_ends_in_a_beat,
  input  logic              cut_without_resume,
  input  logic              reference_is_independent,

  output logic              equivalence_failed,
  output logic              framing_impossible,
  output logic              fragment_kind_confused,
  output logic              residue_classes_untested,
  output logic              dual_frame_untested,
  output logic              none_of_the_above
);

  always_comb begin
    // Correctness. Either of these makes every other verdict moot.
    equivalence_failed     = (c_mismatches != '0);
    framing_impossible     = two_ends_in_a_beat;

    // A standards claim: Chapter 17.3's fragment/frame distinction is
    // either maintained or it is not, and c_kind_mismatch is the
    // evidence. cut_without_resume is the transmit-side twin.
    fragment_kind_confused = (c_kind_mismatch != '0) || cut_without_resume;

    // Coverage. Section 4: the classic seven sizes reach 3 of 64.
    residue_classes_untested =
      (c_frames > 32'd1000) && (residue_classes_count < 8'(MIN_RESIDUE_CLASSES));

    // Section 7: at minimum frame size this is 52.38% of beats, so a run
    // that has not produced MIN_DUAL_BEATS of them has not tested the
    // block the chapter exists for.
    dual_frame_untested = (c_frames > 32'd1000) && (c_dual_beats < MIN_DUAL_BEATS);

    none_of_the_above = !equivalence_failed && !framing_impossible &&
                        !fragment_kind_confused && !residue_classes_untested &&
                        !dual_frame_untested && reference_is_independent;
  end

endmodule

Classification: a verdict generator, and the tenth in Modules 18 and 19.

What it teaches: that reference_is_independent appears in none_of_the_above and nowhere else. It is not a check — Section 14 said no signal can carry that property — so it enters the monitor as a precondition on the whole verdict. A design whose reference was generated from the matrix has that bit tied low by a reviewer, and none_of_the_above then never asserts no matter how clean the run is. That is a human process encoded as a signal, and it is the honest way to encode one.

And it teaches that two of the five verdicts are coverage rather than correctness, which is the split Chapter 19.3 §15 arrived at independently. The engine can be perfectly correct on everything it was shown and untested on 61 of 64 residue classes, and a monitor that reports only correctness says the design is fine. residue_classes_untested is the chapter's central verification finding compiled into one bit.

Deliberately simplified: MIN_RESIDUE_CLASSES defaults to all sixty-four, which is achievable only with a constructed sweep and will fail every traffic-driven regression — deliberately, because Section 4's argument is that traffic does not reach them. MIN_DUAL_BEATS is a raw count rather than a rate, so a jumbo-only run fails it for a legitimate reason and a reviewer has to know that. And the c_frames > 1000 guards make the coverage verdicts vacuous on short runs, which is the right default and hides a run that was too short to mean anything.

Production implication: none_of_the_above for the tenth time, and this instance carries the most conditions of any of them. A port asserting it has a design equivalent to an independently written reference across all sixty-four residue classes, a framing that respects Chapter 5.9's floor, a maintained fragment/frame distinction, and a run that actually produced dual-frame beats. The last clause is the one that fails most often in practice, and the reason is almost always that the regression's frame-size distribution was changed for an unrelated reason and nobody rechecked what it now coversChapter 6.4 §12's warning, eight times sharper at 512 bits.


17. Timing — Nine Levels at 195.3125 MHz

Chapter 19.1 §9 called this block the MAC's critical path. This section is whether it closes, and the answer turns on one level of logic that Section 2 recovered.

The per-beat path is Section 3's network and nothing else. Everything Section 6 sorted into the per-frame bin — the correction, the residue compare, the mCRC select — is pipelined and does not appear here.

Datapath100 Gb/s clockPeriodDepthPer level
128 bits781.25 MHz1.28 ns7183 ps
256 bits390.625 MHz2.56 ns8320 ps
512 bits195.3125 MHz5.12 ns9569 ps

Row three is the design point and row one is why it is the design point. Halving the datapath halves the period and removes only one level, so the budget per level falls by 44% for a 14% saving in depth. That is the trade Chapter 19.1 §9 described qualitatively; the numbers make it lopsided rather than close.

And the level Section 2 recovered is worth its own line. Chapter 19.1 §9 put the 512-bit depth at "~10 levels" from the dense bound's 512 terms per output bit. The generated matrix's worst output bit has 288 terms, and ceil(log2(288)) is 9. One level at 569 ps is 11.1% of the period, which is the difference between a path that closes with margin and one that needs a synthesis argument.

From the dense boundGenerated and counted
worst output bit512 terms288
balanced depth109
slack at 5.12 ns512 ps per level569 ps

Now the honest part, because a balanced XOR tree is not what a synthesiser builds. Depth 9 is the lower bound from the term count; real placement adds buffering on a network with 8 512 terms and 544 inputs, and the fanout on state_in is 523 terms spread across 32 bits. The practical planning number is depth 9 plus two to three levels of buffering, which at 569 ps per level still leaves the path inside 5.12 ns and leaves nothing for a careless floorplan.

What makes it close in practice is that the block is small and dense. 17 024 XOR terms in one place is a region a floorplanner can keep together, and the network has no long-distance signals except the beat itself — which arrives from a register 512 bits wide that wants to be adjacent anyway.

And the scaling beyond 100 Gb/s is already visible in the table.

RateWidth for 195.3125 MHzTermsDepth
100 Gb/s5128 5129
200 Gb/s1 02416 77610
400 Gb/s2 04832 96311

Terms per input bit is 16.6, 16.4 and 16.1the amortisation of F has essentially finishedso beyond 512 bits the engine costs linearly in area and logarithmically in depth, with no further efficiency to collect. Chapter 6.4 §5's falling curve flattens here, and a designer expecting the trend to keep paying is expecting something the matrix stopped offering at about 512 bits.


18. What the Engine Assumes

Eight assumptions, and three of them are contracts with blocks this chapter does not build.

#AssumptionOwnerIf wrong
1a beat's octets belong to at most two framesChapter 19.1 §3Section 8 silently drops a frame's data
2head_octets + tail_octets equals 64 on a dual beatthe framingoctets are counted into no accumulator
3at most one frame ends per beatChapter 5.9's 9-octet floortwo_ends_in_a_beat, and results are meaningless
4the engine is never stalledChapter 19.1 §11the barrel's pipeline has no backpressure
5a tag travels with each correctionChapter 19.5's FIFOs and §6's storecheck values attributed to the wrong frame
6exactly one correction per terminating unitthis chapterthe barrel's 6 stages overflow
7expect_fragment is aligned with the residueChapter 17.3 §5mismatch_kind fires on conformant traffic
8the equivalence reference was written independentlya humanSection 20's class 84 — the run proves nothing

Row three is the only assumption in the list that is a theorem. Section 7 derived it from Chapter 5.9's floor of 9 and Chapter 1.2's 64-octet minimum: the absolute minimum wire period is 81 octets and a beat is 64. Everything else in the table is a contract somebody can break; this one is arithmetic, and that is why two_ends_in_a_beat is an error flag rather than a handled case.

Row six is the one that is subtly this chapter's own fault. Section 5's barrel has six stages and no flow control, so it holds at most six corrections. At 1.312 cycles per frame that is 4.57 in flight — comfortable — but a burst of minimum-size frames on a datapath that has been stalled and released can present them faster than one per 1.312 cycles, and the pipeline has nowhere to put the seventh. The guard is row four: the engine is never stalled, so the input rate is the line rate and the line rate cannot exceed itself.

Row eight is not enforceable and belongs in the table anyway. Sections 14 and 16 encode it as reference_is_independent, a bit a reviewer sets. Chapter 6.4 §9 established the trap; this chapter's contribution is to put the assumption where an assumption belongs — in a list of things that are true because somebody checked, not because the design checks.

And two assumptions that are NOT in the table because the chapter does not need them:

Not assumedWhy not
anything about frame lengthSection 5 handles all 64 residues; Section 4's coverage worry is about testing, not correctness
anything about traffic mixSection 7's rates are measurements, not requirements — the engine is correct at 0% and 100% dual beats

Row two of that second table is worth stating because it is unusual in this module. Chapter 18.7 §16 listed eight traffic assumptions and priced each. This engine has none: it is correct for any sequence of beats that satisfies the framing contract, and the traffic distribution affects only which of its paths get exercised. That is what makes it, as Chapter 19.1 §9 put it, hard and solved.


19. The Cost, Accounted

Chapter 19.1 estimated the media access controller's cyclic redundancy check logic at thirty two thousand seven hundred and sixty eight exclusive-or terms, being two dense thirty two by five hundred and twelve matrices, one for each direction. Three things were wrong with that. Each matrix is eight thousand five hundred and twelve terms rather than sixteen thousand three hundred and eighty four, a factor of one point nine two five too high. Each direction needs two matrices rather than one, because a beat can carry two frames, a factor of two too low. Those two compose to thirty four thousand and forty eight, which is within three point nine percent of the estimate, so the two large errors very nearly cancel. The third error is the one that matters and it is an omission rather than a misestimate: the correction barrels that make a partial final word work are two thousand four hundred and thirty seven terms in each direction and the message cyclic redundancy check network is five hundred and twenty three more, five thousand three hundred and ninety seven terms that were not in the estimate at all because that chapter had not yet met the sixty four residue classes. The real total is thirty nine thousand four hundred and forty five, twenty point four percent above the estimate, and sixteen point five of those twenty points are the omission.Estimate: 32 7682 dense matricesError 1: densebound16 384 vs 8 512Error 2: one perdirectionneeds twoMatrices only: 34048within 3.9%Error 3: notcountedthe correction barrels5 397 terms2 437 each way, plus523Actual: 39 445+20.4%12
Figure 4 — how a 32 768-term estimate became 39 445, and which of the three errors actually mattered.

Eight blocks, one of which is simulation-only, and the chapter's headline is that Chapter 19.1 §18's estimate was wrong three times — twice in ways that cancel and once in a way that does not.

BlockXOR termsFlops
crc_matrix_core ×217 0240 — combinational
partial_word_masker2 437~228
dual_accumulator~70
residue_classifier~130
mcrc_selector523~164
crcint_equivalence_checkersimulation only
crcint_telemetry~450
crcint_conformance_monitor~40
total, one direction19 984~1 082

The two matrix cores are 85.2% of the logic and everything else in the chapter is rounding. That is the shape Chapter 19.1 §18 predicted, and the size is where it went wrong.

The estimate, and the two errors in it:

Chapter 19.1 §18Generated and countedEffect on the total
terms per matrix16 384 — the dense bound8 512 — Section 2÷ 1.925
matrices per direction12 — Section 8× 2
subtotal, matrices only32 76834 048+3.9%
correctionsnot counted2 437 per direction — Section 5+14.9%
mCRCnot counted523, transmit only — Section 11+1.6%
receive16 38419 461
transmit16 38419 984
total32 76839 445+20.4%

Two of the three errors very nearly cancel and the third is the whole gap. The dense bound overstates each matrix by a factor of 1.925; assuming one accumulator per direction understates the count by a factor of 2; together they give 34 048 against an estimate of 32 768 — within 3.9%. What §18 did not have was Section 5: the correction barrels and the mCRC network are 5 397 terms, 16.5% of the estimate, and were not in it at all. A design that budgeted from the estimate is short by about a fifth on the MAC's largest single block, and it is short because of the item nobody thought to list rather than because of the item everybody got wrong.

The flops move the same way. Chapter 19.1 §18 allowed ~1 000 flops for both engines; the real figure is ~1 082 per direction, so ~2 164and the difference is Section 5's pipeline, which that chapter had no reason to know about because it had not yet met the sixty-four residues.

Module 19's running total, with three chapters built:

ChapterLogicMemory
Chapter 19.2 — the parser~4 800 flops + 1 024 byte-muxesnone
Chapter 19.3 — the assembler~1 780 flopsnone
this chapter — the CRC engine~2 164 flops + 39 445 XOR termsnone
subtotal~8 744 flops + 39 445 XOR termsnone
Chapter 19.1 §18's estimate for the whole datapath~11 300 flops + 32 768 XOR terms41 KiB of FIFO

Three chapters, 77.4% of the flop estimate and 120.4% of the XOR estimatewith the FIFOs, the memory interface and the statistics counters still to come. The flops will exceed the estimate too when Chapter 19.5's pointers and Chapter 19.7's twenty counter adders arrive; the interesting result is that the block everybody expected to dominate does dominate, and by more than anyone budgeted.

And no memory. Three chapters into Module 19 and the datapath has still added zero bytesall 41 KiB of it belongs to Chapter 19.5, which is the next chapter and is the first in the module whose cost is measured in kilobytes rather than gates.


20. Properties Worth Asserting, and One Worth Refusing

Thirty-two properties in six groups, and one property that is more tempting here than anywhere else in the track.

Group 1 — the matrix itself. Linearity is checkable and it catches convention bugs.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// GF(2) linearity: a zero beat from a zero state stays zero. Catches an
// init or xor-out folded into the network instead of the accumulator.
a_zero_zero: assert property (@(posedge clk) disable iff (!rst_n)
  (step_en && data_in == '0 && state_in == '0) |-> state_out == '0);

// Superposition on the state. The network is F*S xor H*d, so two states
// differing by delta produce outputs differing by F*delta.
a_state_linear: assert property (@(posedge clk) disable iff (!rst_n)
  step_en |-> state_out == (f_apply(state_in) ^ h_apply(data_in)));

// The step is a bijection: distinct states never collapse. This is the
// property that makes Section 5's inverse exist.
a_bijective: assert property (@(posedge clk) disable iff (!rst_n)
  (step_en && state_in != alt_state) |-> state_out != alt_state_out);

// Depth is a synthesis property, not a simulation one, but the term
// count is checkable at elaboration.
a_term_count: assert property (@(posedge clk) $countones(h_col_mask) == 7989);

Group 2 — the partial-word masker. Every octet is counted once, in exactly one accumulator.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Masked octets beyond valid_octets are zero.
a_mask_clean: assert property (@(posedge clk) disable iff (!rst_n)
  in_valid |-> ((masked_data << (int'(valid_octets)*8)) == '0));

// valid_octets is never zero. A beat belonging to no frame is a framing
// bug, not a partial word -- Section 5's illegal_count.
a_valid_nonzero: assert property (@(posedge clk) disable iff (!rst_n)
  in_valid |-> valid_octets != '0);

// The shortfall drives exactly the barrel stages its bits select.
a_barrel_select: assert property (@(posedge clk) disable iff (!rst_n)
  in_valid |-> sel_q[0] == (DATA_B - valid_octets));

// A full beat bypasses every stage: the correction is the identity.
a_full_beat_identity: assert property (@(posedge clk) disable iff (!rst_n)
  (in_valid && valid_octets == DATA_B) |-> ##STAGES state_out == $past(core_state, STAGES));

// The barrel's latency is exactly STAGES and never varies.
a_barrel_latency: assert property (@(posedge clk) disable iff (!rst_n)
  in_valid |-> ##STAGES out_valid);

// It never produces an output it was not given an input for.
a_no_spurious: assert property (@(posedge clk) disable iff (!rst_n)
  out_valid |-> $past(in_valid, STAGES));

Group 3 — the dual accumulator. This is the group the chapter exists for.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The masks partition the beat: every octet goes to exactly one side.
a_masks_partition: assert property (@(posedge clk) disable iff (!rst_n)
  (beat_valid && beat.starts_frame && beat.ends_frame) |->
    (beat.head_octets + beat.tail_octets == DATA_B));

// The masks do not overlap.
a_masks_disjoint: assert property (@(posedge clk) disable iff (!rst_n)
  beat_valid |-> ((head_masked & tail_masked) == '0));

// Section 9's linearity result, asserted rather than assumed.
a_linear_split: assert property (@(posedge clk) disable iff (!rst_n)
  (beat_valid && beat.head_octets + beat.tail_octets == DATA_B) |->
    (head_masked ^ tail_masked) == beat.data);

// A frame that starts in a beat starts from CRC_INIT.
a_new_frame_init: assert property (@(posedge clk) disable iff (!rst_n)
  (beat_valid && beat.starts_frame) |-> u_new.state_in == CRC_INIT);

// A frame that continues does not.
a_continue_carries: assert property (@(posedge clk) disable iff (!rst_n)
  (beat_valid && !beat.starts_frame && beat.tail_octets != '0) |->
    u_new.state_in == acc_new_q);

// Section 7's theorem: two ends cannot share a beat. 81 > 64.
a_one_end_per_beat: assert property (@(posedge clk) disable iff (!rst_n)
  beat_valid |-> !(beat.ends_frame && beat.head_octets == '0));

// The old accumulator never advances over the new frame's octets.
a_no_cross_contamination: assert property (@(posedge clk) disable iff (!rst_n)
  (beat_valid && beat.head_octets != '0) |->
    u_old.data_in == head_masked);

// Neither accumulator ever stalls -- Chapter 19.1 Section 11.
a_never_stalls: assert property (@(posedge clk) disable iff (!rst_n)
  beat_valid |-> ##1 (c_beats == $past(c_beats) + 1));

Group 4 — the residue classifier and the mCRC.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The three classes are exhaustive and mutually exclusive.
a_class_exclusive: assert property (@(posedge clk) disable iff (!rst_n)
  out_valid |-> $onehot({classification == RES_FCS_OK,
                         classification == RES_MCRC_OK,
                         classification == RES_BAD}));

// The FCS residue is Chapter 6.3's constant and nothing else.
a_fcs_constant: assert property (@(posedge clk) disable iff (!rst_n)
  (out_valid && classification == RES_FCS_OK) |->
    $past(residue) == FCS_RESIDUE);

// The mCRC residue is the complement of it -- Chapter 17.3 Section 8.
a_mcrc_is_complement: assert property (@(posedge clk)
  MCRC_RESIDUE == ~FCS_RESIDUE);

// An unknown residue is never classified as a fragment.
a_no_inference: assert property (@(posedge clk) disable iff (!rst_n)
  (out_valid && classification == RES_MCRC_OK) |->
    $past(residue) == MCRC_RESIDUE);

// Framing and residue must agree.
a_kind_agrees: assert property (@(posedge clk) disable iff (!rst_n)
  (out_valid && !mismatch_kind && classification != RES_BAD) |->
    (($past(expect_fragment)) == (classification == RES_MCRC_OK)));

// A cut emits a complemented fragment accumulator, never a frame one.
a_cut_uses_fragment: assert property (@(posedge clk) disable iff (!rst_n)
  cut_now |-> ##1 (terminating_value == ~$past(frag_acc)));

// A frame end emits the frame accumulator with the xor-out applied.
a_end_uses_frame: assert property (@(posedge clk) disable iff (!rst_n)
  (frame_end && !cut_now) |-> ##1
    (terminating_value == ($past(frame_acc) ^ CRC_XOROUT)));

// The frame accumulator survives a cut untouched.
a_frame_acc_survives: assert property (@(posedge clk) disable iff (!rst_n)
  cut_now |-> ##1 (frame_acc == $past(frame_acc)));

// Every cut is eventually resumed or the frame is abandoned -- and the
// abandonment is reported, not silent.
a_cut_resumed: assert property (@(posedge clk) disable iff (!rst_n)
  cut_now |-> ##[1:$] (fragment_start || cut_without_resume));

Group 5 — the equivalence checker, where the properties are about the environment.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The reference's state is never written from the design's.
a_ref_isolated: assert property (@(posedge clk) disable iff (!rst_n)
  1'b1 |-> !$isunknown(ref_state));

// A mismatch always captures a length.
a_capture_on_fail: assert property (@(posedge clk) disable iff (!rst_n)
  mismatch |-> failing_length != '0);

// Coverage is monotonic: a class once seen stays seen.
a_coverage_monotonic: assert property (@(posedge clk) disable iff (!rst_n)
  $stable(rst_n) |-> ((residues_covered & $past(residues_covered))
                       == $past(residues_covered)));

// The checker compares only at frame end -- mid-frame divergence is not
// observable and asserting on it would be asserting on an internal.
a_compare_at_end: assert property (@(posedge clk) disable iff (!rst_n)
  mismatch |-> $past(frame_end));

Group 6 — coverage, stated as properties so a run that misses them fails rather than passes quietly.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Every one of the 64 residue classes, eventually.
c_all_residues: cover property (@(posedge clk) residues_covered == 64'hFFFF_FFFF_FFFF_FFFF);

// A dual-frame beat -- 52.38% of beats at minimum size, 2.73% at maximum.
c_dual_beat: cover property (@(posedge clk) beat.ends_frame && beat.starts_frame);

// A cut at the first legal octet and at the last.
c_cut_early: cover property (@(posedge clk) cut_now && octets_sent == 64);
c_cut_late:  cover property (@(posedge clk) cut_now && octets_remaining == 64);

// Every barrel stage actually selected at least once.
c_barrel_stages: cover property (@(posedge clk) sel_q[0] == 6'b111111);

// A frame whose covered range is an exact multiple of 64 octets.
c_no_partial: cover property (@(posedge clk) frame_end && valid_octets == DATA_B);

21. Verification Scenarios

Fifty-seven scenarios, plus a six-run directed test whose content is entirely a list of frame sizes and one cut position.

The matrix core — 8 scenarios.

#ScenarioExpected
1a zero beat from a zero statestays zero
2a zero beat from a non-zero stateF · S, not S
3a known vector against Chapter 6.2's serial engineidentical
4the matrix regenerated at elaboration twiceidentical both times
5W = 8, compared against the bit-serial referenceidentical
6W = 512 against 8 invocations of W = 64identical
7the polynomial changed to a different CRC-32every frame mismatches
8state and data both non-zerosuperposition holds

Row six is the cheapest structural check in the chapter and it is worth running first: eight 64-bit steps must equal one 512-bit step, and if they do not, the matrix generator is wrong in a way nothing else here will localise.

The partial-word masker — 11 scenarios.

#ScenarioExpected
9valid_octets = 64the correction is the identity
10valid_octets = 1shortfall 63 — all six stages
11valid_octets = 63shortfall 1 — stage m = 1 only
12valid_octets = 32shortfall 32 — stage m = 32 only
13valid_octets = 0illegal_count
14all 64 values of valid_octetsresidues_seen all ones
15back-to-back partial words6-stage pipeline holds both
16seven partial words in seven cyclesthe 7th has nowhere to go — Section 18 row 6
17a full beat followed by a partiallatency identical
18the barrel's output compared against a 64-matrix referenceidentical for all 64
19reset mid-pipelineno output claims validity

Row eighteen is the masker's real sign-off and it is affordable in simulation precisely because the 64-matrix structure Section 6 rejected for silicon is a fine reference. The structure that is too expensive to build is often the right thing to build in a testbench, which is a habit worth generalising.

The dual accumulator — 12 scenarios.

#ScenarioExpected
20a beat entirely one frametail_octets = 0, one accumulator advances
21a beat with 1 head octet and 63 tailboth advance
22a beat with 63 head and 1 tailboth advance
23a beat with 32 and 32both advance
24starts_frame with a stale acc_new_qCRC_INIT used, not the stale value
25starts_frame deasserted mid-framethe accumulator carries
26two ends in a beat, forcedtwo_ends_in_a_beat, sticky
27head and tail summing to 63an octet counted nowhere — a framing bug
28head and tail summing to 65an octet counted twice
2910 000 minimum-size framesc_dual_beats near 52.38%
3010 000 maximum-size framesnear 2.73%
31a single frame after resetcorrect — this is the case row 32 hides

Row thirty-one and its partner are the chapter's most-missed pair. A design that never reloads CRC_INIT passes scenario 31 and fails scenario 32, and a regression that resets between frames runs scenario 31 a thousand times.

#ScenarioExpected
32two frames with no reset betweenthe second is correct

The residue classifier and the mCRC — 12 scenarios.

#ScenarioExpected
33a valid frame's residueRES_FCS_OK
34a valid fragment's residueRES_MCRC_OK
35one bit flipped in the payloadRES_BAD
36one bit flipped in the FCSRES_BAD
37expect_fragment set, FCS residue seenmismatch_kind
38expect_fragment clear, mCRC residue seenmismatch_kind
39a cut at the first legal octetmCRC emitted; frame accumulator intact
40a cut at the last legal octetthe same
41two cuts in one frametwo fragments, one FCS
42a cut with no resumptioncut_without_resume
43a frame with no cut on a preemption-enabled portc_fragments unchanged
44the mCRC constant compared against ~FCS_RESIDUEequal, at elaboration

Rows thirty-seven and thirty-eight are the pair a design usually implements only one of. The residue and the framing are independent sources for the same fact; checking one direction and not the other is the common shape, and which direction gets checked is arbitrary.

The equivalence checker and coverage — 13 scenarios.

#ScenarioExpected
45the reference fed from the design's matrixreference_is_independent must be tied LOW
46a wrong polynomial in bothequivalence passes — the trap
47a wrong polynomial in the design onlyevery frame mismatches
48dut_valid never assertingc_frames_checked = 0, verdict vacuous
491 000 frames, sizes 64/128/512/1024/1500residue_classes_count = 3
50sizes 64 through 127 sweptall 64 classes
51sizes 1 455 through 1 518 sweptall 64 classes
52uniform random sizes, 1 000 framesall 64 with probability near 1 — but not certainly
53uniform random sizes, 200 framesusually incomplete
54a jumbo-only rundual_frame_untested
55a run with c_frames = 500coverage verdicts vacuous by design
56the reference's reflection convention wrongevery frame mismatches
57the design's XOR-out omittedevery frame mismatches, and the delta is constant

Row fifty-two deserves its qualifier. Covering 64 classes by uniform sampling is coupon collection: the expectation is 303.6 frames, so a 1 000-frame random run usually succeeds. "Usually" is not a sign-off criterion, which is why row 50's sweep exists and why Section 16's verdict is a hard threshold rather than a probability.

Row fifty-seven's "constant delta" is the diagnosis. An omitted CRC_XOROUT makes every value wrong by the same 32-bit constant, so the mismatch log shows one XOR difference repeated — which names the bug in one line and is why failing_length is not the only field worth capturing.

The directed test — six runs random stimulus will not produce.

Three of this chapter's structures are selected by a property of the frame, and the third is selected by a property of a pair of frames.

StructureSelected byReachable randomly?
the 64 residue classescovered range mod 64yes — 303.6 frames on average
the dual accumulatorframe size, through the wire periodyes — 52.38% at minimum size
all six barrel stages in one framecovered range ≡ 1 mod 6423 sizes of 1 455 — 1.6%
a cut immediately before the final partial wordframe size AND cut positionNO — 1 in 506 340

Row four is the one to construct. A cut is legal only between the 64th octet sent and the 64th from the end — Chapter 17.3 §11 — which on a 1 518-octet frame is about 348 four-octet positions, and the position wanted is one of them. Combined with needing a particular frame size, the probability per frame is 1.98 × 10⁻⁶.

Value
probability per frame1.98 × 10⁻⁶
expected occurrences in a 100 000-frame regression0.197
probability a 100 000-frame regression sees it at all17.9%
frames needed for a 90% chanceabout 1.17 million

So four regressions in five never run it, and the one that does reports nothing distinguishing. The case matters because it puts two corrections from the same frame into Section 5's barrel back to back — the fragment's mCRC at one shortfall and the resumed frame's FCS at another — and a barrel that indexes by a per-block register rather than a per-unit tag gets them the wrong way round.

Construct it. Six runs.

RunSizesCutsExercises
A64 … 127, each once, back to backnoneall 64 residue classes; dual beats throughout
B1 455 … 1 518, each oncenoneall 64 classes at maximum size
C69, repeatednoneshortfall 63 — all six barrel stages, every frame
D64, repeatednoneshortfall 4 — one stage, and nothing else
E1 518, repeatedat the last legal octet before the final partial wordrow four above
Frandom 64 … 1 518random legalthe control

Run D is the run that looks like a stress test and exercises one barrel stage. Sixty-four octets is the frame size every small-packet test uses; its covered range is 60, its shortfall is 4, and it selects the m = 4 stage and no other. A design with five broken barrel stages passes run D at full line rate for a week.

Run C is its opposite and it is one size. Sixty-nine octets gives a covered range of 65, a residue of 1 and a shortfall of 63binary 111111, so every stage of the barrel runs on every frame. There are 23 such sizes between 64 and 1 518 and 69 is the smallest, which makes run C both the most thorough barrel test and the cheapest.

The oracle, in four parts:

CheckACDEF
residue_classes_count64111usually 64
barrel stages exercisedall 6all 6, per frame12all 6
c_dual_beats / c_beatsnear 52.38%48.31%52.38%2.73%mixed
c_mismatches00000

Row one and row two disagree in runs C and D, and the disagreement is the point. Run C covers one residue class and all six barrel stages; run D covers one class and one stage. A coverage model that tracks residue classes alone cannot tell them apart, and a design that passes D and fails C has five broken stages and a green coverage report.

Row three's run C entry is the one to check by hand. A 69-octet frame has a wire period of 89 octets — 8 preamble, 69 frame, 12 gap — which against a 64-octet beat gives a dual-beat rate of 43 / 89, or 48.31%, not 50%. A run reporting 50% is not sending 69-octet frames, and that discrepancy has caught a stimulus generator rounding frame sizes to multiples of four more than once.

And row four is the only row that is about correctness. The other three are about whether the run meant anything, which is Section 16's split between correctness verdicts and coverage verdicts arriving as a test plan.


22. Debugging a CRC Engine

Four complaints. Two are diagnosed by a frame size, one by a ratio, and one is not this block's fault at all.

Complaint 1 — "about one frame in twenty fails its check."

CheckIf yesMeaning
does the failing set share a residue class?the masker or one barrel stageSection 5
is residue_classes_count less than 64?the path was never testedSection 16
does the rate track frame size?a length-dependent pathours
c_dual_beats near zero on small frames?the framing is not marking dual beatsSection 8 never runs

Row one is the diagnosis and the residue names the stage. Section 14: a failing set of shortfalls 1, 3, 5, … is the m = 1 stage; a set 32 … 63 is m = 32. The binary decomposition means the set of failing shortfalls is the stage's selector bit read directly off the failure log, which is a diagnosis no waveform produces faster.

Complaint 2 — "the first frame is right and everything after it is wrong."

CheckIf yesMeaning
does a reset between frames fix it?CRC_INIT is not being reloadedSection 8, the starts_frame mux
is the delta constant?an XOR-out applied twice or not at allChapter 6.2 §8
is frame n's value frame n−1's?the accumulator carriedconfirms row one
is it only the second frame of a dual beat?starts_frame is late by a beata timing bug, not a logic one

Row one is the whole answer nine times in ten and it is diagnosed last, because the shape — one correct frame, then failures — reads like a state-machine bug and is a mux select. Section 21's scenarios 31 and 32 are this pair, and a regression that resets between frames runs only the passing half.

Complaint 3 — "preemption fragments are all rejected by the far end."

CheckIf yesMeaning
is the emitted value the frame accumulator?the wrong accumulator at the cutSection 12's select
is it the complement of the frame accumulator?the right complement, wrong operandthe same bug, disguised
c_kind_mismatch non-zero?framing and residue disagreeSection 10
is CRC_XOROUT applied on the fragment path?the complement and the xor-out composedSection 12

Row two is the one that wastes the most time. The complement is applied, so the value looks like an mCRC — it has the right shape and the wrong operandand a reviewer checking "is the complement there?" answers yes and moves on. The check that separates them is row one: compare the emitted value against the fragment accumulator, not against a definition.

Complaint 4 — "CRC errors climb with temperature."

CheckIf yesMeaning
is c_mismatches zero in simulation?the engine is correctnot this block
do errors correlate with link partner?the channelChapter 3.3
does the depth-9 path meet timing with margin?Section 17or it does not, and heat is the trigger
c_bad with c_mcrc_ok at zero on a preemption link?fragments unrecognisedChapter 17.3 §12

Row three is the one that makes this complaint belong here. Chapter 19.1 §9 named this the MAC's critical path, and a path that closes at 5.12 ns with 100 ps of margin fails first when the die warms. Section 17's nine levels plus buffering is why the margin has to be planned rather than discovered — and the one level Section 2 recovered from the dense bound is 569 ps of it.

And the three symptoms this chapter is systematically blamed for:

SymptomBlamed onUsually is
a rising CRC error ratethe CRC enginethe channel, or a framing bug upstream
fragments rejectedthe mCRCthe select, not the complement
"the engine is too slow"the matrixa correction left on the per-beat path — Section 6

23. Misconceptions

Misconception 1 — "a 32 × 512 matrix is 16 384 XOR terms."

The wrong model: the matrix has 32 × 512 cells, so the network has that many terms.

What it costs: a floorplan reserving 92.5% more area than the engine needs, and a timing estimate of ten XOR levels where nine is correct. Chapter 19.1 §18's total came from this, and it then compounded with a second error in the opposite direction.

The corrected model: a cell is a term only if it is a one, and over GF(2) the generated matrix is about half ones. 8 512 terms, worst output bit 288, depth 9. The way to know is to generate it; the way to be wrong is to multiply the dimensions. Sections 2, 19.

Misconception 2 — "sixty-four residues means sixty-four matrices."

The wrong model: the datapath is 64 octets wide, a frame's last word can be any of 64 lengths, so generate 64 networks and mux.

What it costs: 289 731 XOR terms — 26.5 times the right structure — plus a 64-way mux on the block that is already the MAC's critical path.

The corrected model: mask, run the full width, and undo the zero octets afterwards with the inverse of F. The inverse exists because the serial step is a bijection. The correction is 2 437 terms in a six-stage pipeline that runs once per frame, which is why its thirty-four levels never reach a timing report. Sections 5, 6.

Misconception 3 — "the second accumulator is a corner case."

The wrong model: two frames in a beat is a rare alignment, so handle it and move on.

What it costs: 52.38% of beats at minimum frame size. A design with a broken second accumulator is wrong on more than half of all beats at the size the MAC was specified against — and right on 97.27% of beats at jumbo size, which is why it reaches silicon.

The corrected model: the dual-frame beat is the common case at 100 Gb/s, and Chapter 19.1 §3 established it before this chapter began. Two accumulators is also the ceiling, because Chapter 5.9's 9-octet floor puts the minimum wire period at 81 octets and a beat is 64. Sections 7, 8.

Misconception 4 — "two accumulators cost two engines."

The wrong model: each accumulator needs its own matrix, so doubling the accumulators doubles the area.

What it costs: the wrong answer in both directions. Section 8's two accumulators DO cost two networks — 17 024 terms — because they consume different octets. Section 11's frame-and-fragment pair costs 523 terms — 6.1% — because they consume the same octets and share H. A designer with one rule for both overbuilds the preemption path by a factor of sixteen or underbuilds the dual-frame path by a factor of two.

The corrected model: the question is not how many accumulators but whether they see the same data. Same data shares the H term and duplicates only F, which is 32 × 32 at every width. Different data shares nothing. Sections 9, 11.

Misconception 5 — "equivalence against a reference proves the engine."

The wrong model: the parallel engine matches the serial one on a million frames, so it is correct.

What it costs: nothing, if the reference is independent. Everything, if it was generated from the same matrix — which is the natural way to write it, because the matrix functions are parameterised and W = 8 gives a serial reference in one line. That reference agrees with a wrong polynomial, a wrong reflection and a wrong initial value.

The corrected model: the reference is Chapter 6.2's engine, written from the polynomial by hand, and the independence is a property of the authoring that no signal can carry. Where an independent oracle exists — and Chapter 6.3's residue is one — it outranks the equivalence check, because the constant comes from the code rather than from either implementation. Sections 14, 20.

Misconception 6 — "a million random frames covers the design."

The wrong model: random sizes over the legal range exercise everything eventually.

What it costs: a green regression on a design with five of six barrel stages broken. The seven classic sizes reach 3 of 64 residue classes; run D's minimum-size stress test reaches one class and one barrel stage. And the cut-before-the-final-partial-word case has a probability of 1.98 × 10⁻⁶ per frameso a 100 000-frame regression sees it 17.9% of the time and says nothing when it does.

The corrected model: three of the chapter's structures are selected by frame size and one by a pair of properties. The size-selected ones need a constructed sweep — 64 through 127 covers all 64 classes in 64 frames — and the pair needs a directed test. Chapter 6.4 §12 made this argument when there were eight classes; at sixty-four it stops being advice. Section 21.


24. Interview Questions

Question 1 — "How many XOR terms is a 512-bit CRC-32 matrix, and how would you find out?"

What the answer should establish: that the question cannot be answered by multiplying 32 by 512. That product — 16 384 — is the matrix's size; a term is a nonzero cell, and over GF(2) the generated matrix is 51.95% ones, so the count is 8 512. The method is the answer: run the serial step on 32 state unit vectors and 512 data unit vectors and count. A strong answer adds the consequence — the worst output bit has 288 terms, so the balanced depth is 9 rather than the 10 the dense bound implies, and one XOR level at 195.3125 MHz is 569 ps.

Question 2 — "Your datapath is 512 bits and a frame's covered range is 1 471 octets. What does the engine do on the last beat?"

What the answer should establish: 1 471 mod 64 is 63, so the final beat carries 63 valid octets and one invalid one. The engine masks the invalid octet to zero, runs the full 512-bit network, and then undoes one octet of zero advance by multiplying the state by the inverse of F raised to 8. A strong answer names why the inverse exists — the serial step is a bijection on 32-bit states — and why the correction goes after rather than being avoided: sixty-four width-specific matrices cost 289 731 terms against 10 949.

Question 3 — "A beat carries the end of one frame and the start of the next. How many CRC networks do you need?"

What the answer should establish: two, and two is also the maximum. Two because the octets are different and a network evaluates one input; the maximum because the whole-beat value is the XOR of the two, by linearity over GF(2) — so a design that also wants a whole-beat check value for a datapath monitor gets it from 32 XOR gates rather than a third network. A strong answer states the frequency: 52.38% of beats at minimum frame size, so this is the common case and not a corner.

Question 4 — "Preemption is enabled. A frame needs both a fragment mCRC and a whole-frame FCS. What does the second accumulator cost?"

What the answer should establish: 523 XOR terms, not 8 512because the two accumulators consume the same octets, so the H · d term is computed once and only F · S is duplicated. F is 32 × 32 at every datapath width. A strong answer contrasts it with question 3 explicitly: two accumulators over different data cost a whole network; two over the same data cost 6.1% of one. The rule is about the inputs, not about the count.

Question 5 — "You have a parallel engine that matches your bit-serial reference on ten million frames. What have you proved?"

What the answer should establish: that depends entirely on where the reference came from, and if it was generated from the same matrix the answer is nothing. A strong answer names the independent oracle that is available anyway: Chapter 6.3's residue, 0xC704DD7B, is a property of the code rather than of any implementation, so running the engine over a frame and its own check value and comparing against that constant is a check neither side could have derived from the other. And it names the coverage question: ten million frames of realistic traffic may have reached three of sixty-four residue classes.

Question 6 — "Where would you pipeline this engine, and where would you refuse to?"

What the answer should establish: the sort into per-beat work and per-frame work. The matrix step runs every beat and cannot be pipelined without a second beat to run inChapter 19.1 §4's 1.312 cycles per frame — so it must fit in one cycle at 9 levels. The correction, the residue compare and the mCRC select all run once per frame and pipeline freely; the correction is 34 levels deep and costs nothing. A strong answer contrasts this with Chapter 19.3 §8's deficit accumulator, which is three bits and one level and cannot be pipelined at all, because depth does not decide pipelinability — the dependency graph does.


25. Questions and Answers


26. What's Next

This chapter finished the MAC's logic. What is left in Module 19 is memory and counters, and the next chapter is the first in the module whose cost is measured in kilobytes.

Section 19's running total has three chapters and zero bytes of storage. The parser, the assembler and the engine are 8 744 flops and 39 445 XOR terms between themand Chapter 19.1 §18 put 41 KiB of FIFO alongside them, none of which has been built.

Chapter 19.5 sizes it, and the interesting part is that the depth does not come from a latency. Chapter 18.1 §9's asynchronous FIFO was sized from the system's side; the MAC's side asks a different question — how much elasticity a 100 ppm clock tolerance demands between two domains that never stop — and the answer has nothing to do with how long anything takes. The almost-full threshold is the other half, and it comes from Chapter 14.2 §8's headroom, which Chapter 18.1 §18 put at 7.67 KiB at 100 Gb/s over 100 m with the crossover against the stall buffer at about 9 Gb/s.

Chapter 19.6 then connects the datapath to memory, where Chapter 18.5's burst shaping meets this module's beats — and it has to answer what the interface does when the reorder buffer is full and the read data channel must stall, against Chapter 19.1 §11's rule that the receive path has no backpressure.

And Chapter 19.7's statistics counters are the block Chapter 19.1 §9 warned about — twenty counters, easy per instance, and needing an adder rather than an increment because Section 7's dual-frame beat can complete two frames' statistics in one cycle. That is this chapter's dual-frame beat arriving in a block that has nothing to do with CRC, which is the clearest sign that Chapter 19.1 §3's structural observation was a datapath-wide one rather than a parser's problem.


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.