Build the monitor and scoreboard
Watch the pins with a monitor that only observes, predict every edge with a queue model, and check each answer one edge after the read that asked for it.
- Lesson
- 7.4
- Time
- 30 min
- Runs on
- Verilator
- Check
- 5 questions
The first run of a new scoreboard reported 40 errors on a FIFO that was working, one for every read. At the edge where a read was accepted, the scoreboard popped its model and compared the result with read_data straight away. The FIFO's answer only appears after that edge, so every comparison looked at the previous read's data.
One column per rising edge, with values sampled just before it. The answer to the read at edge 1 is visible at edge 2.
A monitor only watches
The monitor reads the pins and writes nothing. At every rising edge it waits for @(vif.mon_cb) and copies all eight signals from the clocking block, which samples them just before the edge (#1step, Module 6). For the inputs, that is the request this edge acts on; for the outputs, it is the result of the previous edge.
It puts its findings into a new fifo_obs object for every edge and sends it through mailbox #(fifo_obs). Creating a new object each time matters for the same reason as in the generator. An object in a mailbox belongs to the receiver, and the monitor must never change it again.
Keeping the monitor apart from the driver also means it reports what really happened on the pins, including anything the driver got wrong.
Predict at one edge, check at the next
The scoreboard holds the model from lesson 7.1: the queue exp_q and its depth. For each observation, it does four things in a fixed order.
Check the answer to the previous edge
If that edge accepted a read,
dueis 1 anddue_dataholds the item it should return.read_validmust equaldue, and when it is 1,read_datamust equaldue_data.Check the flags
fullandemptymust match the size ofexp_qbefore this edge.Decide this edge
From the size of
exp_q, decidetake_wandtake_r, exactly as the rules say.Update the model
On a read, pop the oldest item into
due_dataand setdue; on a write, pushwrite_data. On reset, empty the queue and expect no answer.
Step 1 is what the opening story got wrong: the answer is checked one edge after the decision, because the FIFO registers it. due is the scoreboard's memory of a read that is still on its way.
The scoreboard also counts edges, writes and reads as it goes. Those counts show that checking really happened, and lesson 7.7 builds a verdict that demands proof that checking happened. Each error message names the edge and both values, and printing stops after three, so a broken design cannot bury the first and most useful message.
Because the scoreboard handles one observation at a time, you can also test it on its own by feeding it observations by hand, as the first quiz item below does. That is far quicker than debugging the scoreboard through a full simulation.
Keep the model independent
The scoreboard decides acceptance from its own count, never from the FIFO's flags. If it trusted full, a FIFO whose full never rose would look right: the scoreboard would accept every write the FIFO accepted, and both would agree on a wrong answer.
Trusting the DUT's flag
take_w = o.write_en && !o.full;
If full is broken, the model copies the bug, and nothing is reported.
Using the model's own count
take_w = o.write_en && exp_q.size() < depth;
A broken full now disagrees with the model, and step 2 reports it.
For the same reason, the model is a queue rather than a copy of the RTL's pointers. A wrap bug copied into the checker would wrap the same wrong way and agree with the FIFO.
Assertions on the pins
fifo_props adds the pin-level checks from Module 6 as concurrent assertions. full and empty are never 1 together, and reset leaves the FIFO empty with read_valid at 0. An accepted read is answered at the next edge, and no other edge raises read_valid.
The two kinds of check cover different ground. Assertions see timing rules the moment they break and name the rule; the scoreboard checks data and order across many cycles, which no single property can.
Where the testbench stands
| Layer | Component | Job | Reuses | Status |
|---|---|---|---|---|
| Test | fifo_test, fifo_config | chooses depth, seed and length, runs the environment, gives the verdict | Module 4 classes | lesson 7.7 |
| Generator | fifo_generator | creates fifo_op objects: directed, then seeded random | Modules 4 and 5 | built in 7.3 |
| Mailboxes | mailbox #(fifo_op), mailbox #(fifo_obs) | carry operations and observations between processes | Module 3 | built in 7.3 |
| Driver | fifo_driver | drives one operation per clock through drv_cb | Module 6 | built in 7.3 |
| Monitor | fifo_monitor | samples the pins through mon_cb into a new fifo_obs | Module 6 | this lesson |
| Scoreboard | fifo_scoreboard | predicts each edge with the queue exp_q and compares | Modules 2 and 4 | this lesson |
| Assertions | fifo_props | checks timing rules on the pins at every edge | Module 6 | this lesson |
| Coverage | fifo_coverage, fifo_scenarios | measures which situations the stimulus reached | Module 5 stimulus | lesson 7.5 |
| DUT | fifo | the design under test | lesson 7.1 rules | built in 7.2 |
The layered testbench after lesson 7.4.
The example runs the directed cases and 40 random operations through every layer built so far. It waits until the driver has driven everything, then two more edges: one for the last operation to be sampled and one for its answer to be checked.
Try it
Every edge was checked: 55 observations, 20 accepted writes and 17 accepted reads, and nothing left in the model after the drain. The 3 writes not read back were discarded by resets.
Now break the RTL: change read_valid <= take_r; to if (take_r) read_valid <= 1; and predict which check reports first, the scoreboard or an assertion.
edges=55 writes=20 reads=17 left=0 errors=0Check yourself
Remember
- The monitor samples through
mon_cb, writes nothing, and publishes a newfifo_obsfor every edge. - The scoreboard checks the previous edge's answer, checks the flags, then decides and updates this edge.
- An accepted read is checked one edge later;
dueanddue_dataremember it until then. - Decide acceptance from the model's own count, never from the DUT's flags.
- Assertions catch pin timing rules at once; the scoreboard checks data and order over time.
Practice
- FIFO expected queue Implement · Easy
- The scoreboard that checks too early Debug · Medium