Global training solutions for engineers creating the world's electronics

Easier UVM - for VHDL and Verilog Users: Configuration

John Aynsley, Doulos, March 2011
Updated for UVM 1.0

From VHDL Generics and Verilog Parameters to UVM Configurations

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);
end
Previous:  From VHDL Records to UVM Transactions

Back to Easier UVM - for VHDL and Verilog Users

Back to the full list of UVM Resources