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.
| Modification | Chapter | Octets changed |
|---|---|---|
| padding to the 60-octet floor | Chapter 19.3 §2 | up to 45 added |
| appending the check value | Chapter 19.4 | 4 added |
| inserting or removing a VLAN tag | Chapter 13.2 | 4 or 8 |
| fragmenting with an mCRC | Chapter 17.3 §6 | the whole framing |
| inserting a checksum | Chapter 18.7 §16 | 2 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 check | a scoreboard | |
|---|---|---|
| the reference | a 30-line serial CRC | a model of a MAC |
| what is compared | 32 bits | up to 9 000 octets |
| legal differences | none | five, and the pad's extent is unknowable |
| ordering | one frame at a time | twelve 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
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.
| Region | Octets | May the design change it? |
|---|---|---|
| destination address | 6 | no |
| source address | 6 | no |
| VLAN tags | 0, 4 or 8 | YES — Chapter 13.2 |
| EtherType | 2 | no |
| upper-layer payload | variable | checksum fields only |
| pad | 0 to 45 | YES — it is the design's |
| check value | 4 | YES — 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.
invariant_core = destination ++ source ++ ethertype ++ upper_payload
with the checksum fields maskedAnd its size collapses at the minimum frame.
| Frame | Upper payload | Pad | Invariant core | Share of the frame |
|---|---|---|---|---|
| 64 | 1 | 45 | 15 | 23.4% |
| 64 | 46 | 0 | 60 | 93.8% |
| 1 518 | 1 500 | 0 | 1 514 | 99.7% |
| 9 000 | 8 982 | 0 | 8 996 | 100.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 payload | Pad | Core | Share |
|---|---|---|---|
| 1 | 45 | 15 | 23.4% |
| 10 | 36 | 24 | 37.5% |
| 30 | 16 | 44 | 68.8% |
| 46 | 0 | 60 | 93.8% |
| 47 | 0 | 61 | 93.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
// ---------------------------------------------------------------------
// 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// ---------------------------------------------------------------------
// 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;
endmoduleClassification: 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 entry — a factor of 132 — and 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 width | P(miss a given corruption) | Corrupted frames to expect one miss |
|---|---|---|
| 16 bits | 1.5 × 10⁻⁵ | 65 536 |
| 32 bits | 2.3 × 10⁻¹⁰ | 4.29 × 10⁹ |
| 64 bits | 5.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 corruption | 2⁻³² |
| this digest missing a corruption | 2⁻³² |
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 flight | P(a collision among them) |
|---|---|
| 12 | 1.5 × 10⁻⁸ |
| 64 | 4.7 × 10⁻⁷ |
| 1 024 | 1.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 fast — has 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
// ---------------------------------------------------------------------
// 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
endmoduleClassification: 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'h81 — where 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
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 frame | 1.312 — Chapter 19.1 §4 |
| a 16-stage pipeline holds | 12.2 frames |
| so a scoreboard needs | at least 12 outstanding entries |
| tag width | 4 bits — 16 with margin |
And there are two separate reasons a frame can come out in a different order than it went in.
| Source | Chapter | How far out of order |
|---|---|---|
| pipeline depth alone | Chapter 19.1 §6 | none — a pipeline preserves order |
| multi-queue | Chapter 18.7 §11 | unbounded across queues |
| the reorder sink | Chapter 19.6 §9 | bounded 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 queue | order — a FIFO of 12 |
| across queues | a tag, and a window |
| the window's size | Section 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
// ---------------------------------------------------------------------
// 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
endmoduleClassification: 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 twice — and 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
// ---------------------------------------------------------------------
// 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
endmoduleClassification: 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
// ---------------------------------------------------------------------
// 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)));
endmoduleClassification: 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 upstream — so 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 never | Because | Symptom |
|---|---|---|---|
| 1 | compute its digest with the design's CRC engine | Chapter 19.4 §14 | agrees on a frame the design corrupted |
| 2 | read the design's tag count | the same trap, one field along | agrees about where the payload starts |
| 3 | compare pad octets | Section 2 | breaks when a revision changes the pad value |
| 4 | expect a bad-FCS frame in memory | Chapter 7.3 | class 49 in a testbench |
| 5 | match by order across queues | Chapter 18.7 §11 | false mismatches under multi-queue |
| 6 | report a pass without the core share | Section 9 | a 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 frame | 100.0% of the octets matched |
| on a 1 518-octet frame | 99.7% |
| on a 64-octet frame with 46 payload octets | 93.8% |
| on a 64-octet frame with 10 payload octets | 37.5% |
| on a 64-octet frame with 1 payload octet | 23.4% |
Three different statements, reported identically, and only the core share distinguishes them.
11. RTL 6 — The Reorder Window
// ---------------------------------------------------------------------
// 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
endmoduleClassification: 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 delay | Bound | Derivable? |
|---|---|---|
| the pipeline | 12 frames — Chapter 19.1 §6 | yes |
| the reorder sink | O − 1 bursts — Chapter 19.6 §9 | yes |
| a stalled memory system | Chapter 18.1 §8's worst stall | yes, as a parameter |
| queue service order | none | NO |
Rows one to three add up to a number and row four does not.
| Term | At 100 Gb/s |
|---|---|
| 12 frames of pipeline | 16 cycles |
| 7 bursts of reorder buffer | 224 cycles — 14 336 octets at 64 per beat |
| a 2 µs memory stall | 391 cycles |
| subtotal | 631 cycles — 3.23 µs |
| queue service | unbounded |
Six hundred and thirty-one cycles covers everything the design can do to a frame, and the window is set at 100 000 — a factor of 158 — entirely 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.
| Window | MM_MISSING means |
|---|---|
| 631 cycles | the design lost it |
| 100 000 cycles | the 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.
| Window | Entries the scoreboard must hold | When a real loss is reported |
|---|---|---|
| 631 cycles | 12 — the pipeline's own depth | 3.2 µs after the frame went in |
| 100 000 cycles | 12, if the rate is unchanged | 512 µs after |
| 100 000 cycles at 10× the frame rate | more than 12 — window_full | and 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
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 value | Section 2 — it is the design's, and comparing it breaks on a revision |
| the check value's correctness | Chapter 19.4's job; a scoreboard would need its own engine |
| the interframe gap | not part of a frame |
| whether a dropped frame should have been dropped | requires 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 drop | Chapter |
|---|---|
| a bad check value | Chapter 7.3 |
| an address filter miss | Chapter 7.4 |
| a receive FIFO overflow | Chapter 19.5 §14 |
| a VLAN the port is not a member of | Chapter 13.3 |
| a runt or a giant | Chapter 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:
frames_in − frames_out == crc_errors + filtered + dropped + undersize + oversizeThat 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
// ---------------------------------------------------------------------
// 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;
endmoduleClassification: 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 doubt — Chapter 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
// ---------------------------------------------------------------------
// 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
endmoduleClassification: 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
Eight blocks, and the accounting that matters is memory in the testbench rather than flops in the design.
| Block | Flops | Storage |
|---|---|---|
frame_digest | ~120 | 64 octets of witness |
invariant_masker | ~110 | — |
sb_tag_allocator | ~450 | 12 digests |
rx_scoreboard | ~180 | — |
tx_scoreboard | ~220 | — |
reorder_window | ~500 | 12 timestamps |
scoreboard_telemetry | ~160 | — |
scoreboard_conformance_monitor | ~20 | — |
| total | ~1 760 flops | 816 octets |
| the naive alternative | the same flops | 108 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 000 | 108 000 octets |
| digest plus 64 octets of witness, 12 × 68 | 816 octets |
| ratio | 132× |
A factor of 132 for the cost of not knowing where a difference is — which 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, maximum | 8 996 |
| frames per second at 100 Gb/s, minimum size | 148.81 M |
| octets digested per second, minimum-size traffic | 2.23 G |
| the same as a share of the wire | 17.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:
| Chapter | Flops | Nature |
|---|---|---|
| Chapter 20.1 — the generator | ~1 050 | stimulus |
| Chapter 20.2 — the assertion library | ~730 | checks |
| this chapter — the scoreboards | ~1 760 | checks |
| subtotal | ~3 540 | — |
| Module 19's datapath, for comparison | ~14 166 | all 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.
| # | Assumption | Owner | If wrong |
|---|---|---|---|
| 1 | the stimulus supplies the upper-layer length | Chapter 20.1 | the pad boundary is unrecoverable — Chapter 5.6 |
| 2 | the stimulus and the scoreboard agree on it | the environment | every short frame mismatches on length |
| 3 | the allowance mask matches the configuration | a human | either false mismatches or a silent hole |
| 4 | twelve entries is enough | Chapter 19.1 §6 | window_full; frames dropped by the scoreboard |
| 5 | identical frames stay in order | Chapter 18.7 §8's hash | Section 7's ambiguity is unresolvable |
| 6 | the digest is independently written | a human | Chapter 19.4 §14's trap, at frame scale |
| 7 | a bad-FCS frame does not reach memory | Chapter 7.3 | MM_MISSING on correct behaviour |
| 8 | the window covers queue service | a policy — Section 12 | false losses |
| 9 | the design does not reorder within a queue | Chapter 18.7 §11 | matching 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 not — every 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 set | Octets no longer compared, per frame |
|---|---|
allow_tag_insert | 4 or 8 |
allow_tag_remove | 4 or 8 |
allow_checksum | 2 |
allow_pad | 0 to 45 |
allow_fcs | 4 |
| all five | up 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 pad — and 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 assumed | Why not |
|---|---|
| that the check value is correct | Chapter 19.4's job — Section 13 |
| that a missing frame is a bug | five 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 value | A frame | |
|---|---|---|
| the reference is | a 30-line serial CRC | a model of the design's legal modifications |
| writing it independently | easy — the polynomial is published | hard — the modifications are the design's |
| the trap | generate the reference from the matrix | read the design's tag count, or its pad boundary |
| detecting the trap | a human reads 30 lines | a 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.7 — five 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 modifications | 5 — Section 1's table |
| of those, length-changing | 3 |
| of those, position-changing | 2 |
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?
| Modification | Computed by the scoreboard? |
|---|---|
| padding | yes — from the stimulus's upper_length |
| check value | not compared at all — Section 13 |
| tags | yes — Section 5's independent detection |
| checksum | masked, not computed |
| fragments | not 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 predict | What it costs |
|---|---|
| the wire length | Section 9's arithmetic — 6 lines, worth it |
| the check value | an independent CRC engine — Chapter 19.4 already has one |
| the tag's contents | Chapter 13.2's priority and DEI handling |
| the checksum | Chapter 18.7 §4's pseudo-header |
| the queue | Chapter 18.7 §8's hash and §9's indirection table |
| the interframe gap | Chapter 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 predicting | Check |
|---|---|
| which queue | that it arrived in exactly one |
| the checksum's value | that the receiver accepted it |
| the gap | Chapter 19.3's own conformance monitor |
| the tag's contents | that 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 does — because both were written from the same reading of Chapter 18.7 §8 — agrees 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.
// 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.
// 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.
// 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.
// 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.
// 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.
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.
| # | Scenario | Expected |
|---|---|---|
| 1 | a 64-octet frame, 46 payload octets | core 60 — 93.8% |
| 2 | a 64-octet frame, 1 payload octet | core 15 — 23.4% |
| 3 | a 1 518-octet frame | core 1 514 — 99.7% |
| 4 | a 9 000-octet frame | core 8 996 — 100.0% |
| 5 | a tagged frame, tags in the core | core includes 4 more |
| 6 | the same with allow_tag_insert | core excludes them |
| 7 | upper_length not supplied | pad_boundary_unknown |
| 8 | a checksum at a known offset | 2 octets masked |
| 9 | a checksum at an unknown offset | the mask is wrong and nothing says so |
| 10 | the check value | never in the core |
| 11 | the pad | never in the core |
| 12 | a double-tagged frame | 8 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.
| # | Scenario | Expected |
|---|---|---|
| 13 | two identical cores | identical digests |
| 14 | one octet different | different digests |
| 15 | the same octets in a different order | different — CRC is not a sum |
| 16 | a 9 000-octet core | completes; core_octets = 8 996 |
| 17 | the witness on a 64-octet frame | head and tail overlap |
| 18 | the witness on a 9 000-octet frame | 32 from each end |
| 19 | a digest computed by the design's engine | agrees on a corrupted frame |
| 20 | 100 000 corrupted frames | expect 2.3 × 10⁻⁵ misses at 32 bits |
| 21 | the same at 16 bits | expect 1.5 misses |
| 22 | start mid-frame | the 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.
| # | Scenario | Expected |
|---|---|---|
| 23 | frames in order | matched by order or by tag, identically |
| 24 | twelve frames in flight | outstanding = 12 |
| 25 | a thirteenth | window_full |
| 26 | two identical frames | out_ambiguous |
| 27 | the same, resolved by order | correct — Chapter 18.7 §8's hash |
| 28 | frames from two queues, interleaved | matched by tag |
| 29 | the same matched by order | false mismatches |
| 30 | a frame delayed past the window | MM_LATE |
| 31 | a frame that never arrives | expired |
| 32 | a frame that arrives twice | the second is MM_UNEXPECTED |
| 33 | max_residency at half the window | window_marginal |
| 34 | max_residency at a tenth | clear |
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.
| # | Scenario | Expected |
|---|---|---|
| 35 | a 1-octet payload, untagged | pad 45, wire 64 |
| 36 | a 46-octet payload | pad 0, wire 64 |
| 37 | a 47-octet payload | pad 0, wire 65 |
| 38 | the scoreboard padding to 64 before the FCS | expects 68 — every short frame fails |
| 39 | a 1-octet payload with one tag | pad 41, wire 64 |
| 40 | a 1 500-octet payload | wire 1 518 |
| 41 | receive, 1 518 on the wire | memory expects 1 514 |
| 42 | receive with tag removal | memory expects 1 510 |
| 43 | receive without tag removal configured | length_unexplained |
| 44 | a jumbo frame at MTU 1 518 | oversize; not delivered |
| 45 | a fragment | MM_MISSING then MM_UNEXPECTED |
| 46 | a 9 000-octet payload | wire 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.
| # | Scenario | Expected |
|---|---|---|
| 47 | a bad-FCS frame | dropped; c_bad_fcs_dropped, not MM_MISSING |
| 48 | a filtered frame | explained by Chapter 7.4's counter |
| 49 | a FIFO overflow | explained by Chapter 19.5 §14's counter |
| 50 | a runt | explained by undersize |
| 51 | a frame lost for no reason | MM_MISSING, unexplained |
| 52 | the conservation law at quiescence | exact |
| 53 | the same under load | off by up to 12 — Chapter 19.7 §20's class 87 |
| 54 | the same asserted without a skew bound | fires immediately |
| 55 | MM_MISSING reported as a failure | class 49 in a testbench |
| 56 | a run of ARP replies | coverage_insufficient — correctly |
| 57 | a run of jumbo frames | coverage near 100% |
| 58 | a pass reported without the coverage figure | a_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.
| Failure | Needs | A default provides |
|---|---|---|
| the 23.4% core | 1-octet payloads | 46-octet payloads at the minimum size |
| the off-by-four | a short frame and a careful check of the expected length | a pass, because 1 518 works |
| the ordering failure | multi-queue enabled | one queue |
| the ambiguity | identical frames | Chapter 20.1's w_min supplies them |
| the dependent digest | a deliberately wired-in design engine | an 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 needed — never 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.
| Run | Payload | Config | Exercises |
|---|---|---|---|
| A | uniform 46 … 1 500 | one queue | the nominal path; coverage near 99% |
| B | 1 octet, repeated | one queue | the 23.4% core; the pad path |
| C | uniform | four queues | tag matching; row 29's failure if matched by order |
| D | 1 octet with the scoreboard padding to 64 | one queue | the off-by-four |
| E | uniform, digest wired to the design's CRC | one queue | row 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 matters — Chapter 19.4 §21's scenario 46 at frame scale.
The oracle, in four parts:
| Check | A | B | C | D | E |
|---|---|---|---|---|---|
octet_coverage_pct | near 99 | 23 | near 99 | 23 | near 99 |
c_mismatches | 0 | 0 | 0 with tags, many by order | many | 0 |
coverage_insufficient | clear | SET | clear | SET | clear |
| a corrupted frame is caught | yes | yes | yes | yes | NO |
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 verdicts — and 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."
| Check | If yes | Meaning |
|---|---|---|
| is the expected length 68? | padded to 64 before adding the FCS | Section 9's off-by-four |
| do long frames pass? | confirms — the pad term is the bug | Chapter 19.3 §2 pads to 60 pre-FCS |
does the driver's upper_length match? | if not, the two sides disagree | and nothing says which is wrong |
| are tags being counted twice? | header_len is 18 instead of 14 | a 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."
| Check | If yes | Meaning |
|---|---|---|
| is the scoreboard matching by order? | it cannot, across queues | Section 6 |
| do the mismatched frames both look correct? | they are | confirms |
c_unmatched climbing? | frames matched against the wrong entry | and freed the wrong slot |
| does one queue alone pass? | definitively | the 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."
| Check | If yes | Meaning |
|---|---|---|
octet_coverage_pct low? | the core is a small part of the frame | Section 2 — 23.4% on short frames |
| is the digest wired to the design's CRC? | it agrees with the design | Section 3's trap |
allow mask fully set? | everything is excluded | the scoreboard compares the addresses |
c_checked near zero? | nothing matched at all | c_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."
| Check | If yes | Meaning |
|---|---|---|
window_marginal set? | the window is too short | Section 11 |
max_residency near the window? | confirms | the frames arrived late, not never |
| is multi-queue enabled? | the window must cover queue service | Section 12 — unbounded |
| does the conservation law balance at idle? | then nothing was lost | definitively |
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."
| Check | If yes | Meaning |
|---|---|---|
| is the digest running over whole frames? | the masker is not excluding the pad | Section 5 |
what is octet_coverage_pct? | near 100 on short traffic | confirms — it should be 23 |
is in_core tied high? | the mask was bypassed | and the scoreboard compares the pad |
| does it also mismatch after a pad-value change? | definitively | Section 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:
| Symptom | Blamed on | Usually is |
|---|---|---|
| every short frame failing | the padding logic | a scoreboard padding to 64 |
| mismatches under multi-queue | the queue logic | matching by order |
| a green run that missed a bug | coverage | a 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 fragment — five 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 all — and 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 flight — Chapter 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 flight — Chapter 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 compared — but 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.
| From | What it inherits |
|---|---|
| Chapter 19.4 §21 | a single dimension cannot distinguish a thorough run from a narrow one |
| Chapter 20.1 §9 | the cross's reachable size depends on the testbench's topology |
| this chapter §14 | a 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 one — so 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
Related tutorials
- Related topic
UCIe Scoreboards
Building a distributed UCIe scoreboard that follows obligations rather than expected packets — four independent models instead of one class, associative storage keyed by identity and generation, correlation across semantic operations, transport objects and physical attempts, epoch tracking, recovery-safe state, and a first-divergence report that names the layer instead of the symptom.
- Related topic
Packet Generation
A weight is a per-frame marginal, so it reaches a frame's own properties and nothing else — and 48 of the parser's 64 alignment offsets are unreachable from the transmit side at any weight.
- Related topic
Coverage
A six-dimension cross over this MAC declares 860 160 cells; 54.3% of them are reachable and a loopback topology reaches 13.6% — so 100% means three different things.
- Related topic
Error Injection
A runt is 0.08% of the coverage cross and three of fourteen design paths; and no sequence of frames can overflow a FIFO whose drain rate exceeds the line rate.
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.
