This UVM testbench example verifies a small register/memory block that stores write data and returns read data from a requested address. The same design is verified with a plain class-based testbench in SystemVerilog Testbench Example 1, so the two can be compared side by side to see what UVM adds.
Every component of the testbench is shown below: sequence item, driver, monitor, agent, scoreboard, environment, test and sequence, together with the interface, the top module and a simulation log.
8 min read | Intermediate Level
What You'll Learn
- Build a complete UVM testbench for a simple read/write bus
- Write a driver and a monitor that follow a protocol with a
readyhandshake - Model memory contents in a scoreboard to check read data
- Trace transactions from the sequence to the scoreboard in a log
The Design and Its Protocol
The design, reg_ctrl, holds 256 locations of 16 bits each. After reset every location contains 16'h1234. A transfer starts when sel is high, and wr selects a write or a read:
- A write completes in one clock while
readyis high: the data onwdatais stored ataddr. - A read is accepted while
readyis high. The design then dropsreadyfor one clock and places the data onrdataon the clock after it accepted the read.
These two rules shape the driver and the monitor. The driver must keep the request on the bus until ready is high, and the monitor must wait one extra clock after a read to collect rdata.
Sequence Item
reg_item holds a random address, write data and direction, plus the read data that only the monitor fills in. The field macros give it print(), copy() and compare() for free.
`define ADDR_WIDTH 8
`define DATA_WIDTH 16
`define DEPTH 256
class reg_item extends uvm_sequence_item;
rand bit [`ADDR_WIDTH-1:0] addr;
rand bit [`DATA_WIDTH-1:0] wdata;
rand bit wr;
bit [`DATA_WIDTH-1:0] rdata;
// Use utility macros to implement standard functions
// like print, copy, clone, etc
`uvm_object_utils_begin(reg_item)
`uvm_field_int (addr, UVM_DEFAULT)
`uvm_field_int (wdata, UVM_DEFAULT)
`uvm_field_int (rdata, UVM_DEFAULT)
`uvm_field_int (wr, UVM_DEFAULT)
`uvm_object_utils_end
virtual function string convert2str();
return $sformatf("addr=0x%0h wr=0x%0h wdata=0x%0h rdata=0x%0h", addr, wr, wdata, rdata);
endfunction
function new(string name = "reg_item");
super.new(name);
endfunction
endclass
Driver
The driver is parameterized with reg_item and gets a virtual interface handle from the configuration database. For each item it puts the request on the bus, waits for a clock edge, and keeps waiting while ready is low. Only then does it release sel and call item_done().
class driver extends uvm_driver #(reg_item);
`uvm_component_utils(driver)
function new(string name = "driver", uvm_component parent=null);
super.new(name, parent);
endfunction
virtual reg_if vif;
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(virtual reg_if)::get(this, "", "reg_vif", vif))
`uvm_fatal("DRV", "Could not get vif")
endfunction
virtual task run_phase(uvm_phase phase);
super.run_phase(phase);
forever begin
reg_item m_item;
`uvm_info("DRV", $sformatf("Wait for item from sequencer"), UVM_LOW)
seq_item_port.get_next_item(m_item);
drive_item(m_item);
seq_item_port.item_done();
end
endtask
virtual task drive_item(reg_item m_item);
vif.sel <= 1;
vif.addr <= m_item.addr;
vif.wr <= m_item.wr;
vif.wdata <= m_item.wdata;
@ (posedge vif.clk);
while (!vif.ready) begin
`uvm_info("DRV", "Wait until ready is high", UVM_LOW)
@(posedge vif.clk);
end
vif.sel <= 0;
endtask
endclass
Monitor
The monitor watches the bus on every clock and turns pin activity back into reg_item objects, which it broadcasts through an analysis port. For a read it waits one more clock to pick up rdata. It runs in a forever loop because it must observe the bus for the whole simulation.
class monitor extends uvm_monitor;
`uvm_component_utils(monitor)
function new(string name="monitor", uvm_component parent=null);
super.new(name, parent);
endfunction
uvm_analysis_port #(reg_item) mon_analysis_port;
virtual reg_if vif;
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(virtual reg_if)::get(this, "", "reg_vif", vif))
`uvm_fatal("MON", "Could not get vif")
mon_analysis_port = new ("mon_analysis_port", this);
endfunction
virtual task run_phase(uvm_phase phase);
super.run_phase(phase);
forever begin
@ (posedge vif.clk);
if (vif.sel) begin
reg_item item = reg_item::type_id::create("item");
item.addr = vif.addr;
item.wr = vif.wr;
item.wdata = vif.wdata;
if (!vif.wr) begin
@(posedge vif.clk); // read data arrives one clock later
item.rdata = vif.rdata;
end
`uvm_info(get_type_name(), $sformatf("Monitor found packet %s", item.convert2str()), UVM_LOW)
mon_analysis_port.write(item);
end
end
endtask
endclass
Agent
The agent groups the sequencer, driver and monitor. The sequencer is a plain uvm_sequencer parameterized with reg_item. In connect_phase, the driver's seq_item_port is connected to the sequencer's seq_item_export, which is the path items take from the sequence to the driver.
class agent extends uvm_agent;
`uvm_component_utils(agent)
function new(string name="agent", uvm_component parent=null);
super.new(name, parent);
endfunction
driver d0;
monitor m0;
uvm_sequencer #(reg_item) s0;
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
s0 = uvm_sequencer#(reg_item)::type_id::create("s0", this);
d0 = driver::type_id::create("d0", this);
m0 = monitor::type_id::create("m0", this);
endfunction
virtual function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
d0.seq_item_port.connect(s0.seq_item_export);
endfunction
endclass
Scoreboard
The scoreboard receives every item the monitor sees through a uvm_analysis_imp, so its write() function runs once per transfer. Writes are stored in refq, an array indexed by address. Reads are checked against it: an address that was never written must return the reset value 'h1234, and any other address must return the last data written there.
This array is a small reference model of the design. Reference models range from a few lines like this one to full behavioural models of complex blocks.
class scoreboard extends uvm_scoreboard;
`uvm_component_utils(scoreboard)
function new(string name="scoreboard", uvm_component parent=null);
super.new(name, parent);
endfunction
reg_item refq[`DEPTH];
uvm_analysis_imp #(reg_item, scoreboard) m_analysis_imp;
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
m_analysis_imp = new("m_analysis_imp", this);
endfunction
virtual function void write(reg_item item);
if (item.wr) begin
refq[item.addr] = item; // remember the last write to this address
`uvm_info(get_type_name(), $sformatf("Store addr=0x%0h wr=0x%0h data=0x%0h", item.addr, item.wr, item.wdata), UVM_LOW)
end else begin
if (refq[item.addr] == null) begin
if (item.rdata != 'h1234)
`uvm_error(get_type_name(), $sformatf("First time read, addr=0x%0h exp=1234 act=0x%0h", item.addr, item.rdata))
else
`uvm_info(get_type_name(), $sformatf("PASS! First time read, addr=0x%0h exp=1234 act=0x%0h", item.addr, item.rdata), UVM_LOW)
end else begin
if (item.rdata != refq[item.addr].wdata)
`uvm_error(get_type_name(), $sformatf("addr=0x%0h exp=0x%0h act=0x%0h", item.addr, refq[item.addr].wdata, item.rdata))
else
`uvm_info(get_type_name(), $sformatf("PASS! addr=0x%0h exp=0x%0h act=0x%0h", item.addr, refq[item.addr].wdata, item.rdata), UVM_LOW)
end
end
endfunction
endclass
Storing the item handle is safe here because the monitor creates a new object for every transfer. If a monitor reused one object, the scoreboard would need to store a copy instead.
Environment
The environment creates the agent and the scoreboard and connects the monitor's analysis port to the scoreboard's analysis imp.
class env extends uvm_env;
`uvm_component_utils(env)
function new(string name="env", uvm_component parent=null);
super.new(name, parent);
endfunction
agent a0;
scoreboard sb0;
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
a0 = agent::type_id::create("a0", this);
sb0 = scoreboard::type_id::create("sb0", this);
endfunction
virtual function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
a0.m0.mon_analysis_port.connect(sb0.m_analysis_imp);
endfunction
endclass
Test
The test builds the environment and passes the virtual interface down to the agent's components. In run_phase it raises an objection, resets the design, runs one randomized sequence on the agent's sequencer, waits a little for the last transfer to drain, and drops the objection.
class test extends uvm_test;
`uvm_component_utils(test)
function new(string name = "test", uvm_component parent=null);
super.new(name, parent);
endfunction
env e0;
virtual reg_if vif;
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
e0 = env::type_id::create("e0", this);
if (!uvm_config_db#(virtual reg_if)::get(this, "", "reg_vif", vif))
`uvm_fatal("TEST", "Did not get vif")
uvm_config_db#(virtual reg_if)::set(this, "e0.a0.*", "reg_vif", vif);
endfunction
virtual task run_phase(uvm_phase phase);
gen_item_seq seq = gen_item_seq::type_id::create("seq");
phase.raise_objection(this);
apply_reset();
if (!seq.randomize() with { num inside {[20:30]}; })
`uvm_fatal("TEST", "Sequence randomization failed")
seq.start(e0.a0.s0);
#200;
phase.drop_objection(this);
endtask
virtual task apply_reset();
vif.rstn <= 0;
vif.sel <= 0;
repeat(5) @ (posedge vif.clk);
vif.rstn <= 1;
repeat(10) @ (posedge vif.clk);
endtask
endclass
Sequence
The sequence produces the stimulus. Its body() creates num items, randomizes each one after start_item() returns, and sends it with finish_item(). The soft constraint gives a default of 2 to 5 items, which the test overrides with 20 to 30.
class gen_item_seq extends uvm_sequence #(reg_item);
`uvm_object_utils(gen_item_seq)
function new(string name="gen_item_seq");
super.new(name);
endfunction
rand int num; // Total number of items to send
constraint c1 { soft num inside {[2:5]}; }
virtual task body();
for (int i = 0; i < num; i ++) begin
reg_item m_item = reg_item::type_id::create("m_item");
start_item(m_item);
if (!m_item.randomize()) `uvm_error("SEQ", "Randomization failed")
`uvm_info("SEQ", $sformatf("Generate new item: %s", m_item.convert2str()), UVM_LOW)
finish_item(m_item);
end
`uvm_info("SEQ", $sformatf("Done generation of %0d items", num), UVM_LOW)
endtask
endclass
Interface and Top Module
interface reg_if (input bit clk);
logic rstn;
logic [7:0] addr;
logic [15:0] wdata;
logic [15:0] rdata;
logic wr;
logic sel;
logic ready;
endinterface
module tb;
reg clk;
always #10 clk = ~clk;
reg_if _if (clk);
reg_ctrl u0 ( .clk (clk),
.addr (_if.addr),
.rstn (_if.rstn),
.sel (_if.sel),
.wr (_if.wr),
.wdata (_if.wdata),
.rdata (_if.rdata),
.ready (_if.ready));
initial begin
clk <= 0;
uvm_config_db#(virtual reg_if)::set(null, "uvm_test_top", "reg_vif", _if);
run_test("test");
end
endmodule
Simulation Results

UVM_INFO @ 0: reporter [RNTST] Running test test... UVM_INFO testbench.sv(80) @ 0: uvm_test_top.e0.a0.d0 [DRV] Wait for item from sequencer UVM_INFO testbench.sv(52) @ 290: uvm_test_top.e0.a0.s0@@seq [SEQ] Generate new item: addr=0xa4 wr=0x0 wdata=0x9783 rdata=0x0 UVM_INFO testbench.sv(80) @ 310: uvm_test_top.e0.a0.d0 [DRV] Wait for item from sequencer UVM_INFO testbench.sv(52) @ 310: uvm_test_top.e0.a0.s0@@seq [SEQ] Generate new item: addr=0x1b wr=0x1 wdata=0x3025 rdata=0x0 UVM_INFO testbench.sv(141) @ 330: uvm_test_top.e0.a0.m0 [monitor] Monitor found packet addr=0xa4 wr=0x0 wdata=0x9783 rdata=0x1234 UVM_INFO testbench.sv(182) @ 330: uvm_test_top.e0.sb0 [scoreboard] PASS! First time read, addr=0xa4 exp=1234 act=0x1234 UVM_INFO testbench.sv(94) @ 330: uvm_test_top.e0.a0.d0 [DRV] Wait until ready is high UVM_INFO testbench.sv(141) @ 350: uvm_test_top.e0.a0.m0 [monitor] Monitor found packet addr=0x1b wr=0x1 wdata=0x3025 rdata=0x0 UVM_INFO testbench.sv(172) @ 350: uvm_test_top.e0.sb0 [scoreboard] Store addr=0x1b wr=0x1 data=0x3025 UVM_INFO testbench.sv(80) @ 350: uvm_test_top.e0.a0.d0 [DRV] Wait for item from sequencer UVM_INFO testbench.sv(52) @ 350: uvm_test_top.e0.a0.s0@@seq [SEQ] Generate new item: addr=0x9 wr=0x0 wdata=0x6169 rdata=0x0 UVM_INFO testbench.sv(80) @ 370: uvm_test_top.e0.a0.d0 [DRV] Wait for item from sequencer UVM_INFO testbench.sv(52) @ 370: uvm_test_top.e0.a0.s0@@seq [SEQ] Generate new item: addr=0x6e wr=0x0 wdata=0x2b4f rdata=0x0 UVM_INFO testbench.sv(141) @ 390: uvm_test_top.e0.a0.m0 [monitor] Monitor found packet addr=0x9 wr=0x0 wdata=0x6169 rdata=0x1234 UVM_INFO testbench.sv(182) @ 390: uvm_test_top.e0.sb0 [scoreboard] PASS! First time read, addr=0x9 exp=1234 act=0x1234 UVM_INFO testbench.sv(94) @ 390: uvm_test_top.e0.a0.d0 [DRV] Wait until ready is high UVM_INFO testbench.sv(80) @ 410: uvm_test_top.e0.a0.d0 [DRV] Wait for item from sequencer UVM_INFO testbench.sv(52) @ 410: uvm_test_top.e0.a0.s0@@seq [SEQ] Generate new item: addr=0x4a wr=0x1 wdata=0x8dd7 rdata=0x0 UVM_INFO testbench.sv(141) @ 430: uvm_test_top.e0.a0.m0 [monitor] Monitor found packet addr=0x6e wr=0x0 wdata=0x2b4f rdata=0x1234 UVM_INFO testbench.sv(182) @ 430: uvm_test_top.e0.sb0 [scoreboard] PASS! First time read, addr=0x6e exp=1234 act=0x1234 UVM_INFO testbench.sv(94) @ 430: uvm_test_top.e0.a0.d0 [DRV] Wait until ready is high UVM_INFO testbench.sv(141) @ 450: uvm_test_top.e0.a0.m0 [monitor] Monitor found packet addr=0x4a wr=0x1 wdata=0x8dd7 rdata=0x0 UVM_INFO testbench.sv(172) @ 450: uvm_test_top.e0.sb0 [scoreboard] Store addr=0x4a wr=0x1 data=0x8dd7 UVM_INFO testbench.sv(80) @ 450: uvm_test_top.e0.a0.d0 [DRV] Wait for item from sequencer UVM_INFO testbench.sv(52) @ 450: uvm_test_top.e0.a0.s0@@seq [SEQ] Generate new item: addr=0x9d wr=0x0 wdata=0x8901 rdata=0x0 UVM_INFO testbench.sv(80) @ 470: uvm_test_top.e0.a0.d0 [DRV] Wait for item from sequencer UVM_INFO testbench.sv(52) @ 470: uvm_test_top.e0.a0.s0@@seq [SEQ] Generate new item: addr=0x66 wr=0x1 wdata=0x1cce rdata=0x0 UVM_INFO testbench.sv(141) @ 490: uvm_test_top.e0.a0.m0 [monitor] Monitor found packet addr=0x9d wr=0x0 wdata=0x8901 rdata=0x1234 UVM_INFO testbench.sv(182) @ 490: uvm_test_top.e0.sb0 [scoreboard] PASS! First time read, addr=0x9d exp=1234 act=0x1234 UVM_INFO testbench.sv(94) @ 490: uvm_test_top.e0.a0.d0 [DRV] Wait until ready is high UVM_INFO testbench.sv(141) @ 510: uvm_test_top.e0.a0.m0 [monitor] Monitor found packet addr=0x66 wr=0x1 wdata=0x1cce rdata=0x0 UVM_INFO testbench.sv(172) @ 510: uvm_test_top.e0.sb0 [scoreboard] Store addr=0x66 wr=0x1 data=0x1cce UVM_INFO testbench.sv(80) @ 510: uvm_test_top.e0.a0.d0 [DRV] Wait for item from sequencer UVM_INFO testbench.sv(52) @ 510: uvm_test_top.e0.a0.s0@@seq [SEQ] Generate new item: addr=0xa7 wr=0x1 wdata=0x493c rdata=0x0 UVM_INFO testbench.sv(141) @ 530: uvm_test_top.e0.a0.m0 [monitor] Monitor found packet addr=0xa7 wr=0x1 wdata=0x493c rdata=0x0 UVM_INFO testbench.sv(172) @ 530: uvm_test_top.e0.sb0 [scoreboard] Store addr=0xa7 wr=0x1 data=0x493c UVM_INFO testbench.sv(80) @ 530: uvm_test_top.e0.a0.d0 [DRV] Wait for item from sequencer UVM_INFO testbench.sv(52) @ 530: uvm_test_top.e0.a0.s0@@seq [SEQ] Generate new item: addr=0x5e wr=0x1 wdata=0xe4c rdata=0x0 UVM_INFO testbench.sv(141) @ 550: uvm_test_top.e0.a0.m0 [monitor] Monitor found packet addr=0x5e wr=0x1 wdata=0xe4c rdata=0x0 UVM_INFO testbench.sv(172) @ 550: uvm_test_top.e0.sb0 [scoreboard] Store addr=0x5e wr=0x1 data=0xe4c UVM_INFO testbench.sv(80) @ 550: uvm_test_top.e0.a0.d0 [DRV] Wait for item from sequencer UVM_INFO testbench.sv(52) @ 550: uvm_test_top.e0.a0.s0@@seq [SEQ] Generate new item: addr=0x8d wr=0x1 wdata=0x6bfe rdata=0x0 UVM_INFO testbench.sv(141) @ 570: uvm_test_top.e0.a0.m0 [monitor] Monitor found packet addr=0x8d wr=0x1 wdata=0x6bfe rdata=0x0 UVM_INFO testbench.sv(172) @ 570: uvm_test_top.e0.sb0 [scoreboard] Store addr=0x8d wr=0x1 data=0x6bfe UVM_INFO testbench.sv(80) @ 570: uvm_test_top.e0.a0.d0 [DRV] Wait for item from sequencer UVM_INFO testbench.sv(52) @ 570: uvm_test_top.e0.a0.s0@@seq [SEQ] Generate new item: addr=0x32 wr=0x0 wdata=0x6252 rdata=0x0 UVM_INFO testbench.sv(80) @ 590: uvm_test_top.e0.a0.d0 [DRV] Wait for item from sequencer UVM_INFO testbench.sv(52) @ 590: uvm_test_top.e0.a0.s0@@seq [SEQ] Generate new item: addr=0x4f wr=0x0 wdata=0xf148 rdata=0x0 UVM_INFO testbench.sv(141) @ 610: uvm_test_top.e0.a0.m0 [monitor] Monitor found packet addr=0x32 wr=0x0 wdata=0x6252 rdata=0x1234 UVM_INFO testbench.sv(182) @ 610: uvm_test_top.e0.sb0 [scoreboard] PASS! First time read, addr=0x32 exp=1234 act=0x1234 UVM_INFO testbench.sv(94) @ 610: uvm_test_top.e0.a0.d0 [DRV] Wait until ready is high UVM_INFO testbench.sv(80) @ 630: uvm_test_top.e0.a0.d0 [DRV] Wait for item from sequencer UVM_INFO testbench.sv(52) @ 630: uvm_test_top.e0.a0.s0@@seq [SEQ] Generate new item: addr=0xe2 wr=0x0 wdata=0x18e0 rdata=0x0 UVM_INFO testbench.sv(141) @ 650: uvm_test_top.e0.a0.m0 [monitor] Monitor found packet addr=0x4f wr=0x0 wdata=0xf148 rdata=0x1234 UVM_INFO testbench.sv(182) @ 650: uvm_test_top.e0.sb0 [scoreboard] PASS! First time read, addr=0x4f exp=1234 act=0x1234 UVM_INFO testbench.sv(94) @ 650: uvm_test_top.e0.a0.d0 [DRV] Wait until ready is high UVM_INFO testbench.sv(80) @ 670: uvm_test_top.e0.a0.d0 [DRV] Wait for item from sequencer UVM_INFO testbench.sv(52) @ 670: uvm_test_top.e0.a0.s0@@seq [SEQ] Generate new item: addr=0xec wr=0x0 wdata=0xd91a rdata=0x0 UVM_INFO testbench.sv(141) @ 690: uvm_test_top.e0.a0.m0 [monitor] Monitor found packet addr=0xe2 wr=0x0 wdata=0x18e0 rdata=0x1234 UVM_INFO testbench.sv(182) @ 690: uvm_test_top.e0.sb0 [scoreboard] PASS! First time read, addr=0xe2 exp=1234 act=0x1234 UVM_INFO testbench.sv(94) @ 690: uvm_test_top.e0.a0.d0 [DRV] Wait until ready is high UVM_INFO testbench.sv(80) @ 710: uvm_test_top.e0.a0.d0 [DRV] Wait for item from sequencer UVM_INFO testbench.sv(52) @ 710: uvm_test_top.e0.a0.s0@@seq [SEQ] Generate new item: addr=0x6e wr=0x1 wdata=0x31f6 rdata=0x0 UVM_INFO testbench.sv(141) @ 730: uvm_test_top.e0.a0.m0 [monitor] Monitor found packet addr=0xec wr=0x0 wdata=0xd91a rdata=0x1234 UVM_INFO testbench.sv(182) @ 730: uvm_test_top.e0.sb0 [scoreboard] PASS! First time read, addr=0xec exp=1234 act=0x1234 UVM_INFO testbench.sv(94) @ 730: uvm_test_top.e0.a0.d0 [DRV] Wait until ready is high UVM_INFO testbench.sv(141) @ 750: uvm_test_top.e0.a0.m0 [monitor] Monitor found packet addr=0x6e wr=0x1 wdata=0x31f6 rdata=0x0 UVM_INFO testbench.sv(172) @ 750: uvm_test_top.e0.sb0 [scoreboard] Store addr=0x6e wr=0x1 data=0x31f6 UVM_INFO testbench.sv(80) @ 750: uvm_test_top.e0.a0.d0 [DRV] Wait for item from sequencer UVM_INFO testbench.sv(52) @ 750: uvm_test_top.e0.a0.s0@@seq [SEQ] Generate new item: addr=0x66 wr=0x0 wdata=0x2374 rdata=0x0 UVM_INFO testbench.sv(80) @ 770: uvm_test_top.e0.a0.d0 [DRV] Wait for item from sequencer UVM_INFO testbench.sv(52) @ 770: uvm_test_top.e0.a0.s0@@seq [SEQ] Generate new item: addr=0xd5 wr=0x1 wdata=0xad94 rdata=0x0 UVM_INFO testbench.sv(141) @ 790: uvm_test_top.e0.a0.m0 [monitor] Monitor found packet addr=0x66 wr=0x0 wdata=0x2374 rdata=0x1cce UVM_INFO testbench.sv(189) @ 790: uvm_test_top.e0.sb0 [scoreboard] PASS! addr=0x66 exp=0x1cce act=0x1cce UVM_INFO testbench.sv(94) @ 790: uvm_test_top.e0.a0.d0 [DRV] Wait until ready is high UVM_INFO testbench.sv(141) @ 810: uvm_test_top.e0.a0.m0 [monitor] Monitor found packet addr=0xd5 wr=0x1 wdata=0xad94 rdata=0x0 UVM_INFO testbench.sv(172) @ 810: uvm_test_top.e0.sb0 [scoreboard] Store addr=0xd5 wr=0x1 data=0xad94 UVM_INFO testbench.sv(80) @ 810: uvm_test_top.e0.a0.d0 [DRV] Wait for item from sequencer UVM_INFO testbench.sv(52) @ 810: uvm_test_top.e0.a0.s0@@seq [SEQ] Generate new item: addr=0xbb wr=0x1 wdata=0xd056 rdata=0x0 UVM_INFO testbench.sv(141) @ 830: uvm_test_top.e0.a0.m0 [monitor] Monitor found packet addr=0xbb wr=0x1 wdata=0xd056 rdata=0x0 UVM_INFO testbench.sv(172) @ 830: uvm_test_top.e0.sb0 [scoreboard] Store addr=0xbb wr=0x1 data=0xd056 UVM_INFO testbench.sv(80) @ 830: uvm_test_top.e0.a0.d0 [DRV] Wait for item from sequencer UVM_INFO testbench.sv(52) @ 830: uvm_test_top.e0.a0.s0@@seq [SEQ] Generate new item: addr=0x18 wr=0x0 wdata=0x8bba rdata=0x0 UVM_INFO testbench.sv(80) @ 850: uvm_test_top.e0.a0.d0 [DRV] Wait for item from sequencer UVM_INFO testbench.sv(52) @ 850: uvm_test_top.e0.a0.s0@@seq [SEQ] Generate new item: addr=0xec wr=0x1 wdata=0xdfc1 rdata=0x0 UVM_INFO testbench.sv(141) @ 870: uvm_test_top.e0.a0.m0 [monitor] Monitor found packet addr=0x18 wr=0x0 wdata=0x8bba rdata=0x1234 UVM_INFO testbench.sv(182) @ 870: uvm_test_top.e0.sb0 [scoreboard] PASS! First time read, addr=0x18 exp=1234 act=0x1234 UVM_INFO testbench.sv(94) @ 870: uvm_test_top.e0.a0.d0 [DRV] Wait until ready is high UVM_INFO testbench.sv(141) @ 890: uvm_test_top.e0.a0.m0 [monitor] Monitor found packet addr=0xec wr=0x1 wdata=0xdfc1 rdata=0x0 UVM_INFO testbench.sv(172) @ 890: uvm_test_top.e0.sb0 [scoreboard] Store addr=0xec wr=0x1 data=0xdfc1 UVM_INFO testbench.sv(80) @ 890: uvm_test_top.e0.a0.d0 [DRV] Wait for item from sequencer UVM_INFO testbench.sv(55) @ 890: uvm_test_top.e0.a0.s0@@seq [SEQ] Done generation of 20 items UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_objection.svh(1271) @ 1090: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 1090: reporter [UVM/REPORT/SERVER] --- UVM Report Summary ---
The first item, a read of 0xa4, is generated at 290. The monitor reports it at 330, two clocks later, because it waits one extra clock for rdata. The scoreboard sees 0x1234 and passes it as a first-time read.
Meanwhile the driver has already taken the write to 0x1b at 310, finds ready low at 330 and prints "Wait until ready is high". The write completes at 350. This is exactly the sequence shown in the timing diagram above.
Most reads in this short run hit addresses that were never written. The read of 0x66 at 790 is the interesting one: 0x1cce was written there at 510, and the scoreboard confirms the design returned it. A longer run, or a constraint that keeps addresses in a small range, would exercise many more of these read-after-write checks.
Quick Check
Q: In the log, "Wait until ready is high" always appears one clock after a read was accepted. Why does the driver not wait during the read itself?
Show Answer
The design drops ready on the clock edge where it accepts the read. At that edge the driver still samples the old value 1, so the read completes without waiting. The next transfer, already on the bus, then sees ready low on the following edge and has to wait one clock.
Common Mistakes
Mistake #1: Sampling read data on the same clock as the request
In this protocol, rdata is valid one clock after the design accepts the read. A monitor that reads it in the same clock as sel would capture zero and the scoreboard would report false errors.
Mistake #2: Calling item_done() before the design accepts the transfer
If the driver released the item without waiting for ready, the sequence would move on while the request was still pending and the next item would overwrite it on the bus.
Mistake #3: Fully random addresses in a memory test
With 256 random addresses and only 20 transfers, almost no read hits an address that was written earlier, as the log shows. Constrain addresses to a small window, or write before reading, so the scoreboard actually checks stored data.
Summary
In this article, you learned:
- A complete UVM testbench for a register block, from sequence item to top module
- How the driver follows the
readyhandshake before callingitem_done() - How the monitor waits one extra clock to collect read data
- How a scoreboard uses a simple array as a reference model of the memory
- How to trace a transfer through the log from sequence to scoreboard