This is the first part of a three part series discussing SystemVerilog interfaces and strategies for dealing with parameterization.
SystemVerilog based verification introduces the concept of interfaces to represent communication between design blocks. In its most elemental form a SystemVerilog interface is just a named bundle of signals that can be communicated through a module port as a single item. Design modules that receive this interface can then access signals through this interface reference. However, higher level functions of interfaces can also provide a more strongly typed communication to better represent the design intent. The following is a subset of the higher order functions that are available in SystemVerilog Interfaces:
One very important use of SystemVerilog interfaces is to connect static design elements to dynamic testbench elements. Dynamic testbench elements need access to static design elements in order to sample and drive signals, but reusable testbench elements cannot access static design elements except through a special construct named a virtual interface. Virtual interfaces are interface handles within testbench code that can be assigned with an interface instance. Virtual interfaces are dynamic properties and can be assigned to different interface instances in different testbenches which promotes re-usability.
A common technique for designing reusable design blocks is to use parameters to enable different instances of the design block with unique characteristics. For example, a module could be parameterized to allow the data bus width to be defined when the module is declared and the parameter value is provided. SystemVerilog interfaces also support parameterization, but the use of parameterized interfaces introduces unforeseen complications on the testbench side. In order to be assignment compatible a parameterized virtual interface must be specialized with the same values that the interface instance is specialized with. Unless precautions are taken, this can make for some very ugly testbench code with even uglier use models.
The problem that parameterized virtual interfaces introduce is that the strongly typed interface must be known to the testbench elements that access it. Therefore a generic class cannot be written to use a parameterized virtual interface when the interface specialization is not yet known. One solution to this problem is to parameterize the class that accesses the parameterized virtual interface. For example, a UVM driver could be parameterized with the type of virtual interface that it must utilize. This just moves the problem up a layer however, as now the agent that instantiates that parameterized driver must also be parameterized so that it can create a correctly specialized instance of the driver. This keeps moving up the layers until you get to the top layer testbench component that “knows” about the specific specializations that are being tested exists. The following code segments demonstrate the issue.
First we define the parameterized virtual interface:
interface param_if#(int width = 8); logic clk; logic[width-1:0] data; clocking active_cb @(posedge clk); default input #1 output #1; output data; endclocking modport active_mp (clocking active_cb); endinterface typedef virtual param_if param_vif
Next, we define the reusable VIP code. This testbench code must be designed to be reusable in any environment that the parameterized interface could be used in, and so the VIP code itself must also be parameterized so that the proper interface can be accessed. The following code segment shows how a UVM driver class and the agent that contains the driver must be parameterized:
//======================================================================= class param_driver#(type vif_t=param_vif) extends uvm_driver#(cust_data); `uvm_component_param_utils(param_driver#(vif_t)) vif_t vif; function void build_phase(uvm_phase phase); if (!uvm_config_db#(vif_t)::get(this, "", "vif", vif)) `uvm_fatal("build", "A valid interface was not received."); endfunction endclass //======================================================================= class cust_agent#(type vif_t=param_vif) extends uvm_agent; `uvm_component_param_utils(param_agent#(vif_t)) vif_t vif; param_driver#(vif_t) param_driver; function void build_phase(uvm_phase phase); if (!uvm_config_db#(vif_t)::get(this, "", "vif", vif)) `uvm_fatal("build", "A valid interface was not received."); uvm_config_db#(vif_t)::set(this, "param_driver", "vif", vif); param_driver = param_driver#(vif_t)::type_id::create("param_driver", this); endfunction endclass
This doesn’t look so bad so far! It adds a little complication to the class definitions, but not too much. The problems however don’t become apparent until you examine how the testbench must access these parameterized classes. The following segment shows how a test has to access every instance of the VIP uniquely based on how the interface is parameterized:
//======================================================================= class cust_test extends uvm_test; `uvm_component_utils(cust_test) param_agent#(virtual param_if#(8)) param_agent8; param_agent#(virtual param_if#(16)) param_agent16; param_agent#(virtual param_if#(32)) param_agent32; virtual function void build_phase(uvm_phase phase); param_agent8 = param_agent#(virtual param_if#(8))::type_id::create("param_agent8", this); param_agent16 = param_agent#(virtual param_if#(16))::type_id::create("param_agent16", this); param_agent32 = param_agent#(virtual param_if#(32))::type_id::create("param_agent32", this); endfunction endclass //======================================================================= module test_top; param_if#(8) if8(); param_if#(16) if16(); param_if#(32) if32(); initial begin uvm_config_db#(virtual param_if#(8))::set(uvm_root::get(), "uvm_test_top.param_agent8", "vif", if8); uvm_config_db#(virtual param_if#(16))::set(uvm_root::get(), "uvm_test_top.param_agent16", "vif", if16); uvm_config_db#(virtual param_if#(32))::set(uvm_root::get(), "uvm_test_top.param_agent32", "vif", if32); run_test("cust_test"); end endmodule
As you can see, every reference to the VIP must be parameterized with the proper interface type that it is to work with. This affects not only the VIP construction, but will also affect callback registration, factory overrides, etc. This presents a large burden on the testbench developer, and limits the reusability of these environments.
Adding parameters to the verification components is a valid technical solution for reusable VIP, but it complicates the use model considerably and limits testbench reusability. In the next post in this series we’ll examine a possible workaround to this problem, but this comes with a price!
Authored by Aron Pratt