How to connect the DUT to the UVM Testbench..??
In our traditional directed Testbench environments, all the components are “static” in nature & information (data/control) is also exchanged in the form of signals/wire/net at all levels in the DUT as well as TB.
But this is not the case in the latest “Constrained Random Verification Methodology” like UVM where DUT is static (module based) in nature yet Testbench is class (SystemVerilog OOPs) based. Hence the communication between the DUT and TB can NOT be like the one in traditional Testbenches i.e. port based connection of the DUT ports to the TB ports.
A standard classification of an UVM environment is shown in the figure 1 below:
Figure 1: A Standard Classification of an UVM Environment
In UVM, for this, we utilize the newly introduced SystemVerilog feature called “Virtual Interface”. An “Interface” is a collection of common signals between two entities & the signal direction is governed by the “modports”.
We can see virtual interface as a handle pointing to the interface instance. Virtual interface acts as a medium to connect the DUT (SystemVerilog/Verilog module based) and the Testbench (SystemVerilog OOPs class based). Testbench access the DUT signals via virtual interface and vice versa.
How it all happens, lets understand using a diagram given below:
As we can see in the diagram that the virtual interface information is provided to the Testbench (Test Class) from the DUT (light pink color coding) which acts as a pointer to the interface instance being used by the DUT. In actual UVM environment, the test class receives this virtual interface information from the DUT and later the test class propagates this virtual interface (interface instance handle) information to the physical component e.g. Driver, Monitor, Scoreboard etc, via a configuration object (dark pink color coding), which in the real sense needs this information to communicate with the DUT. Whole of this process happens by utilizing the “uvm_config_db” API.
In particular, passing the virtual interface from the test class to the hierarchy below utilizes one more UVM concept i.e. “Configuration Object”. I will try to write in more details about it in one of the next upcoming posts.
Placing a Virtual Interface into Configuration Database using uvm_config_db API:
The limitation of OVM was that only integral values, strings & objects (drived from ovm_objects) are allowed to kept inside Configuration Database. Due to this limitations, virtual interface needs to be wrapped into an object before using the config_db APIs.
But this limitation has been removed in UVM and now user can place Virtual Interface, User Defined Types etc directly into the Configuration Database. We can utilize this feature directly and use “set()” and “get()” call to place and retrieve the Virtual Interface to/from Configuration Database.
module top; import uvm_pkg::*; `include "uvm_macros.svh" //// Interface Declaration interface dut_if(); logic data, clock, reset; endinterface: dut_if //// Interface Instantiation dut_if dut_if_inst; //// DUT Instantiation DUT DUT_inst (..... ..... ..... ); //// Initial Block initial begin uvm_config_db #(virtual dut_if)::set(null,"*", "dut_vi", dut_if_inst); run_test(); end endmodule: top
Making the Virtual Interface Available in the Testbench:
A configuration object is created inside the test class & this configuration object contains a virtual interface property. Inside the test class, another uvm_config_db method i.e. uvm_config_db::get() is used to fetch the value of the virtual interface and assign it to configuration object property. This process is shown in the code below:
class my_test extends uvm_test; `uvm_component_utils(my_test) //// Configuration Handle my_config my_config_h; ...... ...... ...... //// Build Function function void build_phase (uvm_phase phase); super.build_phase(phase); my_config_h = new(); if (!uvm_config_db #(virtual dut_if)::get(this, "", "dut_vi", my_config_h.v_dut_if)) `uvm_fatal("FATAL MSG", "Virtual Interface Not Set Properly"); //// Propagating my_config object down to the hierarchy uvm_config_db #(my_config)::set(this, "*", "config", my_config_h); endfunction: build_phase endclass: my_test
Assigning Virtual Interface Property into Transactor:
Now in this stage, the physical component i.e. the transactor e.g. Driver, Monitor & Scoreboard etc., which in real sense access the DUT assigns the virtual interface property from the received configuration object from the top i.e. uvm_test (as described above). Lets see the code provide below to make it clear:
class my_driver extends uvm_driver #(my_txn); `uvm_component_utils(my_driver) ... //// Virtual Interface Declaration virtual dut_if dut_vi; //// Configuration Handle my_config my_config_h; //// Build Function function void build_phase (uvm_phase phase); super.build_phase(phase); if(!uvm_config_db #(my_config)::get(this, "", "config", my_config_h)) `uvm_fatal("FATAL MSG", "Configuration Object Not Received Properly"); .... endfunction: build_phase //// Connect Function function void connect_phase (uvm_phase phase); super.connect_phase(phase); dut_vi = my_config_h.v_dut_if; endfunction: connect_phase //// Run Task task run_phase (uvm_phase phase); ... ... ... endtask: run_phase endclass: my_driver
So this way, we can use Virtual Interface and uvm_config_db APIs to set up the effective communication between DUT and UVM Testbench which provides modularity, high re-usability & better control from the top (uvm_test_top).
Thank you again for your time to go through this post. I hope you enjoyed the learning. Pl. feel free to put your comments and/or suggestions for future topics, I’ll try to cover those. Thank you!