SystemVerilog, the standard that originated from Accellera and is now IEEE1800, is not just for Verilog users. VHDL users can also improve their design processes using its proven verification features. Anyone involved in systemon- chip (SoC) design may face a mixed-language environment and will appreciate being able to leverage SystemVerilog with the VHDL portions of their design.
Many commercial simulators support mixed-language simulation, providing proven deployments at high performance by way of a native implementation of the features in the simulator. Some beneficial techniques include assertion-based verification, constrained random stimulus generation, and functional coverage tracking. These are available in SystemVerilog blocks and can be bound into VHDL designs without modification to the original RTL.
A program block is a testbench construct containing statements that execute after all actions within a particular timestep are completed. It allows checking of the settled value of logic, examination of the disposition of design assertions, and the generation of stimulus for the next timestep. Program blocks can be attached to particular instantiations of a module – or all instantiations of a module – via the ‘bind’ statement. Each of these instantiations has an instance name that gives debuggers naming and visibility into the added testbench structures. The program blocks can have ports that attach to local variables in each instantiation that allow testing of internal state variables in addition to signals on the ports.
Assertions can be instantiated in modules or program blocks in SystemVerilog, allowing users to specify both desired and undesired behavior. Assertions can be immediate and enable evaluation of a combinational property such as one-hot or one-cold. They can also be temporal, tracking states over time. Assertions are useful both during design to validate assumptions engineers make while implementing the design, and during the verification phase. Verification engineers can use SystemVerilog’s temporal logic to encode complex protocol requirements from the specification to ensure compliance with the overall design and system goals.
These behaviors can be defined in a reusable fashion using the ‘property’ statement. Properties can have parameters defined to enable customization of the property to the specific contexts to which they are bound. The properties can then be asserted in either a module or a program block and then bound to an instance. This approach to assertions allows testing to be added while leaving the golden RTL unchanged. Accellera’s Open Verification Library (OVL) 2.0 is an example of a re-usable verification library that provides 50 pre-defined checkers that can be bound into VHDL designs.
Figure 1. Binding OVL checkers into a design
Figure 2. Specifying and disabling constraints in SystemVerilog
Figure 3. Functional coverage tracking
Consider the example in Figure 1. In this case, the property ‘req_ack’ takes five parameters: a clock (‘clk’), a request signal (‘req’), an acknowledge signal (‘ack’), a minimum response time (‘min_cycle’) and a maximum response time (‘max_cycle’). This property is asserted in the program‘req_ack_pgm’ that is then bound into all instantiations of ‘bus_arb’ in the design, customized for each instantiation. The simulation will issue an assertion failure whenever ‘ack’ does not rise between ‘min_cycle’ and ‘max_cycle’ cycles after a request is made.
A mixed-language design environment also gives users access to constrained random simulation. Constrained random data generation allows the testbench to create hard-to-find corner cases while guaranteeing valid stimulus to the design. SystemVerilog has expanded the Verilog-type system to allow for aggregate types like VHDL. These SystemVerilog types can have constraints specified that define valid data. For example, the two least significant bits of an address might need to be 0. The constraint specification language is very general. It allows the values of some fields of the constrained object to affect the value of others (e.g., when a type field of a structure is 1, a data field must be between 2 and 8). It also allows the specification of weights impacting the probability that a particular value will be generated (e.g., to force odd values for a field to be twice as likely as even values).
Once an aggregate type has been defined, the values in it can be randomized by calling the ‘randomize’ function. As well as constraints specified in the object definition, call-site specific constraints can be specified at the point that the object is randomized by the testbench. Constraints can also be disabled at specific callsites to enable testing with invalid inputs. These features give testbench designers a lot of control over directing the generation of test vectors.
Consider the example in Figure 2. The class ‘Bus’ is declared with two constraints: ‘word_align’, which forces the two low order bits of ‘addr’ to 0; and ‘data_range’, which depending upon the value of ‘mode’, constrains the data field. In the runtime portion of the program there are three calls to randomize. The first call randomizes the variables based upon the constraints specified in the declaration of ‘Bus’. The second adds a constraint limiting the range of ‘addr’ to between 10 and 20. The final call first disables the ‘word_align’ constraint, and then forces the generation of invalid data, allowing the test of error conditions in the design.
The functional coverage capabilities of SystemVerilog allow users to determine how well their current tests are exercising the design. Prompt identification of weaknesses in the generation of vectors identifies issues that need to be addressed early in the verification process. SystemVerilog designs can track the values of variables and expressions (e.g., ‘Were all the states of a state machine exercised?’), the cross product of two or more variables (e.g., ‘Were all data values used in all states of the state machine and in all operating modes?’), or the transitions variables make (e.g., ‘Did the state variable go from state A to state B?’).
Functional coverage tracking is defined in a ‘covergroup’. The value sampling can be limited to specific times by defining a clocking event. The tracking of variable values within a covergroup is done by describing a ‘coverpoint’, which creates a bin for every set of values to be tracked. The sets can consist of single items, tracking every value, or larger sets (e.g., values less than 1000 as one set and all other values as another). Bins can also track sequences of variable values. The cross product of multiple coverpoints and variables can be created with a cross declaration.
Consider Figure 3. In this case, the covergroup ‘cg’ has coverpoints ‘c’ (tracking three values of ‘color’), ‘a’ (tracking the values of ‘pixel_adr’), ‘d’ (tracking three bins for subsets of the values of ‘pixel_offset’), and ‘s’ (tracking transitions on the variable ‘color’). The cross of coverpoints ‘color’ and ‘pixel_addr’ is called ‘AxC’, and the cross of coverpoints ‘a’, ‘c’, and ‘d’ is called ‘AxCxD’.
Covergroups can be built generically by specifying parameters, and then calling the new function to create them during the execution of the testbench. This allows the groups and the variables to be defined at specific instantiation sites, thus enabling the building of coverage group libraries.
SystemVerilog also adds datatypes that provide useful abstractions for testbenches. These abstractions include dynamic arrays, which allow the size of an array to be changed as the testbench runs without limits. Associative arrays allow complex datatypes to be used as indices and are useful to model sparse collections without requiring the allocation of unreasonable amounts of memory. Queues allow data to be stored as FIFOs, LIFOs, or even as arbitrary insertions of data in the middle of the queue.
SystemVerilog further supports runtime abstractions that are useful for building testbenches. Thread creation is supported through the ‘fork…join’ constructs. There are three ‘join’ constructs that determine when a thread continues after executing a ‘fork’ statement: ‘join’ (all threads complete), ‘join_any’ (any one thread completes), and ‘join_none’ (no thread completes).
SystemVerilog thus has a number of features that aid in building testbenches. These include assertions, random test vector generation, and coverage. There are also datatype and runtime extensions that speed testbench development by providing commonly used functionality in a simple format. By adding these features in program blocks, and then binding the blocks into existing designs, it is a straightforward task to check not only the ports, but also the internal state of a design-under-test independently of the language used by the design and without requiring any modification of the design. These features are well supported natively in multiple simulators today, and are ready for VHDL users to leverage in order to enhance their testbench generation.