This session is a real example of how design and verification happens in the real industry. We'll go through the design specification, write a test plan that details how the design will be tested, develop a UVM testbench structure and verify the design.

Design

This is a simple pattern detector written in Verilog to identify a pattern in a stream of input values. On every clock, there is a new input to the design and when it matches the pattern '1011', the output out will be set to 1. For this purpose, the design is implemented as a state machine which moves through different stages as it progresses through pattern identification sequence.

A previous article showed examples of using a uvm_blocking_get_port TLM port that was blocking in nature where the receiver gets stalled until the sender finishes with the get task.

Similarly, UVM TLM also has a non-blocking method of type uvm_nonblocking_get_port where the sender has to use try_get to see if the get was successful or can_get method to see if the sender is ready to start a transfer. Like before, the UVM TLM non-blocking get port should ultimately be connected to a non-blocking get implementation port.

UVM TLM Nonblocking Get Example

A class called Packet is defined below to act as the data item that will be transferred from one component to another. This class object will have two random variables that can be randomized before sending.

  
  
// Create a class data object that can be sent from one 
// component to another
class Packet extends uvm_object;
  rand bit[7:0] addr;
  rand bit[7:0] data;
  
  `uvm_object_utils_begin(Packet)
  	`uvm_field_int(addr, UVM_ALL_ON)
  	`uvm_field_int(data, UVM_ALL_ON)
  `uvm_object_utils_end
  
  function new(string name = "Packet");
    super.new(name);
  endfunction
endclass

  
1. Create receiver class with a port of type uvm_nonblocking_get_port

A class called componentB is created which has a uvm_nonblocking_get_port parameterized to accept a data object of type Packet. The port has to be instantiated with the new() method preferably in the build_phase of the same component.

In this example, a class object of type Packet is received via the get_port handle by calling the try_get method. Many such packets can be received using a simple loop controlled by a configurable variable. The try_get function should ideally return 1 if the transfer was successful and 0 if it failed and should be provided by the sender which implements the function.

  
  
class componentB extends uvm_component;
   `uvm_component_utils (componentB)
 
   // Create a get_port to request for data from componentA
   uvm_nonblocking_get_port #(Packet) m_get_port;
   int m_num_tx = 2;
 
  function new (string name, uvm_component parent);
      super.new (name, parent);
   endfunction
 
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
     m_get_port = new ("m_get_port", this);
   endfunction
 
   virtual task run_phase (uvm_phase phase);
      Packet pkt;
     phase.raise_objection(this);
     
     // Try to get a transaction which does not consume simulation time
     // as try_get() is a function
     repeat (m_num_tx) begin
       if (m_get_port.try_get(pkt))
       	`uvm_info ("COMPB", "ComponentA just gave me the packet", UVM_LOW)
       else
         `uvm_info ("COMPB", "ComponentA did not give packet", UVM_LOW)
        pkt.print (uvm_default_line_printer);
      end
     phase.drop_objection(this);
   endtask
endclass

  
3. Create sender class that implements the get method

The sender class needs to define an implementation port using uvm_nonblocking_get_imp. Since the port is nonblocking in nature, the try_get implementation is a function which has to be defined by this component.

  
  
class componentA extends uvm_component;
   `uvm_component_utils (componentA)
 
   uvm_nonblocking_get_imp #(Packet, componentA) m_get_imp;
 
  function new (string name, uvm_component parent);
      super.new (name, parent);
   endfunction
 
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      m_get_imp = new ("m_get_imp", this);
   endfunction
 
   virtual function bit try_get (output Packet pkt);
      pkt = new();
      assert (pkt.randomize());
      `uvm_info ("COMPA", "ComponentB has requested for a packet", UVM_LOW)
      pkt.print (uvm_default_line_printer);
      return 1;
   endfunction
     
     virtual function bit can_get();
     endfunction
endclass

  
tlm-get 4. Connect port and its implementation at a higher level

The connection between a port and its implementation has to be done at a higher hierarchical level. Since both components are instantiated directly within the test class in this example, the connection between them can be done during the connect_phase of the test. If these two components were instantiated in another component or environment, they have to be connected during the connect_phase of that component or environment.

  
  
class my_test extends uvm_test;
  `uvm_component_utils (my_test)
 
   componentA compA;
   componentB compB;
 
  function new (string name = "my_test", uvm_component parent = null);
      super.new (name, parent);
   endfunction
 
   // Create objects of both components, set number of transfers
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      compA = componentA::type_id::create ("compA", this);
      compB = componentB::type_id::create ("compB", this);
   endfunction
 
   // Connection between componentA and componentB is done here
   virtual function void connect_phase (uvm_phase phase);
     compB.m_get_port.connect (compA.m_get_imp);  
   endfunction
  
   virtual function void end_of_elaboration_phase(uvm_phase phase);
    super.end_of_elaboration_phase(phase);
    uvm_top.print_topology();
  endfunction
endclass

  
Simulation Log
UVM_INFO @ 0: reporter [RNTST] Running test my_test...
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_root.svh(579) @ 0: reporter [UVMTOP] UVM testbench topology:
-----------------------------------------------------
Name            Type                      Size  Value
-----------------------------------------------------
uvm_test_top    my_test                   -     @1836
  compA         componentA                -     @1905
    m_get_imp   uvm_nonblocking_get_imp   -     @1971
  compB         componentB                -     @1936
    m_get_port  uvm_nonblocking_get_port  -     @2010
-----------------------------------------------------

UVM_INFO testbench.sv(97) @ 0: uvm_test_top.compA [COMPA] ComponentB has requested for a packet
Packet: ([email protected]) { addr: 'he8  data: 'hc5  } 
UVM_INFO testbench.sv(67) @ 0: uvm_test_top.compB [COMPB] ComponentA just gave me the packet
Packet: ([email protected]) { addr: 'he8  data: 'hc5  } 
UVM_INFO testbench.sv(97) @ 0: uvm_test_top.compA [COMPA] ComponentB has requested for a packet
Packet: ([email protected]) { addr: 'hd6  data: 'hd  } 
UVM_INFO testbench.sv(67) @ 0: uvm_test_top.compB [COMPB] ComponentA just gave me the packet
Packet: ([email protected]) { addr: 'hd6  data: 'hd  } 
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_objection.svh(1271) @ 0: 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) @ 0: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---

UVM TLM can_get Example

Instead of directly trying to get a packet, the receiver can first query to see if the sender is ready or not with can_get function and then get the packet.

  
  
class componentB extends uvm_component;
   `uvm_component_utils (componentB)
 
   // Create a get_port to request for data from componentA
   uvm_nonblocking_get_port #(Packet) m_get_port;
   int m_num_tx = 2;
 
  function new (string name, uvm_component parent);
      super.new (name, parent);
   endfunction
 
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
     m_get_port = new ("m_get_port", this);
   endfunction
 
   virtual task run_phase (uvm_phase phase);
      Packet pkt;
     phase.raise_objection(this);
     
     // Try to get a transaction which does not consume simulation time
     // as try_get() is a function
     repeat (m_num_tx) begin
       while (!m_get_port.can_get()) begin
         #10 `uvm_info("COMPB", $sformatf("See if can_get() is ready"), UVM_LOW)
       end
       
       `uvm_info("COMPB", $sformatf("COMPA ready, get packet now"), UVM_LOW)
       m_get_port.try_get(pkt);
       pkt.print (uvm_default_line_printer);
      end
     phase.drop_objection(this);
   endtask
endclass

  

The can_get function in componentA is set to return a random value in this example to model readiness of the receiver.

  
  
class componentA extends uvm_component;
   `uvm_component_utils (componentA)
 
   uvm_nonblocking_get_imp #(Packet, componentA) m_get_imp;
 
  // Rest of the code remains same
 
   virtual function bit try_get (output Packet pkt);
      pkt = new();
      assert (pkt.randomize());
      `uvm_info ("COMPA", "ComponentB has requested for a packet", UVM_LOW)
      pkt.print (uvm_default_line_printer);
      return 1;
   endfunction
     
     virtual function bit can_get();
       bit ready;
       std::randomize(ready) with { ready dist {0:/70, 1:/30}; };
       return ready;
     endfunction
endclass

  
Simulation Log
UVM_INFO @ 0: reporter [RNTST] Running test my_test...
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_root.svh(579) @ 0: reporter [UVMTOP] UVM testbench topology:
-----------------------------------------------------
Name            Type                      Size  Value
-----------------------------------------------------
uvm_test_top    my_test                   -     @1837
  compA         componentA                -     @1906
    m_get_imp   uvm_nonblocking_get_imp   -     @1972
  compB         componentB                -     @1937
    m_get_port  uvm_nonblocking_get_port  -     @2011
-----------------------------------------------------

UVM_INFO testbench.sv(60) @ 10: uvm_test_top.compB [COMPB] See if can_get() is ready
UVM_INFO testbench.sv(60) @ 20: uvm_test_top.compB [COMPB] See if can_get() is ready
UVM_INFO testbench.sv(60) @ 30: uvm_test_top.compB [COMPB] See if can_get() is ready
UVM_INFO testbench.sv(62) @ 30: uvm_test_top.compB [COMPB] COMPA ready, get packet now
UVM_INFO testbench.sv(97) @ 30: uvm_test_top.compA [COMPA] ComponentB has requested for a packet
Packet: ([email protected]) { addr: 'h8c  data: 'h99  } 
Packet: ([email protected]) { addr: 'h8c  data: 'h99  } 
UVM_INFO testbench.sv(62) @ 30: uvm_test_top.compB [COMPB] COMPA ready, get packet now
UVM_INFO testbench.sv(97) @ 30: uvm_test_top.compA [COMPA] ComponentB has requested for a packet
Packet: ([email protected]) { addr: 'h97  data: 'hb8  } 
Packet: ([email protected]) { addr: 'h97  data: 'hb8  } 
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_objection.svh(1271) @ 30: 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) @ 30: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---

UVM TLM ports and exports are also used to send transaction objects cross different levels of testbench hierarchy.

Ports shall be used to initiate and forward packets to the top layer of the hierarchy. Exports shall be used to accept and forward packets from the top layer to destination. Implementation ports shall be used to define the put method at the target. Shown below are a few examples that use ports, exports and implementation for components at different hierarchy levels.

Port to Port to Export to Imp

subCompA is a subcomponent within componentA that is trying to send transactions to another subcomponent called subCompB in componentB. To maintain flexibility and portability of code, it is recommended to allow subCompA to send data to componentA which should then forward them to the top layer of the hierarchy. componentB shall accept the transaction and forward it to subCompB.

uvm-tlm-put-port-port-export-imp

A class called Packet is defined below to act as the data item that will be transferred from one component to another. This class object will have two random variables that can be randomized before sending.

  
  
// Create a class data object that can be sent from one 
// component to another
class Packet extends uvm_object;
  rand bit[7:0] addr;
  rand bit[7:0] data;
  
  `uvm_object_utils_begin(Packet)
  	`uvm_field_int(addr, UVM_ALL_ON)
  	`uvm_field_int(data, UVM_ALL_ON)
  `uvm_object_utils_end
  
  function new(string name = "Packet");
    super.new(name);
  endfunction
endclass

  

Subcomponent A

  
  
class subCompA extends uvm_component;
  `uvm_component_utils (subCompA)
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  
  uvm_blocking_put_port #(Packet) m_put_port;
  int m_num_tx=2;
  
  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    m_put_port = new ("m_put_port", this);
  endfunction
  
  // Create a packet, randomize it and send it through the port
  // Note that put() is a method defined by the receiving component
  // Repeat these steps N times to send N packets
   virtual task run_phase (uvm_phase phase);
     phase.raise_objection(this);
     repeat (m_num_tx) begin
         Packet pkt = Packet::type_id::create ("pkt");
         assert(pkt.randomize ()); 
       
       	 // Print the packet to be displayed in log
       `uvm_info ("SUBCOMPA", "Packet sent to subCompB", UVM_LOW)
         pkt.print (uvm_default_line_printer);
       
         // Call the TLM put() method of put_port class and pass packet as argument
         m_put_port.put (pkt);
      end
      phase.drop_objection(this);
   endtask
endclass

  

Component A

  
  
class componentA extends uvm_component;
   `uvm_component_utils (componentA)
   function new (string name = "componentA", uvm_component parent= null);
      super.new (name, parent);
   endfunction

    subCompA m_subcomp_A;
    uvm_blocking_put_port #(Packet) m_put_port;
  
  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    m_subcomp_A = subCompA::type_id::create("m_subcomp_A", this);
    m_put_port = new ("m_put_port", this);
  endfunction
  
  // Connection with subCompA
  virtual function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    m_subcomp_A.m_put_port.connect(this.m_put_port);
  endfunction
endclass

  

Component B

  
  
class componentB extends uvm_component;
   `uvm_component_utils (componentB)
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
 
	subCompB m_subcomp_B;
   uvm_blocking_put_export#(Packet) m_put_export;
  
  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    m_subcomp_B = subCompB::type_id::create("m_subcomp_B", this);
    m_put_export = new("m_put_export", this);
  endfunction
  
  // Connection with subCompB
  virtual function void connect_phase(uvm_phase phase);
    m_put_export.connect(m_subcomp_B.m_put_imp);
  endfunction
endclass

  

Subcomponent B

  
  
class subCompB extends uvm_component;
  `uvm_component_utils (subCompB)
  function new (string name = "subCompB", uvm_component parent = null);
      super.new (name, parent);
   endfunction

   // Mention type of transaction, and type of class that implements the put ()
  uvm_blocking_put_imp #(Packet, subCompB) m_put_imp;
 
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
     m_put_imp = new ("m_put_imp", this);
   endfunction
 
    // Implementation of the 'put()' method in this case simply prints it.
  	virtual task put (Packet pkt);            
      `uvm_info ("SUBCOMPB", "Packet received from subCompA", UVM_LOW)
      pkt.print(uvm_default_line_printer);
   endtask
endclass

  
  
  
class my_test extends uvm_test;
  `uvm_component_utils (my_test)
 
   componentA compA;
   componentB compB;
 
  function new (string name = "my_test", uvm_component parent = null);
      super.new (name, parent);
   endfunction
 
   // Create objects of both components, set number of transfers
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      compA = componentA::type_id::create ("compA", this);
      compB = componentB::type_id::create ("compB", this);
   endfunction
 
   // Connection between componentA and componentB is done here
   virtual function void connect_phase (uvm_phase phase);
     compA.m_put_port.connect (compB.m_put_export);  
   endfunction
  
   virtual function void end_of_elaboration_phase(uvm_phase phase);
    super.end_of_elaboration_phase(phase);
    uvm_top.print_topology();
  endfunction
endclass

  
Simulation Log
UVM_INFO @ 0: reporter [RNTST] Running test my_test...
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_root.svh(579) @ 0: reporter [UVMTOP] UVM testbench topology:
------------------------------------------------------
Name              Type                     Size  Value
------------------------------------------------------
uvm_test_top      my_test                  -     @1842
  compA           componentA               -     @1911
    m_put_port    uvm_blocking_put_port    -     @2008
    m_subcomp_A   subCompA                 -     @1974
      m_put_port  uvm_blocking_put_port    -     @2047
  compB           componentB               -     @1942
    m_put_export  uvm_blocking_put_export  -     @2115
    m_subcomp_B   subCompB                 -     @2081
      m_put_imp   uvm_blocking_put_imp     -     @2152
------------------------------------------------------

UVM_INFO testbench.sv(71) @ 0: uvm_test_top.compA.m_subcomp_A [SUBCOMPA] Packet sent to subCompB
pkt: ([email protected]) { addr: 'h24  data: 'h31  } 
UVM_INFO testbench.sv(151) @ 0: uvm_test_top.compB.m_subcomp_B [SUBCOMPB] Packet received from subCompA
pkt: ([email protected]) { addr: 'h24  data: 'h31  } 
UVM_INFO testbench.sv(71) @ 0: uvm_test_top.compA.m_subcomp_A [SUBCOMPA] Packet sent to subCompB
pkt: ([email protected]) { addr: 'h9a  data: 'hfb  } 
UVM_INFO testbench.sv(151) @ 0: uvm_test_top.compB.m_subcomp_B [SUBCOMPB] Packet received from subCompA
pkt: ([email protected]) { addr: 'h9a  data: 'hfb  } 
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_objection.svh(1271) @ 0: 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) @ 0: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---

Port to Port to Imp

In this example componentA forwards the packet from subCompA to the destination componentB. The only difference is that componentB is the target and hence should define the implementation port instead of an export.

uvm-tlm-put-port-port-imp
  
  
// componentB shall implement "put" and there's no subCompB
class componentB extends uvm_component;
   `uvm_component_utils (componentB)
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
 
  uvm_blocking_put_imp#(Packet, componentB) m_put_imp;
  
  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    m_put_imp = new("m_put_imp", this);
  endfunction
  
      // Implementation of the 'put()' method in this case simply prints it.
    virtual task put (Packet pkt);            
      `uvm_info ("COMPB", "Packet received from subCompA", UVM_LOW)
      pkt.print(uvm_default_line_printer);
   endtask
endclass

  
Simulation Log
UVM_INFO @ 0: reporter [RNTST] Running test my_test...
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_root.svh(579) @ 0: reporter [UVMTOP] UVM testbench topology:
----------------------------------------------------
Name              Type                   Size  Value
----------------------------------------------------
uvm_test_top      my_test                -     @1839
  compA           componentA             -     @1908
    m_put_port    uvm_blocking_put_port  -     @2005
    m_subcomp_A   subCompA               -     @1971
      m_put_port  uvm_blocking_put_port  -     @2044
  compB           componentB             -     @1939
    m_put_imp     uvm_blocking_put_imp   -     @2081
----------------------------------------------------

UVM_INFO testbench.sv(70) @ 0: uvm_test_top.compA.m_subcomp_A [SUBCOMPA] Packet sent to CompB
pkt: ([email protected]) { addr: 'h24  data: 'h31  } 
UVM_INFO testbench.sv(124) @ 0: uvm_test_top.compB [COMPB] Packet received from subCompA
pkt: ([email protected]) { addr: 'h24  data: 'h31  } 
UVM_INFO testbench.sv(70) @ 0: uvm_test_top.compA.m_subcomp_A [SUBCOMPA] Packet sent to CompB
pkt: ([email protected]) { addr: 'h9a  data: 'hfb  } 
UVM_INFO testbench.sv(124) @ 0: uvm_test_top.compB [COMPB] Packet received from subCompA
pkt: ([email protected]) { addr: 'h9a  data: 'hfb  } 
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_objection.svh(1271) @ 0: 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) @ 0: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---

Port to Export to Imp

In this example componentA is the initiator and sends a packet from its port to the destination subCompB which implements the put method. Since componentB is the container for the target, it should have an export to forward the packets received from the connected port at the top level.

uvm-tlm-put-port-export-imp
  
  
// componentA will start transactions and send to its port
class componentA extends uvm_component;
   `uvm_component_utils (componentA)
   function new (string name = "componentA", uvm_component parent= null);
      super.new (name, parent);
   endfunction

    uvm_blocking_put_port #(Packet) m_put_port;
    int m_num_tx=2;
  
  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    m_put_port = new ("m_put_port", this);
  endfunction
  
  
  // Create a packet, randomize it and send it through the port
  // Note that put() is a method defined by the receiving component
  // Repeat these steps N times to send N packets
   virtual task run_phase (uvm_phase phase);
     phase.raise_objection(this);
     repeat (m_num_tx) begin
         Packet pkt = Packet::type_id::create ("pkt");
         assert(pkt.randomize ()); 
       
       	 // Print the packet to be displayed in log
       `uvm_info ("SUBCOMPA", "Packet sent to subCompB", UVM_LOW)
         pkt.print (uvm_default_line_printer);
       
         // Call the TLM put() method of put_port class and pass packet as argument
         m_put_port.put (pkt);
      end
      phase.drop_objection(this);
   endtask
endclass

  
Simulation Log
UVM_INFO @ 0: reporter [RNTST] Running test my_test...
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_root.svh(579) @ 0: reporter [UVMTOP] UVM testbench topology:
------------------------------------------------------
Name              Type                     Size  Value
------------------------------------------------------
uvm_test_top      my_test                  -     @1839
  compA           componentA               -     @1908
    m_put_port    uvm_blocking_put_port    -     @1974
  compB           componentB               -     @1939
    m_put_export  uvm_blocking_put_export  -     @2044
    m_subcomp_B   subCompB                 -     @2010
      m_put_imp   uvm_blocking_put_imp     -     @2081
------------------------------------------------------

UVM_INFO testbench.sv(73) @ 0: uvm_test_top.compA [SUBCOMPA] Packet sent to subCompB
pkt: ([email protected]) { addr: 'ha1  data: 'h64  } 
UVM_INFO testbench.sv(127) @ 0: uvm_test_top.compB.m_subcomp_B [SUBCOMPB] Packet received from subCompA
pkt: ([email protected]) { addr: 'ha1  data: 'h64  } 
UVM_INFO testbench.sv(73) @ 0: uvm_test_top.compA [SUBCOMPA] Packet sent to subCompB
pkt: ([email protected]) { addr: 'hc1  data: 'hb9  } 
UVM_INFO testbench.sv(127) @ 0: uvm_test_top.compB.m_subcomp_B [SUBCOMPB] Packet received from subCompA
pkt: ([email protected]) { addr: 'hc1  data: 'hb9  } 
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_objection.svh(1271) @ 0: 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) @ 0: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---

Port to Port to Export to Export to Imp

In this example there is an additional layer between componentB and the target subCompB2 called subCompB1. This layer has to simply forward the packet to the destination and hence shall have an export.

uvm-tlm-put-port-port-export-export-imp Simulation Log
UVM_INFO @ 0: reporter [RNTST] Running test my_test...
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_root.svh(579) @ 0: reporter [UVMTOP] UVM testbench topology:
--------------------------------------------------------
Name                Type                     Size  Value
--------------------------------------------------------
uvm_test_top        my_test                  -     @1845
  compA             componentA               -     @1914
    m_put_port      uvm_blocking_put_port    -     @2011
    m_subcomp_A     subCompA                 -     @1977
      m_put_port    uvm_blocking_put_port    -     @2050
  compB             componentB               -     @1945
    m_put_export    uvm_blocking_put_export  -     @2118
    m_subcomp_B1    subCompB1                -     @2084
      m_put_export  uvm_blocking_put_export  -     @2186
      m_subcomp_B2  subCompB2                -     @2152
        m_put_imp   uvm_blocking_put_imp     -     @2223
--------------------------------------------------------

UVM_INFO testbench.sv(75) @ 0: uvm_test_top.compA.m_subcomp_A [SUBCOMPA] Packet sent to subCompB
pkt: ([email protected]) { addr: 'h24  data: 'h31  } 
UVM_INFO testbench.sv(179) @ 0: uvm_test_top.compB.m_subcomp_B1.m_subcomp_B2 [SUBCOMPB2] Packet received from subCompA
pkt: ([email protected]) { addr: 'h24  data: 'h31  } 
UVM_INFO testbench.sv(75) @ 0: uvm_test_top.compA.m_subcomp_A [SUBCOMPA] Packet sent to subCompB
pkt: ([email protected]) { addr: 'h9a  data: 'hfb  } 
UVM_INFO testbench.sv(179) @ 0: uvm_test_top.compB.m_subcomp_B1.m_subcomp_B2 [SUBCOMPB2] Packet received from subCompA
pkt: ([email protected]) { addr: 'h9a  data: 'hfb  } 
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_objection.svh(1271) @ 0: 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) @ 0: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---

uvm_object has many common functions like print, copy and compare that are available to all its child classes and can be used out of the box if UVM automation macros are used inside the class definition. In a previous article, print, do_print and use of automation macros to print were discussed.

Using automation macros

A class called Packet is defined with a single variable and registered using UVM automation macros between uvm_object_utils_begin and uvm_object_utils_end. A variable used with UVM_DEFAULT setting means that this variable will be included in all automated methods like copy, print, etc unless specifically mentioned.

An object of Packet is instantiated in another class called Object along with a bunch of other variables of different data types. Similar to the Packet class, all variables in Object are registered with UVM automation macros with the corresponding macro type. For example, string variables require `uvm_field_string.

  
  
typedef enum {FALSE, TRUE} e_bool;

class Packet extends uvm_object;
  rand bit[15:0] 	m_addr;
  
  // Automation macros
  `uvm_object_utils_begin(Packet)
  	`uvm_field_int(m_addr, UVM_DEFAULT)
  `uvm_object_utils_end
  
  function new(string name = "Packet");
    super.new(name);
  endfunction
endclass

class Object extends uvm_object;
  rand e_bool 				m_bool;
  rand bit[3:0] 			m_mode;
  rand byte 				m_data[4];
  rand shortint 			m_queue[$];
  string 					m_name;
  rand Packet 				m_pkt;
  
  constraint c_queue { m_queue.size() == 3; }
  
  function new(string name = "Object");
    super.new(name);
    m_name = name;
    m_pkt = Packet::type_id::create("m_pkt");
    m_pkt.randomize();
  endfunction
  
  `uvm_object_utils_begin(Object)
  	`uvm_field_enum(e_bool, m_bool, UVM_DEFAULT)
  	`uvm_field_int (m_mode, 		UVM_DEFAULT)
  	`uvm_field_sarray_int(m_data, 	UVM_DEFAULT)
  	`uvm_field_queue_int(m_queue, 	UVM_DEFAULT)
  	`uvm_field_string(m_name, 		UVM_DEFAULT)
  	`uvm_field_object(m_pkt, 		UVM_DEFAULT)
  `uvm_object_utils_end
endclass

  

Let us create a test class and create two objects of type Object, randomize both and display them first. Then contents of obj1 is copied into obj2 using the copy function. Implementation of this copy function is taken care of by the automation macros.

  
  
class base_test extends uvm_test;
  `uvm_component_utils(base_test)
  function new(string name = "base_test", uvm_component parent=null);
    super.new(name, parent);
  endfunction
  
  function void build_phase(uvm_phase phase);
    Object obj1 = Object::type_id::create("obj1");
    Object obj2 = Object::type_id::create("obj2");
    obj1.randomize();
    obj1.print();
    obj2.randomize();
   	obj2.print();
    
    obj2.copy(obj1);
    `uvm_info("TEST", "After copy", UVM_LOW)
    obj2.print();
  endfunction
endclass

module tb;
	initial begin
		run_test("base_test");
	end
endmodule

  

The first table shows the values of obj1 after randomization, the second table that of obj2 after randomization and the third one shows contents of obj2 after the copy method is called.

Simulation Log
ncsim> run
UVM_INFO @ 0: reporter [RNTST] Running test base_test...
--------------------------------------
Name        Type          Size  Value 
--------------------------------------
obj1        Object        -     @1903 
  m_bool    e_bool        32    TRUE  
  m_mode    integral      4     'he   
  m_data    sa(integral)  4     -     
    [0]     integral      8     'hf4  
    [1]     integral      8     'he   
    [2]     integral      8     'h58  
    [3]     integral      8     'hbd  
  m_queue   da(integral)  3     -     
    [0]     integral      16    'h9ae9
    [1]     integral      16    'hd31d
    [2]     integral      16    'ha96c
  m_name    string        4     obj1  
  m_pkt     Packet        -     @1906 
    m_addr  integral      16    'h3cb6
--------------------------------------
--------------------------------------
Name        Type          Size  Value 
--------------------------------------
obj2        Object        -     @1908 
  m_bool    e_bool        32    FALSE 
  m_mode    integral      4     'he   
  m_data    sa(integral)  4     -     
    [0]     integral      8     'h60  
    [1]     integral      8     'h24  
    [2]     integral      8     'h27  
    [3]     integral      8     'hb5  
  m_queue   da(integral)  3     -     
    [0]     integral      16    'he17f
    [1]     integral      16    'h98e6
    [2]     integral      16    'h5a41
  m_name    string        4     obj2  
  m_pkt     Packet        -     @1910 
    m_addr  integral      16    'h64c1
--------------------------------------
UVM_INFO testbench.sv(60) @ 0: uvm_test_top [TEST] After copy
--------------------------------------
Name        Type          Size  Value 
--------------------------------------
obj2        Object        -     @1908 
  m_bool    e_bool        32    TRUE  
  m_mode    integral      4     'he   
  m_data    sa(integral)  4     -     
    [0]     integral      8     'hf4  
    [1]     integral      8     'he   
    [2]     integral      8     'h58  
    [3]     integral      8     'hbd  
  m_queue   da(integral)  3     -     
    [0]     integral      16    'h9ae9
    [1]     integral      16    'hd31d
    [2]     integral      16    'ha96c
  m_name    string        4     obj1  
  m_pkt     Packet        -     @1909 
    m_addr  integral      16    'h3cb6
--------------------------------------
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 0: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---

Automation macros introduce a lot of additional code and is not generally recommended

Using do_copy

Another way is for the user to implement do_copy method inside the child class and assign the value from each variable of the class to be copied into the current one. Note in the following example that uvm_object_utils_* macros are not used and hence the print() method we used above will not work as is. Instead another method called convert2string is implemented for both classes so that it returns the contents in a string format when called.

Just like the way print calls do_print method, a copy calls the do_copy method and implementation of the function is all that is required to be done.

  
  
typedef enum {FALSE, TRUE} e_bool;

class Packet extends uvm_object;
  rand bit[15:0] 	m_addr;
  
  // Function is used to return contents of this class in a 
  // string format
  virtual function string convert2string();
    string contents;
    contents = $sformatf("m_addr=0x%0h", m_addr);
  endfunction

  `uvm_object_utils(Packet)
  
  // Implementation of "do_copy". A generic uvm_object called "rhs"
  // is received and type casted into Packet called "_pkt". Then 
  // m_addr is copied from _pkt to the variable in current class
  virtual function void do_copy(uvm_object rhs);
    Packet _pkt;
    super.do_copy(rhs);
    $cast(_pkt, rhs);
   	m_addr = _pkt.m_addr;
    `uvm_info(get_name(), "In Packet::do_copy()", UVM_LOW)
  endfunction
  
  function new(string name = "Packet");
    super.new(name);
  endfunction
endclass

class Object extends uvm_object;
  rand e_bool 				m_bool;
  rand bit[3:0] 			m_mode;
  rand byte 				m_data[4];
  rand shortint 			m_queue[$];
  string 					m_name;
  rand Packet 				m_pkt;
  
  constraint c_queue { m_queue.size() == 3; }
  
  function new(string name = "Object");
    super.new(name);
    m_name = name;
    m_pkt = Packet::type_id::create("m_pkt");
    m_pkt.randomize();
  endfunction
  
  // Function used to return contents of this class in a 
  // string format
  virtual function string convert2string();
    string contents = "";
    $sformat(contents, "%s m_name=%s", contents, m_name);
    $sformat(contents, "%s m_bool=%s", contents, m_bool.name());
    $sformat(contents, "%s m_mode=0x%0h", contents, m_mode);
    foreach(m_data[i]) begin
      $sformat(contents, "%s m_data[%0d]=0x%0h", contents, i, m_data[i]);
    end
    return contents;
  endfunction
  
  `uvm_object_utils(Object)
  
  // "rhs" does not contain m_bool, m_mode, etc since its a parent
  // handle. So cast into child data type and access using child handle
  // Copy each field from the casted handle into local variables
  virtual function void do_copy(uvm_object rhs);
    Object _obj;
    super.do_copy(rhs);
    $cast(_obj, rhs);
    m_bool 	= _obj.m_bool;
    m_mode 	= _obj.m_mode;
    m_data 	= _obj.m_data;
    m_queue = _obj.m_queue;
    m_name 	= _obj.m_name;
    m_pkt.copy(_obj.m_pkt);
    `uvm_info(get_name(), "In Object::do_copy()", UVM_LOW)
  endfunction
endclass

  

Like in the earlier example, two objects are created and contents of one is copied into another. Because do_print method is not implemented and automation macros are not used, convert2string will be used to print contents of each class.

  
  
class base_test extends uvm_test;
	`uvm_component_utils(base_test)
  function new(string name = "base_test", uvm_component parent=null);
    super.new(name, parent);
  endfunction
  
  function void build_phase(uvm_phase phase);
    Object obj1 = Object::type_id::create("obj1");
    Object obj2 = Object::type_id::create("obj2");
    obj1.randomize();
    `uvm_info("TEST", $sformatf("Obj1.print: %s", obj1.convert2string()), UVM_LOW)
    obj2.randomize();
    `uvm_info("TEST", $sformatf("Obj2.print: %s", obj2.convert2string()), UVM_LOW)
    
    obj2.copy(obj1);
    `uvm_info("TEST", "After copy", UVM_LOW)
    `uvm_info("TEST", $sformatf("Obj2.print: %s", obj2.convert2string()), UVM_LOW)
  endfunction
endclass

module tb;
	initial begin
		run_test("base_test");
	end
endmodule

  
Simulation Log
ncsim> run
UVM_INFO @ 0: reporter [RNTST] Running test base_test...
UVM_INFO testbench.sv(93) @ 0: uvm_test_top [TEST] Obj1.print:  m_name=obj1 m_bool=TRUE m_mode=0xe m_data[0]=0xf4 m_data[1]=0xe m_data[2]=0x58 m_data[3]=0xbd
UVM_INFO testbench.sv(95) @ 0: uvm_test_top [TEST] Obj2.print:  m_name=obj2 m_bool=FALSE m_mode=0xe m_data[0]=0x60 m_data[1]=0x24 m_data[2]=0x27 m_data[3]=0xb5
UVM_INFO testbench.sv(26) @ 0: reporter [m_pkt] In Packet::do_copy()
UVM_INFO testbench.sv(79) @ 0: reporter [obj2] In Object::do_copy()
UVM_INFO testbench.sv(98) @ 0: uvm_test_top [TEST] After copy
UVM_INFO testbench.sv(99) @ 0: uvm_test_top [TEST] Obj2.print:  m_name=obj1 m_bool=TRUE m_mode=0xe m_data[0]=0xf4 m_data[1]=0xe m_data[2]=0x58 m_data[3]=0xbd
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 0: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---

Using clone method

clone method works exactly the same as a copy method, the difference being that a clone will return an object with the copied contents. So this saves some trouble of creating the second object before copy.

  
  
class base_test extends uvm_test;
  `uvm_component_utils(base_test)
  function new(string name = "base_test", uvm_component parent=null);
    super.new(name, parent);
  endfunction
  
  function void build_phase(uvm_phase phase);
  	// Create obj1, but only declare handle for obj2
    Object obj2;
    Object obj1 = Object::type_id::create("obj1");
    obj1.randomize();
    `uvm_info("TEST", $sformatf("Obj1.print: %s", obj1.convert2string()), UVM_LOW)
    
    // Use $cast to clone obj1 into obj2
    $cast(obj2, obj1.clone());
    `uvm_info("TEST", "After clone", UVM_LOW)
    `uvm_info("TEST", $sformatf("Obj2.print: %s", obj2.convert2string()), UVM_LOW)
  endfunction
endclass

module tb;
	initial begin
		run_test("base_test");
	end
endmodule

  
Simulation Log
ncsim> run
UVM_INFO @ 0: reporter [RNTST] Running test base_test...
UVM_INFO testbench.sv(105) @ 0: uvm_test_top [TEST] Obj1.print:  m_name=obj1 m_bool=TRUE m_mode=0xe m_data[0]=0xf4 m_data[1]=0xe m_data[2]=0x58 m_data[3]=0xbd
UVM_INFO testbench.sv(20) @ 0: reporter [m_pkt] In Packet::do_copy()
UVM_INFO testbench.sv(86) @ 0: reporter [obj1] In Object::do_copy()
UVM_INFO testbench.sv(108) @ 0: uvm_test_top [TEST] After clone
UVM_INFO testbench.sv(109) @ 0: uvm_test_top [TEST] Obj2.print:  m_name=obj1 m_bool=TRUE m_mode=0xe m_data[0]=0xf4 m_data[1]=0xe m_data[2]=0x58 m_data[3]=0xbd
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 0: reporter [UVM/REPORT/SERVER]

UVM has the facility of doing backdoor reads from HDL paths via DPI/PLI interface.

Consider a simple design hierarchy shown below for illustration purposes. We also need a testbench to instantiate the design and start the test.

  
  
module B;
	reg [3:0] cfg;
endmodule

module A;
	B b;
endmodule

// Testbench module
module tb;
	A a();
	initial 
		run_test("base_test");
endmodule

  

uvm_hdl_check_path

import "DPI-C" context function int uvm_hdl_check_path (string path);

This method returns 1 if the given HDL path exists, else it returns a 0.

  
  
class base_test extends uvm_test;
	...
	virtual function void build_phase (uvm_phase phase);
		if (uvm_hdl_check_path ("tb.a.b.cfg"))
			`uvm_info ("TEST", "Path tb.a.b.cfg exists", UVM_MEDIUM)
			
		if (!uvm_hdl_check_path ("tb.a.def"))
			`uvm_info ("TEST", "Path tb.a.def does not exist", UVM_MEDIUM)
	endfunction
endclass