Part 6 · Testbench Architecture · Intermediate
Scoreboard Architecture
Expected vs actual streams, in-order FIFO compare, out-of-order associative-array compare, and where expected transactions come from.
Two streams, one verdict
Every scoreboard reduces to the same shape: an expected stream (what the DUT should produce) and an actual stream (what the output monitor observed), with a compare engine in between. Everything else — FIFOs, associative arrays, timeouts — is bookkeeping around that core.
EXPECTED vs ACTUAL STREAM MODEL
input monitor ──► reference model ──► expected stream ─┐
▼
┌────────────┐
│ scoreboard │── match? ──► count
└────────────┘ │
▲ └─ MISMATCH → error
output monitor ─────────────────► actual stream ───────┘
In-order DUT: expected_q[$] — pop front, compare 1:1
Out-of-order DUT: expected[id] — look up by transaction idWhere does "expected" come from?
Reference model fed by the input monitor — the standard path: model transforms observed inputs into predicted outputs. Works for any DUT with defined functional behavior.
Monitor-on-input directly (pass-through DUTs) — for FIFOs, bridges, and switches the input transaction IS the expected output; no model needed.
Never from the generator/driver — the DUT may legally drop, reorder, or transform stimulus; predicting from intent instead of observed input bakes in wrong assumptions.
In-order compare: the FIFO scoreboard
When the DUT preserves ordering (FIFOs, pipelines, simple buses), the scoreboard is a queue: push expected at the back, and every actual transaction must match the front. Order violations surface automatically as data mismatches.
class fifo_scoreboard;
mailbox #(bus_txn) mbx_exp; // from ref model / input monitor
mailbox #(bus_txn) mbx_act; // from output monitor
bus_txn expected_q[$];
int match_count, mismatch_count;
function new(mailbox #(bus_txn) mbx_exp, mailbox #(bus_txn) mbx_act);
this.mbx_exp = mbx_exp;
this.mbx_act = mbx_act;
endfunction
task run();
fork
forever begin // expected side: just enqueue
bus_txn t;
mbx_exp.get(t);
expected_q.push_back(t);
end
forever begin // actual side: compare to front
bus_txn act, exp;
mbx_act.get(act);
if (expected_q.size() == 0) begin
mismatch_count++;
$error("[SCB] actual txn with empty expected queue: %s",
act.convert2string());
continue;
end
exp = expected_q.pop_front();
if (exp.compare(act)) match_count++;
else begin
mismatch_count++;
$error("[SCB] MISMATCH\n exp: %s\n act: %s",
exp.convert2string(), act.convert2string());
end
end
join_none
endtask
endclassOut-of-order compare: associative array by id
DUTs that complete transactions out of order — multi-bank memories, out-of-order interconnects, anything with per-id channels — break the FIFO model. The fix: key expected transactions by an id field in an associative array and look up each actual transaction by its id.
class ooo_scoreboard;
mailbox #(bus_txn) mbx_exp, mbx_act;
bus_txn expected[int]; // keyed by txn id
int match_count, mismatch_count, orphan_count;
task run();
fork
forever begin
bus_txn t;
mbx_exp.get(t);
if (expected.exists(t.id))
$error("[SCB] duplicate expected id=%0d", t.id);
expected[t.id] = t;
end
forever begin
bus_txn act;
mbx_act.get(act);
if (!expected.exists(act.id)) begin
orphan_count++;
$error("[SCB] actual id=%0d has no expected entry: %s",
act.id, act.convert2string());
continue;
end
if (expected[act.id].compare(act)) match_count++;
else begin
mismatch_count++;
$error("[SCB] MISMATCH id=%0d\n exp: %s\n act: %s",
act.id, expected[act.id].convert2string(),
act.convert2string());
end
expected.delete(act.id); // consumed — leftovers checked at EOT
end
join_none
endtask
endclassChoosing the structure
Queue (in-order): ordering is part of the spec — a reorder bug shows up as a compare mismatch for free.
Associative array (out-of-order): only data integrity per id is checked; if ordering rules exist per id, keep a queue per id (associative array of queues).
Either way, leftovers at end of test (non-empty queue, non-empty array) are failures — covered in End-of-Test Checks.
Interview angle
Expect: "Design a scoreboard for a DUT that returns responses out of order." Walk through the by-id associative array, mention the duplicate-id guard, deleting consumed entries, and checking the array is empty at end of test. Then mention the hybrid: an associative array of queues when each id must stay in order internally.
Key takeaways
Scoreboard = expected stream vs actual stream; the data structure follows the DUT's ordering contract.
In-order DUT → queue compare against the front; reorder bugs surface automatically.
Out-of-order DUT → associative array keyed by id, delete on consume, check empty at EOT.
Expected comes from a model fed by the input monitor — never from driver intent.
Common pitfalls
Feeding expected from the generator — DUT-legal drops/reorders become false mismatches.
Using a FIFO compare on an out-of-order DUT — random pass/fail depending on completion timing.
Forgetting to delete consumed associative-array entries — leaks and false leftover errors at EOT.
Comparing inside the monitor — couples observation to checking and kills reuse of the monitor.