Dynamic Construction and Configuration of Testbenches
by Mike Baird, Willamette HDL, Inc.
There are a number of requirements in developing an advanced testbench. Such requirements include making the testbench flexible and reusable because with complex designs in general we spend as much or more time developing the verification environment and testing as we do developing the DUT. It has been said that any testbench is reusable; it just depends upon how much effort we are willing to put into adapting it for reuse! Given that however, there are concepts that go into making a testbench that is reusable with reasonable effort.
- Abstract, standardized communication between testbench components
- Testbench components with standardized API’s
- Standardized transactions
- Dynamic (run-time) construction of testbench topology
- Dynamic (run-time) configuration of testbench topology and parameters
- Test as top level class
- Stimulus generation separate from testbench structure
- Analysis components
We will primarily address dynamic (run-time) construction of testbench topology and dynamic (run-time) configuration of testbench topology and parameters in this article. In doing so we will lightly touch on test as top level class and stimulus generation separate from testbench structure as these are related topics.
Traditional Approach Limitations
A standard testbench structure has a top level module (top). Inside top are the DUT (alu), DUT interface (alu_if) and a top level class (test_env) which is the test environment that contains the testbench components.
In a standard testbench structure the top level object is the environment class which embeds the stimulus generator which contains the algorithm (test) for generating stimulus objects. This limits flexibility when changing tests. To change a test a new or different stimulus generator must be compiled into the testbench structure. The use of polymorphism or stimulus generators that gather information from files etc. may help but is not a complete answer.
Traditionally a hierarchical class-based environment is built using an object’s constructor, a special function which creates the object. Higher level components create lower level components by calling the lower level component’s constructor.
In this approach the initial block of the top level module (top) calls the constructor of the test environment class (test_env), which in turn calls the constructor of its child objects and so on down the hierarchy. Once construction is complete the simulation phases begin where connection of components, running and post run processing is done.
This approach limits the flexibility of creating testbench components as their types are determined before the simulation phases begin, essentially at compile time. To change a component type a change to the structural code and a recompile is needed. Polymorphism helps but again is not a complete answer.
Configuration information is passed from higher level components to lower level components through the constructor arguments. Things such as structural topology, setting parameters (array sizes, constants etc.) and operational modes (error injection, debug etc.) are examples of what may be configured in this manner.
With multiple levels of hierarchy and potentially many parameters, using constructor arguments becomes difficult. It becomes hard or even messy to pass parameters down several levels and if we have more than 2 or 3 levels of hierarchy the problem becomes progressively worse. This approach does not scale well when adding additional configuration parameters.
OVM provides classes for use as testbench components and base classes to derive testbench components whose semantics of use provide a methodology which addresses the limitations described above.
Test As Top Level Class
OVM provides a means for separating the stimulus generation algorithm (test) from the test bench structure through layered stimulus (scenarios) or sequences. This allows us to have a structure where a “test” class is the top level object instead of the environment class. The test or stimulus generating object is contained within this top level class and is not a testbench component. In Figure 2 the MAC_scenario is a stimulus generation object. It is a scenario and implements a multiply accumulate algorithm.
Dynamic (Run-Time) Construction of Testbench Components
A well known object oriented best practice (pattern) is called the factory pattern. A factory is a class that creates objects dynamically. Its main benefit is the type of object that is created is specified at run time. OVM provides a factory that can create any type of transaction or any testbench component that is registered with this factory. The OVM factory is not part of the testbench component hierarchy but rather a static object that exists outside of the hierarchical structure.
To register a class with factory requires the addition of a property and a method in the class declaration. This property is a specialization of a registry class (a library base class). The method (get_type_name()) that is added returns a string which identifies the type of the class. Optionally there is a macro provided for registration. Each class is registered with an associated string which is used to lookup or index the class in the factory.
Creation of a testbench component object is done by calling a factory method (create_component()), while a second factory method (create_ object()) is provided for creation of stimulus objects. These methods create the requested object and return a handle to the object to the calling component.
OVM simulation has a number of phases that could be grouped into three general activities.
- Elaboration – phases for building and connecting the testbench components.
- Run – phase for generation and application of stimulus and checking of results.
- Post run – phases for cleanup and reporting.
In the methodology using the factory, a testbench component does not create a child component in its constructor. Rather all the testbench components create child components dynamically in the build phase using the factory. The build phase is part of the elaboration activities and is the first post-new phase, that is it is the first phase that occurs after the creation of the top level class. During the build phase, the top level test class creates the environment class object using the factory, which in turn creates its child components using the factory and so on.
During the run phase of simulation the factory may be used to create stimulus objects as opposed to calling the constructor directly to create the stimulus object.
The Power Of The Factory Comes To Life With Factory Overrides
Factory overrides may be set up to dynamically change the type of object the factory constructs. With a factory override the factory is modified such that when one type of object is requested from the factory it returns a second or substitute type of object instead. The second type must be type compatible with the original type. Typically it is a derived type. An override may be further restricted by specifying not only the type that will be overridden but the instance path where the type may be replaced. This allows for instance specific overrides in addition to general type overrides.
A factory override may be set up at any time during the simulation. Typically however they are set up during the build phase and most often in the build phase of the top level class prior to the construction of the test environment.
In that arrangement the test customizes the test environment structure to what is required by the test through factory overrides. An example is the MAC_scenario test requires a driver transactor which provides feedback from the ALU DUT to the MAC_scenario in order to implement the multiply accumulate. Other tests that simply generate arithmetic operations to the ALU may not require a feedback path from the DUT and would use a different driver transactor. Each test can specify using factory overrides which type of driver to create in the testbench structure. Another example is the test may have an override which specifies the type of stimulus object that is generated for the DUT. This approach allows each test to be run, with each one dynamically configuring the testbench structure using the factory and specifying the type of stimulus object, without re-compiling!
Dynamic (Run-Time) Configuration Of Testbench Topology and Parameters
OVM has an alternate approach for providing information from a higher level component to a lower level component which avoids the need to pass information through constructor arguments. Indeed the OVM factory does not allow for additional constructor arguments beyond what is required by the library itself (a name and parent argument) so this alternate approach is required when using the factory!
The alternate approach provides for configuration data to be stored at each level of the hierarchy. A higher level component influences a lower level component by setting configuration data which may be retrieved and used by a lower level component. API methods are provided for setting and retrieving configuration data.
Configuration data may be in the form of an integer value, a string or an object. An object may be used to encapsulate data that is not an integer value or string. The higher level component stores the data with a unique lookup string and further specifies an OVM hierarchical path relative to itself, specifying which of the lower level components in its sub hierarchy is allowed to access this data. The lower level component retrieves data using the lookup string. Its search for the data begins at the top in the global space and proceeds down the hierarchical path to the component. It retrieves the data at the first successful match it finds. Wildcarding is allowed in both the lookup string and the path to provide flexibility in setting and getting data.
Configuration data may be set at anytime during simulation. Typically it is set and retrieved during the build phase. It is common to set configuration data with regards to the testbench structure in the build phase of the test class.
An example of the use of an object configuration data is to encapsulate a virtual interface. A container class may be created with a property of type virtual interface (virtual alu_if). An object of this type is created in the top level module and its virtual interface property set to the interface (alu_if). This object is then placed into the global configuration data and set to be retrieved by any class object, such as a driver or monitor transactor that requires a virtual interface connection to the DUT. This avoids the messy approach of creating virtual interface properties at all levels of the hierarchy and passing the virtual interface connection down through the hierarchy.
The semantics of use of the classes in the OVM library define a methodology which allows for dynamic (run-time) configuration and construction of the test environment. This, together with other features, provides for greater flexibility in testbench construction and a higher degree of reuse.
With the top level class being a test, using test environment configuration data together with test factory overrides provides each test with the flexibility to configure the test environment to its requirements. Once all the test bench component classes, stimulus classes and tests have been compiled, different tests can be run without recompiling.
Mike Baird has 26 years of industry experience in hardware design and verification. He is the author and an instructor of Willamette HDL’s OVM training course. He has 15 years experience teaching design and verification languages including Verilog, SystemVerilog, OVM, C++ and SystemC. He is the founder of Willamette HDL (1993 – Present)
Willamette HDL, Inc. (WHDL) is a leading provider of design and verification training in the U.S. and around the world. Founded in 1993, it has an extensive offering of technology training for SystemVerilog, the Open Verification Methodology (OVM), SystemC, Verilog and VHDL. Willamette is based in Beaverton, OR (503) 590-8499, www.whdl.com.