Every scoreboard has the same shape: an expected stream of what the DUT should produce, an actual stream of what the output monitor observed, and a compare engine between them. FIFOs and lookup tables are bookkeeping around that core.
Expected comes from | Fits | Rule |
|---|---|---|
Reference model fed by the input monitor | Any DUT with defined behavior | Predict from what the DUT received. |
Input monitor directly | FIFOs, bridges, pass-through paths | The input transaction is the expected output. |
Never the generator or driver | Nothing | The DUT may legally drop or reorder stimulus. |
A DUT that preserves order needs only a queue. Push expected at the back; each actual transaction must match the front.
The next two listings are classes with no top module, so this course's checks do not run them. They share one transaction type with an id field and a plain compare wrapper.
// not-run: classes only, no top module
class bus_txn;
int unsigned id;
logic [31:0] addr;
logic [31:0] data;
logic [1:0] resp;
int unsigned len;
time observed_t; // metadata, never compared
int unsigned dbg_id; // bookkeeping, never compared
typedef enum int unsigned {
F_ADDR = 'h1, F_DATA = 'h2, F_RESP = 'h4, F_LEN = 'h8
} field_e;
localparam int unsigned F_ALL = 'hF;
function bit do_compare(bus_txn rhs, int unsigned mask,
output string mismatch_desc);
bit ok = 1;
mismatch_desc = "";
if ((mask & F_ADDR) && addr !== rhs.addr) begin
ok = 0;
mismatch_desc = {mismatch_desc,
$sformatf(" addr(exp=0x%08h act=0x%08h)", addr, rhs.addr)};
end
if ((mask & F_DATA) && data !== rhs.data) begin
ok = 0;
mismatch_desc = {mismatch_desc,
$sformatf(" data(exp=0x%08h act=0x%08h)", data, rhs.data)};
end
if ((mask & F_RESP) && resp !== rhs.resp) begin
ok = 0;
mismatch_desc = {mismatch_desc,
$sformatf(" resp(exp=%0d act=%0d)", resp, rhs.resp)};
end
if ((mask & F_LEN) && len != rhs.len) begin
ok = 0;
mismatch_desc = {mismatch_desc,
$sformatf(" len(exp=%0d act=%0d)", len, rhs.len)};
end
return ok;
endfunction
// Plain full compare used by the scoreboards below
function bit compare(bus_txn rhs);
string desc;
return do_compare(rhs, F_ALL, desc);
endfunction
function string convert2string();
return $sformatf("id=%0d addr=0x%08h data=0x%08h resp=%0d len=%0d",
id, addr, data, resp, len);
endfunction
endclass
class fifo_scoreboard;
mailbox #(bus_txn) mbx_exp; // from model or input monitor
mailbox #(bus_txn) mbx_act; // from output monitor
bus_txn expected_q[$];
int match_count, mismatch_count;
bit stop; // set by env at drain
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
while (!stop) begin // expected side: enqueue
bus_txn t;
mbx_exp.get(t);
expected_q.push_back(t);
end
while (!stop) begin // actual side: match the front
bus_txn act, exp;
mbx_act.get(act);
if (expected_q.size() == 0) begin
mismatch_count++;
$error("[SCB] actual with empty expected queue: %s",
act.convert2string());
end else begin
exp = expected_q.pop_front();
if (exp.compare(act)) match_count++;
else begin
mismatch_count++;
$error("[SCB] DIFFERENCE\n exp: %s\n act: %s",
exp.convert2string(), act.convert2string());
end
end
end
join_none
endtask
endclassOrder violations surface for free: a reordered DUT fails position-by-position compares. The !== operator keeps X propagation failing instead of slipping through.
A DUT that completes out of order breaks the queue model. Key expected transactions by id in an associative array and look each actual transaction up.
// not-run: fragment: uses bus_txn from the listing above, no top module
class ooo_scoreboard;
mailbox #(bus_txn) mbx_exp, mbx_act;
bus_txn expected[int]; // keyed by transaction id
int match_count, mismatch_count, orphan_count;
bit stop;
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
while (!stop) 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
while (!stop) 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", act.id);
end else begin
if (expected[act.id].compare(act)) match_count++;
else begin
mismatch_count++;
$error("[SCB] DIFFERENCE id=%0d\n exp: %s\n act: %s",
act.id, expected[act.id].convert2string(),
act.convert2string());
end
expected.delete(act.id); // consumed; rest checked at end of test
end
end
join_none
endtask
endclassDUT ordering | Structure | Note |
|---|---|---|
In order | Queue, compare against the front | A reorder bug fails compares by itself. |
Out of order | Associative array keyed by id | Delete on consume; leftovers fail at end of test. |
In order within each id | Array of queues, one queue per id | Hybrid for per-channel ordering rules. |
Almost certainly not. One alignment slip puts the queue out of step, so every later compare fails. Fix the first one and re-run.
The DUT produced something unpredicted: a duplicated, invented, or mis-tagged transaction. Count it as an orphan and report it like any other difference.
FIFO versus by-id compare on one reordered arrival sequence.
module tb_scoreboard_order;
timeunit 1ns; timeprecision 1ps;
reg [7:0] exp_data [0:3];
reg [7:0] act_data [0:3];
reg [1:0] act_id [0:3];
int errors = 0;
int fifo_diff = 0;
int byid_match = 0;
initial begin
for (int i = 0; i < 4; i++)
exp_data[i] = 8'h10 * (i + 1); // 10, 20, 30, 40
act_id[0] = 2; act_data[0] = 8'h30; // arrival order 2,0,3,1
act_id[1] = 0; act_data[1] = 8'h10;
act_id[2] = 3; act_data[2] = 8'h40;
act_id[3] = 1; act_data[3] = 8'h20;
#1;
for (int i = 0; i < 4; i++) // positional compare
if (act_data[i] !== exp_data[i]) fifo_diff++;
for (int i = 0; i < 4; i++) // lookup by id
if (act_data[i] === exp_data[act_id[i]]) byid_match++;
if (fifo_diff !== 4) errors++;
if (byid_match !== 4) errors++;
if (errors == 0) $display("PASS: fifo flags 4 positional differences on reorder, by-id matches all 4");
else $fatal(1, "FAIL: fifo_diff=%0d byid_match=%0d", fifo_diff, byid_match);
$finish;
end
endmodule