Image
image
image
image


Design Articles

A Practical Guide to Adopting the Universal Verification Methodology—Part 2


This article is excerpted from Chapter 4: UVM Library Basics from the newly published book A Practical Guide to Adopting the Universal Verification Methodology by Sharon Rosenberg and Kathleen A. Meade (Copyright 2010 by Cadence Design Systems). New installments of this chapter will appear every Monday for the next four weeks. If you can't wait--or want to learn more--the book is available at Amazon.com.

By Sharon Rosenberg and Kathleen A. Meade, Cadence Design Systems

uvm book

4.4 The uvm_component Class

All of the infrastructure components in a UVM verification environment, including environments and tests, are derived either directly or indirectly from the uvm_component class. The component class is quasi-static and should only be created at the beginning of the simulation (build phase). User-defined classes derived from this class inherit additional built-in automation.

Note: Typically, you will derive your classes from the methodology classes, which are themselves extensions of uvm_component. However, understanding the uvm_component is important because many of the facilities that the methodology classes offer are derived from this class.

The following sections describe some of the capabilities provided by the uvm_component base class and how to use them. The key pieces of functionality provided by the uvm_component base class include:

  • Phasing and execution control
  • Hierarchy information functions
  • Configuration methods
  • Factory convenience methods
  • Hierarchical reporting control

4.4.1 Simulation Phase Methods

In HDL, such as Verilog and VHDL, static elaboration of the instances hierarchy occurs before simulation starts. This ensures that all instances are in place and connected properly before run-time simulation. In SystemVerilog, classes are instantiated at run time. This raises a few questions: When is it safe to start traffic generation and execution? When is a good time to assume that all the UVC components have been created? and What TLM ports can be connected?

While each developer can devise their own synchronization points for these operations, the SystemVerilog UVM Class Library provides a set of standard built-in simulation phase methods that allow synchronization of environments without up-front planning. These phases are hooks for users to include logic to be executed at critical points in time. For example, if you need checking logic to be executed at the end of the simulation, you can extend the check() phase and embed procedural code in it. Your code will then be executed at the desired time during simulation. All built-in phases are executed in zero time except the run phase. See uvm_phase documentation in the UVM Class Reference for more information on using built-in phases.

From a high-level view, the existing simulation phases (in simulation order) are:

new()—While this is not a UVM phase, the component constructor is the first function to be executed when a component is being allocated. The uvm_component constructor includes two parameters: a string name parameter that holds the component name, and a reference to the component’s parent. We recommend against using default values for constructor parameters of components. This ensures that the user provides meaningful values for the name and parent. The constructor signature should look as follows:

class street extends uvm_component;
  `uvm_component_utils(street)
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction : new
endclass : street

Tip: Some guidelines about the usage of the constructor include:

  • You must define a constructor that takes a name and parent argument for all component types because the factory provides the name and parent argument when constructing the components.
  • Do not use default values for the name and parent argument to the constructor so that the user is forced to give meaningful values for these parameters.
  • Name components with short but meaningful names.
  • For arrays of components, use indexes in their names: agent[index], which will form names like env.agent[0].monitor, env.agent[1].monitor, and so on.

build()—The first phase of the UVM phasing mechanism is the build() phase, which is called automatically for all components in a top-down fashion. This method optionally configures and then creates a component’s child components. We use this method for building child components and not the constructor, since the constructor is not fully polymorphic. Since build() is called top-down, the parent’s configuration calls are completed before the child’s build() method is called.

Some guidelines about the usage of build are:

  • Every build() implementation should call super.build() as the first statement of build() unless the component is explicitly turning off automatic configuration. Calling super.build() updates the component configuration field values by invoking a function called apply_config_settings. Also though not recommended, a parent component may explicitly call build() on its children as part of the parent.build(). Calling the super.build() ensures that build() does not get called twice.
  • Allocate all sub-components in the build() phase rather than in the constructor. This allows the component to be configured before it allocates the hierarchy underneath it. Also, the constructor is not polymorphic, so it is not possible for a derivative class to change the structure unless the structure is created in the build function.
  • If a component is allocated after the build phase, UVM will generate an error message.
  • Configuration settings for a components descendents should be done in the build method just before the component’s children are created.
  • If you are changing the structure of a component by overriding the build method, you cannot call super.build() as this will cause the super classes build to be executed. However, if you still want the behavior of uvm_component’s build method, you can get this by calling uvm_component::build(). This effectively jumps over the super class’ build method.

class my_derived_comp extends mycomp;
    function new(string name, uvm_component parent);
        super.new(name,parent);
    endfunction
    function void build();
        uvm_component::build(); //doesn’t call super.build()
        ...//do other stuff
    endfunction
endclass

connect()—The connect() phase is executed after build(). Because all sub-components of the environment are created during build() in a top-down fashion, users may rely on the fact that the hierarchical test/environment/component topology has been fully created when connect() is called. Use this method to make TLM connections, assign pointer references, and make virtual interface assignments.

  • end_of_elaboration()— The end_of_elaboration() phase ensures that all of your connections and references are properly set up from the connect phase. Just before the end_of_elaboration phase is executed, UVM completes the TLM bindings so that, during this phase, all port bindings are established and can be examined.
  • start_of_simulation()—The start_of_simulation() phase provides a mechanism to avoid zero time dependencies as components start up. Component initializations can be done in this phase so that when the components start their run phase, everything is properly initialized.
  • run()—The run() phase is the only predefined time-consuming phase, which defines the implementation of a component’s primary run-time functionality. Implemented as a task, it can perform time-consuming operations. When a component returns from its run task, it does not signify completion of its run phase. Any processes that it may have forked continue to run. The run phase terminates by execution of the global_stop_request(), calling the kill method, if time-out was set and reached, or if the objection mechanism was used.
  • extract()—This phase can be used to extract simulation results at the end of a simulation run, but prior to checking in the next phase. This phase can be used to collect assertion-error count, extract coverage information, extract the internal signals and register values of the DUT, extract internal variable values from components, or extract statistics or other information from components. This phase is a function and executes in zero time. It is called in bottom-up order.
  • check()—Having extracted vital simulation results in the previous phase, the check phase can be used to validate such data and determine the overall simulation outcome. This phase is a function and executes in zero time. It is called in bottom-up order.
  • report()—This phase executes last and is used to output results to files and/or the screen. This phase is a function and executes in zero time. It is called in bottom-up order.

Although all of these hook methods are defined for uvm_component and its derivatives, you only need to implement the methods you will use.

Components do not need to use the `uvm_field_object macro for their child components. The component hierarchy is created by the fact that the parent object is always provided when a component is constructed.

Examples of the phases and hierarchy construction are provided below.

4.4.2 Hierarchy Information Functions

Because the component tree is static throughout the simulation, components are aware of their hierarchy location and can query for their parent class, child classes or their location within the hierarchy. Some of the functions that uvm_component class provides are:

  • get_parent()—Returns a handle to this component’s parent, or null if it has no parent
  • get_full_name()—Returns the full hierarchical name of this object. The default implementation concatenates the hierarchical name of the parent, if any, with the leaf name of this object.
  • get_child(string_name)—Returns a handle to this component’s child, based on its name

The following example demonstrates the UVM simulation phases and component hierarchy information methods. It includes a composition of street, city, and state, and illustrates the get_child(), get_parent(), and get_full_name() capabilities. Allocating components using new() is discouraged. See more on this in “UVM Factory” on page 62.

Example 4-5: uvm_component Simulation Phases and Hierarchy Methods

This example illustrates a hierarchy of components that includes state, city, and street instances. Note the use of the constructor and the build(), run(), and end_of_elaboration() phases. The user implements the desired logic within the phases hooks. The run_test() call at Line 50 triggers the automatic phases execution. The example also demonstrates the hierarchical information functions get_child(), get_parent(), and get_full_name().

  1. module test;
  2. import uvm_pkg::*;
  3. `include "uvm_macros.svh"
  4. class street extends uvm_component;
  5.   `uvm_component_utils(street)
  6.   function new(string name, uvm_component parent);
  7.     super.new(name, parent);
  8.   endfunction : new
  9.                task run ();
  10.     `uvm_info("INFO1", "I vacationed here in 2010", UVM_LOW)
  11.   endtask : run
  12. endclass : street
  13. class city extends uvm_component;
  14.   street Main_St;
  15.   `uvm_component_utils(city)
  16.   function new(string name, uvm_component parent);
  17.     super.new(name, parent);
  18.   endfunction : new
  19.   function void build(); // note that we allocate in the build()
  20.     super.build();
  21. Main_St = street::type_id::create(“Main_St”, this);
  22.   endfunction : build
  23.   task run();
  24.    uvm_component child, parent;
  25.    string pr, ch;
  26.    child = get_child("Main_St");
  27.    parent = get_parent();
  28.    pr = parent.get_full_name();
  29.    ch = child.get_name();
  30.     `uvm_info(get_type_name(), $psprintf("Parent:%s  Child:%s", pr, ch), UVM_LOW)
  31.   endtask : run
  32. endclass : city
  33. class state extends uvm_component;
  34.    city Capital_city;
  35.   `uvm_component_utils(state)
  36.   function new(string name, uvm_component parent);
  37.     super.new(name, parent);
  38.   endfunction : new
  39.   function void build();
  40.     super.build();
  41.     Capital_city = city::type_id::create(“Capital_city”, this);
  42.   endfunction : build
  43.   function void end_of_elaboration();
  44.     this.print();
  45.   endfunction : end_of_elaboration
  46. endclass : state
  47. state Florida = state::type_id::create("Florida", null);
  48. state New_York = state::type_id::create("New_York", null);
  49. // Start UVM Phases
  50. initial run_test();
  51. initial #100 global_stop_request();
  52. endmodule : test

Line 4: A street class definition. Since the street has no child components, a build() method is not needed. The new() and the build() methods call super.new() and super.build().

Line 9: The run method prints an informational message.

Line 13: The city component contains a street child component.

Line 19: The street is created in the build() phase using the factory.

Line 26: From within the run() phase, the city calls get_child() to receive information about the street child component.

Line 27: get_parent() returns the parent component of the city that is in this case the state.

Line 28: get_full_name() returns the full hierarchical path of the parent all the way to the top.

Lines 47-48: Instantiation and creation of two states; these are also created using the factory.

Line 50: run_test() call to start the UVM phases.

The result of executing the code above looks like this:

----------------------------------------------------------------------
Name                     Type                Size                Value
----------------------------------------------------------------------
Florida                  state                -                    @580
  Capital_City           city                -                    @711
    Main_St              street              -                    @712
----------------------------------------------------------------------
----------------------------------------------------------------------
Name                     Type                Size                Value
----------------------------------------------------------------------
New_York                 state               -                    @638
  Capital_City           city                -                    @776
    Main_St              street              -                    @769
----------------------------------------------------------------------
UVM_INFO @0: New_York.Capital_City [city] Parent:New_York  Child:Main_St
UVM_INFO @0: New_York.Capital_City.Main_St [INFO1] I vacationed here in 2010
UVM_INFO @0: Florida.Capital_City [city] Parent:Florida  Child:Main_St
UVM_INFO @0: Florida.Capital_City.Main_St [INFO1] I vacationed here in 2010
Simulation complete via $finish(1) at time 100 NS + 0
./uvm_st_ci_st.sv:75 initial #100 $finish;

4.4.3 uvm_top Component

There is a special component in UVM called uvm_top. This component is a singleton instance of the uvm_root type which is derived from uvm_component. The uvm_top component is responsible for the phasing of all components, and provides searching services (uvm_top.find() and uvm_top.find_all()) to allow you to locate specific components or groups of components in the verification hierarchy. The uvm_top component also provides a place to store global configuration settings; calling set_config_* from outside of a component is equivalent to calling uvm_top.set_config_*. This is true for messages as well; calling uvm_report_* (or `uvm_info/warning/error/fatal) from outside of a component is equivalent to calling uvm_top.uvm_report*.

4.5 UVM Configuration Mechanism

An important aspect of a generic reusable verification component is the ability to configure it for a desired operation mode. The UVM provides a flexible configuration mechanism that allows configuring run-time attributes and component topology without derivation or use of the factory. The configuration of component attributes is achieved via set_config* API that includes three functions:

virtual function void set_config_int (string inst_name,string field_name, uvm_bitstream_t value)
virtual function void set_config_string (string inst_name,string field_name, string value )
virtual function void set_config_object (string inst_name, string field_name,uvm_object value, bit clone = 1 )

Argument description:

  • inst_name: A hierarchical path to a component instance. Use of wildcards is allowed as part of the instance name.
  • field_name: The name of the field that needs to be configured. This field is defined in the instance.
  • value: The value to be assigned to the field. Depending on the set_config call, it can be either uvm_bitstream, string, or uvm_object.
  • clone: Valid only for set_config object and indicates whether a different copy of the object is provided or just a reference to the value object.

Properties that are registered as UVM fields using the uvm_field_* macros are automatically updated by the components super.build() method. These properties can then be used to determine the build() execution for the component. You can also use the manual get_config_* method to get the configuration values before the build() phase (for example, in the component constructor).

Note: To influence the testbench topology, the configuration settings must be provided before the component build occurs.

The following example illustrates the use of the configuration mechanism. Instead of using class derivation, we use the configuration mechanism to set the state names and number of votes for each state.

Example 4-6: Configuration Mechanism

  1. module test;
  2. class state extends uvm_component;
  3.   string state_name;
  4.   int unsigned num_votes;
  5. // Configurable fields must use the field declaration macros. Use the
  6. // UVM_READONLY flag if the property should not be configurable.
  7.   `uvm_component_utils_begin(state)
  8.     `uvm_field_string(state_name, UVM_DEFAULT)
  9.     `uvm_field_int(num_votes, UVM_DEFAULT | UVM_DEC)
  10.   `uvm_component_utils_end
  11.   function new(string name, uvm_component parent);
  12.     super.new(name, parent);
  13.   endfunction : new
  14.   function void end_of_elaboration();
  15.     this.print();
  16.   endfunction : end_of_elaboration
  17. endclass : state
  18.   state my_state1, my_state2;
  19.   initial begin
  20.     // configure BEFORE create
  21.     set_config_string("my_state1", "state_name", "GEORGIA");
  22.     set_config_string("my_state2", "state_name", "CONNECTICUT");
  23.     set_config_int("*", "num_votes", 7);
  24.     // create
  25.     my_state1 = state::type_id::create("my_state1", null);
  26.     my_state2 = state::type_id::create("my_state2", null);
  27.     fork
  28.       run_test();
  29.       #100 global_stop_request();
  30.     join
  31.   end
  32. endmodule : test

Line 23: We used the “*” notation to configure the num_votes field. All states will use that configuration value.

The log information below shows the component configurations in the end_of_elaboration phase().

---------------------------------------------------------------
Name                 Type              Size               Value
---------------------------------------------------------------
my_state1            state             -                    @16
  state_name         string            7                GEORGIA
  num_votes          integral          32                   'd7
---------------------------------------------------------------
my_state2            state             -                    @18
  state_name         string            11           CONNECTICUT
  num_votes          integral          32                   'd7
---------------------------------------------------------------

The example below demonstrates a use for the get_config_* API. You can use this API if you do not use the automation macros, or you want to query the configuration settings before or after the build() phase. In the example we use the get_config_int to decide if the coverage group needs to be constructed.

  1. module test;
  2. typedef enum bit [2:0] {FLORIDA, NEW_YORK, GEORGIA, CALIFORNIA, MICHIGAN,
  3.                         TEXAS, MARYLAND, CONNECTICUT} state_name_enum;
  4. class state extends uvm_component;
  5.   bit coverage_enable = 1;
  6.   state_name_enum = FLORIDA;
  7.   `uvm_component_utils_begin(state)
  8.      `uvm_field_string(state_name_enum, state_name, UVM_DEFAULT)
  9.      `uvm_field_int(coverage_enable, UVM_DEFAULT|UVM_READONLY)
  10.   `uvm_component_utils_end
  11.   covergroup state_cg;
  12.     coverpoint state_name;
  13.   endgroup : state_cg
  14.   function new(string name="state", uvm_component parent);
  15.     super.new(name, parent);
  16.     void'(get_config_int("coverage_enable", coverage_enable));
  17.     if (coverage_enable)
  18.       state_cg = new(); // covergroup must be new’ed in constructor
  19.   endfunction : new
  20.   function void end_of_elaboration();
  21.     if(coverage_enable) state_cg.sample();
  22.     this.print();
  23.   endfunction : end_of_elaboration
  24. endclass : state
  25. state my_state;
  26. initial begin
  27.   set_config_int("my_state", "state_name", NEW_YORK);
  28.   set_config_int("my_state", "coverage_enable", 0);
  29.   my_state = state::type_id::create("my_state", null);
  30.   fork
  31.     run_test();
  32.     #100 $finish;
  33.   join
  34. end
  35. endmodule : test

Line 16: Since a covergroup can only be created in the constructor, we must call the get_config_int() method to retrieve the coverage_enable field. The other fields will be retrieved in the build() method.

Line 28: Sets coverage_enable to 0 for my_state. In this example, the coverage group will not be created.

Next Week: Transaction-Level Modeling in UVM

"Over two decades ago, designers shifted from gate-level to RTL design. This shift was driven by the development of standard Verilog and VHDL RTL coding styles, as well as the availability of RTL synthesis and implementation tools."

Bookmark and Share


Insert your comment

Author Name(required):

Author Web Site:

Author email address(required):

Comment(required):

Please Introduce Secure Code:


image
image