This is an example showing how to access a parameterized SystemVerilog interface from a UVM verification environment by calling the methods of an abstract base class from the UVM environment while making a concrete instantiation of that abstract class within the SystemVerilog interface itself. This technique overcomes the limitations of using virtual interfaces to access parameterized SystemVerilog interfaces. This might be considered an advanced coding technique: The code may look a little more complex than "simply" accessing the SystemVerilog interface using virtual interfaces, but it has the advantage that a single "generic" UVM driver or monitor class can access the contents of a parameterized SystemVerilog interface without the need to create a new version of that class for each and every instantiation of the interface.
This example was inspired by ideas from Jonathan Bromley, Dave Rich, and Gunther Clasen.
The complete working example is available on EDA Playground at the following links:
Example of a parameterized interface generated from an Easier UVM interface template file
Example that pulls in a user-defined parameterized interface
(Note that the following base class is not technically an abstract class in the sense that it does not include a pure virtual function, but it is playing the role of an abstract class in that the write task is meant to be overridden.)
class clkndata_if_base extends uvm_component; `uvm_component_utils(clkndata_if_base) function new(string name, uvm_component parent); super.new(name, parent); endfunction // Methods to be called from the UVM verification env // In this example the methods are pseudo-parameterized by using N bits of a longint virtual task write(longint addr_arg, data_arg); `uvm_error(get_type_name(), "write not implemented") endtask // Other methods, e.g. read, peek, reset ...
Note that the class above extends uvm_component. It would have been sufficient to extend uvm_object, but the advantage of making the class a component is that it can be configured through the configuration database. The concrete instantiation of this abstract class will be created from a UVM env (or possibly from a test), which means that the concrete object has a proper place in the UVM component hierarchy that can be used when setting and getting configuration information.
A UVM driver that calls the methods of the abstract base class above. The same principle would apply for the UVM monitor or any other UVM component that would otherwise have used a virtual interface to access the SystemVerilog interface. The driver gets a reference to the concrete implementation of the interface class from the configuration database.
class clkndata_driver extends uvm_driver #(clkndata_transaction); ... clkndata_if_base m_clkndata_if; // Reference to abstract base class ... task run_phase(uvm_phase phase); ... seq_item_port.get_next_item(req); if (req.cmd) m_clkndata_if.write(req.addr, req.data); // Call methods of abstract base class
The agent (or some other component in the UVM component hierarchy) creates the concrete implementation of the interface object and propagates references to that object to the driver and monitor. The choice of which component actually creates the concrete implementation does not really matter because the instance will be distinguished using its local name and type, but conceptually the agent is the obvious choice because the agent drives and monitors the interface. The string name used to identify the specific instance of the parameterized concrete class can be set locally or can be passed in through the configuration database so that multiple instances of the same agent can use different parameterizations of the interface.
class clkndata_agent extends uvm_agent; ... clkndata_if_base m_clkndata_if; // Reference to abstract base class ... function void build_phase(uvm_phase phase); ... m_sequencer = clkndata_sequencer::type_id::create("m_sequencer", this); m_driver = clkndata_driver ::type_id::create("m_driver", this); m_monitor = clkndata_monitor ::type_id::create("m_monitor", this); ... // The factory method call to 'create' below instantiates the concrete clkndata_class objects. // This works because of the factory overrides made from the calls to clkndata_if.use_concrete_class. // In this example, m_config.iface_string will be set to "clkndata_if_4_4" or "clkndata_if_8_8" m_clkndata_if = clkndata_if_base::type_id::create(m_config.iface_string, this); endclass function void connect_phase(uvm_phase phase); ... m_driver.m_clkndata_if = m_clkndata_if; m_monitor.m_clkndata_if = m_clkndata_if; endclass
(Note that the abstract base class variables name m_clkndata_if differs from the string names "clkndata_if_4_4" and "clkndata_if_8_8" because the string names play a special role in identifying the concrete interface objects, explained below. In general, if this were not the case, it would be better style for the variable names and string names to match.)
This example has two integral parameters, but this technique does not impose any inherent limitations on the number or type of parameters. The interface contains the concrete implementation of the abstract base class within its scope. Because of this, the concrete class implementation can access any variables, wires, or parameters defined in the SystemVerilog interface. It is important that the concrete interface class has the same number and type of parameters as the SystemVerilog interface itself, because this permits a unique instantiation of the interface class for each unique instantiation of the SystemVerilog interface, which in turn allows a single variable of type clkndata_if_base (in this example) to access any instance of the parameterized interface in an unambiguous way (because the variable actually refers to a concrete object the type of which is defined inside the interface). This is the crux of the matter, though it might require some thought to really appreciate what is going on.
interface clkndata_if #(parameter AW = 1, DW = 1); ... bit [AW-1:0] addr; bit [DW-1:0] data; // Other variables, wires, and parameters ... // Concrete implementation of interface class within the scope of the SystemVerilog interface class clkndata_class #(int AW = 0, DW = 0) extends clkndata_if_base; `uvm_component_param_utils(clkndata_class#(AW,DW)) function new(string name, uvm_component parent); super.new(name, parent); endfunction // Implement dummy methods from abstract base class task write(longint addr_arg, data_arg); ... addr[AW-1:0] <= addr_arg[AW-1:0]; data[DW-1:0] <= data_arg[DW-1:0]; ... endtask // Other methods ... endclass function void use_concrete_class; string path_name; path_name = $sformatf("*.clkndata_if_%0d_%0d", AW, DW); clkndata_if_base::type_id::set_inst_override( clkndata_class#(AW,DW)::get_type(), path_name, null); endfunction endinterface
The module that instantiates this parameterized SystemVerilog interface can call the convenience function use_concrete_class for each parameterized interface instance to set factory overrides to instantiate the concrete objects in place of the abstract base class.
module top; ... clkndata_if #(4, 4) clkndata_if_4 (); clkndata_if #(8, 8) clkndata_if_8 (); dut dut_inst ( .ifport_4(clkndata_if_4), .ifport_8(clkndata_if_8) ); initial begin clkndata_if_4.use_concrete_class(); clkndata_if_8.use_concrete_class(); // Call for each instance of the parameterized interface uvm_top.run_test(); end endmodule
The trick here is that use_concrete_class sets a path name for the factory instance override that uniquely identifies the values of the parameters to the particular interface instantiation. The final part of the path name used for the factory override, e.g.
"clkndata_if_8_8"
must be used as the string name of the concrete interface object when it is instantiated within the UVM verification environment using the factory as shown in the example above, that is:
clkndata_if_base::type_id::create("clkndata_if_8_8", this)
The factory instance override replaces type clkndata_if_base with type clkndata_class#(AW,DW) when the factory creates the object, and that class has access to the variables declared within precisely the correct instantiation of the interface, i.e. the one with AW = 8 and DW = 8.
Easier UVM Coding Guidelines
Easier UVM - Deeper Explanations
Easier UVM Code Generator
Easier UVM Video Tutorial
Easier UVM Paper and Poster
Easier UVM Examples Ready-to-Run on EDA Playground
Back to the full list of UVM Resources