Most component defects break one of two rules. Each component may observe exactly one source of truth, and each follows exactly one timing discipline.
Component | Source of truth | Timing rule |
|---|---|---|
Driver | The sequencer item | Drive off the edge; hold valid until the handshake |
Monitor | The pins only | Sample the full legal timing range, not current RTL habits |
Scoreboard | The monitor stream | Drain means empty queue and nonzero compares |
A monitor that reads driver state verifies the testbench against itself. Checking dies and the regression stays green.
The listing below is not run in this course's checks: it needs the UVM library, which neither course tool (Icarus 13, Verilator 5.048) runs here.
// not-run: needs the UVM library (uvm_driver, sequencer port); neither Icarus 13 nor Verilator 5.048 here runs uvm_pkg
class bus_driver extends uvm_driver #(bus_txn);
virtual bus_if vif;
`uvm_component_utils(bus_driver)
task run_phase(uvm_phase phase);
forever begin
seq_item_port.get_next_item(req);
fork
drive_one(req);
join_none
seq_item_port.item_done();
end
endtask
task drive_one(bus_txn t);
@(posedge vif.clk);
vif.valid = 1; // blocking drives
vif.addr = t.addr;
vif.write = t.write;
while (vif.ready !== 1)
@(posedge vif.clk);
vif.valid = 0;
endtask
endclassBLOCK — item_done fires before any driving. The sequencer handshake becomes meaningless and transactions overlap on shared pins. Completion is reported only after the drive finishes.
BLOCK — blocking drives on the active edge. Whether the DUT samples old or new values is scheduler-dependent. All pin access goes through the clocking block or an equivalent off-edge discipline.
BLOCK — no reset branch. On mid-burst reset the driver holds valid through reset. Every driver needs a reset path that aborts the in-flight transaction and rests the pins.
BLOCK — ready sampled raw, not through the clocking block. The sample lands in the wrong region relative to the DUT update. It is the same race family as the drives.
NOTE — no timeout on the ready wait. A dead DUT turns this into an unattributed hang. A bounded wait names the culprit at once.
NOTE — no transaction logging. The driver stays invisible to the debug layers the team relies on.
The testbench below is plain SystemVerilog and runs in both course tools. A small slave stalls one cycle in four, and a stall hook forces one reset to land mid-send.
The driver holds valid until the handshake, aborts cleanly on reset, and bounds every wait at 32 cycles. The monitor watches pins only, and the end of test checks both drain gates.
Watch valid against ready. The request waits two slots before the grant arrives.
Slot 0: idle, valid 0 and ready 0 with data 0x0.
Slot 1: valid rises with data 0xa1 while ready stays low.
Slot 2: still waiting; valid holds because the handshake has not happened.
Slot 3: ready rises, so this slot is the handshake both sides count.
Slot 4: valid and ready rest at 0 with data 0x0.
module tiny_slave (
input logic clk,
input logic rst_n,
input logic stall, // test hook: hold ready low on demand
input logic valid,
output logic ready,
input logic [7:0] addr,
output logic [7:0] rdata
);
logic [1:0] cnt;
assign ready = (cnt != 2'd2) && !stall;
assign rdata = addr + 8'd1;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) cnt <= 2'd0;
else cnt <= cnt + 2'd1;
end
endmodule
A driver that drops a transaction early would trip the compared != N_TXNS gate above. That is the whole point of gating passes on activity instead of on zero complaints.
// snippet
fork begin : drive_or_reset
fork
drive_one(req);
@(negedge rst_n); // reset wins the race
join_any
disable fork;
end join
// The outer fork...join isolates the disable: without it, disable fork
// can kill sibling threads elsewhere in the same parent process.Approving a monitor that reads driver state temporarily until pins are ready. Temporary becomes permanent and checking turns circular.
Overlooking a fork...join_none before item_done in a request loop. Transactions overlap on shared pins and corrupt each other.
Letting unbounded waits through review. Each one is a future hang with no name on it.
Gating the pass on zero mismatches alone, with no activity count beside it.
The sequencer believes the item finished before it started, and the next item overlaps it on the same pins. Pin traffic corrupts while the handshake claims success.
Investigate. An empty queue with zero compares is consistent with nothing ever arriving. Both gates must hold together.