The Scoreboard
The monitor observes; the reference model predicts. The scoreboard is the component that actually puts the two together and decides pass or fail.
Comparing observed traffic against the model
class scoreboard;
mailbox #(fifo_txn) mon2sb;
fifo_ref_model model;
int pass_count = 0;
int fail_count = 0;
function new(mailbox #(fifo_txn) mon2sb, fifo_ref_model model);
this.mon2sb = mon2sb;
this.model = model;
endfunction
task run();
forever begin
fifo_txn txn;
mon2sb.get(txn);
if (txn.op == fifo_txn::WRITE) begin
model.write(txn);
end else begin
bit [7:0] expected = model.predict_read();
if (txn.rd_data !== expected) begin
fail_count++;
$error("Mismatch: expected=%0h actual=%0h", expected, txn.rd_data);
end else begin
pass_count++;
end
end
end
endtask
endclass
run() blocks on mon2sb.get(txn) — every transaction the monitor observed, whether write or read, passes through here. A write updates the model (there's nothing to check yet — the value hasn't come back out). A read asks the model what it predicts, compares that against what the monitor actually saw on rd_data, and tallies the result.
Notice how little this class actually does on its own now that The Reference Model owns the queue — scoreboard has no state describing "what the FIFO should contain" at all, only a pass/fail count. That's the direct payoff of keeping the two separate: this class's only job is comparison, and it shows.
Worth naming why this scoreboard can get away with something a lot of real scoreboards can't: comparing strictly in the order transactions arrive. This FIFO's reads always come back in the same order the corresponding writes went in, so predict_read()'s plain model_q.pop_front() is always comparing against the right value. A DUT whose outputs can legitimately complete out of order relative to how they were issued (a pipelined or out-of-order design, for instance) needs a fundamentally different matching strategy — typically an associative array keyed by a transaction ID, holding not-yet-matched expected results until the corresponding actual result shows up, however late — rather than a single in-order queue like model_q. Nothing about this FIFO's behavior needs that complexity, which is exactly why fifo_ref_model/scoreboard stay this simple.
Compared against UVM's version
UVM's own scoreboard for this identical FIFO receives transactions via write(fifo_txn txn) — a callback UVM invokes automatically the moment a connected analysis port fires, with no explicit blocking get() call anywhere in the scoreboard's own code. This topic's mon2sb.get(txn) inside a forever loop is the direct, simpler ancestor of that: both react to whatever the monitor produces, one transaction at a time, but a mailbox get() is something this class has to actively wait on, while UVM's analysis write() is something that happens to the scoreboard, driven by the monitor's own ap.write(txn) call.
What's next
Every piece checking correctness so far compares actual data against expected data. The next page adds a different kind of check entirely — one that watches protocol-level behavior at the pins, independent of whether any of this pipeline is even working correctly.