A SystemVerilog based testbench was explored before to verify a simple design that sends incoming packets to two output ports based on address range. Let us build a similar testbench using UVM components so that you can compare it with a traditional SystemVerilog testbench.

uvm testbench example

Sequence Item

A class called switch_item is defined to hold random input address and data. It also has variables to hold output address and data so that they can be compared easily in a scoreboard. The pros/cons of this method will be discussed later. Here, addr_a and data_a represent the output packet that comes from port A of the design while addr_b and data_b represent output packet that comes from port B of the design.

Since only one packet comes out of either port A or port B, use of the same class object is okay. The class also has utility macros to help with common requirements like print, copy and clone.


// This is the base transaction object that will be used
// in the environment to initiate new transactions and 
// capture transactions at DUT interface
class switch_item extends uvm_sequence_item;
  rand bit [7:0]  	addr;
  rand bit [15:0] 	data;
  bit [7:0] 		addr_a;
  bit [15:0] 		data_a;
  bit [7:0] 		addr_b;
  bit [15:0] 		data_b;

  // Use utility macros to implement standard functions
  // like print, copy, clone, etc
  `uvm_object_utils_begin(switch_item)
  	`uvm_field_int (addr, UVM_DEFAULT)
  	`uvm_field_int (data, UVM_DEFAULT)
  	`uvm_field_int (addr_a, UVM_DEFAULT)
  	`uvm_field_int (data_a, UVM_DEFAULT)
  	`uvm_field_int (addr_b, UVM_DEFAULT)
  	`uvm_field_int (data_b, UVM_DEFAULT)
  `uvm_object_utils_end
  
  function new(string name = "switch_item");
    super.new(name);
  endfunction
endclass

UVM Driver

The UVM driver extends from uvm_driver and is parameterized to accept an object of type switch_item . It also recieves handle to a virtual interface that is used to toggle pins of the DUT. The driver uses standard method calls get_next_item and item_done to communicate with its sequencer.


class driver extends uvm_driver #(switch_item);              
  `uvm_component_utils(driver)
  function new(string name = "driver", uvm_component parent=null);
    super.new(name, parent);
  endfunction
  
  virtual switch_if vif;
  
  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    if (!uvm_config_db#(virtual switch_if)::get(this, "", "switch_vif", vif))
      `uvm_fatal("DRV", "Could not get vif")
  endfunction
  
  virtual task run_phase(uvm_phase phase);
    super.run_phase(phase);
    forever begin
      switch_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(switch_item m_item);
  	vif.vld 	<= 1;
    vif.addr 	<= m_item.addr;
    vif.data    <= m_item.data;
      
    @ (posedge vif.clk);
    vif.vld 	<= 0;
  endtask
endclass

UVM Monitor

The UVM monitor is derived from uvm_monitor and has a virtual interface handle to listen to activity on the given interface. It tries to decode pin level activity into a protocol packet which it can send out to other testbench components. In this case, it tries to see if address and data pairs are available on the DUT interface, capture them into a class object of type switch_item and send out via an analysis port.

Since it has to decode and capture any activity happening on the DUT interface, it has to run for as long as the simulation is active and hence it is placed inside a forever loop.


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  #(switch_item) mon_analysis_port;
  virtual switch_if vif;
  semaphore sema4;
  
  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    if (!uvm_config_db#(virtual switch_if)::get(this, "", "switch_vif", vif))
      `uvm_fatal("MON", "Could not get vif")
    sema4 = new(1);
    mon_analysis_port = new ("mon_analysis_port", this);
  endfunction
  
  virtual task run_phase(uvm_phase phase);
    super.run_phase(phase);
    fork
      sample_port("Thread0");
      sample_port("Thread1");
    join
  endtask
  
  virtual task sample_port(string tag="");
    // This task monitors the interface for a complete 
    // transaction and pushes into the mailbox when the 
    // transaction is complete
    forever begin
      @(posedge vif.clk);
      if (vif.rstn & vif.vld) begin
        switch_item item = new;
        sema4.get();
        item.addr = vif.addr;
        item.data = vif.data;
        `uvm_info("MON", $sformatf("T=%0t [Monitor] %s First part over", 
                                   $time, tag), UVM_LOW)
        @(posedge vif.clk);
		sema4.put();
        item.addr_a = vif.addr_a;
        item.data_a = vif.data_a;              
        item.addr_b = vif.addr_b;
        item.data_b = vif.data_b;
        mon_analysis_port.write(item);
        `uvm_info("MON", $sformatf("T=%0t [Monitor] %s Second part over, item:", 
                 $time, tag), UVM_LOW)        
        item.print();             
      end
    end
  endtask
endclass

Note that every packet that is sent on its input port should be found on one of the output ports. The monitor will capture input port on every clock, and capture both output ports on every clock and send a complete packet to scoreboard. This makes a monitor slightly complicated. We will see an alternative strategy later that shifts the complexity to scoreboard instead.

UVM Agent

The role of a UVM agent is to encapsulate the sequencer, driver and monitor into a single container. The sequencer is responsible for acting on a given sequence and send the driver with data objects. It is parameterized to accept objects of type switch_item and its seq_item_port is connected to the driver's seq_item_export.


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; 		// Driver handle
  monitor 		m0; 		// Monitor handle
  uvm_sequencer #(switch_item)	s0; 		// Sequencer Handle

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    s0 = uvm_sequencer#(switch_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

UVM Scoreboard

The scoreboard receives a data object through its uvm_analysis_imp port from the monitor. Since our monitor captures an entire transfer with both input and output data, implementation of the scoreboard becomes really simple.

Since we know from design specifications that addresses that is less than a specified value should go to output port A and those that are above it should go to port B, the checker logic can be implemented on that basis. As soon as the scoreboard receives an item, its write method will be executed which in turn runs the checker and predicts the result.


class scoreboard extends uvm_scoreboard;
  `uvm_component_utils(scoreboard)
  function new(string name="scoreboard", uvm_component parent=null);
    super.new(name, parent);
  endfunction
  
  uvm_analysis_imp #(switch_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 write(switch_item item);      
      if (item.addr inside {[0:'h3f]}) begin
        if (item.addr_a != item.addr | item.data_a != item.data)
          `uvm_error("SCBD", $sformatf("ERROR! Mismatch addr=0x%0h data=0x%0h addr_a=0x%0h data_a=0x%0h", item.addr, item.data, item.addr_a, item.data_a))
        else
          `uvm_info("SCBD", $sformatf("PASS! Match addr=0x%0h data=0x%0h addr_a=0x%0h data_a=0x%0h", item.addr, item.data, item.addr_a, item.data_a), UVM_LOW)
      
      end else begin
        if (item.addr_b != item.addr | item.data_b != item.data)
          `uvm_error("SCBD", $sformatf("ERROR! Mismatch addr=0x%0h data=0x%0h addr_b=0x%0h data_b=0x%0h", item.addr, item.data, item.addr_b, item.data_b))
        else
          `uvm_info("SCBD", $sformatf("PASS! Match addr=0x%0h data=0x%0h addr_b=0x%0h data_b=0x%0h", item.addr, item.data, item.addr_b, item.data_b), UVM_LOW)
      end
  endfunction
endclass

UVM Environment

The UVM environment is responsible for instantiating and connecting all testbench components. Here, analysis port from the agent's monitor is connected to the analysis implementation port in scoreboard. This connection allows the scoreboard to receive packets whenever the monitor pushes new data into the analysis port.


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; 		// Agent handle
  scoreboard	sb0; 		// Scoreboard handle
    
  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

UVM Test

The test instantiates an environment, sets up virtual interface handles to sub components and starts a top level sequence. In this simple example, we have chosen to also reset the DUT using a reset task, after which a sequence of type gen_item_seq is started on the agent's sequencer.

Since the sequence consumes simulation time and all other testbench components have to run as long as the sequence is active, it is important to raise and drop objections appropriately.


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 switch_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 switch_if)::get(this, "", "switch_vif", vif))
      `uvm_fatal("TEST", "Did not get vif")
      
      uvm_config_db#(virtual switch_if)::set(this, "e0.a0.*", "switch_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();
    
    seq.randomize();
    seq.start(e0.a0.s0);
    phase.drop_objection(this);
  endtask
  
  virtual task apply_reset();
    vif.rstn <= 0;
    repeat(5) @ (posedge vif.clk);
    vif.rstn <= 1;
    repeat(10) @ (posedge vif.clk);
  endtask
endclass

UVM Sequence

Last but not the least, we have the main sequence that forms the stimulus and randomizer aspect of the testbench. The sequence when started on a sequencer gets the body method executed which ultimately randomizes address/data and send them to the driver.


class gen_item_seq extends uvm_sequence;
  `uvm_object_utils(gen_item_seq)
  function new(string name="gen_item_seq");
    super.new(name);
  endfunction
  
  rand int num; 	// Config total number of items to be sent
  
  constraint c1 { num inside {[2:5]}; }
  
  virtual task body();
    for (int i = 0; i < num; i ++) begin
    	switch_item m_item = switch_item::type_id::create("m_item");
    	start_item(m_item);
    	m_item.randomize();
    	`uvm_info("SEQ", $sformatf("Generate new item: "), UVM_LOW)
    	m_item.print();
      	finish_item(m_item);
    end
    `uvm_info("SEQ", $sformatf("Done generation of %0d items", num), UVM_LOW)
  endtask
endclass
 Simulation Log
UVM_INFO @ 0: reporter [RNTST] Running test test...
UVM_INFO testbench.sv(78) @ 0: uvm_test_top.e0.a0.d0 [DRV] Wait for item from sequencer
UVM_INFO testbench.sv(49) @ 290: uvm_test_top.e0.a0.s0@@seq [SEQ] Generate new item: 
---------------------------------------------------------------------------
Name                           Type         Size  Value                    
---------------------------------------------------------------------------
m_item                         switch_item  -     @2847                    
  addr                         integral     8     'he6                     
  data                         integral     16    'h9248                   
  addr_a                       integral     8     'h0                      
  data_a                       integral     16    'h0                      
  addr_b                       integral     8     'h0                      
  data_b                       integral     16    'h0                      
  begin_time                   time         64    290                      
  depth                        int          32    'd2                      
  parent sequence (name)       string       3     seq                      
  parent sequence (full name)  string       25    uvm_test_top.e0.a0.s0.seq
  sequencer                    string       21    uvm_test_top.e0.a0.s0    
---------------------------------------------------------------------------
UVM_INFO testbench.sv(139) @ 310: uvm_test_top.e0.a0.m0 [MON] T=310 [Monitor] Thread0 First part over
UVM_INFO testbench.sv(78) @ 310: uvm_test_top.e0.a0.d0 [DRV] Wait for item from sequencer
UVM_INFO testbench.sv(49) @ 310: uvm_test_top.e0.a0.s0@@seq [SEQ] Generate new item: 
---------------------------------------------------------------------------
Name                           Type         Size  Value                    
---------------------------------------------------------------------------
m_item                         switch_item  -     @2890                    
  addr                         integral     8     'h88                     
  data                         integral     16    'h8675                   
  addr_a                       integral     8     'h0                      
  data_a                       integral     16    'h0                      
  addr_b                       integral     8     'h0                      
  data_b                       integral     16    'h0                      
  begin_time                   time         64    310                      
  depth                        int          32    'd2                      
  parent sequence (name)       string       3     seq                      
  parent sequence (full name)  string       25    uvm_test_top.e0.a0.s0.seq
  sequencer                    string       21    uvm_test_top.e0.a0.s0    
---------------------------------------------------------------------------
UVM_INFO testbench.sv(183) @ 330: uvm_test_top.e0.sb0 [SCBD] PASS! Match addr=0xe6 data=0x9248 addr_b=0xe6 data_b=0x9248
UVM_INFO testbench.sv(148) @ 330: uvm_test_top.e0.a0.m0 [MON] T=330 [Monitor] Thread0 Second part over, item:
--------------------------------------
Name         Type         Size  Value 
--------------------------------------
switch_item  switch_item  -     @2911 
  addr       integral     8     'he6  
  data       integral     16    'h9248
  addr_a     integral     8     'h0   
  data_a     integral     16    'h0   
  addr_b     integral     8     'he6  
  data_b     integral     16    'h9248
--------------------------------------
UVM_INFO testbench.sv(78) @ 330: uvm_test_top.e0.a0.d0 [DRV] Wait for item from sequencer
UVM_INFO testbench.sv(139) @ 330: uvm_test_top.e0.a0.m0 [MON] T=330 [Monitor] Thread1 First part over
UVM_INFO testbench.sv(49) @ 330: uvm_test_top.e0.a0.s0@@seq [SEQ] Generate new item: 
---------------------------------------------------------------------------
Name                           Type         Size  Value                    
---------------------------------------------------------------------------
m_item                         switch_item  -     @2928                    
  addr                         integral     8     'hcc                     
  data                         integral     16    'h6ed0                   
  addr_a                       integral     8     'h0                      
  data_a                       integral     16    'h0                      
  addr_b                       integral     8     'h0                      
  data_b                       integral     16    'h0                      
  begin_time                   time         64    330                      
  depth                        int          32    'd2                      
  parent sequence (name)       string       3     seq                      
  parent sequence (full name)  string       25    uvm_test_top.e0.a0.s0.seq
  sequencer                    string       21    uvm_test_top.e0.a0.s0    
---------------------------------------------------------------------------
UVM_INFO testbench.sv(183) @ 350: uvm_test_top.e0.sb0 [SCBD] PASS! Match addr=0x88 data=0x8675 addr_b=0x88 data_b=0x8675
UVM_INFO testbench.sv(148) @ 350: uvm_test_top.e0.a0.m0 [MON] T=350 [Monitor] Thread1 Second part over, item:
--------------------------------------
Name         Type         Size  Value 
--------------------------------------
switch_item  switch_item  -     @2893 
  addr       integral     8     'h88  
  data       integral     16    'h8675
  addr_a     integral     8     'h0   
  data_a     integral     16    'h0   
  addr_b     integral     8     'h88  
  data_b     integral     16    'h8675
--------------------------------------
UVM_INFO testbench.sv(78) @ 350: uvm_test_top.e0.a0.d0 [DRV] Wait for item from sequencer
UVM_INFO testbench.sv(139) @ 350: uvm_test_top.e0.a0.m0 [MON] T=350 [Monitor] Thread0 First part over
UVM_INFO testbench.sv(49) @ 350: uvm_test_top.e0.a0.s0@@seq [SEQ] Generate new item: 
---------------------------------------------------------------------------
Name                           Type         Size  Value                    
---------------------------------------------------------------------------
m_item                         switch_item  -     @2950                    
  addr                         integral     8     'hb2                     
  data                         integral     16    'h4b58                   
  addr_a                       integral     8     'h0                      
  data_a                       integral     16    'h0                      
  addr_b                       integral     8     'h0                      
  data_b                       integral     16    'h0                      
  begin_time                   time         64    350                      
  depth                        int          32    'd2                      
  parent sequence (name)       string       3     seq                      
  parent sequence (full name)  string       25    uvm_test_top.e0.a0.s0.seq
  sequencer                    string       21    uvm_test_top.e0.a0.s0    
---------------------------------------------------------------------------
UVM_INFO testbench.sv(183) @ 370: uvm_test_top.e0.sb0 [SCBD] PASS! Match addr=0xcc data=0x6ed0 addr_b=0xcc data_b=0x6ed0
UVM_INFO testbench.sv(148) @ 370: uvm_test_top.e0.a0.m0 [MON] T=370 [Monitor] Thread0 Second part over, item:
--------------------------------------
Name         Type         Size  Value 
--------------------------------------
switch_item  switch_item  -     @2872 
  addr       integral     8     'hcc  
  data       integral     16    'h6ed0
  addr_a     integral     8     'h0   
  data_a     integral     16    'h0   
  addr_b     integral     8     'hcc  
  data_b     integral     16    'h6ed0
--------------------------------------
UVM_INFO testbench.sv(78) @ 370: uvm_test_top.e0.a0.d0 [DRV] Wait for item from sequencer
UVM_INFO testbench.sv(139) @ 370: uvm_test_top.e0.a0.m0 [MON] T=370 [Monitor] Thread1 First part over
UVM_INFO testbench.sv(53) @ 370: uvm_test_top.e0.a0.s0@@seq [SEQ] Done generation of 4 items
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_objection.svh(1271) @ 370: 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) @ 370: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---