John Aynsley, Doulos, March 2011
Updated for UVM 1.0
HDLs allow their design entities / modules to be parameterized, VHDL using generics and Verilog using parameters. SystemVerilog provides the convenience of allowing the parameters to be defined on the module line and overridden on the module instance line:
// SystemVerilog module producer #(parameter bit param1 = 0, int param2 = 0, string param3 = ""); ... endmodule module top; producer #( .param1(1'b1), .param2(2), .param3("3") ) producer_inst (); endmodule
Once again, the UVM code appears very verbose compared to the VHDL, Verilog, or SystemVerilog equivalents. Once again, the justification is the increased flexibility, which only becomes apparent when considering the various verification use cases.
UVM allows components to be parameterized in a number of ways (and, of course, UVM is SystemVerilog too). Easier UVM restricts this to a single option: one UVM configuration object for each component that needs to be parameterized. The first step is to define a class that represents a parameter block and contains all the parameters for a given component. The class uses a similar template to that we saw above for a transaction, except that is extends uvm_object rather than uvm_sequence_item. You are recommended to define the utility method convert2string, because it can be handy for debugging.
// UVM class producer_config extends uvm_object; // Standard macro for a config object, transaction, or sequence `uvm_object_utils(producer_config) rand bit param1; rand int param2; string param3; // Other configuration parameters // Standard constructor for a config object, transaction, or sequence function new (string name = ""); super.new(name); endfunction // Standard utility method for a config object or transaction function string convert2string; return $sformatf("param1=%b, param2=%0d, param3=%s", param1, param2, param3); endfunction endclass
Note the inclusion of the rand keyword in front of the parameter definitions. This is to anticate the fact that you might want to randomize a block of configuration parameters as part of a test case. Having all the configuration parameters for a given component in a single class makes this easy.
The next step is to have the a UVM component grab the parameters from the configuration object. This is usually best done during the build phase, because doing so allows the parameters to be used to control the building of lower-level components. With Easier UVM, configuration parameters should be accesed by calling uvm_config_db #(T)::get explicitly from the build_phase method and copying their values to local variables:
// UVM class producer extends uvm_component; ... producer_config config_h; // Configuration parameters bit param1 = 0; int param2 = 0; string param3; ... function void build_phase(uvm_phase phase); super.build_phase(phase); my_port = new("my_port", this); begin if ( uvm_config_db #(producer_config)::get(this, "", "config", config_h) ) begin param1 = config_h.param1; // Local parameters copied from configuration object param2 = config_h.param2; param3 = config_h.param3; end end endfunction ... endclass
Note that the config object may not necessarily exist, in which case the parameters will take default values set locally.
The third step is to set the values of the configuration parameters from elsewhere, which could be from higher-level components in the test bench, or could be from a specific test. Do do so, a UVM component calls set_config_object from the build method prior to creating the relevant lower-level components:
// UVM class my_test extends uvm_test; ... function void build_phase(uvm_phase phase); super.build_phase(phase); begin producer_config config_h = new; config_h.param1 = 1; // Set test-specific values for configuration parameters config_h.param2 = 2; config_h.param3 = 3; uvm_config_db #(my_agent_config)::set(this, "*.*producer*", "config", config_h); end top_h = top::type_id::create("top_h", this); endfunction endclass
The second argument to set is the hierarchical pathname of the component or components to which the configuration is to apply, specified relative to the path of the current component. As you can see, the pathname may include wild cards, which is a significant enhancement to what is possible in Verilog or VHDL. The third argument is used to identify this particular configuration object when it is retrieved from the config_db.
Finally, with UVM configurations it is possible for configuration information to cascade down the hierarchy of verification components such that that each component can choose its own defaults in the absence of information from above, and where necessary can override any configuration information used by components below. If the same configuration object is set from multiple places in the component hierarchy, the call made from the highest level component will be the winner.
For example, after calling uvm_config_db #(T)::get, a build_phase method may call uvm_config_db #(T)::set to set configuration parameters for lower-level components before creating them:
if ( uvm_config_db #(producer_config)::get(this, "", "config", config_h) ) begin ano_config_h.param1 = config_h.param3; uvm_config_db #(my_agent_config)::set(this, "*.*producer*", "config", config_h); endPrevious: From VHDL Records to UVM Transactions