Skip to content
VLSI Mentor

Ethernet · Module 20

Scoreboards

A scoreboard that compares octet for octet fails on padding, on an appended check value and on a tag — and on a minimum-size frame only fifteen octets are invariant.

A scoreboard checks that the frame that went in is the frame that came out. Every word in that sentence is doing work, and the one that breaks first is "the frame."

Module 19's MAC is allowed to change what it carries, and it does so in five different ways, all of them correct.

ModificationChapterOctets changed
padding to the 60-octet floorChapter 19.3 §2up to 45 added
appending the check valueChapter 19.44 added
inserting or removing a VLAN tagChapter 13.24 or 8
fragmenting with an mCRCChapter 17.3 §6the whole framing
inserting a checksumChapter 18.7 §162 modified

An octet-for-octet comparison fails on all five, and a scoreboard that waives them one at a time ends up comparing nothing. The question this chapter answers is what is left, and on a minimum-size frame the answer is fifteen octets of sixty-four.


1. Scope, and a Frame the Design May Rewrite

Chapter 19.4 §14 established that an equivalence reference must be independently authored, and that a reference generated from the design under test is a tautology. A scoreboard is that problem at frame granularity, with two things that make it harder.

Chapter 19.4's equivalence checka scoreboard
the referencea 30-line serial CRCa model of a MAC
what is compared32 bitsup to 9 000 octets
legal differencesnonefive, and the pad's extent is unknowable
orderingone frame at a timetwelve in flight — Chapter 19.1 §6

Row three is this chapter's subject and row four is its structure.

What this chapter owns: the invariant core and how to compute it; the digest that makes twelve frames in flight affordable; the receive and transmit scoreboards; the tagging that lets them match out of order; and the reordering window that Chapter 18.7's multi-queue makes necessary.

What it does not own: the stimulus — Chapter 20.1 — the assertions — Chapter 20.2 — or the coverage model, which is Chapter 20.4. A scoreboard reports a mismatch; whether the mismatch was worth finding is a coverage question.

And one thing is worth stating before any of the machinery: the pad's extent cannot be recovered from the frame.

Chapter 5.6 established that on a type-form frame there is no length field at all — the EtherType occupies those two octets — so a 64-octet frame carrying one octet of upper-layer data and forty-five octets of pad is, on the wire, indistinguishable from one carrying forty-six octets of data. The receiver's upper layer knows; the MAC does not, and neither does a scoreboard looking at the wire.

So the scoreboard must carry the pad boundary from the stimulus, which makes it a two-sided component rather than a wire monitor, and that decision shapes everything after it.


2. The Invariant Core

A frame has seven regions and four of them are the design's to change. The destination address, the source address and the EtherType are fixed and always compared. A VLAN tag may be inserted or removed, which moves every field after it. The upper layer payload is compared except for any checksum field the offload engine writes. The pad is the design's, produced to bring the pre check sequence length to sixty octets, and the check value is the design's too. So the invariant core is the addresses, the EtherType and the upper layer payload with the checksum masked. On a fifteen hundred and eighteen octet frame that is fifteen hundred and fourteen octets, ninety nine point seven per cent. On a sixty four octet frame carrying forty six payload octets it is sixty, ninety three point eight per cent. But on a sixty four octet control frame carrying one payload octet it is fifteen octets, twenty three point four per cent: forty five octets are pad, which is seventy point three per cent of the wire, and four more are the check value. Acknowledgements, address resolution replies and pause frames are all in that shape, so a scoreboard reporting ten thousand frames checked on control traffic has compared about two thousand three hundred frames' worth of octets.A 64-octet frameone payload octetHeader, 14addresses andEtherTypePayload, 1the stimulusPad, 45the design's — 70.3%Check value, 4the design's — 6.2%Invariant core,1523.4% of the frameAt 1 518 octetscore is 99.7%At 64 with 46payloadcore is 93.8%Report the sharea pass is quantified12
Figure 1 — on a minimum-size control frame, fifteen of sixty-four octets are the stimulus and the rest is the design.

Given that five kinds of modification are legal, what is a scoreboard entitled to compare?

Work outward from the frame and remove what the design may change.

RegionOctetsMay the design change it?
destination address6no
source address6no
VLAN tags0, 4 or 8YES — Chapter 13.2
EtherType2no
upper-layer payloadvariablechecksum fields only
pad0 to 45YES — it is the design's
check value4YES — the design appends it

Four of seven regions are the design's to change, and two of them — the tags and the pad — change the positions of everything after them. So the invariant core is not a byte range; it is a computation.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
invariant_core = destination ++ source ++ ethertype ++ upper_payload
                 with the checksum fields masked

And its size collapses at the minimum frame.

FrameUpper payloadPadInvariant coreShare of the frame
641451523.4%
644606093.8%
1 5181 50001 51499.7%
9 0008 98208 996100.0%

Row one is the case that matters and it is the common case. A control frame — an acknowledgement, an ARP reply, a PAUSE — carries a handful of octets and is padded to the floor, so 70.3% of what crosses the wire is the design's own pad and another 6.2% is the check value it appended** — **a scoreboard comparing the whole frame is comparing 76.6% design output against 23.4% stimulus.

And the collapse is not gradual. It is a step at the sixty-octet floor.

Upper-layer payloadPadCoreShare
1451523.4%
10362437.5%
30164468.8%
4606093.8%
4706193.8%

Rows four and five are the step: above forty-six payload octets there is no pad at all, and the share stops moving. Below it the share falls linearly with the payload, and the whole of the interesting range is the forty-six octets between an empty frame and the floor — which is exactly the range that control traffic occupies.

Which has a consequence worth stating plainly: comparing the pad is not merely unnecessary, it is wrong.

Chapter 19.3 §3's inserter writes a configured pad value, and a design that changes that value — for a new revision, for a debug build, for a leak-detection experiment — breaks every scoreboard that was comparing it. The pad is not the frame's content; it is the design's filler, and the only property worth asserting about it is Chapter 19.3 §22's: that it was written at all.


3. RTL 1 — The Scoreboard Package and the Digest

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// scoreboard_pkg -- what a scoreboard entry holds, and why it is not a
// frame. Sections 2 through 5.
//
// Twelve frames in flight -- Chapter 19.1 Section 6 -- at up to 9 000
// octets each is 108 000 octets of testbench storage per direction. A
// digest of the invariant core plus the first and last 32 octets is 68
// octets per entry: 816 in total, a factor of 132.
//
// The digest is a CRC-32 over the invariant core. That is deliberate
// reuse of a known-good function and it is NOT Chapter 19.4's engine:
// a digest computed with the design's own CRC block would be Chapter
// 19.4 Section 14's trap in a new place.
// ---------------------------------------------------------------------
package scoreboard_pkg;

  localparam int MAX_FRAME    = 9000;
  localparam int IN_FLIGHT    = 12;     // Chapter 19.1 Section 6
  localparam int TAG_W        = 4;      // 16 > 12, with margin
  localparam int WITNESS_OCT  = 32;     // head and tail kept for diagnosis
  localparam int HDR_OCT      = 14;
  localparam int FCS_OCT      = 4;
  localparam int MIN_FRAME    = 64;

  typedef struct packed {
    logic [TAG_W-1:0]  tag;
    logic [15:0]       wire_length;     // what the design emitted
    logic [15:0]       core_length;     // what the scoreboard compares
    logic [15:0]       upper_length;    // carried from the stimulus
    logic [1:0]        tags_expected;
    logic [31:0]       digest;          // over the invariant core
    logic [31:0]       cycle_in;
    logic              valid;
  } sb_entry_t;

  typedef enum logic [2:0] {
    MM_NONE        = 3'd0,
    MM_DIGEST      = 3'd1,   // the core differs
    MM_LENGTH      = 3'd2,   // the wire length is not explicable
    MM_MISSING     = 3'd3,   // a frame went in and did not come out
    MM_UNEXPECTED  = 3'd4,   // a frame came out that did not go in
    MM_LATE        = 3'd5    // outside the reorder window -- Section 11
  } mismatch_e;

  // The legal modifications, as a mask. A scoreboard that cannot name
  // which one it is allowing has waived them all.
  typedef struct packed {
    logic allow_pad;         // Chapter 19.3 Section 2
    logic allow_fcs;         // Chapter 19.4
    logic allow_tag_insert;  // Chapter 13.2
    logic allow_tag_remove;
    logic allow_checksum;    // Chapter 18.7 Section 16
    logic allow_fragment;    // Chapter 17.3 Section 6
  } allowances_t;

endpackage
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// frame_digest -- a CRC-32 over the invariant core, computed
// independently of the design. Sections 3 and 4.
//
// Chapter 19.4 Section 14's trap, at frame scale: a digest computed by
// the design's own CRC engine compares the design against itself. This
// block uses a bit-serial CRC written from the polynomial -- the same
// reference that chapter used -- and it runs at testbench speed
// because it is a testbench component.
// ---------------------------------------------------------------------
module frame_digest
  import scoreboard_pkg::*;
(
  input  logic              clk,
  input  logic              rst_n,

  input  logic              start,
  input  logic              octet_valid,
  input  logic [7:0]        octet,
  input  logic              octet_in_core,     // Section 5's masker says
  input  logic              finish,

  output logic [31:0]       digest,
  output logic [15:0]       core_octets,
  output logic              digest_valid,

  // The witness. Section 3: a digest says "different" and a witness
  // says "here". Sixty-four octets is the difference between a bug
  // report and a bug report somebody can act on.
  output logic [7:0]        head [WITNESS_OCT],
  output logic [7:0]        tail [WITNESS_OCT]
);

  localparam logic [31:0] POLY = 32'h04C1_1DB7;

  logic [31:0] crc_q;
  logic [15:0] n_q;
  int unsigned tail_ptr;

  // Written from the polynomial, one bit at a time. NOT a call into the
  // design's matrix functions -- Chapter 19.4 Section 14.
  function automatic logic [31:0] step8(input logic [31:0] c, input logic [7:0] b);
    logic [31:0] acc; logic top;
    begin
      acc = c;
      for (int i = 7; i >= 0; i--) begin
        top = acc[31] ^ b[i];
        acc = {acc[30:0], 1'b0} ^ (top ? POLY : 32'd0);
      end
      step8 = acc;
    end
  endfunction

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      crc_q <= '1; n_q <= '0; digest_valid <= 1'b0; tail_ptr <= 0;
      for (int i = 0; i < WITNESS_OCT; i++) begin head[i] <= '0; tail[i] <= '0; end
    end else begin
      if (start) begin
        crc_q <= '1; n_q <= '0; digest_valid <= 1'b0; tail_ptr <= 0;
      end else if (octet_valid && octet_in_core) begin
        crc_q <= step8(crc_q, octet);
        if (n_q < 16'(WITNESS_OCT)) head[n_q] <= octet;
        tail[tail_ptr] <= octet;
        tail_ptr <= (tail_ptr + 1) % WITNESS_OCT;
        n_q <= n_q + 1;
      end

      if (finish) digest_valid <= 1'b1;
    end
  end

  assign digest      = crc_q ^ 32'hFFFF_FFFF;
  assign core_octets = n_q;

endmodule

Classification: the scoreboard's storage primitive, and the block whose independence is the whole argument.

What it teaches: that a digest turns 108 000 octets into 816 and loses exactly one thing: where the difference is. Twelve frames in flight at 9 000 octets each is the storage a naive scoreboard needs; a 32-bit digest plus 64 octets of witness is 68 per entrya factor of 132and the witness is what stops the saving from being a false economy. A mismatch reports "the cores differ, here are the first and last 32 octets of each", which is a bug report rather than a boolean.

And it teaches that step8 must be written and not called. Chapter 19.4 §14 made this argument about a 32-bit check value; at frame scale the temptation is larger, because the design has a perfectly good CRC engine sitting right there and using it costs nothing. A digest computed by the design under test compares the design against itself — and would agree on a frame the design corrupted, because it corrupted the digest the same way.

Deliberately simplified: tail is a circular buffer of the last 32 core octets and is written on every octet, which is fine in a testbench and would be silly in hardware. The digest is CRC-32 for familiarity, where any good hash serves; the choice matters only for the miss probability in Section 4. And octet_in_core arrives as an input — Section 5's masker computes it — so this block trusts somebody else to have decided what the core is.

Production implication: the witness size is the parameter to set deliberately and 32 octets is a reasonable default. A head of 32 covers the destination address, the source address, the EtherType and the first 18 octets of payload — enough to identify which frame it was and what protocol it carried. A tail of 32 covers the end of the payload, which is where truncation shows. A scoreboard that keeps a digest and no witness reports mismatches nobody can act on, and one that keeps whole frames spends 132 times the memory for a case that occurs once per regression.


4. What a Digest Can Miss

A 32-bit digest maps 9 000 octets onto 32 bits, so some corruptions are invisible. This section is how many.

Given that a frame's core has been corrupted, the digest still matches with probability 2⁻³².

Digest widthP(miss a given corruption)Corrupted frames to expect one miss
16 bits1.5 × 10⁻⁵65 536
32 bits2.3 × 10⁻¹⁰4.29 × 10⁹
64 bits5.4 × 10⁻²⁰1.8 × 10¹⁹

Row two is the design point and the number to read is the third column: 4.29 billion corrupted frames. That is not 4.29 billion frames; it is 4.29 billion frames that the design got wrong, and a design getting that many frames wrong has been caught by something else long before.

And the comparison that makes 32 bits obviously sufficient is with the thing it is checking.

Escape probability
Chapter 19.4's FCS missing a corruption2⁻³²
this digest missing a corruption2⁻³²

They are the same number, which is not a coincidence — both are 32-bit CRCs — and it means the scoreboard is exactly as good at detecting corruption as the protocol the design implements. A scoreboard with a stronger digest would catch errors the Ethernet FCS would have let through, which is a real capability and is not what a scoreboard is for.

Row one is worth keeping in the table because 16 bits is what somebody reaches for when the entry size matters. 65 536 corrupted frames to a miss sounds large and is not: a directed error-injection test — Chapter 20.5's subject — corrupts frames deliberately, and a run of a hundred thousand injected errors expects one to go unnoticed. At 32 bits the same run expects 2.3 × 10⁻⁵ of a miss.

And there is a second kind of miss the table does not show, which is a collision between two different frames rather than a failure to notice a corruption.

With twelve entries live, the probability that two of them share a digest by accident is a birthday bound.

Entries in flightP(a collision among them)
121.5 × 10⁻⁸
644.7 × 10⁻⁷
1 0241.2 × 10⁻⁴

Row one is the design point and it is negligible; row three is why the window matters. A scoreboard that holds a thousand frames — because its window is long and its traffic is fasthas a collision every eight thousand fills, and a collision presents as Section 7's out_ambiguous on two frames that are genuinely different. The entry count and the digest width are coupled, and twelve entries at 32 bits leaves five orders of magnitude of margin.

So the sizing rule is short.

Size the digest against the number of frames you intend to corrupt, not against the number you intend to send.


5. RTL 2 — The Invariant Masker

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// invariant_masker -- decide, octet by octet, what the digest sees.
// Sections 2 and 5.
//
// Four of a frame's seven regions are the design's to change, and two
// of them move everything after them. So the mask is a small state
// machine over the frame rather than a byte range, and it needs one
// input the wire does not carry: the upper-layer length.
//
// Chapter 5.6: on a type-form frame there is no length field, so the
// pad boundary is unrecoverable from the frame alone. The masker takes
// it from the stimulus, which is what makes a scoreboard two-sided.
// ---------------------------------------------------------------------
module invariant_masker
  import scoreboard_pkg::*;
(
  input  logic              clk,
  input  logic              rst_n,

  input  logic              sof,
  input  logic              octet_valid,
  input  logic [7:0]        octet,
  input  logic              eof,

  // From the stimulus, not from the wire.
  input  logic [15:0]       upper_length,
  input  allowances_t       allow,
  input  logic [15:0]       checksum_offset,   // 0 if none

  output logic              in_core,
  output logic [15:0]       core_octets,
  output logic [15:0]       pad_octets,
  output logic              pad_boundary_unknown
);

  logic [15:0] pos;
  logic [1:0]  tags_seen;
  logic [15:0] header_end;
  logic [15:0] payload_end;

  // Chapter 13.2: nothing in a frame announces that a tag follows, so
  // the masker detects them the same way Chapter 19.2's parser does --
  // by reading the EtherType at 12 and looking again at 16 and 20.
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      pos <= '0; tags_seen <= '0; core_octets <= '0; pad_octets <= '0;
      pad_boundary_unknown <= 1'b0;
    end else if (sof) begin
      pos <= '0; tags_seen <= '0; core_octets <= '0; pad_octets <= '0;
    end else if (octet_valid) begin
      pos <= pos + 1;

      // A tag at 12 or at 16. Detecting it is the same computation the
      // design does and the masker must do it independently -- reading
      // the design's own tag count would be Chapter 19.4 Section 14
      // again, at a different granularity.
      if ((pos == 16'd12 || pos == 16'd16) &&
          (octet == 8'h81)) tags_seen <= tags_seen + 1;

      if (in_core)                core_octets <= core_octets + 1;
      if (pos >= payload_end && !eof) pad_octets <= pad_octets + 1;

      // Section 2: the pad's extent is unrecoverable from the frame.
      // If the stimulus did not supply upper_length, the masker cannot
      // place the boundary and says so rather than guessing.
      if (upper_length == 16'd0 && allow.allow_pad)
        pad_boundary_unknown <= 1'b1;
    end
  end

  assign header_end  = 16'd14 + 16'd4 * 16'(tags_seen);
  assign payload_end = header_end + upper_length;

  always_comb begin
    in_core = 1'b0;
    if (octet_valid) begin
      // Addresses and EtherType: always in the core.
      if (pos < 16'd12) in_core = 1'b1;
      // The tag itself: in the core only if the design is not allowed
      // to insert or remove one. Section 2's table, row three.
      else if (pos < header_end)
        in_core = !(allow.allow_tag_insert || allow.allow_tag_remove);
      // The upper-layer payload, minus the checksum the design writes.
      else if (pos < payload_end)
        in_core = !(allow.allow_checksum &&
                    (pos >= checksum_offset) && (pos < checksum_offset + 16'd2));
      // Pad and check value: never.
      else in_core = 1'b0;
    end
  end

endmodule

Classification: the block that decides what a scoreboard is entitled to compare, and the one that needs an input the wire does not carry.

What it teaches: that pad_boundary_unknown is not a defensive check, it is the chapter's central limitation encoded. Chapter 5.6 established that a type-form frame carries no length, so a 64-octet frame is indistinguishable from the outside whether it holds one octet of data and forty-five of pad or forty-six of data. The masker cannot place the boundary; it must be told, and a scoreboard built as a pure wire monitor cannot check any frame at the minimum size.

And it teaches that the tag detection must be done again, independently. The design counts tags — Chapter 19.2 §5's stack detector — and reading its count would make the scoreboard agree with the design about where the payload starts, which is exactly the frame-scale form of Chapter 19.4 §14's trap. The masker looks at octet 12 and octet 16 itself, which is fifteen lines and is the difference between a check and a tautology.

Deliberately simplified: tag detection tests one octet of the TPID — 8'h81where a correct detector matches the full 16-bit 0x8100 and Chapter 13.2's alternative TPIDs besides. checksum_offset is a single 16-bit position where Chapter 18.7 §16's engine may write two checksums at two depths. And the masker handles neither Chapter 17.3's fragments nor jumbo tags, both of which change the header's extent again.

Production implication: allow should come from a configuration that a reviewer reads, and every bit in it narrows what the scoreboard checks. A run with allow_tag_insert set stops comparing four to eight octets of every tagged frame — which is correct when the design inserts tags and is a silent hole when it does not. The honest report is the number of octets excluded, per frame and in aggregate, which Section 15 computes: a scoreboard comparing 23.4% of a minimum-size frame should say so on its summary line, because the alternative is a green run whose green covers a quarter of the traffic.


6. Twelve Frames in Flight, and the Tag

At one hundred gigabits per second a media access controller has one point three one two cycles per minimum size frame, so a sixteen stage pipeline holds twelve frames. That sets the scoreboard's capacity at twelve entries but it does not, by itself, require any matching machinery: a pipeline delivers frames in the order it received them, so a simple first in first out scoreboard of depth twelve would work. Two other mechanisms do reorder. The read reorder sink in the memory interface holds responses that arrived before the one being waited for, bounded at the outstanding limit minus one bursts. And multi queue distribution sends frames to different receive queues which complete independently, so two frames in different queues have no defined relative order at all and the reordering is unbounded. The consequence for a scoreboard is that matching by order works within one queue and fails across queues, producing mismatch reports that name two frames which are both correct. The fix is a tag derived from the frame's own content, since the controller has no field to carry one, together with a window that decides how long a frame may be absent before it counts as lost.1.312cycles/frame19.1 Section 416 stages hold 12capacity, notreorderingReorder sinkbounded at O - 1burstsMulti-queueunbounded acrossqueuesA FIFO of 12worksif there is one queueMatching by orderfailstwo correct framesnamedA content-derivedtag4 bits, 16 entriesPlus a windowSection 12's policy12
Figure 2 — a pipeline needs twelve entries and preserves order; queues need a tag and have no order at all.

Chapter 19.1 §6 established that at 100 Gb/s a sixteen-stage pipeline holds twelve frames. A scoreboard that matches the next frame out against the next frame in is wrong from the first cycle.

The arithmetic is that chapter's and it reproduces.

Value
cycles per minimum-size frame1.312 — Chapter 19.1 §4
a 16-stage pipeline holds12.2 frames
so a scoreboard needsat least 12 outstanding entries
tag width4 bits — 16 with margin

And there are two separate reasons a frame can come out in a different order than it went in.

SourceChapterHow far out of order
pipeline depth aloneChapter 19.1 §6none — a pipeline preserves order
multi-queueChapter 18.7 §11unbounded across queues
the reorder sinkChapter 19.6 §9bounded by the outstanding limit

Row one is the one people expect and it is not a reordering at all. A pipeline holds twelve frames and delivers them in order; the scoreboard needs twelve entries for capacity, not for matching. If the design had one queue and no reorder sink, a simple FIFO scoreboard would work — and twelve entries deep.

Rows two and three are the real reordering and they have different bounds. Chapter 18.7's multi-queue sends frames to different queues which complete independently, so two frames in different queues have no defined relative order at all. Chapter 19.6's reorder sink is bounded at O − 1 bursts.

Which gives the matching rule.

Match by
within one queueorder — a FIFO of 12
across queuesa tag, and a window
the window's sizeSection 11 derives it

Row two is why the scoreboard needs a tag rather than a counter, and the tag has to be carried through the design or reconstructed. Carrying it through is not available — the MAC has no field for it — so the scoreboard reconstructs it from the frame's own content, which is Section 7's subject and is the second place this chapter has to be careful about independence.


7. RTL 3 — The Tag Allocator

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// sb_tag_allocator -- give each in-flight frame an identity the
// scoreboard can match on, without adding anything to the frame.
// Sections 6 and 7.
//
// The MAC has no field for a scoreboard's tag, so the tag has to be
// derived from the frame's own content. The obvious derivation --
// hash the whole frame -- is wrong twice: the design may change the
// frame (Section 2) and two identical frames are legal.
//
// The tag is therefore the digest of the invariant core PLUS a
// sequence number for duplicates, and the duplicate case is the one
// that makes it interesting.
// ---------------------------------------------------------------------
module sb_tag_allocator
  import scoreboard_pkg::*;
(
  input  logic              clk,
  input  logic              rst_n,

  input  logic              in_valid,
  input  logic [31:0]       in_digest,
  output logic [TAG_W-1:0]  in_tag,
  output logic              in_ready,

  input  logic              out_valid,
  input  logic [31:0]       out_digest,
  output logic [TAG_W-1:0]  out_tag,
  output logic              out_matched,
  output logic              out_ambiguous,

  // Observability. Sections 14 and 15.
  output logic [31:0]       c_allocated,
  output logic [31:0]       c_duplicates,
  output logic [31:0]       c_unmatched,
  output logic [TAG_W:0]    outstanding,
  output logic              window_full
);

  logic [31:0]        digest_of [IN_FLIGHT];
  logic [IN_FLIGHT-1:0] busy;
  logic [7:0]         dup_count [IN_FLIGHT];

  int unsigned free_slot, match_slot, n_match;
  logic        have_free;

  always_comb begin
    have_free = 1'b0; free_slot = 0;
    for (int i = IN_FLIGHT-1; i >= 0; i--)
      if (!busy[i]) begin have_free = 1'b1; free_slot = unsigned'(i); end

    // How many outstanding entries share this digest. TWO IDENTICAL
    // FRAMES ARE LEGAL -- a retransmission, a broadcast repeated, a
    // generator sending the same item twice -- so a digest match is
    // not an identification. Section 9's matcher has to break the tie
    // by order within the duplicate set.
    n_match = 0; match_slot = 0;
    for (int i = 0; i < IN_FLIGHT; i++)
      if (busy[i] && digest_of[i] == out_digest) begin
        n_match++; if (n_match == 1) match_slot = unsigned'(i);
      end
  end

  assign in_tag        = TAG_W'(free_slot);
  assign in_ready      = have_free;
  assign out_tag       = TAG_W'(match_slot);
  assign out_matched   = (n_match >= 1);
  assign out_ambiguous = (n_match > 1);
  assign window_full   = !have_free;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      busy <= '0;
      for (int i = 0; i < IN_FLIGHT; i++) begin
        digest_of[i] <= '0; dup_count[i] <= '0;
      end
      c_allocated <= '0; c_duplicates <= '0; c_unmatched <= '0;
    end else begin
      if (in_valid && in_ready) begin
        busy[free_slot]      <= 1'b1;
        digest_of[free_slot] <= in_digest;
        c_allocated          <= c_allocated + 1;
        for (int i = 0; i < IN_FLIGHT; i++)
          if (busy[i] && digest_of[i] == in_digest) c_duplicates <= c_duplicates + 1;
      end

      if (out_valid) begin
        if (out_matched) busy[match_slot] <= 1'b0;
        else             c_unmatched <= c_unmatched + 1;
      end
    end
  end

  always_comb begin
    outstanding = '0;
    for (int i = 0; i < IN_FLIGHT; i++) if (busy[i]) outstanding = outstanding + 1;
  end

endmodule

Classification: an identity allocator, and the block whose interesting case is two frames that are the same.

What it teaches: that a digest is not an identifier. Two identical frames are legal — a retransmission, a repeated broadcast, a generator emitting the same item twiceand Chapter 20.1 §5's weighted distribution makes them common: with w_min at 10% a run produces many identical 64-octet frames. out_ambiguous says the match was not unique, and a scoreboard that resolves it by taking the first match is correct only if the duplicates cannot be reordered relative to each other.

And it teaches that they usually cannot. Chapter 18.7 §8's RSS hash sends identical frames to the same queue — the hash is over the frame's fields — so two identical frames are always in the same queue and always in order. The ambiguity is therefore resolvable by order within the duplicate set, and the argument for that comes from the design's hash rather than from the scoreboard.

Deliberately simplified: n_match and the free-slot search are combinational loops over twelve entries in the same always block that uses them, which is a testbench construct. dup_count is declared and unused — the duplicate ordering Section 9 needs is left to the matcher. And a digest collision between two different frames is not distinguished from a genuine duplicate, which at 2⁻³² across twelve entries is 1.7 × 10⁻⁸ per allocation and is worth the simplification.

Production implication: c_duplicates against c_allocated is the number that says whether the ambiguity path is being exercised, and on Chapter 20.1's default weights it is large. Ten per cent of frames at exactly the minimum size produces duplicates constantly, so a scoreboard that has never handled out_ambiguous has not been run against realistic stimulus. The counter costs nothing and it converts a rare-looking corner into a measured routine one.


8. RTL 4 — The Receive Scoreboard

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// rx_scoreboard -- the frame that arrived on the wire against the frame
// that reached host memory. Sections 8 and 9.
//
// The receive direction is the easier of the two and it is still not
// symmetric with transmit. On receive the design REMOVES things -- the
// preamble, the check value it verified, possibly a tag -- and adds
// almost nothing. On transmit it ADDS: pad, check value, possibly a
// tag. So the two scoreboards have different allowance masks and the
// same structure.
// ---------------------------------------------------------------------
module rx_scoreboard
  import scoreboard_pkg::*;
(
  input  logic              clk,
  input  logic              rst_n,

  // The wire side.
  input  logic              wire_frame_valid,
  input  logic [31:0]       wire_digest,
  input  logic [15:0]       wire_length,
  input  logic              wire_fcs_ok,

  // The memory side, from Chapter 18.3's descriptor completion.
  input  logic              mem_frame_valid,
  input  logic [31:0]       mem_digest,
  input  logic [15:0]       mem_length,
  input  logic [3:0]        mem_queue,

  input  allowances_t       allow,

  output logic              mismatch,
  output mismatch_e         mismatch_kind,
  output logic [TAG_W-1:0]  mismatch_tag,

  // Observability. Sections 14 and 15.
  output logic [31:0]       c_checked,
  output logic [31:0]       c_mismatches,
  output logic [31:0]       c_bad_fcs_dropped,
  output logic [31:0]       c_length_explained,
  output logic              length_unexplained
);

  logic [15:0] expected_mem_length;

  // What the design is allowed to have changed about the length. On
  // receive it strips the check value; Chapter 13.2's tag removal takes
  // four more. Anything else is a mismatch.
  assign expected_mem_length =
    wire_length - 16'(FCS_OCT) - (allow.allow_tag_remove ? 16'd4 : 16'd0);

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      mismatch <= 1'b0; mismatch_kind <= MM_NONE; mismatch_tag <= '0;
      c_checked <= '0; c_mismatches <= '0;
      c_bad_fcs_dropped <= '0; c_length_explained <= '0;
      length_unexplained <= 1'b0;
    end else begin
      mismatch <= 1'b0; mismatch_kind <= MM_NONE;

      // Chapter 19.4 Section 10 classified it RES_BAD and Chapter 7.3
      // says it is discarded. A frame with a bad check value SHOULD NOT
      // reach memory, and a scoreboard that expects it to has asserted
      // the opposite of the design's contract.
      if (wire_frame_valid && !wire_fcs_ok) begin
        c_bad_fcs_dropped <= c_bad_fcs_dropped + 1;
      end

      if (mem_frame_valid) begin
        c_checked <= c_checked + 1;

        if (mem_digest != wire_digest) begin
          mismatch      <= 1'b1;
          mismatch_kind <= MM_DIGEST;
          c_mismatches  <= c_mismatches + 1;
        end else if (mem_length != expected_mem_length) begin
          mismatch           <= 1'b1;
          mismatch_kind      <= MM_LENGTH;
          c_mismatches       <= c_mismatches + 1;
          length_unexplained <= 1'b1;
        end else begin
          c_length_explained <= c_length_explained + 1;
        end
      end
    end
  end

endmodule

Classification: the simpler of the two scoreboards, and the one whose contract is mostly about subtraction.

What it teaches: that the receive direction's legal modifications are removals and they are arithmetically checkable. The design strips the check value — four octets, always — and may strip a tag — four more, if configured. So expected_mem_length is a subtraction, and a length that is not explicable by the allowance mask is a mismatch in its own right, separate from the digest. Two different failures with two different causes, and a scoreboard that checks only the digest misses the second entirely.

And it teaches that c_bad_fcs_dropped is an expectation rather than an error. Chapter 7.3 says a frame with a bad check value is discarded and Chapter 19.4 §10 classifies it; so a bad-FCS frame appearing on the wire and not in memory is the design working. A scoreboard that reports it as MM_MISSING has asserted the opposite of the contract — which is Chapter 18.1's class 49 arriving in a testbench.

Deliberately simplified: the wire and memory sides are assumed to present frames in the same order, so the tag allocator of Section 7 is not instantiated here — a multi-queue design needs it and Section 11's window. mem_queue is an input and is unused, which is where the queue-aware matching would go. And the pad is excluded by Section 5's masker upstream, so this block never sees the frame's octets at all.

Production implication: c_length_explained against c_checked should be equal, and the gap is the number to watch. A design that occasionally delivers a frame whose length is not wire_length − 4 is doing something the allowance mask does not describe — a tag removed when the configuration says it should not be, a jumbo frame truncated, a fragment reassembled — and the length check finds it without any reference to the frame's contents. It is the cheapest check in the scoreboard and the one most likely to fire first.


9. RTL 5 — The Transmit Scoreboard

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// tx_scoreboard -- the frame the driver asked for against the frame on
// the wire. Sections 2, 5 and 9.
//
// The transmit direction is the hard one because the design ADDS, and
// two of the additions change the length by an amount the scoreboard
// has to derive rather than know:
//   pad  = max(0, 60 - (header + upper_length))   -- Chapter 19.3 Section 2
//   fcs  = 4                                      -- Chapter 19.4
//   tag  = 4 per inserted tag                     -- Chapter 13.2
//
// Getting the pad term right requires upper_length, which Chapter 5.6
// says the wire does not carry. Section 5's masker takes it from the
// stimulus and so does this block.
// ---------------------------------------------------------------------
module tx_scoreboard
  import scoreboard_pkg::*;
(
  input  logic              clk,
  input  logic              rst_n,

  // The stimulus side.
  input  logic              req_valid,
  input  logic [31:0]       req_digest,
  input  logic [15:0]       req_upper_length,
  input  logic [1:0]        req_tags,

  // The wire side.
  input  logic              wire_valid,
  input  logic [31:0]       wire_digest,
  input  logic [15:0]       wire_length,
  input  logic [1:0]        wire_tags,

  input  allowances_t       allow,

  output logic              mismatch,
  output mismatch_e         mismatch_kind,

  // Observability. Sections 14 and 15.
  output logic [31:0]       c_checked,
  output logic [31:0]       c_padded,
  output logic [31:0]       c_pad_octets,
  output logic [31:0]       c_mismatches,
  output logic [15:0]       core_share_pct,
  output logic              pad_value_unchecked
);

  logic [15:0] header_len, unpadded, expected_wire, pad_len;

  assign header_len = 16'd14 + 16'd4 * 16'(req_tags);
  assign unpadded   = header_len + req_upper_length;

  // Chapter 19.3 Section 2: the floor is 60 octets BEFORE the check
  // value, so the pad brings the pre-FCS length to 60 and the wire
  // length to 64. A scoreboard that pads to 64 before adding the FCS
  // expects 68 and mismatches on every short frame.
  assign pad_len       = (unpadded < 16'd60) ? (16'd60 - unpadded) : 16'd0;
  assign expected_wire = unpadded + pad_len + 16'(FCS_OCT);

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      mismatch <= 1'b0; mismatch_kind <= MM_NONE;
      c_checked <= '0; c_padded <= '0; c_pad_octets <= '0;
      c_mismatches <= '0; pad_value_unchecked <= 1'b0;
    end else begin
      mismatch <= 1'b0; mismatch_kind <= MM_NONE;

      if (wire_valid) begin
        c_checked <= c_checked + 1;

        if (pad_len != 16'd0) begin
          c_padded     <= c_padded + 1;
          c_pad_octets <= c_pad_octets + 32'(pad_len);
          // Section 2: the pad's VALUE is the design's and comparing it
          // breaks when a revision changes it. Chapter 19.3 Section 22
          // says the property worth asserting is that it was WRITTEN,
          // and that is an assertion rather than a comparison.
          if (!allow.allow_pad) pad_value_unchecked <= 1'b1;
        end

        if (wire_digest != req_digest) begin
          mismatch <= 1'b1; mismatch_kind <= MM_DIGEST;
          c_mismatches <= c_mismatches + 1;
        end else if (wire_length != expected_wire) begin
          mismatch <= 1'b1; mismatch_kind <= MM_LENGTH;
          c_mismatches <= c_mismatches + 1;
        end else if (wire_tags != req_tags && !allow.allow_tag_insert) begin
          mismatch <= 1'b1; mismatch_kind <= MM_UNEXPECTED;
          c_mismatches <= c_mismatches + 1;
        end
      end
    end
  end

  // Section 2's table, measured on this run's actual traffic rather
  // than assumed from the frame-size distribution.
  assign core_share_pct = (c_checked == 0) ? 16'd0
    : 16'(100 - ((c_pad_octets * 32'd100) / (c_checked * 32'd64)));

endmodule

Classification: the harder scoreboard, and the one whose arithmetic has an off-by-four that everybody writes once.

What it teaches: that the pad brings the pre-FCS length to 60 and not the wire length to 64. Chapter 19.3 §2 is explicit and the mistake is natural: the minimum frame is 64 octets on the wire, so a scoreboard that pads to 64 and then adds the four-octet check value expects 68 and mismatches on every short frame. The failure is loud, immediate and uniform, which is the good kind — and it takes twenty minutes to find the first time.

And it teaches that pad_value_unchecked is a report about what the scoreboard did not do. With allow_pad clear the block still does not compare pad octets — Section 5's masker excluded them upstreamso the flag says "there was a pad here and nobody looked at it." The pad's value is Chapter 19.3 §22's assertion, not a scoreboard's comparison, and a scoreboard that silently skips 45 octets of every short frame should say so.

Deliberately simplified: req_upper_length arrives from the stimulus and is trusted absolutely; if the driver and the scoreboard disagree about it, every short frame mismatches on length and nothing says which side is wrong. core_share_pct assumes a 64-octet frame in its denominator, so it is meaningful only on minimum-size traffic. And Chapter 17.3's fragments are not handled at all — a preempted frame arrives as two wire events for one request, which this block would report as MM_MISSING and MM_UNEXPECTED.

Production implication: core_share_pct is the number that belongs on the summary line next to the pass count, because it is the fraction of the traffic the scoreboard actually compared. A run of minimum-size control frames reports something near 23% — Section 2's arithmetic — and a reviewer reading "10 000 frames checked, no mismatches" without it has been told less than they think. The alternative framing is worth stating: 10 000 frames checked over 23% of their octets is 2 300 frames' worth of comparison, and that is the honest number.


10. What the Scoreboards Must Never Do

Six prohibitions, and the first two are the ways a scoreboard stops being a check.

#Must neverBecauseSymptom
1compute its digest with the design's CRC engineChapter 19.4 §14agrees on a frame the design corrupted
2read the design's tag countthe same trap, one field alongagrees about where the payload starts
3compare pad octetsSection 2breaks when a revision changes the pad value
4expect a bad-FCS frame in memoryChapter 7.3class 49 in a testbench
5match by order across queuesChapter 18.7 §11false mismatches under multi-queue
6report a pass without the core shareSection 9a green run over a quarter of the octets

Rows one and two are the same prohibition at two granularities and row two is the one that gets violated. Nobody wires the design's CRC output into a scoreboard's digest; plenty of scoreboards read the design's VLAN tag count to decide where the payload begins, because it is right there and computing it again feels redundant. It is not redundant: it is the independence, and fifteen lines of tag detection is what stands between a check and an agreement.

Row five is the prohibition that produces the most confusing failures. Under Chapter 18.7's multi-queue two frames in different queues have no defined relative order, so a scoreboard matching by order reports a mismatch on a correct design — and the mismatch names two frames that are both fine. The diagnosis takes a day and the fix is Section 7's tag.

And row six is the prohibition about reporting rather than about checking.

What a pass means
on a 9 000-octet frame100.0% of the octets matched
on a 1 518-octet frame99.7%
on a 64-octet frame with 46 payload octets93.8%
on a 64-octet frame with 10 payload octets37.5%
on a 64-octet frame with 1 payload octet23.4%

Three different statements, reported identically, and only the core share distinguishes them.


11. RTL 6 — The Reorder Window

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// reorder_window -- decide how long a frame may be missing before it
// counts as lost. Sections 6, 11 and 12.
//
// Two sources of reordering with two different bounds:
//   multi-queue    -- unbounded across queues (Chapter 18.7 Section 11)
//   the reorder sink -- (O-1) bursts (Chapter 19.6 Section 9)
//
// An unbounded source means the window cannot be derived from the
// design at all. It is derived from the TEST: how long the test is
// prepared to wait before calling a frame missing, which is a policy
// and should be stated as one.
// ---------------------------------------------------------------------
module reorder_window
  import scoreboard_pkg::*;
#(
  parameter int WINDOW_CYCLES = 100_000,
  parameter int NUM_QUEUES    = 4
) (
  input  logic              clk,
  input  logic              rst_n,

  input  logic              enter,
  input  logic [TAG_W-1:0]  enter_tag,
  input  logic [3:0]        enter_queue,

  input  logic              leave,
  input  logic [TAG_W-1:0]  leave_tag,

  output logic              expired,
  output logic [TAG_W-1:0]  expired_tag,

  // Observability. Sections 14 and 15.
  output logic [31:0]       c_entered,
  output logic [31:0]       c_left,
  output logic [31:0]       c_expired,
  output logic [31:0]       max_residency,
  output logic [15:0]       max_out_of_order,
  output logic              window_too_short
);

  logic [31:0] entered_at [IN_FLIGHT];
  logic [3:0]  queue_of   [IN_FLIGHT];
  logic        live       [IN_FLIGHT];
  logic [31:0] now;
  logic [31:0] residency;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      now <= '0; expired <= 1'b0; expired_tag <= '0;
      c_entered <= '0; c_left <= '0; c_expired <= '0;
      max_residency <= '0; max_out_of_order <= '0;
      window_too_short <= 1'b0;
      for (int i = 0; i < IN_FLIGHT; i++) begin
        live[i] <= 1'b0; entered_at[i] <= '0; queue_of[i] <= '0;
      end
    end else begin
      now     <= now + 1;
      expired <= 1'b0;

      if (enter) begin
        live[enter_tag]       <= 1'b1;
        entered_at[enter_tag] <= now;
        queue_of[enter_tag]   <= enter_queue;
        c_entered             <= c_entered + 1;
      end

      if (leave && live[leave_tag]) begin
        live[leave_tag] <= 1'b0;
        c_left          <= c_left + 1;
        residency = now - entered_at[leave_tag];
        if (residency > max_residency) max_residency <= residency;

        // The number that says whether the window is sized right. A run
        // whose worst residency is within a factor of two of the window
        // is a run that is about to start reporting false losses.
        if (residency * 32'd2 > 32'(WINDOW_CYCLES)) window_too_short <= 1'b1;
      end

      // Expiry. Section 12: this is a POLICY, not a derived bound,
      // because multi-queue reordering is unbounded.
      for (int i = 0; i < IN_FLIGHT; i++)
        if (live[i] && (now - entered_at[i]) > 32'(WINDOW_CYCLES)) begin
          expired     <= 1'b1;
          expired_tag <= TAG_W'(i);
          c_expired   <= c_expired + 1;
          live[i]     <= 1'b0;
        end
    end
  end

  always_comb begin
    max_out_of_order = '0;
    for (int i = 0; i < IN_FLIGHT; i++) if (live[i]) max_out_of_order += 16'd1;
  end

endmodule

Classification: a timeout, and the only block in this chapter whose parameter is a policy rather than a derivation.

What it teaches: that an unbounded reordering source makes the window undeliverable from the design. Chapter 19.6 §9's reorder sink is bounded at O − 1 bursts and could be derived; Chapter 18.7 §11's multi-queue has no bound at all — two frames in different queues complete whenever their queues are serviced, and a queue can be starved for as long as the driver leaves it alone. So the window is how long this test waits, and calling it anything else is a fiction.

And it teaches that window_too_short is the only honest way to size it. The block cannot compute the right window; it can report how close the worst observed residency came, and a run whose maximum is within a factor of two of the window is a run whose next execution may report a false loss. That is a measurement rather than a derivation, and it is what a policy parameter deserves.

Deliberately simplified: the expiry loop can fire on several entries in one cycle and reports only the last, which loses expiries under a burst. residency is a variable assigned inside a clocked block, which works and is not good style. And queue_of is recorded and never used — a queue-aware window would apply different timeouts per queue, which is the right refinement and needs Chapter 18.7's service policy to be modelled.

Production implication: max_residency is the number to record across a regression and compare against the window, and the ratio is the only evidence anybody gets that the window is not about to start lying. A scoreboard reporting MM_MISSING is either finding a real bug or has a window that is too short, and the two are indistinguishable from the report. window_too_short separates them before the failure rather than after, and it costs one comparison per completed frame.


12. Where the Window Comes From

Section 11 called the window a policy. This section is what the policy has to cover, because two of the three terms are derivable and the third is not.

Source of delayBoundDerivable?
the pipeline12 frames — Chapter 19.1 §6yes
the reorder sinkO − 1 bursts — Chapter 19.6 §9yes
a stalled memory systemChapter 18.1 §8's worst stallyes, as a parameter
queue service ordernoneNO

Rows one to three add up to a number and row four does not.

TermAt 100 Gb/s
12 frames of pipeline16 cycles
7 bursts of reorder buffer224 cycles — 14 336 octets at 64 per beat
a 2 µs memory stall391 cycles
subtotal631 cycles — 3.23 µs
queue serviceunbounded

Six hundred and thirty-one cycles covers everything the design can do to a frame, and the window is set at 100 000a factor of 158entirely to cover the fourth row. That margin is not a safety factor; it is a guess about how long a driver might leave a queue unserviced, and it should be written down as one.

And the consequence is worth stating because it changes what a missing frame means.

WindowMM_MISSING means
631 cyclesthe design lost it
100 000 cyclesthe design lost it, or a queue went unserviced for 512 µs

Row two is a weaker statement and it is the one a multi-queue environment can make. A scoreboard on a single-queue configuration can set the window at 631 and report losses with confidence; the same scoreboard under multi-queue cannot, and the difference is a property of the test's configuration rather than of the design.

And the window has a cost that is not correctness, which is worth stating because it is the reason nobody sets it generously by default.

WindowEntries the scoreboard must holdWhen a real loss is reported
631 cycles12 — the pipeline's own depth3.2 µs after the frame went in
100 000 cycles12, if the rate is unchanged512 µs after
100 000 cycles at 10× the frame ratemore than 12 — window_fulland frames are dropped by the scoreboard

Row three is the interaction that catches people. The window is a time and the entry count is a capacity; lengthening the window without widening the table means a burst fills it, and window_full means the scoreboard stops accepting frames — which produces MM_UNEXPECTED on the output side for frames it never recorded on the input side. The two parameters move together and only one of them is usually tuned.

Which gives the rule this chapter takes.

Derive the window where the design bounds the delay, and state it as a policy where it does not. A single-queue run and a multi-queue run should not use the same number.


13. What a Scoreboard Cannot Check

A frame that entered the media access controller and did not leave it has not necessarily been lost. The controller is allowed to drop frames for at least five reasons, and each one is counted somewhere. A frame with a bad check value is discarded by the frame validity rules. A frame whose destination address does not match is discarded by the address filter. A frame arriving when the receive buffer is full is dropped, and the counter chapter records both whole frame drops and mid frame truncations. A frame carrying a virtual local area network identifier the port is not a member of is discarded by the port mode rules. And a runt or a giant is discarded as invalid. So a missing frame report is not a failure until it has been checked against all five counters, which means the scoreboard's real end to end check is a conservation law: frames in minus frames out equals the sum of the legal drop counters. That identity is exact only when the pipeline is empty, because the counters commit at different pipeline depths, so under load it needs a skew bound of up to twelve frames in flight. A scoreboard that compares content and does not account for frames cannot distinguish a lost frame from a legally dropped one or from a late one.A frame ismissingin, not outBad check value7.3 discards itAddress filtermiss7.4FIFO overflow19.5 Section 14Wrong VLAN13.3Runt or giant7.3The conservationlawin - out = sum ofdropsExact atquiescenceskewed by 12 underloadOnly then afailureunexplained loss12
Figure 3 — a scoreboard is a content comparison plus a frame accounting, and the accounting is usually missing.

Four things, and naming them is worth more than the checks in the previous sections, because each one is quietly assumed to be covered.

Why not
the pad's valueSection 2 — it is the design's, and comparing it breaks on a revision
the check value's correctnessChapter 19.4's job; a scoreboard would need its own engine
the interframe gapnot part of a frame
whether a dropped frame should have been droppedrequires the design's intent

Row two is the one that looks like an omission and is a boundary. A scoreboard could compute the expected FCS over the transmitted frame and compare it. That is Chapter 19.4's equivalence check, it needs an independently written CRC engine, and that chapter built one. Doing it again in the scoreboard duplicates a check that already exists — and duplicating it with the design's engine is Section 10's first prohibition.

Row four is the one that matters most and it has a specific shape. A frame that went in and did not come out is MM_MISSING, and the design is allowed to drop frames for at least five reasons:

Legal dropChapter
a bad check valueChapter 7.3
an address filter missChapter 7.4
a receive FIFO overflowChapter 19.5 §14
a VLAN the port is not a member ofChapter 13.3
a runt or a giantChapter 7.3

So MM_MISSING is not a failure until it has been checked against all five, and the scoreboard cannot do that alone — it needs the counters. Chapter 19.7's c_bad, c_frames_dropped_whole and undersize are what turn a missing frame into an explained one, and the check is arithmetic:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
frames_in − frames_out == crc_errors + filtered + dropped + undersize + oversize

That identity is the scoreboard's real end-to-end check, and it is a conservation law across the scoreboard and the counter block. Chapter 19.7 §20's class 87 is the warning that comes with it — the counters commit at different pipeline depths — so the identity holds at quiescence and needs a skew bound in flight.

Which is the honest summary of what a scoreboard is: a comparison of content, plus an accounting of frames, plus a reason for every difference in both. Any one of the three alone is incomplete, and the third is the one that is usually missing.


14. RTL 7 — Scoreboard Telemetry

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// scoreboard_telemetry -- what a scoreboard's pass actually covered.
// Section 14.
//
// A pass count is the least informative number a scoreboard produces.
// What a reviewer needs is the SHARE of octets compared, the share of
// frames matched by tag rather than by order, and whether the window
// is close to expiring -- three numbers that turn "10 000 checked, no
// mismatches" into a statement with content.
// ---------------------------------------------------------------------
module scoreboard_telemetry
  import scoreboard_pkg::*;
(
  input  logic              clk,
  input  logic              rst_n,

  input  logic [31:0]       c_checked,
  input  logic [31:0]       c_mismatches,
  input  logic [31:0]       c_core_octets,
  input  logic [31:0]       c_wire_octets,
  input  logic [31:0]       c_ambiguous,
  input  logic [31:0]       c_expired,
  input  logic [31:0]       max_residency,
  input  logic [31:0]       window_cycles,
  input  logic              pad_boundary_unknown,

  // What was actually compared.
  output logic [15:0]       octet_coverage_pct,
  output logic [15:0]       mismatch_per_million,

  // How the matching was done.
  output logic [15:0]       ambiguous_pct_x10,

  // How close the window is to lying.
  output logic [15:0]       window_headroom_pct,
  output logic              window_marginal,
  output logic              coverage_thin
);

  assign octet_coverage_pct = (c_wire_octets == 0) ? 16'd0
    : 16'((c_core_octets * 32'd100) / c_wire_octets);

  assign mismatch_per_million = (c_checked == 0) ? 16'd0
    : 16'((c_mismatches * 32'd1_000_000) / c_checked);

  assign ambiguous_pct_x10 = (c_checked == 0) ? 16'd0
    : 16'((c_ambiguous * 32'd1000) / c_checked);

  assign window_headroom_pct = (window_cycles == 0) ? 16'd0
    : 16'd100 - 16'((max_residency * 32'd100) / window_cycles);

  // Section 11: a run whose worst residency is within a factor of two
  // of the window is about to start reporting false losses.
  assign window_marginal = (window_headroom_pct < 16'd50) && (c_checked > 32'd1000);

  // Section 2: on minimum-size control traffic the invariant core is
  // 23.4% of the frame. That is not a fault and it must be reported,
  // because a pass over a quarter of the octets is a quarter of a pass.
  assign coverage_thin = (octet_coverage_pct < 16'd50) || pad_boundary_unknown;

endmodule

Classification: an observability block whose purpose is to weaken a pass into an accurate statement.

What it teaches: that octet_coverage_pct is the number that belongs beside every pass count. A run of minimum-size control frames reports something near 23% — Section 2 — and "10 000 frames checked, no mismatches" without it claims more than it earned. The honest phrasing is 10 000 frames checked over 23% of their octets, which is the same information and a different impression.

And it teaches that window_marginal reports a future failure rather than a present one. Section 11's window is a policy; a run whose worst residency reached half of it has no margin left, and the next run — with one more queue, or a slower driver — starts reporting MM_MISSING on a correct design. The flag costs one subtraction and it is the difference between a scheduled adjustment and a day of debugging.

Deliberately simplified: coverage_thin folds two unrelated conditions — a low core share and an unknown pad boundary — into one flag, where the responses differ: the first is expected on short traffic and the second is a missing stimulus input. All four outputs divide combinationally. And c_wire_octets counts the wire length including the check value, so the coverage percentage is against the whole frame rather than against what a scoreboard could in principle compare.

Production implication: the three numbers to print are octet_coverage_pct, ambiguous_pct_x10 and window_headroom_pct, and each answers a question a pass count cannot. The first says how much was compared; the second says how often identity was in doubtChapter 20.1's default weights make it large — and the third says whether the next run will still work. None of them is a pass or a fail, and all three change what a pass means.


15. RTL 8 — The Scoreboard Conformance Monitor

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// scoreboard_conformance_monitor -- verdicts about the scoreboard.
// Section 15, and the sixteenth in Modules 18 to 20.
//
// Six verdicts. Two are about the design, two about what the scoreboard
// was able to compare, one about how it matched, and one is the
// independence claim that no signal can carry -- Chapter 19.4 Section
// 14's device, at frame granularity.
// ---------------------------------------------------------------------
module scoreboard_conformance_monitor
  import scoreboard_pkg::*;
#(
  parameter int MIN_COVERAGE_PCT = 50,
  parameter int MIN_CHECKED      = 1000
) (
  input  logic              clk,
  input  logic              rst_n,

  input  logic [31:0]       c_checked,
  input  logic [31:0]       c_mismatches,
  input  logic [31:0]       c_expired,
  input  logic [31:0]       c_unmatched,
  input  logic [15:0]       octet_coverage_pct,
  input  logic              window_marginal,
  input  logic              pad_boundary_unknown,
  input  logic              length_unexplained,
  input  logic              digest_is_independent,   // a human sets this

  output logic              frames_differ,
  output logic              frames_lost,
  output logic              coverage_insufficient,
  output logic              window_unsound,
  output logic              stimulus_incomplete,
  output logic              digest_not_independent,
  output logic              none_of_the_above
);

  always_comb begin
    // About the design. Either means the design did something wrong.
    frames_differ = (c_mismatches != '0) || length_unexplained;
    frames_lost   = (c_expired != '0) || (c_unmatched != '0);

    // About what the scoreboard compared. Section 2: on minimum-size
    // control traffic the core is 23.4% of the frame, so this verdict
    // fires on a correct scoreboard checking realistic traffic -- which
    // is the intended behaviour and needs saying.
    coverage_insufficient = (c_checked > 32'(MIN_CHECKED)) &&
                            (octet_coverage_pct < 16'(MIN_COVERAGE_PCT));

    // About the window. Section 11: a policy, and this says whether it
    // is still a safe one.
    window_unsound = window_marginal;

    // About the stimulus. Chapter 5.6: the pad boundary is not
    // recoverable from the wire, so the stimulus must supply it.
    stimulus_incomplete = pad_boundary_unknown;

    // THE claim no signal can carry. Chapter 19.4 Section 14's device:
    // a digest computed by the design's own engine compares the design
    // against itself, and only a reviewer knows which it is.
    digest_not_independent = !digest_is_independent;

    none_of_the_above = !frames_differ && !frames_lost &&
                        !coverage_insufficient && !window_unsound &&
                        !stimulus_incomplete && !digest_not_independent;
  end

endmodule

Classification: a verdict generator, and the third in the track to depend on a bit a human sets.

What it teaches: that coverage_insufficient fires on a correct scoreboard doing correct work. Minimum-size control traffic gives a 23.4% core share — Section 2 — so a run of ARP replies and acknowledgements trips it every time, and the verdict is not saying the scoreboard is broken. It is saying that a pass over a quarter of the octets should not be reported as a pass over a frame, which is a reporting requirement rather than a fault.

And it teaches that digest_is_independent is Chapter 19.4 §14's device at a new granularity. That chapter needed a reviewer to confirm a 30-line serial CRC was written rather than generated; this one needs a reviewer to confirm the same about a digest over up to 9 000 octets. The surface is larger and the trap is identical: a digest computed with the design's CRC engine agrees on a frame the design corrupted, and no signal distinguishes the two.

Deliberately simplified: frames_lost merges expiry and non-match, which Section 13 argued are different: an expired frame may have been legally dropped and an unmatched one may be a digest collision. MIN_COVERAGE_PCT at 50 is a literal that a short-frame regression should lower deliberately and with a note. And there is no verdict about the allowance mask at all, though every bit set in it narrows the check — which Section 5's production note says should be reported.

Production implication: none_of_the_above for the sixteenth time, and the first time two of the six verdicts are expected to fire on a working setup. coverage_insufficient fires on short traffic and digest_not_independent fires until somebody signs. The pair to read is frames_differ and frames_lost — the design's verdicts — with the other four as context, and an environment that reports all six as a single pass or fail has thrown away the distinction between "the design is wrong" and "we did not look at much."


16. The Cost, Accounted

A scoreboard holding twelve frames in flight at up to nine thousand octets each needs one hundred and eight thousand octets of testbench storage per direction. Storing instead a thirty two bit digest of the invariant core plus thirty two octets from the head and thirty two from the tail is sixty eight octets per entry, or eight hundred and sixteen in total, a factor of one hundred and thirty two. What the compression loses is the location of a difference, and the witness buys most of that back: the head identifies the frame and its protocol, the tail shows truncation, and the case that remains uncovered is a difference in the middle of a long payload. The digest itself must be computed independently of the design. The temptation to use the controller's own cyclic redundancy check engine is larger at frame scale than at thirty two bit scale, because the engine is present and working and the digest covers thousands of octets, but a digest computed by the design under test agrees with the design on a frame the design corrupted. The run time cost is modest: on minimum size traffic the core is fifteen of the eighty four octets of wire period, so the scoreboard digests seventeen point nine per cent of the octets that cross the wire.Whole frames12 x 9 000 = 108 000Digest, 4 octetsover the invariantcoreWitness, 64octets32 head, 32 tail68 octets perentry816 for twelve132x smallerand still diagnosableWritten from thepolynomialnot the design'sengineOtherwise itagreeson a corrupted frame17.9% of wireoctetsnot the bottleneck12
Figure 4 — a digest and a witness are 816 octets where whole frames are 108 000.

Eight blocks, and the accounting that matters is memory in the testbench rather than flops in the design.

BlockFlopsStorage
frame_digest~12064 octets of witness
invariant_masker~110
sb_tag_allocator~45012 digests
rx_scoreboard~180
tx_scoreboard~220
reorder_window~50012 timestamps
scoreboard_telemetry~160
scoreboard_conformance_monitor~20
total~1 760 flops816 octets
the naive alternativethe same flops108 000 octets

The 816 octets is the number to compare, and the comparison is with the naive design.

Storage per direction
whole frames, 12 × 9 000108 000 octets
digest plus 64 octets of witness, 12 × 68816 octets
ratio132×

A factor of 132 for the cost of not knowing where a difference iswhich the 64 octets of witness mostly buy back. A mismatch reports the first and last 32 octets of each side, which identifies the frame and shows truncation, and the cases it does not cover are a difference in the middle of a long payload. Those exist and they are rare enough to justify the trade.

And the run-time cost is the digest.

Value
octets digested per frame, maximum8 996
frames per second at 100 Gb/s, minimum size148.81 M
octets digested per second, minimum-size traffic2.23 G
the same as a share of the wire17.9%

Row four is the useful one: on minimum-size traffic the scoreboard digests 17.9% of the octets that cross the wire, because Section 2's core is 23.4% of the frame and the frame is 64 of the 84 octets of wire period. The digest is not the simulation's bottleneck, which is worth knowing before anybody optimises it.

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

ChapterFlopsNature
Chapter 20.1 — the generator~1 050stimulus
Chapter 20.2 — the assertion library~730checks
this chapter — the scoreboards~1 760checks
subtotal~3 540
Module 19's datapath, for comparison~14 166all of it ships

Three chapters of verification environment against a datapath 4.0 times its size, and the scoreboards are the largest of the three — which is the right shape, because a scoreboard is the only component that has to model what the design is allowed to do rather than merely observe what it did.


17. What the Scoreboards Assume

Nine assumptions, and the first three are about the stimulus rather than about the design.

#AssumptionOwnerIf wrong
1the stimulus supplies the upper-layer lengthChapter 20.1the pad boundary is unrecoverable — Chapter 5.6
2the stimulus and the scoreboard agree on itthe environmentevery short frame mismatches on length
3the allowance mask matches the configurationa humaneither false mismatches or a silent hole
4twelve entries is enoughChapter 19.1 §6window_full; frames dropped by the scoreboard
5identical frames stay in orderChapter 18.7 §8's hashSection 7's ambiguity is unresolvable
6the digest is independently writtena humanChapter 19.4 §14's trap, at frame scale
7a bad-FCS frame does not reach memoryChapter 7.3MM_MISSING on correct behaviour
8the window covers queue servicea policy — Section 12false losses
9the design does not reorder within a queueChapter 18.7 §11matching by order fails

Row five is the assumption this chapter leans on most and it comes from the design. Chapter 18.7 §8's RSS hash is computed over the frame's own fields, so two identical frames hash to the same queue and cannot be reordered relative to each other. That is what makes Section 7's ambiguity resolvable by order within the duplicate set, and a design that steered by something other than a content hash — a round-robin, a load metric — would break it.

Row two is the failure that is hardest to attribute. If the driver and the scoreboard compute upper_length differently — one counting the EtherType, the other notevery short frame mismatches on length and nothing says which side is wrong. The symptom is a scoreboard that reports 100% failures on minimum-size traffic and 0% on everything else, which reads as a design bug in the padding logic.

And row three deserves its own note, because the allowance mask is the scoreboard's largest silent surface.

Allowance setOctets no longer compared, per frame
allow_tag_insert4 or 8
allow_tag_remove4 or 8
allow_checksum2
allow_pad0 to 45
allow_fcs4
all fiveup to 61 of a 64-octet frame

Row six is the accumulation and it is reachable by five reasonable decisions. Each bit is set for a real reason — the design does insert tags, it does write checksums, it does padand the endpoint is a scoreboard comparing three octets of a minimum-size frame. Section 14's octet_coverage_pct is the only thing that reports it, and a run whose mask is fully set should be expected to read in the single digits.

And two deliberately not assumed:

Not assumedWhy not
that the check value is correctChapter 19.4's job — Section 13
that a missing frame is a bugfive legal drops — Section 13

Row two is the boundary that keeps the scoreboard honest. A frame that went in and did not come out is not a failure until the counters have explained it, and the explanation is Chapter 19.7's. A scoreboard that reports MM_MISSING as a failure has asserted that the design never drops, which is Chapter 18.1's class 49 wearing a testbench's clothes.


18. Independence, at Frame Scale

Chapter 19.4 §14 established the principle for thirty-two bits. This section is what changes when the object is a frame, because two things get harder and one gets easier.

A 32-bit check valueA frame
the reference isa 30-line serial CRCa model of the design's legal modifications
writing it independentlyeasy — the polynomial is publishedhard — the modifications are the design's
the trapgenerate the reference from the matrixread the design's tag count, or its pad boundary
detecting the trapa human reads 30 linesa human reads the whole model

Row two is the difficulty. The serial CRC's specification is a polynomial in a standard; the scoreboard's reference is "what this MAC is allowed to do to a frame", which is spread across Chapter 19.3, Chapter 19.4, Chapter 13.2, Chapter 17.3 and Chapter 18.7five chapters and no single document. So the reference is assembled from the same chapters the design was, and the independence is about the reading rather than about the source.

And the thing that gets easier is that the modifications are few and enumerable.

Count
legal modifications5 — Section 1's table
of those, length-changing3
of those, position-changing2

Five is a small number and it is the whole model. A scoreboard's reference does not need to reimplement the MAC; it needs to reimplement five transformations, and Section 9's transmit scoreboard is thirty lines of arithmetic. That is the sense in which frame-scale independence is achievable at all — the reference is not a MAC, it is a list of five things a MAC may do.

Which gives the practical test, and it is a reading test rather than a code test.

For each of the five modifications, does the scoreboard compute it, or does it read it from the design?

ModificationComputed by the scoreboard?
paddingyes — from the stimulus's upper_length
check valuenot compared at all — Section 13
tagsyes — Section 5's independent detection
checksummasked, not computed
fragmentsnot handled — Section 5's simplification

Rows two, four and five are exclusions rather than computations, and that is a legitimate and under-declared strategy: a scoreboard that excludes what it cannot independently model is more honest than one that reads the design's answer. What it owes in return is Section 14's coverage percentage, so that the exclusions are visible in the report rather than in the source.


19. A Scoreboard Is Not a Model

Section 18's reference is five transformations. This section is why it should stay that small, because the pressure to grow it is constant and the growth is where scoreboards fail.

Each thing a scoreboard predicts rather than compares is a thing that can be wrong in the scoreboard.

What a scoreboard could predictWhat it costs
the wire lengthSection 9's arithmetic — 6 lines, worth it
the check valuean independent CRC engine — Chapter 19.4 already has one
the tag's contentsChapter 13.2's priority and DEI handling
the checksumChapter 18.7 §4's pseudo-header
the queueChapter 18.7 §8's hash and §9's indirection table
the interframe gapChapter 19.3 §6's deficit

Row five is the clearest case of the pressure and of why to resist it. Predicting which queue a frame lands in requires reimplementing the RSS hash and the indirection table — and at that point the scoreboard contains a second implementation of a design block, which is a second thing that can be wrong and a second thing to maintain through every change.

The alternative is to check a weaker property that needs no model.

Instead of predictingCheck
which queuethat it arrived in exactly one
the checksum's valuethat the receiver accepted it
the gapChapter 19.3's own conformance monitor
the tag's contentsthat the tag count changed by the configured amount

Every row is weaker and every row is free, and the difference between the columns is a maintenance burden that grows with the design. A scoreboard that predicts everything is a second design; a scoreboard that predicts five things and checks the rest structurally is a check.

And there is a specific failure mode that follows from growing it, worth naming because it is quiet.

A scoreboard that reimplements the RSS hash and gets it wrong in the same way the design doesbecause both were written from the same reading of Chapter 18.7 §8agrees with the design and reports nothing. That is Chapter 19.4 §14's trap arriving through the back door: not by sharing code, but by sharing a misreading. The more a scoreboard predicts, the more surface it shares with the design's author.


20. Properties Worth Asserting, and One Worth Refusing

Thirty-two properties, and the refused one is the check a scoreboard is usually asked for by name.

Group 1 — the digest and its independence.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The digest covers exactly the octets the masker admitted.
a_digest_covers_core: assert property (@(posedge clk) disable iff (!rst_n)
  digest_valid |-> (core_octets == expected_core_octets));

// A frame with a different core has a different digest. This can fail
// -- at 2^-32 -- and asserting it is how the failure is counted.
a_digest_discriminates: assert property (@(posedge clk) disable iff (!rst_n)
  (digest_valid && core_differs) |-> (digest != reference_digest));

// The witness is captured for every digest.
a_witness_captured: assert property (@(posedge clk) disable iff (!rst_n)
  digest_valid |-> (head[0] !== 8'hxx));

// The digest never consults the design's CRC engine. This is a
// CONNECTIVITY property and a tool checks it -- Chapter 20.2 Section 9's
// class 88 detector, pointed at a testbench.
a_digest_independent_wiring: assert property (@(posedge clk)
  digest_source_is_testbench);

// The pad is never digested.
a_pad_excluded: assert property (@(posedge clk) disable iff (!rst_n)
  (octet_valid && in_pad) |-> !in_core);

Group 2 — the masker, where the properties are about the frame's geometry.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The first twelve octets are always in the core.
a_addresses_in_core: assert property (@(posedge clk) disable iff (!rst_n)
  (octet_valid && pos < 16'd12) |-> in_core);

// The check value is never in the core.
a_fcs_out_of_core: assert property (@(posedge clk) disable iff (!rst_n)
  (octet_valid && pos >= wire_length - 16'd4) |-> !in_core);

// A tag is in the core only when the design may not touch it.
a_tag_conditional: assert property (@(posedge clk) disable iff (!rst_n)
  (octet_valid && in_tag) |->
    (in_core == !(allow.allow_tag_insert || allow.allow_tag_remove)));

// The masker's tag detection is its own. Section 5.
a_tags_detected_locally: assert property (@(posedge clk) disable iff (!rst_n)
  tags_seen_source_is_masker);

// Without an upper length the masker declares itself blind rather than
// guessing. Chapter 5.6.
a_blind_declared: assert property (@(posedge clk) disable iff (!rst_n)
  (allow.allow_pad && upper_length == 16'd0) |-> pad_boundary_unknown);

// The core is never larger than the frame.
a_core_bounded: assert property (@(posedge clk) disable iff (!rst_n)
  core_octets <= wire_length);

Group 3 — the length arithmetic, which is where the off-by-four lives.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Chapter 19.3 Section 2: the pad brings the PRE-FCS length to 60.
a_pad_to_sixty: assert property (@(posedge clk) disable iff (!rst_n)
  (wire_valid && pad_len != 16'd0) |-> (unpadded + pad_len == 16'd60));

// So the minimum wire length is 64 and never 68.
a_min_wire_sixty_four: assert property (@(posedge clk) disable iff (!rst_n)
  wire_valid |-> (expected_wire >= 16'd64));

// A frame at or above the floor is not padded.
a_no_pad_above_floor: assert property (@(posedge clk) disable iff (!rst_n)
  (wire_valid && unpadded >= 16'd60) |-> (pad_len == 16'd0));

// On receive the design removes exactly the check value, plus a tag if
// configured.
a_rx_length_explained: assert property (@(posedge clk) disable iff (!rst_n)
  (mem_frame_valid && !length_unexplained) |->
    (mem_length == expected_mem_length));

// The transmit length is the sum of four terms and nothing else.
a_tx_length_sum: assert property (@(posedge clk) disable iff (!rst_n)
  wire_valid |-> (expected_wire == header_len + req_upper_length +
                                   pad_len + 16'(FCS_OCT)));

Group 4 — matching and the window.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// A tag is allocated only when one is free.
a_tag_when_free: assert property (@(posedge clk) disable iff (!rst_n)
  (in_valid && in_ready) |-> !busy[free_slot]);

// Outstanding never exceeds the pipeline depth Chapter 19.1 Section 6
// derived.
a_outstanding_bounded: assert property (@(posedge clk) disable iff (!rst_n)
  outstanding <= (TAG_W+1)'(IN_FLIGHT));

// An ambiguous match is reported and not silently resolved.
a_ambiguity_reported: assert property (@(posedge clk) disable iff (!rst_n)
  (out_valid && n_match > 1) |-> out_ambiguous);

// A matched entry is freed exactly once.
a_freed_once: assert property (@(posedge clk) disable iff (!rst_n)
  (out_valid && out_matched) |=> !busy[$past(match_slot)]);

// Residency is measured from entry.
a_residency_from_entry: assert property (@(posedge clk) disable iff (!rst_n)
  (leave && live[leave_tag]) |->
    ((now - entered_at[leave_tag]) <= 32'(WINDOW_CYCLES)));

// Expiry happens only past the window.
a_expiry_past_window: assert property (@(posedge clk) disable iff (!rst_n)
  expired |-> ((now - entered_at[expired_tag]) > 32'(WINDOW_CYCLES)));

// The window's headroom is reported before it is exhausted. Section 11.
a_marginal_before_failure: assert property (@(posedge clk) disable iff (!rst_n)
  (max_residency * 32'd2 > 32'(WINDOW_CYCLES)) |-> window_marginal);

Group 5 — the drops, where the properties are about what a mismatch means.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// A bad-FCS frame reaching memory is a design failure. Chapter 7.3.
a_bad_fcs_not_delivered: assert property (@(posedge clk) disable iff (!rst_n)
  (wire_frame_valid && !wire_fcs_ok) |-> ##[1:$] !mem_frame_for_that_tag);

// A missing frame is explained by a counter or it is a failure.
// Section 13's conservation law, WITH the skew bound Chapter 19.7
// Section 20's class 87 requires.
a_losses_explained: assert property (@(posedge clk) disable iff (!rst_n)
  ((c_in - c_out) - (c_crc_err + c_filtered + c_dropped +
                     c_undersize + c_oversize)) <= 32'(IN_FLIGHT));

// And at quiescence it is exact.
a_losses_exact_at_idle: assert property (@(posedge clk) disable iff (!rst_n)
  (idle_for > 16'd1000) |->
    ((c_in - c_out) == (c_crc_err + c_filtered + c_dropped +
                        c_undersize + c_oversize)));

// An unexplained length is a mismatch in its own right.
a_length_is_a_mismatch: assert property (@(posedge clk) disable iff (!rst_n)
  length_unexplained |-> mismatch);

// The scoreboard never reports a pass without a coverage figure.
a_pass_carries_coverage: assert property (@(posedge clk) disable iff (!rst_n)
  (c_checked > 32'd0) |-> (octet_coverage_pct != 16'd0));

Group 6 — coverage.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
c_short_frame:     cover property (@(posedge clk) wire_valid && wire_length == 16'd64);
c_padded_frame:    cover property (@(posedge clk) wire_valid && pad_len > 16'd40);
c_ambiguous_match: cover property (@(posedge clk) out_ambiguous);
c_window_used:     cover property (@(posedge clk) outstanding >= (TAG_W+1)'(IN_FLIGHT-2));
c_tag_insert:      cover property (@(posedge clk) wire_tags > req_tags);
c_expiry:          cover property (@(posedge clk) expired);
c_thin_coverage:   cover property (@(posedge clk) octet_coverage_pct < 16'd30);

21. Verification Scenarios

Fifty-eight scenarios for a component whose job is checking, plus a five-run directed test whose content is a frame size and an allowance mask.

The invariant core — 12 scenarios.

#ScenarioExpected
1a 64-octet frame, 46 payload octetscore 60 — 93.8%
2a 64-octet frame, 1 payload octetcore 15 — 23.4%
3a 1 518-octet framecore 1 514 — 99.7%
4a 9 000-octet framecore 8 996 — 100.0%
5a tagged frame, tags in the corecore includes 4 more
6the same with allow_tag_insertcore excludes them
7upper_length not suppliedpad_boundary_unknown
8a checksum at a known offset2 octets masked
9a checksum at an unknown offsetthe mask is wrong and nothing says so
10the check valuenever in the core
11the padnever in the core
12a double-tagged frame8 octets of tag handled

Row nine is the scenario that names a limitation rather than testing one. Chapter 18.7 §4's inserter writes at an offset derived from the frame's own headers; a masker given the wrong offset masks two payload octets and compares two checksum octets, and both mistakes are silent. The scenario exists so that the dependency is recorded.

The digest — 10 scenarios.

#ScenarioExpected
13two identical coresidentical digests
14one octet differentdifferent digests
15the same octets in a different orderdifferent — CRC is not a sum
16a 9 000-octet corecompletes; core_octets = 8 996
17the witness on a 64-octet framehead and tail overlap
18the witness on a 9 000-octet frame32 from each end
19a digest computed by the design's engineagrees on a corrupted frame
20100 000 corrupted framesexpect 2.3 × 10⁻⁵ misses at 32 bits
21the same at 16 bitsexpect 1.5 misses
22start mid-framethe digest restarts

Rows twenty and twenty-one are Section 4's sizing argument as a test, and row nineteen is the trap: a scoreboard wired to the design's CRC engine passes every scenario in this table except that one, and that one has to be built deliberately.

Matching and ordering — 12 scenarios.

#ScenarioExpected
23frames in ordermatched by order or by tag, identically
24twelve frames in flightoutstanding = 12
25a thirteenthwindow_full
26two identical framesout_ambiguous
27the same, resolved by ordercorrect — Chapter 18.7 §8's hash
28frames from two queues, interleavedmatched by tag
29the same matched by orderfalse mismatches
30a frame delayed past the windowMM_LATE
31a frame that never arrivesexpired
32a frame that arrives twicethe second is MM_UNEXPECTED
33max_residency at half the windowwindow_marginal
34max_residency at a tenthclear

Row twenty-nine is the failure that costs a day — a correct design reported as broken, with the mismatch naming two frames that are both fine — and row twenty-eight is the one-line fix.

Length arithmetic — 12 scenarios.

#ScenarioExpected
35a 1-octet payload, untaggedpad 45, wire 64
36a 46-octet payloadpad 0, wire 64
37a 47-octet payloadpad 0, wire 65
38the scoreboard padding to 64 before the FCSexpects 68 — every short frame fails
39a 1-octet payload with one tagpad 41, wire 64
40a 1 500-octet payloadwire 1 518
41receive, 1 518 on the wirememory expects 1 514
42receive with tag removalmemory expects 1 510
43receive without tag removal configuredlength_unexplained
44a jumbo frame at MTU 1 518oversize; not delivered
45a fragmentMM_MISSING then MM_UNEXPECTED
46a 9 000-octet payloadwire 9 018

Row thirty-eight is the off-by-four and it is worth running first, because it fails uniformly on short frames and cleanly on long ones — a signature that reads as a padding bug in the design and is a bug in the scoreboard.

Drops and accounting — 12 scenarios.

#ScenarioExpected
47a bad-FCS framedropped; c_bad_fcs_dropped, not MM_MISSING
48a filtered frameexplained by Chapter 7.4's counter
49a FIFO overflowexplained by Chapter 19.5 §14's counter
50a runtexplained by undersize
51a frame lost for no reasonMM_MISSING, unexplained
52the conservation law at quiescenceexact
53the same under loadoff by up to 12 — Chapter 19.7 §20's class 87
54the same asserted without a skew boundfires immediately
55MM_MISSING reported as a failureclass 49 in a testbench
56a run of ARP repliescoverage_insufficient — correctly
57a run of jumbo framescoverage near 100%
58a pass reported without the coverage figurea_pass_carries_coverage

Rows fifty-two to fifty-four are Section 13's conservation law and its skew, and they are the clearest place in this chapter where a check belonging to the scoreboard depends on a class from another chapter.

The directed test — five runs a stimulus change alone will not produce.

Three of this chapter's failures need a configuration that a default environment does not have.

FailureNeedsA default provides
the 23.4% core1-octet payloads46-octet payloads at the minimum size
the off-by-foura short frame and a careful check of the expected lengtha pass, because 1 518 works
the ordering failuremulti-queue enabledone queue
the ambiguityidentical framesChapter 20.1's w_min supplies them
the dependent digesta deliberately wired-in design enginean independent one

Row one is the entry to notice. A generator that fills the payload to the minimum — 46 octets, so that no padding is needednever exercises the pad path, never produces a thin core, and never trips coverage_insufficient. That is the natural thing for a generator to do and it hides the whole of Section 2.

Construct it. Five runs.

RunPayloadConfigExercises
Auniform 46 … 1 500one queuethe nominal path; coverage near 99%
B1 octet, repeatedone queuethe 23.4% core; the pad path
Cuniformfour queuestag matching; row 29's failure if matched by order
D1 octet with the scoreboard padding to 64one queuethe off-by-four
Euniform, digest wired to the design's CRCone queuerow 19 — agrees on a corrupted frame

Run B is one line of stimulus configuration and it is the run that makes Section 2 real. Every frame is padded by 45 octets, the core is 15 of 64, and octet_coverage_pct reads 23 — which is the number a reviewer needs to see once before trusting any pass count from short traffic.

Run E is the independence test and it must be built deliberately. Wire the scoreboard's digest to the design's CRC engine, corrupt a frame in the datapath after the engine, and watch the scoreboard agree. It is a ten-line change and it is the only way to demonstrate that the independence mattersChapter 19.4 §21's scenario 46 at frame scale.

The oracle, in four parts:

CheckABCDE
octet_coverage_pctnear 9923near 9923near 99
c_mismatches000 with tags, many by ordermany0
coverage_insufficientclearSETclearSETclear
a corrupted frame is caughtyesyesyesyesNO

Row four is the table's point and run E is the only column where it is no. Every other number in run E looks like run A's — same coverage, no mismatches, no verdictsand the scoreboard is not checking anything. That is Chapter 19.4 §14's trap reproduced at frame scale, and the only way it shows is a deliberate corruption.

Row two's run C entry is two results in one cell on purpose. The same stimulus, the same design, and the scoreboard's matching strategy decides whether it reports zero mismatches or hundreds — which is the sharpest illustration in this chapter that a scoreboard's failures are often its own.


22. Debugging a Scoreboard

Four complaints, and three of them are the scoreboard's own failures reported as the design's.

Complaint 1 — "every short frame mismatches on length."

CheckIf yesMeaning
is the expected length 68?padded to 64 before adding the FCSSection 9's off-by-four
do long frames pass?confirms — the pad term is the bugChapter 19.3 §2 pads to 60 pre-FCS
does the driver's upper_length match?if not, the two sides disagreeand nothing says which is wrong
are tags being counted twice?header_len is 18 instead of 14a second off-by-four

Row two is the diagnostic and it takes one run. The failure is uniform on short frames and absent on long ones, which is the signature of a constant added in the wrong place — and it reads as a padding bug in the design, which is where the day goes.

Complaint 2 — "mismatches appeared when we enabled the second queue."

CheckIf yesMeaning
is the scoreboard matching by order?it cannot, across queuesSection 6
do the mismatched frames both look correct?they areconfirms
c_unmatched climbing?frames matched against the wrong entryand freed the wrong slot
does one queue alone pass?definitivelythe design is fine

Row two is the tell and it is unusual: the mismatch report names two frames that are both valid. Chapter 18.7 §11's queues complete independently, so "the next frame out" is not "the next frame in" — and the fix is Section 7's tag, not a change to the design.

Complaint 3 — "the scoreboard passes and the design is broken."

CheckIf yesMeaning
octet_coverage_pct low?the core is a small part of the frameSection 2 — 23.4% on short frames
is the digest wired to the design's CRC?it agrees with the designSection 3's trap
allow mask fully set?everything is excludedthe scoreboard compares the addresses
c_checked near zero?nothing matched at allc_unmatched will say

Row three is the accumulation Section 20's rejected class describes, arriving as a configuration rather than as a property: an allowance mask with every bit set excludes the tags, the pad, the checksum and the check value, and what remains is the addresses and the EtherType. The mask is easier to set than a waiver is to write and it has the same endpoint.

Complaint 4 — "frames are reported missing and the counters say nothing was dropped."

CheckIf yesMeaning
window_marginal set?the window is too shortSection 11
max_residency near the window?confirmsthe frames arrived late, not never
is multi-queue enabled?the window must cover queue serviceSection 12 — unbounded
does the conservation law balance at idle?then nothing was lostdefinitively

Row four is the check that settles it without changing anything. Section 13's identity is exact at quiescence; if it balances, every frame is accounted for and the MM_MISSING reports were window expiries. A scoreboard that does not implement the identity cannot distinguish a lost frame from a late one, which is the most common false alarm in this chapter.

Complaint 5 — "the scoreboard slowed the regression by a third."

CheckIf yesMeaning
is the digest running over whole frames?the masker is not excluding the padSection 5
what is octet_coverage_pct?near 100 on short trafficconfirms — it should be 23
is in_core tied high?the mask was bypassedand the scoreboard compares the pad
does it also mismatch after a pad-value change?definitivelySection 10's row three

Row three is a one-line bypass that somebody added to make a mismatch go away — the pad differed, the mask looked suspicious, and tying in_core high made the failure look like a real one. The cost is 4.3× the digest work on minimum-size traffic and a scoreboard that breaks on the next pad-value change, and octet_coverage_pct reading 100 where Section 2 predicts 23 is the evidence.

And the three symptoms this chapter is systematically blamed for:

SymptomBlamed onUsually is
every short frame failingthe padding logica scoreboard padding to 64
mismatches under multi-queuethe queue logicmatching by order
a green run that missed a bugcoveragea thin core, or a dependent digest

23. Misconceptions

Misconception 1 — "a scoreboard compares the frame in with the frame out."

The wrong model: the frame is preserved and any difference is a bug.

What it costs: an assertion that fires on every frame, followed by four reasonable waivers that end with fourteen octets being compared. The design is specified to transform: it pads, it appends a check value, it may insert a tag, it may write a checksum, it may fragmentfive modifications, three of which change the length.

The corrected model: compute the invariant core and compare that, with the transformations expressed as a mask rather than as exclusions. A mask removes octets and counts them; a waiver removes them silently. Adding a legal transformation then changes the masker and not the property. Sections 2, 5, 20.

Misconception 2 — "the pad is part of the frame, so compare it."

The wrong model: the pad crossed the wire, so it is data.

What it costs: a scoreboard that breaks when a revision changes the pad value — and Chapter 19.3 §3's inserter takes it from a register. On a minimum-size control frame the pad is 70.3% of the wire, so the scoreboard is mostly comparing the design's filler against a copy of the design's filler.

The corrected model: the pad is the design's output, not the stimulus's. The only property worth asserting about it is Chapter 19.3 §22's — that it was written at alland that is an assertion, not a comparison. Sections 2, 9.

Misconception 3 — "we can work out the pad boundary from the frame."

The wrong model: the frame says how long its payload is.

What it costs: a scoreboard that cannot check any minimum-size frame, discovered late. Chapter 5.6: on a type-form frame there is no length field — the EtherType occupies those octets — so a 64-octet frame with one payload octet and forty-five of pad is indistinguishable on the wire from one with forty-six payload octets.

The corrected model: the stimulus supplies upper_length, which makes the scoreboard a two-sided component rather than a wire monitor and is the single assumption everything else in this chapter rests on. A scoreboard built as a pure monitor can check long frames and nothing at the floor. Sections 1, 5, 17.

Misconception 4 — "match the next frame out against the next frame in."

The wrong model: frames come out in the order they went in.

What it costs: false mismatches the moment a second queue is enabled, naming two frames that are both correct. Chapter 18.7 §11's queues complete independently and two frames in different queues have no defined relative order at all.

The corrected model: match by a tag derived from the frame's own content, with a window for the reordering. Identical frames are legal and common — Chapter 20.1's weights produce them — so the tag is ambiguous, and the ambiguity is resolvable only because Chapter 18.7 §8's hash puts identical frames in the same queue. Sections 6, 7.

Misconception 5 — "a missing frame is a bug."

The wrong model: a frame that went in and did not come out has been lost.

What it costs: a scoreboard that reports failures on a working design and gets muted. The MAC may legally drop for at least five reasons — a bad check value, an address filter miss, a FIFO overflow, a VLAN it is not a member of, a runt — and every one of them is counted somewhere in Chapter 19.7.

The corrected model: a scoreboard is a content comparison plus a frame accounting plus a reason for every difference in both, and the third is usually missing. The accounting is a conservation law across the scoreboard and the counters, exact at quiescence and skewed by up to twelve frames in flightChapter 19.7 §20's class 87. Sections 13, 20.

Misconception 6 — "a scoreboard should model the design."

The wrong model: the more the scoreboard predicts, the more it checks.

What it costs: a second implementation of every block it predicts, and a shared misreading with the design's author. A scoreboard that reimplements Chapter 18.7 §8's RSS hash from the same chapter the design was written from agrees with the design when both are wrong — which is Chapter 19.4 §14's trap arriving without any shared code.

The corrected model: predict the five legal transformations and check everything else structurally. Instead of predicting the queue, check that the frame arrived in exactly one; instead of predicting the checksum, check that the receiver accepted it. Weaker, free, and it does not grow with the design. Sections 18, 19.


24. Interview Questions

Question 1 — "Your scoreboard compares the frame in against the frame out. What goes wrong?"

What the answer should establish: the design is specified to transform the frame, in five ways. It pads to Chapter 19.3 §2's sixty-octet floor, appends a four-octet check value, may insert or remove a tag, may write a checksum, and may fragment. The comparison fails on every frame — the check value alone guarantees it. A strong answer names the real failure, which is not the firing: four reasonable waivers later the scoreboard compares the addresses and the EtherType, fourteen octets and no payload, and nobody decided that.

Question 2 — "How much of a minimum-size frame can a scoreboard actually compare?"

What the answer should establish: fifteen octets of sixty-four — 23.4% — on a control frame with one octet of upper-layer data. Fourteen octets of header plus one of payload; forty-five octets are pad and four are the check value the design computed. A strong answer draws the reporting conclusion: a pass over 23.4% of the octets should not be reported the same way as a pass over 99.7%, so the core share belongs on the summary line beside the frame count.

Question 3 — "Can the scoreboard work out where the pad starts?"

What the answer should establish: not from the frame. Chapter 5.6: on a type-form frame there is no length field — the EtherType occupies those two octets — so a 64-octet frame carrying one payload octet is indistinguishable on the wire from one carrying forty-six. The boundary has to come from the stimulus. A strong answer names the architectural consequence: the scoreboard is a two-sided component, not a wire monitor, and a pure monitor can check long frames and nothing at the minimum size.

Question 4 — "You enable a second receive queue and the scoreboard starts reporting mismatches. What happened?"

What the answer should establish: it is matching by order and frames in different queues have no defined relative order. Chapter 18.7 §11's queues complete independently, so "the next frame out" is not "the next frame in." A strong answer notices the diagnostic signature: the mismatch names two frames that are both correct, which is unusual and identifies the cause immediately. The fix is a tag derived from the frame's content, and the tag is ambiguous when two frames are identical — resolvable only because Chapter 18.7 §8's hash puts identical frames in the same queue.

Question 5 — "A frame went in and did not come out. Is that a bug?"

What the answer should establish: not until the counters have explained it. The MAC may legally drop for at least five reasons: a bad check value, an address filter miss, a receive FIFO overflow, a VLAN it is not a member of, and a runt or a giant. A strong answer gives the check: frames in minus frames out equals the sum of those counters, exact at quiescence and off by up to twelve frames in flightChapter 19.7 §20's class 87 — and asserting it without the skew bound fires immediately.

Question 6 — "Why not compute the scoreboard's digest with the design's CRC engine?"

What the answer should establish: because it would agree with the design on a frame the design corrupted. Chapter 19.4 §14 made this argument for a thirty-line serial CRC; at frame scale the temptation is larger — the design has a working engine right there, and the digest is over thousands of octets rather than thirty-two bits. A strong answer extends it to the subtler case: reading the design's tag count to find where the payload starts is the same trap one field along, and fifteen lines of independent tag detection is what stands between a check and an agreement.


25. Questions and Answers


26. What's Next

This chapter checked content and accounted for frames. What neither it nor Chapter 20.2 can say is whether the run was worth doing.

A scoreboard reports mismatches and a suite reports failures; both are silent about the cases that never occurred. Chapter 20.2 §15's fired_pct_x10 says how many properties were exercised, and Section 14's octet_coverage_pct says how much of each frame was comparedbut neither says which of Module 19's structures the run reached.

Chapter 20.4 builds the coverage model, and it inherits three things from the chapters before it.

FromWhat it inherits
Chapter 19.4 §21a single dimension cannot distinguish a thorough run from a narrow one
Chapter 20.1 §9the cross's reachable size depends on the testbench's topology
this chapter §14a pass is a quantified statement, not a boolean

Row one is the model's whole problem. A coverage model tracking Chapter 19.4 §4's sixty-four residue classes cannot tell a run that covered one class and six barrel stages from one that covered one and oneso the cross is the model, and the cross's size is the question. Sixty-four residues by sixty-four alignment offsets is 4 096 cells and only 1 024 of them exist in a loopback topology, which means a coverage goal of 100% is a different statement in two environments that look identical from the outside.

And row three is this chapter's contribution to it. A frame can be checked without being compared — 23.4% of it on short traffic — so a coverage model that counts checked frames is counting something weaker than it sounds. The honest bin is not "a frame of this size was checked"; it is "a frame of this size was compared over this fraction of its octets", which is a cross nobody builds and which Section 14's percentage makes available.


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.