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 ready handshake
  • 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 ready is high: the data on wdata is stored at addr.
  • A read is accepted while ready is high. The design then drops ready for one clock and places the data on rdata on 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

Output
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 ready handshake before calling item_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
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5