Part 8 · Checking & Coverage · Intermediate
Expected vs Actual: The Two-Stream Model
Why scoreboards compare two streams, where expected values come from, and the analysis TLM path from monitor to check.
The checking question
Every verification test ultimately asks one question: did the DUT do the right thing? Before scoreboards, engineers answered that by staring at waveforms — workable for three transactions, hopeless for ten million. A scoreboard automates the answer by comparing two representations of the same logical event: what should have happened (expected) versus what did happen (actual).
The two-stream model exists because stimulus and observation are fundamentally different activities. The driver expresses intent — what the test wants to send. The monitor samples reality — what actually appeared on the interface after protocol adapters, arbiters, and the DUT transformed the transaction. Comparing driver to monitor checks whether your testbench drove correctly; comparing expected to actual checks whether the DUT behaved correctly. Only the latter is self-checking.
Manual checking (waveform eyeballing) fails at scale for three reasons: humans miss subtle field errors, regression runs have no human in the loop, and out-of-order protocols make temporal alignment impossible without structured pairing. Self-checking via scoreboard fixes all three.
[CHECK] manual vs self-checking
MANUAL (waveform review)
engineer watches paddr, pwdata, prdata
compares against spreadsheet / spec PDF
fails at: volume, regression, OOO reorder
SELF-CHECKING (scoreboard)
monitor → actual stream (automated)
ref model / golden → expected stream (automated)
compare() → UVM_ERROR on mismatch (automated)
scales to millions of transactions per regressionDriver vs monitor — why actual comes from monitor only
The most common scoreboard wiring mistake connects the driver analysis port to the scoreboard. That seems convenient — the driver already has the transaction object — but it validates the wrong thing.
Consider an APB write where the driver sets addr=0x1000 and data=0xDEAD. The DUT has a bug that maps address bit 12 incorrectly, so the slave at 0x0000 receives the write instead. The driver item still shows addr=0x1000. A driver-fed scoreboard would compare expected 0x1000 against driver 0x1000 and pass. The monitor, sampling pins, sees paddr=0x0000 on the bus and reports the truth.
[UVM] driver vs monitor — different truths
SEQUENCE ──► DRIVER ──► DUT pins
│ │
│ ▼
│ MONITOR samples pins
│ │
▼ ▼
intent txn actual txn
(addr=0x1000) (addr=0x0000) ← DUT bug visible here only
WRONG: driver.ap ──► scoreboard.act_imp
RIGHT: monitor.ap ──► scoreboard.act_impDriver knows what it tried to send — not what the bus carried after mux/arbiter/DUT.
Monitor samples virtual interface signals at protocol-legal sample points.
Actual stream = monitor only. No exceptions for 'simple' protocols.
Three sources of expected
The expected stream answers: given what the DUT received, what response does the specification require? There are three common ways to generate that answer, each with different trade-offs.
[CHECK] three expected sources
1. REFERENCE MODEL (most common)
DUT monitor req ──► ref_model.predict(req) ──► exp response
Pros: spec-accurate, independent of DUT internals
Cons: model development cost
2. GOLDEN-PATH MONITOR (known-good DUT or VIP)
Same stimulus ──► golden DUT ──► golden monitor ──► exp response
Pros: no model to write; golden is the spec
Cons: two DUTs to maintain; stimulus sync
3. SECOND BUS MONITOR (reference interconnect)
Traffic on ref bus ──► ref monitor ──► exp stream
Pros: good for bus bridges / protocol converters
Cons: need matched stimulus on both sidesReference model path — most flexible
The reference model receives observed requests from the DUT monitor (what the slave actually saw), computes the spec-required response, and publishes it to the scoreboard's exp_imp. The scoreboard never talks to the model directly — TLM connections in connect_phase wire ref_model.ap to scb.exp_imp.
// Typical ref-model path (connect_phase)
dut_agt.mon.ap.connect(ref_model.req_imp); // observed requests
ref_model.ap.connect(scb.exp_imp); // predicted responses
dut_agt.mon.ap.connect(scb.act_imp); // actual responsesModel input = monitor-sampled request, not driver item.
Model output = expected response transaction with same ID/addr as request.
Model is unit-testable outside UVM — pure predict() function.
Golden-path path — when you have a trusted reference
Some blocks ship with a cycle-accurate C model or prior-generation RTL that is known correct. You instantiate it as a second DUT, drive identical stimulus to both, and compare the golden monitor's output against the DUT monitor's output. The golden monitor stream becomes expected.
[UVM] golden-path topology
stimulus ──┬──► DUT (device under test) ──► dut_mon ──► act_imp
│
└──► GOLDEN (trusted ref) ──► gold_mon ──► exp_imp
│
▼
[CHECK] compareBoth DUTs must see identical stimulus — use broadcast or dual-driver virtual seq.
Golden monitor → exp_imp; DUT monitor → act_imp.
No reference model code to write — but two implementations to maintain.
APB write walkthrough — end-to-end timeline
Walk through a single APB write to see how expected and actual streams converge in the scoreboard. This is the simplest case — strict ordering, one outstanding transaction — so FIFO matching works (covered in the FIFO lesson).
[CHECK] APB write — event timeline
T0 seq starts APB write: addr=0x4000, data=0xBEEF, write=1
T1 driver drives pins: paddr=0x4000, pwdata=0xBEEF, pwrite=1
T2 DUT slave accepts write, updates register
T3 dut_mon samples response: addr=0x4000, data=0xBEEF, kind=RESPONSE, err=0
T4 ref_model.predict(req) queued expected: addr=0x4000, data=0xBEEF, err=0
write_exp → exp_q.push_back(exp)
T5 dut_mon.ap.write(act) → write_act(act)
T6 scoreboard compare: pop_front(exp_q), act.compare(exp) → MATCH// Simplified APB transaction for the walkthrough
class apb_txn extends uvm_sequence_item;
rand bit [31:0] addr;
rand bit [31:0] data;
rand bit write;
bit err; // 0 = OK, 1 = slave error
apb_kind_e kind; // REQUEST or RESPONSE
`uvm_object_utils_begin(apb_txn)
`uvm_field_int(addr, UVM_ALL_ON)
`uvm_field_int(data, UVM_ALL_ON)
`uvm_field_int(write, UVM_ALL_ON)
`uvm_field_int(err, UVM_ALL_ON)
`uvm_field_enum(apb_kind_e, kind, UVM_ALL_ON)
`uvm_object_utils_end
endclassT3–T4 order may swap — ref model may predict before monitor samples actual.
FIFO queue absorbs ordering difference as long as responses stay in-order.
kind=RESPONSE filter in write_act prevents comparing requests to responses.
Analysis TLM path — monitor to scoreboard
Transactions flow from monitor to scoreboard through UVM's analysis port / analysis export TLM pattern. The monitor calls ap.write(txn), which invokes the scoreboard's write_act(txn) (or write_exp(txn)) via the connected analysis imp. This is a broadcast fan-out — multiple subscribers can connect to one monitor port.
[UVM] analysis TLM fan-out from one monitor
dut_mon.ap (uvm_analysis_port)
│
├──► ref_model.req_imp (feeds predict)
├──► scb.act_imp (actual stream)
└──► cov_collector.imp ([COVER] parallel sampling)
ref_model.ap
│
└──► scb.exp_imp (expected stream)Expected can arrive before or after actual — matching strategy must handle both.
Actual with no expected = unexpected response (DUT bug or wrong monitor filter).
Expected with no actual = DUT dropped a response — caught in check_phase drain.
Never tap driver.ap for checking — only monitor.ap.
Key takeaways
Expected = spec intent (ref model, golden monitor, or ref bus monitor).
Actual = DUT-facing monitor sample — always ground truth for checking.
Driver = intent, monitor = reality — scoreboard compares the latter pair.
Analysis TLM connects monitors to scoreboard imps in connect_phase.
Common pitfalls
Checking driver stream — validates stimulus, not DUT behavior.
Single-stream scoreboard counting transactions — not self-checking.
Feeding ref model from driver items — model sees intent, not DUT input.
Comparing requests to responses — filter by txn.kind in write_act/write_exp.