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/nets at all levels in the DUT as well as in TestBench.
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 TestBench can not be like the one in traditional Testbenches i.e. port-based connection of the DUT ports to the TestBench ports.
A standard classification of a UVM environment is shown in the figure below:
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 the virtual interface as a handle pointing to the interface instance. The 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, let’s 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 an 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. The 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 detail 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 (driven from ovm_objects) are allowed to kept inside Configuration Database. Due to these limitations, the 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 users 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 the 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). Let’s see the code provided 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 an effective communication between DUT and UVM Testbench which provides modularity, high re-usability & better control from the top (uvm_test_top).
I hope you enjoyed the learning. Please, feel free to put your comments and/or suggestions for future topics. This blog post is amazingly explained by one of the senior-most engineer from Mentor Graphics Manish. Till then see ya take care and Keep on learning Keep on Growing 🙂