Every DMA test in the course passes. The scoreboard compares every output word, and every word matches. Then a designer refactors the output register and, in doing so, stops asserting TLAST on the final word. You rerun the whole suite. It stays green. Not one test fails, because not one check in the bench looks at TLAST on the output. The scoreboard was matching data perfectly while ignoring half the meaning of the stream. In-order matching is the simplest pairing rule there is. What does it actually promise, and what does it leave to you?
In-order matching keeps predictions in a first-in, first-out queue and pairs each actual item with the oldest prediction. It is correct exactly when the DUT preserves order: one input stream, one output stream, no overtaking. Think of a bakery counter with a single queue. The baker may be slow or fast, and several customers may be waiting at once, but the first customer to order is the first to be served. You never need ticket numbers.
The DMA is that kind of design. It takes one word at a time and emits it one register later, so its output order is its input order. The queue's depth, the number of predictions waiting at any moment, is simply the number of words inside the DUT plus any skew between when the input monitor and the output monitor report. Here it never exceeds two.
dma_scoreboard's compare is short, so read it as a list of outcomes rather than as code. Each actual word, and the end of the test, can end in exactly one of these:
Outcome | When | Bench ID | Where it is raised |
|---|---|---|---|
Match | The oldest prediction equals the actual word (four-state) | counted in |
|
Mismatch | The oldest prediction differs |
|
|
Unexpected | An actual word arrives with nothing predicted |
|
|
Leftover | Predictions still queued when the test ends |
|
|
Add the zero-compare check, SB_EMPTY, and every way a stream can go wrong is covered: wrong data, extra data, missing data, and no data at all. Report mismatches and unexpected items at once, inside write_out, so the error carries the simulation time of the bad word and you can jump straight to it in the waves. Only things that can be known only at the end belong in check_phase.
The bench compares with !==. With !=, an X on the output bus makes the comparison X, and an if on X takes the else branch, which here is the match branch. A DUT driving X would pass. !== treats X and Z as values, so an X is a mismatch, as it should be.
This is the opening story. dma_scoreboard predicts and compares data only. The output also carries TLAST, which the DUT sets on the LEN-th word or when the input packet ends early. Nobody decided that TLAST was unimportant; the compare simply did not include it. The fix is to predict it from the same observed inputs, using the same rule the spec gives:
// snippet: predict TLAST as well as data (a derived scoreboard)
bit exp_last[$];
int unsigned len_reg, taken;
virtual function void write_apb(apb_item t);
super.write_apb(t);
if (t.write && !t.slverr && t.addr == LEN) len_reg = t.data[15:0];
if (t.write && !t.slverr && t.addr == CTRL && t.data[0]) taken = 0; // GO: a new job
endfunction
virtual function void write_in(axis_item t);
super.write_in(t);
taken++;
exp_last.push_back(taken == len_reg || t.last); // the spec's TLAST rule
endfunction
virtual function void write_out(axis_item t);
if (exp_last.size() != 0 && t.last !== exp_last.pop_front())
`uvm_error("SB_LAST", $sformatf("TLAST wrong on word %08h: got %0b", t.data, t.last))
super.write_out(t);
endfunctionThe general rule: list every field of the output transaction and, for each one, either predict it or write down why it is excluded. Excluded fields are a decision; forgotten fields are a hole.
In-order matching is per stream. A DUT with two output ports that each keep their own order needs two queues, one per port, and a prediction must go into the queue of the port it will leave on. One shared queue would pair a word from port A with a prediction for port B as soon as the two ports' timing differs. The subsystem bench does the equivalent by giving each DMA its own scoreboard. When the DUT may reorder within one stream, a single queue is wrong altogether: that is the next lesson.
+UVM_TESTNAME=dma_rand_test
UVM_INFO dma_tb.sv(731) @ 0: uvm_test_top [JOB] key=90fdd8fd len=10 irq_en=1
UVM_INFO dma_tb.sv(731) @ 645000: uvm_test_top [JOB] key=0e564b57 len=1 irq_en=1
UVM_INFO dma_tb.sv(731) @ 885000: uvm_test_top [JOB] key=5a1793f4 len=3 irq_en=1
UVM_INFO dma_tb.sv(411) @ 1305000: uvm_test_top.env.sb [SB] 14 words matched, 0 mismatched
UVM_ERROR : 0Three jobs, three different keys, 10 + 1 + 3 = 14 words, every one matched in order. The random values differ by simulator and seed; the match count always equals the sum of the job lengths, which is the cross-check to make when you read a log like this.
The opening story is not exotic. A scoreboard written for the first smoke test compares the field that test cared about, and nobody returns to it as the design grows. TLAST, byte strobes, a user sideband, a response code: each is easy to forget because the data still matches. The symptom is the worst kind: no symptom. It is found late, by a downstream block or by software, as a packet boundary in the wrong place.
Two habits prevent it. First, the field list above: every output field predicted or explicitly excluded, reviewed with the designer. Second, a mutation check: break the DUT on purpose (stop driving TLAST, invert one data bit, swap two words) and confirm that some test fails. A mutation that no test notices marks a hole in checking, the same way an uncovered bin marks a hole in stimulus.
No. Buffering changes latency and queue depth (up to about 16 predictions waiting), not order. In-order matching only requires that words leave in the order they entered, which a FIFO preserves. It would be worth adding a sanity bound on the queue depth, so that a scoreboard holding hundreds of predictions reports it.
!=. What does the log show, and why?A match. With !=, comparing against X gives X, the if condition is not true, and the code falls into the match branch. The X-driving DUT passes. With !==, X is compared as a value and the word is reported as SB_MISMATCH.
SB_MISMATCH and SB_UNEXP raised inside write_out rather than collected and reported in check_phase?So each error carries the simulation time and context of the bad word. An error at 345 ns sends you straight to that point in the waveform; a summary at the end only tells you something went wrong somewhere. check_phase is for facts that are known only once traffic has stopped, such as leftovers and zero compares.
Next: some DUTs are allowed to answer out of order. Out-of-Order Matching builds the AXI-style scoreboard, and shows why the key is a queue per ID rather than a slot per ID.