We have seen in previous sessions that by instantiating the environment inside each testcase, we have the ability to tweak environment parameters to enable multiple scenarios. A test writer may not have the knowledge of how verification components are hooked-up with each other or how they interact with each other. Hence, it becomes important to hide those details and only present the test writer with ways to tweak the environment for each testcase and here are a few ways to do that.
Use Configurable Parameters
In order to give maximum flexibility to the testbench, it is recommended to add certain knobs to verification components such that they can be controlled from a testcase. Parameters that affect how the testbench is constructed or behaves, can be made a standard across different testbench structures:
- An agent can be configured to operate in either ACTIVE or PASSIVE mode. In active mode, the agent will instantiate a driver and sequencer and will drive transactions to the DUT, while in passive mode, it will only monitor the signal changes happening in DUT interface. This can be done by using a knob variable of type
uvm_active_passive_enum. See Agent for example.
- Similarly, coverage collection and protocol checkers in a monitor can be enabled/disabled from testcase by adding similar knob variables. See Monitor for example.
Other design specific configurations such as the ones below can also be parameterized.
- Number of master and slave agents in an AHB environment
- Speeds and modes of operation of a bus
UVM Configuration Mechanism
Imagine that there's an internal 2-column table within UVM where the first column stores a variable name, and the second column stores the value for that particular variable. If all the components in the environment can access the table, then it becomes very easy to pass information between them. For example, the test component can store a virtual interface variable which can then be accessed by the driver and monitor for their own use. This is the mechanism implemented via
Sometimes, we do not want all the components to access the table. For example, an analog interface need not be passed to an AHB driver. Such restrictions are put in place by the hierarchical path in uvm_config_db. It essentially makes the object available to whatever is specified in the path. If the path contains a single element, then only that particular element will be able to retrieve the object by using
uvm_config_db #(int) :: set (this, "*.wbSlaves", "slave_id", 0); uvm_config_db #(virtual dut_if)::set (null, "uvm_test_top", "dut_if", dut_if1); uvm_resource_db #(myObj) :: set ("test", "shared_config", data, this);
uvm_config_db is a type specific configuration mechanism that offers a way to specify a hierachical path to control which components can access the database object.
In the first example above, only those components below wbSlaves can get the slave_id from the database. All others will result in an error, since the object is not visible to them. The second example shows how a virtual interface can be stored inside the database which is made available to all components beneath the top-level test component.
uvm_resource_object is used in the final example to show how data can be made avaialable for access by any object anywhere in the hierarchy.
Use a Configuration Class
You can also put in all the configuration related tweaks and variables inside a separate class, and pass the class object via
uvm_config_db to the database, and allow a hierarchical path that allows the object to trickle down to each level. For example, the configuration object can be set into the database at the test top level, which can be retrieved by the environment and passed to other agents and components in the same way.
Check our code on GitHub to see how this is done
The advantage to creating a class is that you can derive future versions of the class by inheritance and also randomize the variables and constrain them appropriately for valid scenarios.