CXL · Module 25
CXL Scoreboards
A scoreboard matches a response to a request by a key, and everything else follows from whether that key is unique. This chapter builds keying, out-of-order matching, end-of-test drain, timeouts, distributed audits, comparison granularity, capacity, ordering rules, cost and the assembled sign-off.
25.3 built checks that evaluate at a clock. A scoreboard evaluates when two things meet — a request and its response, a write and the read that follows it — and everything it can tell you follows from whether it paired them correctly.
The first model is the whole subject. A scoreboard keyed on address instead of tag, over a thousand transactions and two hundred and fifty addresses, matches 375 pairs to the wrong partner and reports nothing wrong, because a wrong match is still a match.
1. The Engineering Problem — Everything Follows From The Key
A key that is not unique matches the wrong pair silently. A thousand transactions over 250 addresses is 750 collisions and 375 mismatched pairs, all of them reported as clean matches. Section 5.
A matcher that pops the head of a queue fails every legal reordering. Forty out-of-order responses on a protocol that permits forty is forty false mismatches — noise that gets the scoreboard disabled. Section 6.
A scoreboard that is not drained never reports a lost transaction. Ten left over with four still in flight is six lost, and an undrained board calls the test clean. Section 7.
A timeout set from the average declares live transactions lost. Twice a hundred-cycle mean is 200 against a worst legal latency of 400 — a 350-cycle response declared lost. Section 8.
And per-interface scoreboards each balance while the system leaks. Four boards clean, ten transactions that entered and never left. Section 9.
This chapter against 25.3, stated precisely. That one owns checks that evaluate at a clock. This one owns checks that evaluate at a match — which is why every model here is about pairing, and why section 14's weak definition is an end-of-test report that says the board is empty.
2. The One-Sentence Model
A scoreboard is reliable when no two transactions share a key, legal reordering is not a mismatch, the board is drained and its leftovers inspected, the end-to-end count balances, and the board holds the peak rather than the mean — and "the board is empty and nothing mismatched" is none of those five.
3. What This Chapter Owns
| Ground | Owner |
|---|---|
| Whether a rule was checked at all | 25.1 |
| Whether a check was wide enough | 25.2 |
| When a check samples and who is told | 25.3 |
| Building the coverage model | 25.5 |
| Pairing a response to its request, and what the pair proves | this chapter |
Deferred:
| Deferred ground | Owner |
|---|---|
| Vacuity, severity and proof depth | 25.3 §6 · §12 · §13 |
| Coherency invariants and snoop-filter recall | 25.2 §5 · §7 |
| Coverage closure arithmetic | 25.5 |
| Buffer depth and watermark policy | 24.5 |
| Cryptographic primitives | out of scope — see §4 |
4. Teaching-Model Boundary
Every model is a small synchronous block isolating one scoreboard decision. A real scoreboard is an associative array, a set of monitors, a transaction class with a comparison method and an end-of-test phase, and none of that is reproduced. What is reproduced is the arithmetic each decision implies, and the shape of the mistake when the decision is made by default.
Three simplifications are worth stating. Section 5 treats collisions as uniformly distributed, where real address reuse is heavily clustered. Section 6 counts reorderings rather than modelling a reorder buffer. Section 13 prices memory linearly in entries, ignoring the allocator. In each case the conclusion is the same and the model is abbreviated.
Each model is built twice — a correct build and a broken build selected by a parameter. Every broken build here is the version somebody writes first: key on address because it is in the packet, pop the head because the queue is already there, skip the drain because the test ended, time out on twice the average, audit each interface because that is where the monitor is. None is careless, and each is the shortest path to a scoreboard that runs.
Figure 1 — Both scoreboards report every transaction matched. A wrong match is still a match, which is why a colliding key produces no error of its own and corrupts every comparison downstream of it.
5. RTL 1 — A Key That Is Not Unique Matches The Wrong Pair
// RTL 1 - the key. A scoreboard matches a response to a request by a key, and
// a key that is not unique matches the wrong pair without ever complaining.
module scoreboard_key #(parameter int KEY_ON_ADDRESS = 0) (
input logic clk, rst_n,
input logic lookup,
input logic [15:0] txns, distinct_addresses, distinct_tags,
output logic [15:0] key_space, collisions, mismatched_pairs,
output logic key_unique,
output logic [7:0] n_lookups, n_colliding,
output logic key_collision_err
);
// Address alone is not unique when two transactions target the same line.
assign key_space = (KEY_ON_ADDRESS != 0) ? distinct_addresses : distinct_tags;
assign collisions = (txns > key_space) ? (txns - key_space) : 16'd0;
// Half a collision pair is matched to the wrong partner on average.
assign mismatched_pairs = collisions >> 1;
assign key_unique = (collisions == 16'd0);
// Transactions sharing a key, matched anyway.
assign key_collision_err = lookup && (txns > key_space) && key_unique;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
n_lookups <= 8'd0; n_colliding <= 8'd0;
end else if (lookup) begin
n_lookups <= n_lookups + 8'd1;
if (!key_unique) n_colliding <= n_colliding + 8'd1;
end
end
endmoduleFive lookups. A thousand transactions unless stated.
| Distinct addresses | Tag key · Address key |
|---|---|
| 250 | 1,000 keys, 0 collisions, unique · 250 keys, 750 collisions, 375 wrong pairs |
| 1,000 | unique · unique — an address key is fine here |
| 999 | unique · 1 collision, 0 wrong pairs once rounded |
| 998 | unique · 2 collisions, 1 wrong pair |
| 250, no transactions | 0 · 0 · trivially unique |
No collisions on a tag key; three of five on an address key.
A scoreboard's key decides what "the same transaction" means, and nothing downstream can recover from getting it wrong. Two outstanding reads to the same address are two transactions; a scoreboard keyed on address sees one entry, matches the first response to it, and the second response finds an empty slot or overwrites a live one. Either way the comparison it performs is between the wrong pair.
Row one is why the failure is silent. Seven hundred and fifty transactions collide, three hundred and seventy-five comparisons are made against the wrong partner, and on a uniform-data test most of those comparisons pass anyway. The scoreboard reports a full match rate. The bug it was built to find is one of the comparisons it got wrong.
Row two is the configuration that keeps the mistake alive. With as many addresses as transactions the address key is genuinely unique, and every early directed test looks exactly like that. The key is not wrong until the traffic gets dense, which is late.
Rows three and four are the smallest observable collision. One collision rounds to no mismatched pair — the two transactions share a key and only one comparison is made. Two collisions is one wrong pair, and that is the first configuration in which the scoreboard actually compares mismatched data.
6. RTL 2 — Popping The Head Fails Every Legal Reordering
// RTL 2 - out-of-order arrival. A scoreboard that pops the head of a queue
// fails every response that overtakes another, on a protocol that allows it.
module order_matching #(parameter int POP_THE_HEAD = 0) (
input logic clk, rst_n,
input logic compare_it,
input logic [15:0] responses, out_of_order, reorder_allowed,
output logic [15:0] matched_ok, false_fails, real_fails,
output logic sound_matcher,
output logic [7:0] n_compares, n_wrong,
output logic order_assumed_err
);
// Associative matching finds a response wherever it lands; a head-of-queue
// matcher fails everything that arrived early.
assign false_fails = (POP_THE_HEAD != 0)
? ((out_of_order > reorder_allowed) ? reorder_allowed
: out_of_order)
: 16'd0;
assign real_fails = (out_of_order > reorder_allowed)
? (out_of_order - reorder_allowed) : 16'd0;
assign matched_ok = (responses > false_fails) ? (responses - false_fails) : 16'd0;
assign sound_matcher = (false_fails == 16'd0);
// Legal reordering reported as a mismatch. No guard on reorder_allowed is
// needed: false_fails is clamped to reorder_allowed, so it is non-zero only
// when reordering is allowed.
assign order_assumed_err = compare_it && (false_fails != 16'd0);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
n_compares <= 8'd0; n_wrong <= 8'd0;
end else if (compare_it) begin
n_compares <= n_compares + 8'd1;
if (!sound_matcher) n_wrong <= n_wrong + 8'd1;
end
end
endmoduleFive comparisons. Two hundred responses unless stated.
| Out of order / allowed | Associative · Head-of-queue |
|---|---|
| 40 / 40 | 0 false fails, 200 matched · 40 false fails, 160 matched |
| 50 / 40 | 10 real failures · 40 false on top of the ten real |
| 0 / 0 | 0 · 0 · both matchers agree |
| 10 / 0 — reordering forbidden | 10 real failures · the same ten — this is what it is right for |
| 1 / 1 | 0 · 1 — the smallest legal reordering it can reject |
No unsound comparisons with associative matching; three of five with head-of-queue matching.
A head-of-queue matcher encodes an ordering assumption that CXL does not make. Responses on .mem and .cache may return out of order within the rules, so a matcher that pops the front of a FIFO fails every response that overtakes another — and each failure is a bug report against correct hardware.
Row two is the failure mode that matters. Fifty reorderings on a protocol that permits forty is ten real violations, buried under forty false ones. The signal-to-noise ratio is one in five, and the response to a scoreboard reporting fifty failures a night is to stop reading it.
Row four is the case the head matcher is right for. On a strictly ordered channel there is nothing to reorder, the two matchers agree exactly, and the simple implementation is the correct one. The mistake is not using a queue; it is using a queue on a channel that permits reordering.
7. RTL 3 — A Board That Is Not Drained Reports Nothing Lost
// RTL 3 - what is left in the scoreboard. A request with no response is a lost
// transaction, and a scoreboard that is not drained at the end never says so.
module end_of_test #(parameter int SKIP_THE_DRAIN = 0) (
input logic clk, rst_n,
input logic finish_it,
input logic [15:0] issued, completed, in_flight_at_end,
output logic [15:0] leftover, lost, legitimately_pending,
output logic test_clean,
output logic [7:0] n_finishes, n_dirty,
output logic drain_skipped_err
);
assign leftover = (issued > completed) ? (issued - completed) : 16'd0;
assign legitimately_pending = (in_flight_at_end > leftover)
? leftover : in_flight_at_end;
// Anything left that was not still in flight was lost.
assign lost = (SKIP_THE_DRAIN != 0) ? 16'd0
: (leftover - legitimately_pending);
assign test_clean = (lost == 16'd0);
// Requests that never completed, on a test that reported clean.
assign drain_skipped_err = finish_it
&& (leftover > legitimately_pending)
&& (lost == 16'd0);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
n_finishes <= 8'd0; n_dirty <= 8'd0;
end else if (finish_it) begin
n_finishes <= n_finishes + 8'd1;
if (!test_clean) n_dirty <= n_dirty + 8'd1;
end
end
endmoduleFive end-of-test checks. A thousand issued unless stated.
| Completed / in flight at end | Leftover · Pending · Lost |
|---|---|
| 990 / 4 | 10 · 4 · 6 lost — the undrained model reports 0 and calls it clean |
| 1,000 / 4 | 0 · 0 · 0 · clean |
| 996 / 4 | 4 · 4 · 0 — exactly clean |
| 995 / 4 | 5 · 4 · 1 lost |
| 990 / 0 | 10 · 0 · all ten lost |
Three dirty when the board is drained; none when it is not.
A transaction with no response is the most serious thing a scoreboard can find and the easiest to not look for. The comparison logic only runs when a pair meets; a request that never gets a partner produces no comparison, no mismatch and no message. It sits in the board until the simulation ends, and if nothing inspects the board at the end it is never mentioned.
Rows three and four are the boundary, and it is the reason the drain needs a second input. Four left over with four still legitimately in flight is clean; five left over with four in flight is one lost. Without the in-flight count the drain either reports every unfinished transaction as lost — noise at the end of every test — or reports none, which is silence.
Row five is the same board with the pipeline empty. Nothing was in flight, so all ten leftovers are lost. The difference between six and ten is entirely the in-flight number, which is the piece of end-of-test information nobody records.
8. RTL 4 — A Timeout From The Average Declares Live Transactions Lost
// RTL 4 - the timeout. A scoreboard declares a transaction lost after some
// wait, and the wait has to exceed the protocol's own worst case.
module match_timeout #(parameter int TIMEOUT_ON_AVERAGE = 0) (
input logic clk, rst_n,
input logic judge,
input logic [15:0] timeout_cycles, mean_latency, worst_latency, actual,
output logic [15:0] threshold, margin, false_timeouts,
output logic declares_lost, threshold_safe,
output logic [7:0] n_judgements, n_false,
output logic early_timeout_err
);
assign threshold = (TIMEOUT_ON_AVERAGE != 0) ? (mean_latency << 1) : timeout_cycles;
assign margin = (threshold > worst_latency) ? (threshold - worst_latency) : 16'd0;
assign declares_lost = (actual > threshold);
assign false_timeouts = (actual <= worst_latency) && (actual > threshold)
? 16'd1 : 16'd0;
assign threshold_safe = (threshold >= worst_latency);
// A live transaction declared lost.
assign early_timeout_err = judge && (false_timeouts != 16'd0);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
n_judgements <= 8'd0; n_false <= 8'd0;
end else if (judge) begin
n_judgements <= n_judgements + 8'd1;
if (false_timeouts != 16'd0) n_false <= n_false + 8'd1;
end
end
endmoduleFive judgements. A mean latency of 100 and a worst legal latency of 400.
| Timeout / actual latency | Threshold · Margin · Verdict |
|---|---|
| 500 / 350 | 500 · 100 · safe, not lost — twice the mean is 200, and declares it lost |
| 500 / 400 | 500 · 100 · the last latency that must not time out |
| 500 / 600 | 500 · 100 · declared lost, and correctly so |
| 400 / 400 | 400 · 0 · exactly safe |
| 399 / 400 | 399 · 0 · not safe — a legal response declared lost |
One false timeout with a specified threshold; four with twice the mean.
A timeout is a claim about the protocol, and the average is not a protocol number. Twice a hundred-cycle mean is two hundred, the worst legal latency is four hundred, and every response between 200 and 400 is a live transaction reported as lost. The mean is a property of the traffic that happened to be run; the worst case is a property of the specification.
Rows four and five are the boundary and it is inclusive. A threshold of exactly the worst legal latency is safe — a 400-cycle response must not time out at 400 — and 399 is not. That single cycle is the difference between a scoreboard that is silent about legal traffic and one that files a bug against it.
Row three is what the timeout is for. A 600-cycle response is past anything the protocol permits, and the scoreboard declares it lost correctly. The threshold has to be high enough to never fire on legal traffic and low enough to fire before the test ends — and only the first of those two has a specification number behind it.
9. RTL 5 — Every Board Balances And The System Leaks
// RTL 5 - distributed scoreboards. Per-interface scoreboards each balance
// while the system loses transactions between them.
module distributed_boards #(parameter int PER_INTERFACE_ONLY = 0) (
input logic clk, rst_n,
input logic audit,
input logic [15:0] into_system, out_of_system, per_board_balanced,
input logic [15:0] boards,
output logic [15:0] system_delta, boards_clean, unaccounted,
output logic system_balanced,
output logic [7:0] n_audits, n_leaking,
output logic end_to_end_ignored_err
);
assign system_delta = (into_system > out_of_system)
? (into_system - out_of_system) : 16'd0;
assign boards_clean = (per_board_balanced > boards) ? boards : per_board_balanced;
// A per-interface audit sees each board balance and never crosses a boundary.
assign unaccounted = (PER_INTERFACE_ONLY != 0) ? 16'd0 : system_delta;
assign system_balanced = (unaccounted == 16'd0);
// Transactions that entered and never left, on boards that all balanced.
assign end_to_end_ignored_err = audit && (system_delta != 16'd0)
&& (unaccounted == 16'd0);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
n_audits <= 8'd0; n_leaking <= 8'd0;
end else if (audit) begin
n_audits <= n_audits + 8'd1;
if (!system_balanced) n_leaking <= n_leaking + 8'd1;
end
end
endmoduleFive audits. Ten thousand transactions into the system, four boards.
| Out of system / boards balanced | Delta · Boards clean · Verdict |
|---|---|
| 9,990 / 4 | 10 · 4 · 10 unaccounted — the per-interface audit reports 0 and balances |
| 10,000 / 4 | 0 · 4 · balanced |
| 9,999 / 4 | 1 · 4 · one is enough to fail the audit |
| 10,010 — more out than in | 0, floored · 4 · reads as balanced |
| 9,990 / 6 reported of 4 | 10 · 4, clamped · 10 unaccounted |
Three leaking under an end-to-end audit; none under a per-interface one.
Every scoreboard in a distributed environment can balance while the system loses transactions. A board on each interface checks that what entered that interface left it. Nothing checks that what entered the device left the device — and a transaction dropped in the fabric between two monitored interfaces is inside neither board's scope.
Row one is the arithmetic of the blind spot. Four boards clean, ten transactions unaccounted, and the only number that shows it is a count taken at the system edges rather than at an interface. It costs two counters and is almost never built.
Row four is a real limit of the model and worth stating. More responses out than requests in floors to zero rather than wrapping — so a duplicated response reads as balanced. A one-directional count detects loss and not duplication, and catching both needs the delta signed or a second counter.
10. RTL 6 — A Whole-Line Comparison Fails On Bytes Nobody Wrote
// RTL 6 - comparison granularity. A write with byte enables changes some bytes
// and not others, and a whole-line comparison fails on the bytes it must not
// have changed.
module compare_grain #(parameter int WHOLE_LINE = 0) (
input logic clk, rst_n,
input logic compare_it,
input logic [15:0] line_bytes, enabled_bytes, differing_bytes,
output logic [15:0] compared, legitimate_diffs, reported_diffs,
output logic comparison_sound,
output logic [7:0] n_compares, n_false,
output logic grain_too_coarse_err
);
// Only the enabled bytes were written, so only they may be compared.
assign compared = (WHOLE_LINE != 0) ? line_bytes : enabled_bytes;
assign legitimate_diffs = (differing_bytes > enabled_bytes)
? enabled_bytes : differing_bytes;
assign reported_diffs = (WHOLE_LINE != 0) ? differing_bytes : legitimate_diffs;
assign comparison_sound = (reported_diffs == legitimate_diffs);
// Bytes outside the write mask, reported as differences. No guard on
// differing_bytes is needed: legitimate_diffs is clamped to enabled_bytes,
// so reported_diffs can exceed it only when more bytes differ than were
// enabled.
assign grain_too_coarse_err = compare_it
&& (reported_diffs > legitimate_diffs);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
n_compares <= 8'd0; n_false <= 8'd0;
end else if (compare_it) begin
n_compares <= n_compares + 8'd1;
if (!comparison_sound) n_false <= n_false + 8'd1;
end
end
endmoduleFive comparisons. A 64-byte line with 20 bytes differing unless stated.
| Enabled bytes | Masked comparison · Whole-line comparison |
|---|---|
| 8 | compares 8, reports 8, sound · compares 64, reports 20 — 12 outside the mask |
| 64 | compares 64, reports 20 · the same 20 — both sound |
| 20 | reports 20, all legitimate · the same 20 — exactly the mask |
| 19 | reports 19 · 20 — one byte outside |
| 8, nothing differing | 0 · 0 |
No unsound comparisons with a masked model; two of five with a whole-line one.
A partial write changes the bytes its mask enables and leaves the rest alone, so those are the only bytes the scoreboard has an expectation for. Comparing all sixty-four manufactures a difference out of every byte the reference model happened to initialise differently — twelve of them here, none of which is a bug.
Rows three and four are the boundary. Twenty bytes differing with twenty enabled is exactly the mask and both models agree; nineteen enabled is one byte outside it, and that single byte is the first false failure. The comparison must be masked, not merely bounded — a whole-line compare that happens to agree is agreeing by luck.
Row two is the case that hides it. A full-line write enables every byte, so the mask is the line and the two models are identical. Full-line writes are the majority of most early traffic, which is why a whole-line comparator survives to the point where partial writes appear.
11. RTL 7 — A Board Sized For The Mean Drops Checks At The Peak
// RTL 7 - scoreboard capacity. A scoreboard sized for the average outstanding
// count drops entries at the peak, and a dropped entry is a check that never
// happens.
module board_capacity #(parameter int SIZE_FOR_MEAN = 0) (
input logic clk, rst_n,
input logic size_it,
input logic [15:0] mean_outstanding, peak_outstanding, entries,
output logic [15:0] needed, shortfall, dropped_checks,
output logic sufficient,
output logic [7:0] n_sizings, n_short,
output logic peak_ignored_err
);
assign needed = (SIZE_FOR_MEAN != 0) ? mean_outstanding : peak_outstanding;
assign shortfall = (needed > entries) ? (needed - entries) : 16'd0;
assign dropped_checks = (peak_outstanding > entries)
? (peak_outstanding - entries) : 16'd0;
assign sufficient = (shortfall == 16'd0);
// A peak above the scoreboard's depth, reported as sufficient.
assign peak_ignored_err = size_it && (peak_outstanding > entries) && sufficient;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
n_sizings <= 8'd0; n_short <= 8'd0;
end else if (size_it) begin
n_sizings <= n_sizings + 8'd1;
if (!sufficient) n_short <= n_short + 8'd1;
end
end
endmoduleFive sizings. A mean of 12 outstanding against a peak of 64 unless stated.
| Board entries | Needed · Shortfall · Dropped |
|---|---|
| 32 | 64 · 32 · 32 checks dropped — the mean model needs 12 and calls it sufficient |
| 64 | 64 · 0 · 0 · exactly sufficient |
| 63 | 64 · 1 · 1 dropped |
| 32, peak equals the mean | 12 · 0 · 0 · both models agree |
| 32, nothing outstanding | 0 · 0 · 0 |
Two short against the peak; none against the mean.
A dropped scoreboard entry is not a lost transaction — it is a check that never happens. The hardware is fine; the environment simply had nowhere to record the request, so when the response arrives there is nothing to compare it against. Most implementations either silently discard or match against an empty slot, and neither reports anything.
Row one is the sizing mistake in one line. A board sized on the mean holds twelve, the traffic peaks at sixty-four, and thirty-two checks are dropped at the peak — which is precisely when the design is under the most stress and the checks matter most.
Row four is the exemption. A workload with no burstiness has a peak equal to its mean, both models agree, and sizing on the average is correct. The mistake is not using the mean; it is using the mean on bursty traffic, which is all real traffic.
12. RTL 8 — Permitting Every Reordering Checks None Of Them
// RTL 8 - which reorderings are legal. A scoreboard that permits every
// reordering cannot detect an ordering violation at all.
module ordering_rules #(parameter int PERMIT_ANYTHING = 0) (
input logic clk, rst_n,
input logic judge,
input logic [15:0] pairs, orderable_pairs, observed_swaps,
output logic [15:0] checked_pairs, illegal_swaps, undetectable,
output logic ordering_checked,
output logic [7:0] n_judgements, n_blind,
output logic ordering_unchecked_err
);
// Only the ordered pairs constrain anything; permitting everything checks
// none of them.
assign checked_pairs = (PERMIT_ANYTHING != 0) ? 16'd0 : orderable_pairs;
assign illegal_swaps = (observed_swaps > checked_pairs)
? checked_pairs : observed_swaps;
assign undetectable = (observed_swaps > illegal_swaps)
? (observed_swaps - illegal_swaps) : 16'd0;
assign ordering_checked = (checked_pairs != 16'd0) || (orderable_pairs == 16'd0);
// Ordered pairs that the scoreboard does not constrain.
assign ordering_unchecked_err = judge && (orderable_pairs != 16'd0)
&& (checked_pairs == 16'd0);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
n_judgements <= 8'd0; n_blind <= 8'd0;
end else if (judge) begin
n_judgements <= n_judgements + 8'd1;
if (!ordering_checked) n_blind <= n_blind + 8'd1;
end
end
endmoduleFive judgements. Two hundred pairs of which forty are ordered.
| Observed swaps / ordered pairs | Checked · Illegal · Undetectable |
|---|---|
| 12 / 40 | 40 · 12 illegal · 0 — the permissive model checks 0 and cannot see any of the 12 |
| 50 / 40 | 40 · 40, clamped · 10 undetectable |
| 12 / 0 — no ordering rules | 0 · 0 · both models are correct here |
| 1 / 1 | 1 · 1 · the smallest ordering rule a permissive board can lose |
| 0 / 40 | 40 · 0 · 0 |
Never blind when the ordered pairs are checked; blind on four of five when they are not.
Section 6's fix and this section's failure are one step apart. A head-of-queue matcher rejects legal reorderings, so the correction is to permit reordering — and the correction applied without a rule set permits all of it, which removes ordering from the scoreboard's remit entirely. The environment goes from noisy to silent in one commit.
Row one is what is lost. Forty ordered pairs, twelve violations observed, and a permissive board detects none of them while reporting a full match rate. Nothing distinguishes its output from a run in which the ordering was correct.
Row three is the exemption that makes the distinction real. On a channel with no ordering constraints, permitting everything is exactly right and both models agree. The question a review has to ask is not "does the scoreboard allow reordering" but "which reorderings, and against what list."
13. RTL 9 — A Scoreboard Is Memory Held For The Whole Run
// RTL 9 - what a scoreboard costs to run. Every outstanding entry is memory
// held for the length of the run, and every match is a search.
module board_cost #(parameter int ENTRIES_ARE_FREE = 0) (
input logic clk, rst_n,
input logic budget,
input logic [15:0] entries, bytes_per_entry, lookups_k, ns_per_lookup,
input logic [15:0] budget_kb,
output logic [15:0] memory_kb, index_kb, total_kb, search_min, overrun,
output logic affordable,
output logic [7:0] n_budgets, n_over,
output logic board_cost_ignored_err
);
logic [31:0] m_q, i_q, s_q, t_q;
assign m_q = (ENTRIES_ARE_FREE != 0) ? 32'd0
: (({16'd0, entries} * {16'd0, bytes_per_entry}) / 32'd1024);
assign memory_kb = (m_q > 32'd65535) ? 16'hFFFF : m_q[15:0];
// An associative board carries an index as well as the entries.
assign i_q = (ENTRIES_ARE_FREE != 0) ? 32'd0
: (({16'd0, entries} * 32'd8) / 32'd1024);
assign index_kb = (i_q > 32'd65535) ? 16'hFFFF : i_q[15:0];
assign s_q = (ENTRIES_ARE_FREE != 0) ? 32'd0
: (({16'd0, lookups_k} * {16'd0, ns_per_lookup}) / 32'd60000);
assign search_min = (s_q > 32'd65535) ? 16'hFFFF : s_q[15:0];
assign t_q = {16'd0, memory_kb} + {16'd0, index_kb};
assign total_kb = (t_q > 32'd65535) ? 16'hFFFF : t_q[15:0];
assign overrun = (total_kb > budget_kb) ? (total_kb - budget_kb) : 16'd0;
assign affordable = (total_kb <= budget_kb);
// A scoreboard holding entries, costed at nothing.
assign board_cost_ignored_err = budget && (entries != 16'd0)
&& (bytes_per_entry != 16'd0)
&& (memory_kb == 16'd0);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
n_budgets <= 8'd0; n_over <= 8'd0;
end else if (budget) begin
n_budgets <= n_budgets + 8'd1;
if (!affordable) n_over <= n_over + 8'd1;
end
end
endmoduleFive budgets. 64-byte entries, 20 million lookups at 200 ns.
| Entries / budget | Memory · Index · Total · Search |
|---|---|
| 4,096 / 256 KB | 256 · 32 · 288 · 66 min · 32 KB over |
| 4,096 / 288 KB | 256 · 32 · 288 · exactly fits |
| 1,024 / 288 KB | 64 · 8 · 72 · comfortable |
| 0 / 288 KB | 0 · 0 · 0 |
| 8 / 288 KB | 0 — 512 bytes rounds to no kilobytes · both models report nothing |
One over budget when the entries are charged; none when they are not.
A scoreboard holds every outstanding transaction for as long as it is outstanding, and the index that finds them again is not free. Four thousand entries of sixty-four bytes is 256 KB, and the associative index adds another 32 KB — twelve percent, which is the part that never appears in an estimate.
Sixty-six minutes of searching is the number that decides the implementation. Twenty million lookups at 200 ns is more than an hour of a regression spent finding partners. An associative array and a linear scan differ by orders of magnitude here, and the choice is usually made without measuring.
Row five is a degenerate case both models agree on, and it is worth driving deliberately. Eight entries of sixty-four bytes is 512 bytes, which rounds to zero kilobytes, so the full model and the free-entries model report the same thing and both raise the same flag. A checker that fired only on the broken build here would be detecting the parameter rather than the cost.
Figure 3 — Four green boards and ten transactions that entered and never left. Every board's scope ends at its own interface, and the loss happens between them — which is why the audit that finds it is a count at the system edges rather than a better scoreboard.
14. RTL 10 — A Scoreboard Sign-Off Assembled
// RTL 10 - a scoreboard assembled. Everything that must hold before "the
// scoreboard is empty and nothing mismatched" is a claim about the design.
module board_signoff #(parameter int BOARD_IS_EMPTY = 0) (
input logic clk, rst_n,
input logic evaluate,
input logic board_empty, // nothing mismatched, nothing left
input logic key_is_unique, // no two transactions share a key
input logic order_respected, // legal reordering is not a mismatch
input logic drained_at_end, // leftovers were inspected
input logic system_audited, // the end-to-end count balances
input logic capacity_at_peak, // the board holds the peak, not the mean
output logic reliable,
output logic [5:0] fail_mask,
output logic [7:0] n_eval, n_reliable,
output logic false_clean_err
);
assign fail_mask[0] = ~board_empty;
assign fail_mask[1] = ~key_is_unique;
assign fail_mask[2] = ~order_respected;
assign fail_mask[3] = ~drained_at_end;
assign fail_mask[4] = ~system_audited;
assign fail_mask[5] = ~capacity_at_peak;
// The board-is-empty build is what an end-of-test report says.
assign reliable = (BOARD_IS_EMPTY != 0) ? board_empty : (fail_mask == 6'd0);
assign false_clean_err = evaluate && reliable && (fail_mask != 6'd0);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
n_eval <= 8'd0; n_reliable <= 8'd0;
end else if (evaluate) begin
n_eval <= n_eval + 8'd1;
if (reliable) n_reliable <= n_reliable + 8'd1;
end
end
endmoduleSeven configurations.
| What fails | Mask · Full model · End-of-test report |
|---|---|
| nothing | 000000 · reliable · reliable |
| two transactions share a key — §5 | 000010 · not reliable · claims reliable |
| legal reordering called a mismatch — §6 | 000100 · not reliable · claims reliable |
| the board was never drained — §7 | 001000 · not reliable · claims reliable |
| no end-to-end audit — §9 | 010000 · not reliable · claims reliable |
| sized for the mean — §11 | 100000 · not reliable · claims reliable |
| the board reported a mismatch | 000001 · not reliable · not reliable |
One reliable under the full model; six under the end-of-test report.
Row four is the one that explains the other five. A board that was never drained produces a clean report by construction — the report says the board is empty because nobody looked in it. That is not a subtle interaction; it is the mechanism by which most of this chapter's failures reach a sign-off.
Row two is the failure with the widest blast radius. A colliding key does not just miss bugs — it compares the wrong data, so every comparison downstream of it is meaningless in a way that no count of mismatches will reveal.
Row three is the only row where the environment is loud. Legal reordering reported as a mismatch produces failures, which get investigated, which cost days and end in the scoreboard being loosened. A false failure is not a harmless error; it is the thing that causes section 12.
Figure 4 — The key is asked first because everything downstream of it is a comparison against the wrong partner. The drain is asked third and is the mechanism for most of the rest: a board nobody opened reports empty. Every exit above is a clean end-of-test report.
15. Quantitative Reasoning
Keying. A thousand transactions over 250 addresses is 750 collisions and 375 wrong pairs, all reported as matches; two collisions is the smallest configuration that mismatches one.
Ordering. Forty legal reorderings under a head-of-queue matcher is forty false failures; at fifty reorderings on a forty-allowance the ratio is ten real to forty false.
Draining. Ten leftover with four in flight is six lost; with nothing in flight it is ten, and the difference is the number nobody records.
Timeouts. Twice a hundred-cycle mean is 200 against a worst legal latency of 400 — every response between them is a live transaction declared lost. A threshold of exactly 400 is safe; 399 is not.
Distributed audits. Four boards clean and ten transactions unaccounted; one is enough to fail an end-to-end count and no per-interface audit sees any of them.
Granularity. A 64-byte line with 8 bytes enabled and 20 differing is 12 false differences; at 19 enabled it is one.
Capacity. A mean of 12 against a peak of 64 on a 32-entry board is 32 checks dropped at the peak, and 63 entries drops one.
Ordering rules. Forty ordered pairs with twelve swaps observed is twelve caught or twelve undetectable, depending on one allowance list.
Cost. Four thousand entries of 64 bytes is 256 KB plus 32 KB of index, and twenty million lookups at 200 ns is 66 minutes of searching.
The assembled model. Six properties, seven configurations, one reliable. The end-of-test report called six reliable.
| Quantity | Correct · Broken · Ratio |
|---|---|
| Pairs compared against the wrong partner | 0 · 375 · 37% of the run |
| False failures on 40 legal reorderings | 0 · 40 · all of them |
| Lost transactions reported | 6 · 0 · all of them |
| Timeout threshold against a 400-cycle worst case | 500 · 200 · 2.5x short |
| Transactions unaccounted across four boards | 10 · 0 reported · the whole leak |
| False byte differences, 8 enabled of 64 | 0 · 12 · 12 bytes of noise |
| Checks dropped at a peak of 64 on 32 entries | 32 · 0 counted · half the peak |
| Ordering violations detectable, of 12 | 12 · 0 · all of them |
| Scoreboard memory, 4,096 entries | 288 KB · 0 counted · 12% is index |
| Configurations called reliable, of 7 | 1 · 6 · 5 false claims |
16. Assertions
Every check is an explicit comparison against an exact value. Icarus Verilog 13.0 has no concurrent assertion support, so each is a procedural comparison against 1'b1, and every one is an equality.
Every model's outputs were listed and each confirmed to appear in an equality before the campaign ran — the step 25.2 §18 introduced and 25.3 §16 formalised. It found one gap here: false_timeouts on the twice-the-mean build, non-zero and observed only through its counter.
Alongside it: every inclusive threshold at exactly equal, every ceiling on and off its boundary, every floor past it, and both builds asserted on every degenerate case.
Keying. Collisions are driven at one and at two, because one collision rounds to no mismatched pair and two is the first that produces one.
chk(kBc == 16'd1, "one collision");
chk(kBm == 16'd0, "which rounds to no mismatched pair");Ordering. Reordering is driven at, above and below the allowance, and the strictly-ordered channel is asserted to make both matchers agree.
Draining. Leftovers equal to the in-flight count and one greater are both driven — the boundary between a clean test and a lost transaction.
Timeouts. A threshold exactly equal to the worst legal latency is asserted safe and one cycle less is not; a genuinely lost transaction is asserted not a false timeout.
chk(tGs == 1'b1, "which is exactly safe");
chk(tGf == 16'd1, "which is a false timeout");Distributed audits. More responses out than in is driven and asserted to floor rather than wrap — a stated limit of a one-directional count.
Granularity. Differences exactly filling the write mask and one byte outside it are both driven.
Capacity. A board holding exactly the peak and one entry short are both driven, and the no-burstiness case is asserted to make both models agree.
Ordering rules. A single ordered pair is driven — the smallest rule a permissive board can lose — and the no-constraints channel is asserted correct in both builds.
Cost. A budget exactly equal to the total is driven, and the rounds-to-zero board is asserted to raise the same flag in both builds.
The assembled model. Every fail mask is asserted as an exact six-bit value, and each of the six bits is driven false alone.
Totals: 270 checks across two testbenches, 132 on the front five models and 138 on the back five, all passing on the unmutated sources.
17. Mutation Testing
Seventy-five mutations were injected one at a time. 75 injected, 75 killed, after two survivors.
| Mutation class | Killed by |
|---|---|
| The two key spaces swapped | 250 keys against 1,000 — §5 row one |
| Every collision mismatching a pair | 375, not 750 — §5 row one |
| A transaction count equal to the key space called colliding | 1,000 addresses for 1,000 transactions — §5 row two |
| The head matcher failing nothing | 40 false failures, not zero — §6 row one |
| The false-fail clamp taken the wrong way | Fifty reorderings against a forty allowance — §6 row two |
| Matched counted against the real failures | 160 matched, not 190 — §6 row one |
| The pending clamp taken the wrong way | Four in flight against ten leftover — §7 row one |
| Leftovers equal to the pending count called lost | Four leftover, four in flight — §7 row three |
| The specified threshold doubling the mean | 500, not 200 — §8 row one |
| A response at the threshold declared lost | 400 against a 400-cycle threshold — §8 row four |
| Safe requiring strictly more than the worst case | A threshold of exactly 400 — §8 row four |
| The system delta inverted | Ten unaccounted, not zero — §9 row one |
| The clean-board clamp taken the wrong way | Six reported of four — §9 row five |
| The two comparison grains swapped | 8 bytes compared against 64 — §10 row one |
| An exact report counted as too coarse | Twenty differences filling a twenty-byte mask — §10 row three |
| The peak and mean models swapped | 64 needed, not 12 — §11 row one |
| A peak equal to the depth called ignored | 64 outstanding on 64 entries — §11 row two |
| The illegal-swap clamp taken the wrong way | Fifty swaps against forty checked pairs — §12 row two |
| The nothing-to-check exemption dropped | A channel with no ordering rules — §12 row three |
| The index dropped from the total | 288 KB, not 256 — §13 row one |
| Bytes-to-kilobytes at 512 | 256 KB, not 512 — §13 row one |
| A matched response counted as an assumed order | Two hundred matched and no assumption — §6 row one |
| Each of the six mask bits reading a neighbour | Six configurations, each failing one property alone — §14 |
| Every counter's polarity inverted | Ten pairs of totals — every section |
Both survivors were dominated guards, and both were deleted. Section 6's order_assumed_err carried a guard on reorder_allowed != 0 — but false_fails is itself clamped to reorder_allowed, so it cannot be non-zero unless reordering is allowed. Section 10's grain_too_coarse_err carried a guard on differing_bytes > enabled_bytes — but legitimate_diffs is clamped to enabled_bytes, so reported_diffs can only exceed it under exactly that condition. In both cases the guard restates a clamp that has already happened upstream.
That is eight dead guards across three chapters — four in batch 023, two in 25.3, two here — and the rate is holding at roughly a quarter of survivors. A clamp is the commonest source: writing min(a, b) and then guarding a later expression with a > b restates a fact the clamp already established.
The survivor class that dominated 25.2 has not reappeared in two chapters, because the output-listing step now runs before the campaign. It found false_timeouts here in under a minute.
18. Verification Strategy
What a testbench for a matching model must cover.
Run the output-listing step before the campaign. Two chapters, two catches, two minutes each. It has now prevented more survivors than any other single practice in this batch.
Look upstream of a guard for a clamp. Both survivors here restate a min that already happened. The test is whether the guard can be false while the rest of the expression is true — and after a clamp, it usually cannot.
Drive the smallest configuration that produces the defect. One collision produces no wrong pair and two produces one; one ordered pair is the smallest rule a permissive board can lose. The smallest case is where an off-by-one lives, and it is not the case a random test generates.
The cases where the simple implementation is right. As many addresses as transactions. A strictly ordered channel. A test with nothing left over. A workload with no burstiness. A full-line write. A channel with no ordering rules. Six exemptions across nine models, each a real configuration and each the reason the simple choice survives review.
Separate the count from the claim. §7's leftover and lost are different outputs because a transaction still in flight is not a lost one — and the whole section is that a board without the in-flight number can only choose between noise and silence.
Counters as a second signature. Ten models, ten pairs of totals, differing in all ten.
What a real scoreboard needs that these models do not have. A clustered address distribution for §5, a reorder-buffer model for §6, and an allocator-aware memory model for §13. All three are abbreviations that preserve the conclusion, and section 26 exercises 1, 3 and 8 are where they come back.
19. Synthesis and Implementation Reality
The key is a class field, and nothing checks that it is unique. A transaction class with an address field and a comparison method compiles and runs whether or not two live transactions can share it. The uniqueness argument is made once, in a review, or never.
Associative matching costs a hash and a bucket walk; queue matching costs nothing. Section 13's sixty-six minutes is the price of the former on a large regression, which is a real reason teams reach for the latter — and section 6 is what they get.
The end-of-test drain lives in a phase that is easy to omit. In a UVM environment it belongs in check_phase, and a scoreboard with no check_phase is syntactically complete and semantically silent. Nothing warns.
Timeouts are usually implemented as a watchdog on the oldest entry, which means the threshold applies to the oldest rather than to each. That is close enough when latencies are similar and wrong when one channel is much slower — the threshold has to be per-channel or set by the slowest.
A distributed environment has a scoreboard per agent because agents are where monitors are. The end-to-end audit of section 9 has no natural home: it belongs to the environment rather than to any agent, which is why it is nobody's to write.
Section 11's capacity is usually unbounded in practice, since an associative array grows. That converts a dropped check into unbounded memory growth — section 13's problem instead of section 11's — and a long soak test is where it surfaces.
20. Silicon Observability
| Counter | Why it matters |
|---|---|
| Distinct key values against transactions in flight, per run | §5 — the direct uniqueness check, and it is one line |
| Scoreboard matches made against entries expected | §5 — a wrong match still increments a match count |
| Responses arriving out of issue order, per channel | §6 — the input a reorder allowance should be written from |
| Entries remaining in each board at end of test | §7 — the number a missing check_phase never prints |
| Transactions in flight at end of test, per channel | §7 — without it the drain must choose noise or silence |
| Match latencies, per channel, as a histogram | §8 — the evidence a timeout should be set from, alongside the specification |
| Transactions into and out of the system, per run | §9 — two counters, and the only detector for a fabric leak |
| Peak board occupancy against its capacity | §11 — a dropped entry is a check that never ran |
| Board memory high-water mark, per run | §13 — an unbounded board's failure mode is memory, not drops |
| Comparison count against transaction count | §14 — the two are different, and only one is usually reported |
"Comparison count against transaction count" is the single most useful line here. A scoreboard reports mismatches; almost none report how many comparisons it actually performed. A board with a colliding key, a dropped entry or a missing drain has a comparison count well below its transaction count — and that gap is visible in every one of this chapter's failures.
21. Debug Lab
Symptom. A CXL type-3 device has run a clean regression for three months. The scoreboard reports zero mismatches on every test. In the lab, a memory read returns data from a different address roughly once in ten million accesses.
Step 1 — how many comparisons did the board make? Section 20's counter does not exist, so it is added: it reports 740,000 comparisons against 1,000,000 transactions. A quarter of the traffic was never compared to anything, and no report had ever said so.
Step 2 — where the quarter went. The board is keyed on address. Section 5. The test generates a thousand transactions over a working set of two hundred and fifty lines, so 750 of every thousand collide — the second request to a line overwrites the first's entry, and one of the two responses finds nothing to compare against.
Step 3 — what the surviving comparisons proved. Of those that did compare, roughly half were matched against the wrong partner. Most passed anyway, because the test's data pattern was a function of address and the two colliding transactions targeted the same address. The scoreboard was structurally unable to detect the bug it was built for.
Step 4 — the leftovers. The board is inspected for the first time at the end of a run: it holds 1,400 entries. Section 7. Nothing had ever looked, because the environment has no check_phase on this scoreboard — section 19's silent omission.
Step 5 — why the number is not the loss. Some of those 1,400 were genuinely in flight when the test ended. The in-flight count is not recorded, so the number of actually-lost transactions cannot be recovered from any existing run and the test has to be re-run with the counter added. It comes back at 1,100 lost.
Step 6 — the fabric. With the key fixed and the drain added, mismatches appear immediately — but the counts still do not balance. Section 9. Transactions into the device: 1,000,000. Out: 999,988. Twelve unaccounted, in a fabric between two monitored interfaces that no board's scope includes.
The finding. One hardware bug — a tag freed before its data landed — and four independent reasons the environment could not see it: an address key that collided on three-quarters of the traffic, a comparison method that passed on wrong pairs because the data was address-derived, a board that was never drained, and a fabric gap outside every board's scope.
The fix. In the RTL, hold the tag until the data is written. In the environment: re-key on the tag, make the test data a function of tag rather than address so a wrong pair cannot pass, add the check_phase and the in-flight counter, and add two counters at the system edges. The first change fixes the machine; the second is what makes every future scoreboard failure visible.
What made this hard. The board reported zero mismatches for three months, and that was true. It had made 740,000 comparisons, roughly half of them against the wrong partner, on data that could not distinguish a wrong partner from a right one.
22. Design Review
1. What is the scoreboard's key, and can two live transactions share it? Address is in the packet and is not unique. Section 5.
2. How many comparisons does the board make, against how many transactions? The gap is the direct symptom of every failure here. Sections 5 and 20.
3. Can the test data distinguish a wrong pair from a right one? Address-derived data cannot. Section 21 step 3.
4. Does the matcher permit reordering, and against what allowance list? Permitting everything removes ordering from the scoreboard entirely. Sections 6 and 12.
5. Is there a check_phase, and what does it do with the leftovers? A board nobody opens reports empty. Sections 7 and 19.
6. Is the in-flight count recorded at end of test? Without it the drain chooses between noise and silence. Section 7.
7. Where did the timeout come from — the mean or the specification? Twice the mean is 200 against a worst legal 400. Section 8.
8. Who owns the end-to-end count? It belongs to no agent, which is why nobody writes it. Sections 9 and 19.
9. Is the comparison masked by the byte enables? Twelve false differences on an eight-byte write. Section 10.
10. What does a clean end-of-test report establish? Section 14 exists because the answer is the last property only.
23. How This Appears In Real Engineering
A verification engineer writes the scoreboard early, when the traffic is sparse and every key is unique. The key is chosen in the first hour of the project and reviewed never, and it is the decision everything else depends on.
A verification lead sees mismatch counts and match rates. Neither number reveals a wrong pair, a dropped entry or an unopened board — the comparison count does, and it is the one nobody asks for.
An architect owns the reorder allowance and the worst legal latency, and both are usually communicated as prose. Sections 6, 8 and 12 all need them as numbers, and a scoreboard built without them defaults to whatever the implementation makes easy.
A designer meets this chapter as section 21 — a bug that a three-month clean regression could not have found. The corrective is not more tests; it is one counter and one field change.
24. Common Misconceptions
"The scoreboard reported no mismatches." It reported no mismatches among the comparisons it made, which may be three-quarters of the traffic (section 5) or none at all if the board was never drained (section 7). The comparison count is the number that matters.
"Address is a fine key — it's unique per line." It is unique per line and not per transaction. Two outstanding reads to the same line share it, and the scoreboard compares the wrong pair without complaint (section 5).
"We allow reordering now, so the false failures are fixed." Allowing everything fixes the noise by removing the check (section 12). The question is which reorderings, against what list.
"The board was empty at the end." It was empty because nothing inspected it, or it held entries nobody printed. A missing check_phase is syntactically complete and semantically silent (sections 7 and 19).
"Each interface's scoreboard balances." And ten transactions entered the device and never left (section 9). No per-interface audit crosses an interface boundary, which is exactly where a fabric loses things.
"We sized the board for the outstanding count." For the mean outstanding count. At a peak of 64 on 32 entries, half the peak's checks never happen (section 11) — under exactly the load where they matter.
25. Interview Reasoning
"What would you key a CXL scoreboard on?" The tag, and the reasoning matters more than the answer: the key must be unique across every transaction that can be live at the same time. Address fails that; a candidate who says so has understood the property rather than memorised the field name.
"Your scoreboard reports zero mismatches. What do you check first?" How many comparisons it made. A match rate is a ratio whose denominator nobody prints, and every failure in this chapter shows up as a comparison count below the transaction count.
"The scoreboard reports fifty failures a night and forty are false. What do you do?" Not loosen it. Separate the two populations first — forty legal reorderings and ten real violations (section 6) — because the tempting fix removes ordering from the scoreboard entirely (section 12) and the ten disappear with the forty.
"How would you set a scoreboard timeout?" From the protocol's worst legal latency, never the observed mean. Twice a hundred-cycle mean is 200 against a legal 400, so every response in between is a live transaction reported lost. Ask back: what does the scoreboard do at exactly the worst legal value?
"Your board is empty at the end of test. What does that establish?" Section 14's six properties. The distinction between an empty board and an inspected one is the whole chapter, and the answer that names check_phase has found the mechanism behind most of the rest.
26. Exercises
1. Re-key under clustered addresses. §5 assumes uniform reuse. Take a working set where 10% of lines carry 90% of traffic and recompute the collision count and wrong-pair count for an address key.
2. Write the reorder allowance. For CXL.mem reads and writes to the same and different addresses, enumerate which pairs may be reordered. How many entries does the allowance list have?
3. Model the reorder buffer. §6 counts reorderings. Replace it with a buffer of depth N and find the depth at which a head-of-queue matcher's false-failure rate first exceeds the real failure rate.
4. Drain properly. Design the end-of-test check for a board with three channels of different latencies. What does it need to know to distinguish lost from in-flight, and where does that number come from?
5. Set a timeout from two sources. Given a specification worst case of 400 and an observed 99.9th percentile of 180, state the threshold you would use and why, and what you would do if they were the other way round.
6. Build the end-to-end audit. Two counters at the system edges. Extend it to detect duplication as well as loss — §9 row four floors at zero — and state the cost.
7. Mask the comparison. Write the comparison for a partial write with byte enables, then compute the false-failure rate for a whole-line comparator across a traffic mix that is 70% full-line and 30% 8-byte writes.
8. Price the board with an allocator. §13 is linear in entries. Add a per-entry allocation overhead and a growth policy, and find the entry count at which the index dominates the entries.
9. Instrument the comparison count. For each of §14's six failures, state what the comparison-count-to-transaction-count ratio would be and whether the ratio alone distinguishes them.
10. Add the seventh property. Propose one none of §14's six implies, name its section, and construct the configuration where the six hold and it fails. A property that cannot fail alone is not a seventh property.
27. Summary
Everything a scoreboard tells you follows from its key. A thousand transactions over 250 addresses is 750 collisions and 375 pairs compared against the wrong partner — and a wrong match is still a match, so nothing is reported.
Two collisions is the smallest configuration that mismatches a pair. One collision rounds to none, which is why the defect needs density before it produces a single wrong comparison.
A head-of-queue matcher fails every legal reordering. Forty on a forty-allowance is forty false failures, and at fifty reorderings the ratio is ten real to forty false — a signal-to-noise ratio that gets scoreboards disabled.
And the fix removes the check. Permitting all reordering makes twelve ordering violations undetectable while the match rate stays at a hundred percent. Noisy to silent in one commit.
A board that is not drained reports nothing lost. Ten leftover with four in flight is six lost — and without the in-flight number the drain can only choose between reporting every unfinished transaction and reporting none.
A timeout from the average declares live transactions lost. Twice a hundred-cycle mean is 200 against a worst legal 400; a threshold of exactly 400 is safe and 399 is not.
Every board balances while the system leaks. Four clean boards and ten transactions that entered and never left, in a fabric inside no board's scope — found by two counters that belong to no agent.
A whole-line comparison manufactures differences. Eight bytes enabled of sixty-four with twenty differing is twelve false failures, none of which is a bug.
A board sized for the mean drops checks at the peak. Twelve against a peak of sixty-four on thirty-two entries is thirty-two checks that never happen — at exactly the load where they matter.
And the board is memory held for the whole run. Four thousand entries of sixty-four bytes is 256 KB plus 32 KB of index, with sixty-six minutes of searching on twenty million lookups.
Both mutation survivors were dominated guards restating an upstream clamp — eight now across three chapters, holding at about a quarter. The output-listing step has caught the previous chapter's dominant class twice in a row, in under a minute each time.
"The board is empty and nothing mismatched" is one property of six. The end-of-test report called six of seven configurations reliable when one was — and §21 is a device three months green whose scoreboard had made 740,000 comparisons against a million transactions, half of them against the wrong partner, on data that could not tell the difference.
25.5 — CXL Functional Coverage takes the number every chapter of this module has leaned on and never defined. This one reported a match rate, 25.3 reported passes, 25.2 reported 100% on a quarter of the space — and coverage is the measurement that is supposed to say when any of it is enough.
Continue learning
Related tutorials
- Related topic
UVM Architecture for CXL
A UVM environment that builds and completes can still be blind to most of what it watches. This chapter builds agent scope, active and passive modes, factory timing, objections, configuration scope, arbitration, analysis wiring, cross-agent coordination, elaboration cost and the assembled sign-off.
- 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
UVM Architecture for UCIe
Assembling a UVM environment for a layered, bidirectional, multi-protocol UCIe endpoint — agents chosen by the DUT boundary, four transaction layers instead of one mega item, sequence layering that composes scenarios across interfaces, a reset coordinator that distributes meaning rather than a wire, a drain condition that stops a test passing with obligations live, and observed coverage rather than intended coverage.
- Related topic
Senior Verification
Designing a UVM environment for a UCIe subsystem — why an environment only checks anything when the expected value comes from somewhere other than the thing being checked, how a mirrored predictor passes every test while verifying nothing, and the scoreboard structure that separates orphan, stale-generation, reallocation and mismatch into four distinct failures.
Standards & specifications
- Governing standard
- CXL Specification (CXL Consortium)(opens CXL Consortium in a new tab)
Defines CXL.io, CXL.cache and CXL.mem, and the coherence and memory-pooling behaviour built on them. System design and deployment topology are not mandated.
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 CXL curriculum.
