Image
image
image
image


Design Articles

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


This article is the fourth and final excerpt 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). To learn more the book is available at Amazon.com.

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

uvm book 4.7 UVM Factory

As part of the verification task and in order to follow the verification plan, a user may need to extend the generic verification environment behavior beyond its original intent. Unlike design, where specifications can capture the desired functionality in a complete and deterministic form, the verification process is fluid, dynamic and unpredictable. A reusable environment developer cannot foresee each and every corner case test for a certain project, or future projects. The global UVM factory is an advanced implementation of the classic software factory design pattern that is used to create generic code, deferring to run-time to decide the exact subtype of the object that will be allocated. Consider the following classes definitions that may be targeted for reuse:

Example 4-14: UVM Non-Factory Allocation

This first example uses a direct allocation using new(). After the example we explain why calling the new() is not recommended and limits reuse.

  1. class driver extends uvm_component
  2.     `uvm_component_utils(driver)
  3.     function new(string name, uvm_component parent);
  4.         super.new(name, parent);
  5.     endfunction
  6.     virtual task drive_transfer();
  7.     ...
  8.     endtask
  9. endclass: driver
  10. class agent extends uvm_component; // bad example that uses new()
  11.     `uvm_component_utils(agent)
  12.     driver my_driver;
  13.     function new(string name, uvm_component parent);
  14.       super.new(name, parent);
  15.       // create the driver
  16.       my_driver = new (“my_driver”,this); // using new()
  17.     endfunction
  18. endclass: agent

For our discussion purposes let’s say that the user wants to integrate an agent that is part of an interface UVC into a testbench and would like to specialize the drive_transfer() task implementation to print an information message. OOP recommends the user derive a new driver component, as follows.

  1. class my_project_driver extends driver;
  2.   `uvm_component_utils(my_project_driver)
  3.   virtual task drive_transfer();
  4.       super.drive_transfer();    
  5.       `uvm_info("MYINFO1",“Finished driving transfer”, UVM_LOW)
  6.   endtask
  7.   function new(string name, uvm_component parent);
  8.   ....
  9.   endfunction
  10. endclass: my_project_driver

This new class extension is not enough to cause the desired effect, since the agent instantiates the previous definition of the driver and not the extended my_project_driver component. To fix this, the integrator is forced to extend the definition of the agent class and potentially other classes that instantiate the driver. Since the driver parent components definition needs to be extended, the interface UVC definition needs to be extended and we experience a ripple effect of many code modifications all over the testbench. UVM factory introduces an elegant solution that allows overriding the exact driver from outside the agent class. Instead of allocating using new(), the user uses a create() method, which uses a central facility that allocates the driver and returns a handle. A more reusable version of the agent that uses the factory is illustrated below:

Example 4-15: Using the UVM Factory

  1. class agent extends uvm_component;
  2.     `uvm_component_utils(agent)
  3.        driver my_driver;
        
  4.     function new(string name, uvm_component parent);
  5.         super.new(name, parent);
  6. // create the driver
  7.        my_driver = driver::type_id::create("my_driver",this);
  8.     endfunction
  9. endclass: agent

When this code is created, the user can override all of the drivers in the system using:

set_type_override_by_type(driver::get_type(), my_project_driver::get_type());

Or the user can override drivers in the system by replacing a specific instance of the driver using:

set_inst_override_by_type(“env0.agent0.driver”, driver::get_type(),
       my_project driver::get_type());

Factory allocation is not limited to components and is extremely important for objects and sequence items. The scenario in which data items need to be extend to follow verification plan requirements is further discussed in the interface UVC section.

To use the factory, these steps should be followed:

  1. Register all classes within the factory.

This is automatically achieved by using the utility macros `uvm_object_utils and `uvm_component_utils for objects and components, respectively.

  1. Create objects and components using the create() API. This API is the same for both data objects and components.

my_driver = driver::type_id::create("my_driver",this);

  1. Use the type and instance override calls to override the base types with a derived type. Remember, you must set the desired type before you create the class.

There are more capabilities to UVM factory, such as selecting the instances to be overridden using a wildcard expression string, providing a soft override that is ignored if other overrides exist, and much more. For more details please see the UVM SystemVerilog User Guide.

The example below illustrates the same example as above but with factory calls instead of hard-coded allocation.

Example 4-16: UVM Factory Usage

This example defines a state component that has florida and new_york sub-types. We demonstrate how the factory is used to control the allocated state using a create() call, and type overrides.

  1. class state extends uvm_component;
  2.   `uvm_component_utils(state)
  3.   function new(string name, uvm_component parent);
  4.     super.new(name, parent);
  5.   endfunction : new
  6.   function void end_of_elaboration();
  7.     this.print();
  8.   endfunction : end_of_elaboration
  9. endclass : state
  10. class florida extends state;
  11. `uvm_component_utils(florida)
  12.   function new(string name, uvm_component parent);
  13.     super.new(name, parent);
  14.   endfunction : new
  15. endclass : florida
  16. class new_york extends state;
  17. `uvm_component_utils(new_york)
  18.   function new(string name, uvm_component parent);
  19.     super.new(name, parent);
  20.   endfunction : new
  21. endclass : new_york
  22. state my_state1, my_state2;
  23. // Start UVM Phases
  24. initial begin 
  25.   my_state1 = state::type_id::create(“my_state1”, null);
  26.    `uvm_info(“INFO1”,{ my_state1.type=”, my_state1.get_type_name()},
                         UVM_LOW)  
  27.   // set factory to allocate new_york state whenever a state is created
  28.   factory.set_type_override_by_type(state::get_type(), new_york::get_type());
  29.   my_state2 = state::type_id::create(“my_state2”, null);
  30.    `uvm_info(“INFO2”,{“my_state2.type=”,my_state2.get_type_name()},UVM_LOW)
  31.   #100 $finish;
  32. end

Line 1: Definition of a state base class
Lines 10-21: florida and new_york are derived from the state component.
Line 22: Declares two states handles, state1 and state2.
Lines 25-26: Allocating a state using the factory API state::type_id::create(...). Since no overrides were provided to the factory a state object will be allocated and printed in line 30.
Line 28: The factory is used to return the new_york sub-type upon a request to create a state.
Line 29: Create the state using the factory.
Line 30: Now the printed component name is new_york.

And the resulting printout is:

UVM_INFO ./test.sv(51) @ 0: reporter [INFO1] my_state1.type=state
UVM_INFO ./test.sv(54) @ 0: reporter [INFO2] my_state2.type=new_york

Note: We cannot stress enough how important it is to use the factory to create both components and objects. For some users, the factory may be a new concept, but using the factory is simple, and the implementation is built in within the UVM library.

4.8 UVM Message Facilities

While developing or using environments, users will need to print information or issue error messages. A user may want to get trace messages from a suspect component or filter out undesired errors. Verilog’s $display does not allow nonintrusive filtering and control of messages. Changing the verbosity on the command line or via Tcl run-time commands does NOT require that you recompile and re-elaborate the design to observe different trace messages. UVM reporting services are built into all components and are derived from a parent uvm_report_object class. The services can also be used in SystemVerilog modules, interfaces and programs. The mechanism provides a simple solution for most requirements and the ability to customize them with flags, parameters and user-defined hooks.

4.8.1 UVM Message APIs

The messages facilities have a routine interface:

uvm_report_info(string id, string message, int verbosity = UVM_MEDIUM, string filename = “”, int line = 0);
uvm_report_warning(string id, string message, int verbosity = UVM_MEDIUM, string filename = “”, int line = 0);
uvm_report_error(string id, string message, int verbosity = UVM_LOW, string filename = “”, int line = 0);
uvm_report_fatal(string id, string message, int verbosity = UVM_NONE, string filename = “”, int line = 0);

They also have a macro API:

`uvm_info(string id, string message, int verbosity)
`uvm_warning(string id, string message)
`uvm_error(string id, string message)
`uvm_fatal(string id, string message)

The macro API is recommended because it checks if a message needs to be printed before executing expensive string manipulation. The macros also automatically add the filename and line numbers.

The string id is a tag that can be used for filtering. The verbosity level is an integer value to indicate the relative importance of the message. If the value is smaller or equal to the current verbosity level, then the report is issued. For example, if the verbosity is set to 100, all messages coded with 101 and higher will not print.

An enumerated type called uvm_verbosity provides several standard verbosity levels including:

UVM_NONE=0, UVM_LOW=100, UVM_MEDIUM=200, UVM_HIGH=300, UVM_FULL=400, UVM_DEBUG=500.

Note: When using the message macros, verbosity is ignored for warnings, errors and fatal errors. This prevents verbosity modifications from hiding bugs. The verbosity parameter is retained in the routines for backward compatibility purposes. If a warning, error, or fatal error needs to be turned off, this can be done by setting the action to UVM_NOACTION. For example, to turn off a specific message in component mycomp that is an UVM_ERROR with id MYTAG, you might do the following in a test or testbench:

mycomp.set_report_severity_id_action(UVM_ERROR, “MYTAG”, UVM_NO_ACTION);

4.8.2 Guidelines for Using UVM Messages

  • The run-time differences between using the macro API and the routines has proven to be significant in many testbenches. Use the macros API whenever you can.
  • To enable easy message control of a testbench that contains multiple components, use only the enumerated values. Also use this standard interpretation of verbosity levels:
    • UVM_NONE—For non-maskable and critical message
    • UVM_LOW—For maskable messages that are printed only rarely during simulation (for example, when is reset done)
    • UVM_MEDIUM—Short messages that happen once per data item or sequence
    • UVM_HIGH—More detailed per-data-item information, including printing the actual value of the packet and printing sub-transaction details
    • UVM_FULL—Anything else, including message prints in specific methods (just to follow the algorithm of that method)
    • UVM_DEBUG—Debug mode with internal developer debug messages
  • For information messages we recommend using the get_type_name() as the id argument. It is easy to see how many messages came from the different components during a simulation run, and it can be a quick sanity check that all components operated properly. This usage also allows filtering by type.
  • Sometimes users need to ignore warnings for a particular project needs or a certain test requirements. For malfunction-related reports (warnings, errors, and fatals), use a unique name as the id argument. This allows fine control of ignoring errors.

4.8.3 Modifying Message Verbosity

There are three ways to change the verbosity for debug:

  • Command-line argument (simulator-dependent). For example, use:

    % irun … +UVM_VERBOSITY=UVM_NONE

  • Procedurally in SystemVerilog. For example:

    set_report_verbosity_level_hier(UVM_MEDIUM);

  • Tcl (run time). For example, use:

    uvm_message uvm_test_top.uart_ctrl_tb.apb0 UVM_FULL

Note: Changing the verbosity on the command line or by way of Tcl run-time commands does not require that you recompile and re-elaborate the design to see different trace message recording.

You can use the same message macros in the context of non class-based code too. The following example illustrates this in the context of an initial block inside a module:

Example 4-17: Using the Info Message within Modules

  1. module test;
  2.   import uvm_pkg::*;
  3.   `include "uvm_macros.svh"
  4.   initial begin
  5.     uvm_report_info("TEST", "I am in the top-level test", UVM_NONE);
  6.     `uvm_info("TEST", "I am in the top-level test", UVM_NONE)
  7.   end
  8. endmodule : test

The output of the previous snippet looks like this:

--------------------------------------------------------------------------
UVM_INFO @ 0: reporter [TEST] I am in the top-level test
UVM_INFO ./messages.sv(13) @ 0: reporter [TEST] I am in the top-level test
--------------------------------------------------------------------------

4.9 Callbacks

Callbacks, as with the factory, are a way to effect or monitor an environment from the outside. Callbacks are different from the factory in that an object developer must insert calls to specific callback methods/tasks inside of this class at points where he deems it appropriate for the user to take action. The callback users can then create their derived callback class and attach to one or more desired objects.

4.9.1 Using Callbacks

The use model of callbacks is broken into two parts, the developer piece and the user piece.

4.9.1.1 Developer

The first thing the developer must do is decide on an interface to make available. Often the callback function will include a reference to the object that is issuing the callback. And, it will include other information that is necessary for the callback user.

Using our example from “Hierarchy Information Functions” on page 46, we may want to add a callback to the city class which gets executed before it prints its parent/child information. To do this we would do something like:

  1. typedef class city;
  2. virtual class city_cbs extends uvm_callback;
  3.   function new(string name="city_cb");
  4.     super.new(name);
  5.   endfunction
  6.   pure virtual function void pre_info (city c);
  7. endclass

Next, the developer must register their callback type with the type which will use the callback. This registration enables UVM to do type checking when a user tries to add a callback to a specific object. If the registration is left out, the user will get a warning from UVM that the callback type was not registered with the object type they are attempting to add it to.

  1. class city extends uvm_component;
  2.   `uvm_register_cb(city, city_cbs)
  3.   ...
  4. endclass

Note: If the type using the callback is derived from a class which also used callbacks, you must use the `uvm_set_super_type macro so that UVM is aware of the class hierarchy and can properly manage the callbacks.

Finally, the developer inserts a call to the callback functions in their code. A utility macro, `uvm_do_callbacks, is provided to simplify the process. A developer may choose to manually iterate the callbacks using the uvm_callback_iter class as well; this provides ultimate flexibility as to how the callbacks are executed.

  1. task run();
  2.   uvm_component child, parent;
  3.   string pr, ch;
  4.   child = get_child("Main_St");
  5.   parent = get_parent();
  6.   pr = parent.get_full_name();
  7.   ch = child.get_name();
  8.   `uvm_do_callbacks(city, city_cbs, pre_info(this))
  9.   `uvm_info(get_type_name(), $psprintf("Parent:%s Child:%s", pr, ch), UVM_LOW)
  10.  endtask : run

For convenience, the developer should also create a callback typedef.

  typedef uvm_callbacks#(city, city_cbs) city_cb;

4.9.1.2 User

Now that a callback class has been defined and used within an object, a user may use the callback as necessary. For the end user, the first thing to do is to extend the callback class with the desired behavior.

  1. class my_city_cb extends city_cbs;
  2.   function new(string name="my_city_cb");
  3.     super.new(name);
  4.   endfunction
  5.   virtual function void pre_info(city c);
  6.     `uvm_info("my_city_cb", $psprintf("pre_info called for city %s", c.get_full_name()), UVM_LOW)
  7.   endfunction
  8. endclass

Next, the user attaches the callback to a specific instance or to all instances of the type. A null handle specifies a type-wide callback.

  1. my_city_cb cb = new;
  2. initial begin
  3.   city_cb::add(null, cb);
  4.   run_test();
  5. end

Running this code with the previous example results in the following:

UVM_INFO @ 0: reporter [my_city_cb] pre_info called for city New_York.capital_city
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: reporter [my_city_cb] pre_info called for city Florida.capital_city
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

4.9.2 Guidelines for Using Callbacks

4.9.2.1 Developer Guidelines

  • Create an abstract (virtual) class for the callbacks with pure virtual methods for the interface.
  • Pass a reference of the object making the callback call.
  • Take care when passing references to data objects, as they can be changed (this is okay as long as that is your intent).
  • Provide a typedef for the uvm_callbacks#(T,CB) class with the name T_cb.
  • Use the `uvm_set_super_type macro if a super class utilizes callbacks.

4.9.2.2 User Guidelines

  • Create a callback implementation class which implements all of the pure virtual methods in the callback class.
  • Allocate a new callback object for each callback you add.
  • Use type-wide callbacks if you want your callback to effect all types and instance specific callbacks if you only want to affect specific instances of an object.

Note: Instance-specific callbacks cause the instance handle of the object to be stored in a queue until all objects are removed. Care must be taken when associating instance specific callbacks with transient data objects because those data objects cannot be garbage collected until all references are removed. For callbacks to transient objects, prefer using type-wide callbacks if they meet your needs.

4.9.3 The Report Catcher Built-In Callback

UVM has a handful of built-in callbacks. One such callback is the report catcher. The report catcher callback is called just before a message is to be issued. In this callback, it is possible to catch a message so that it is not processed further; modify the message type (demote errors, and so forth), or modify the message text.

Below is an example of how a report catcher can be used to demote a specific type of error message to an info message. This may be done in a test that is specifically attempting to create an error condition.

  1. module test;
  2.   import uvm_pkg::*;
  3.   `include "uvm_macros.svh"
  4.   class my_catcher extends uvm_report_catcher;
  5.      virtual function action_e catch();
  6.         if(get_severity() == UVM_ERROR && get_id() == "MYID") begin
  7.           set_severity(UVM_INFO);
  8.         end
  9.         return THROW;
  10.      endfunction
  11.   endclass
  12.   my_catcher catcher = new;
  13.   initial begin
  14.      uvm_report_cb::add(null,catcher);
  15.      `uvm_error("MYID", "This one should be demoted")
  16.      catcher.callback_mode(0);  //disable the catcher
  17.      `uvm_error("MYID", "This one should not be demoted")
  18.   end
  19. endmodule

This example results in the following output:

UVM_INFO @ 0: reporter [MYID] This one should be demoted
UVM_ERROR @ 0: reporter [MYID] This one should not be demoted

Summary

The chapter introduced key feature of the UVM library. While the UVM features are capable and flexible, they were created as enablers to the higher-level methodology that is needed to speed up verification for large and small designs. The next chapters introduce the UVM methodology for developing reusable verification components for design interface protocols.

Bookmark and Share


Insert your comment

Author Name(required):

Author Web Site:

Author email address(required):

Comment(required):

Please Introduce Secure Code:


image
image