by Ramprasad Chandrasekaran, Principal Verification Consultant
What are we talking about?
We as a verification community like to talk about creating UVM VIP and its benefits, but do we know how to integrate a non-UVM VIP provided by FPGA vendors or other vendors written in System Verilog into a hierarchically structured UVM Test bench? We are going to assume for this blog that the non-UVM VIP is written in System Verilog. Perhaps it is possible to create a native System Verilog VIP, but if we’d like to reuse it as well as take advantage of UVM benefits such as the OOP (Object-Oriented Programming) concepts to model the data structures, synchronizing/data transfers between UVM and the non-UVM VIP, then I wanted to shed some light on how we can approach it.
What’s the problem?
Let’s assume we are building a testbench for a DUT which will use the UVM methodology to architect the verification framework. If there is a specific interface (either proprietary or an existing VIP which is non-UVM), then we could follow the steps below to incorporate it as part of the UVM test bench. This allows us to use the benefits of the UVM test bench such as randomization, configuration of the components, and ensuring consistent testbench flow. UVM introduces “Phases” to synchronize major functional steps a simulation runs through. These steps are sequential in nature which are executed in the following order:
- Build Phases
Where the testbench is constructed, connected and configured.
- Run-time Phases
Stimulus generation & time consuming simulation takes place here.
- Clean up Phases
Here the Test results are collected and reported.
We also are trying to address some different issues using non-UVM IP which affects the following data points, such as:
- Number of lines of code can be reduced if we employ the factory and configuration database methods available as part of the UVM constructs.
- Randomization of the data structures is not possible
- Reuse of the stimulus is limited.
- Scalability of the testbench is affected
- Readability of the code and number of lines code written is too much compared to UVM.
What’s the solution?
We are using the MDIO Verification IP which is a System Verilog VIP which is what we are integrating as part of the UVM VIP testbench. Here are a few examples of how we integrate the VIP into the existing testbench. The solution addresses the easy reuse of the non-UVM IP provided by vendors and also enables the benefits of UVM constructs to be leveraged with limited lines of code.
For usage, the following steps should be done:
- Import MDIO_S package into your test or the testbench
- Create MDIO_s_env class object
Description: mdio_ifc_s is the reference to the MDIO Slave interface instance name.
Challenge: We have to create the instance of the environment in build phase. As this is the phase used to build testbench components and create their instances
class test_top_env extend uvm_env; MDIO_s_env m_mdio_env; function new (string name, uvm_component parent); super.new(name, parent); printer = new(); endfunction extern virtual function void build_phase(uvm_phase phase); extern virtual function void connect_phase(uvm_phase phase); endclass : test_top_env function void test_top_env::build_phase(uvm_phase phase); if (!uvm_config_db#(cfg)::get(this, "", "cfg", cfg)) `uvm_fatal("NOCONFIG") m_mdio_env = new(cfg.mdio_ifc_s); endfunction : build_phase
- Instantiate the mdio_s_if interface in you testbench and connect interface ports to your DUT.
`include "mdio_s_if.sv" module tb_dut_top; import MDIO_S::*; wire mdc; mdio_s_if u_mdio_s_if(mdc); test_top dut ( .mdio_mdc_mdc(mdc), .mdio_mdc_mdio_i(u_mdio_s_if.mdo), .mdio_mdc_mdio_o(u_mdio_s_if.mdi), .mdio_mdc_mdio_t(u_mdio_s_if.mdo_en)); uvm_config_db#(virtual mdio_s_if)::set(null,"*","mdio_ifc_s", u_mdio_s_if); endmodule : tb_dut_top
- The example below shows how the MDIO environment and interface can be instantiated in the UVM environment:
class cfg extends uvm_object; virtual mdio_s_if mdio_ifc_s; function new (string name, uvm_component parent); super.new(name, parent); printer = new(); endfunction endclass : cfg
- Start MDIO Slave Environment for triggering the stimulus and also the tasks/functions as part of the non-UVM IP in sequence or the monitor:
Description: Now MDIO slave verification IP is ready to respond transactions initiated by master device. We want to call this function in the build_phase of the common sequence so that it starts to look for all the transactions triggered by the sequencers
Challenge: Make the tasks to be virtual so we can override them in the test to add additional functionality using the polymorphism concept as part of UVM
class test_mdio_config_ral_seq extends uvm_sequence; test_top_env env; test_top_base_seq init_seq; `uvm_object_param_utils(test_mdio_config_ral_seq) function new(string name="test_mdio_config_ral_seq"); super.new(name); endfunction virtual task body(); env.m_mdio_env.startEnv(); configure_mdio(); endtask : body virtual task configure_mdio(); rand this_addr_t l_addr; rand this_data_t l_data; rand int dev_type this_data_t value; env.m_mdio_env.setAddr(l_addr,dev_type); end task: configure_mdio task run_phase(uvm_phase phase); phase.raise_objection(this, "Test Started"); init_seq = mpp_top_base_seq::type_id::create(); init_seq.start(top_env.m_vsqr); phase.drop_objection(this, "Test Finished"); endtask : run_phase endclass : test_mdio_config_ral_seq
Note: Randomization can be done as part of the encompassing sequence. Once the static value is received, then it is passed to the non-UVM IP. This way the non-UVM IP tasks can be called to leverage the functionality and use the UVM methodology benefits as well.
- UVM has the factory concept which essentially means that you can modify or substitute the nature of the components created by the factory without making changes to the testbench. Say, you have written two driver classes, and the environment uses only one of them. This helps reducing the number of lines in the code as we can just override the tasks instead of creating redundant code.
class test_top_test extend uvm_test; function new (string name, uvm_component parent); super.new(name, parent); printer = new(); endfunction function void test_top_env::build_phase(uvm_phase phase); set_type_override_by_type (uvm_object_wrapper original_type, uvm_object_wrapper override_type, bit replace=1); set_type_override_by_name (string original_type_name, string override_type_name, bit replace=1); set_inst_override_by_type (uvm_object_wrapper original_type, uvm_object_wrapper override_type, string full_inst_path); set_inst_override_by_name (string original_type_name, string override_type_name, string full_inst_path); endfunction: build_phase endclass: test_top_test
We could follow the same strategy as we used in the sequence to call the functions and tasks of non-UVM IP in the monitor to decode and work on the data structures. Once the data structure is captured, we could use UVM constructs to do checking and eventually pass it on to the coverage monitor for functional coverage.
WorkLifeBalance: Interview with Craig MaimanNovember 24, 2021
Hear from one of our Principal RTL Design Engineers about what its like to work at XtremeEDA and be able […]Learn More
A Template For Evaluating Portable StimulusSeptember 24, 2018
by Neil Johnson, Chief Technologist I’ve written a lot about portable stimulus over the last year, all of it being theoretical. […]Learn More
Video: Building An Integrated Verification FlowJuly 31, 2018
by Neil Johnson, Chief Technologist DAC2018 has come and gone. It was a great conference as usual with lots of […]Learn More
UVM Gotchas: UVM Register Layer Prediction ModesJuly 17, 2018
by Robin Hotchkiss, Senior Verification Consultant What are we talking about? This post talks about the difference between implicit and […]Learn More