Here is an alternative and effective way to code assertions and functional coverage, and overcome the illustrated challenges.
Assertions have been key contributors in increasing confidence in the accuracy of the design & quality of verification since coding effective coverage is fundamental in ensuring the completeness of verification. In a typical verification environment, we restrict our access mostly to the input/output ports of the modules for coding assertions. With increasing complexity in SoC designs, the hierarchies of modules have increased considerably. As a result, it is an increasingly tedious and error-prone task to write assertions and functional coverage on signals which are deep within the hierarchy, or a part of the module which is instantiated multiple times.
This article discusses an alternative and effective way to code assertions and functional coverage and overcome the illustrated challenges. It shows the usage of VUnits as a replacement of the conventional way of coding module-based assertions and linking it with the design through ports. It also helps in the reduction of hundreds of lines of assertions and functional coverage to smaller, manageable, systematic and effective pieces of code. Usage of VUnit not only reduces the time and effort required, but also decreases the chances of errors and false passes/coverage.
Challenges with conventional assertion techniques
In order to have an exhaustive coverage and tight behavioural assertions, it should interest coders to access the values and transitions of certain internal variables of the RTL. In such a case, we have to rely on access to port signals of the modules or the interface signals and write some glue logic around them to mimic the RTL functionality as we cannot change the design files. A lot of effort goes into mimicking this internal logic which can be saved if somehow there is a "read-only" access to the variables of the design itself.
By writing assertions and coverage on the signals derived from the glue logic, we can be close to the actual RTL behaviour but not without subtle bugs that are left behind in the glue logic. There are chances of false passes in assertions and wrong coverage in such cases.
It is a cumbersome task when there are many instances of same modules, and we need to create as many instances of our assertions/covergroups and bind them to correct module instances. All this increases the chances of human errors and also the development time for these repetitive tasks.
Using Verification Units or VUnit provides a very convenient and effective way to overcome above challenges.
What is a VUnit? VUnit or "Verification Unit" serves as a container for set of properties that can be linked to the design. It is a side file that is linked to the design file for simulation. This file can contain all the regular code including assert statements, coverage, initial – always blocks, etc. All these properties can use the signals that are expected to be existing in a RTL design. The link to the design under test occurs via an argument of the VUnit specification.
Binding a VUnit to a particular module is analogous to writing the code inside that module itself. You can access the internal variables of the design module inside the VUnit. Any assignment to the RTL signal from a VUnit has no effect ("read-only" kind of access that we talked earlier<img alt="). Thus, the design file remains untouched and the assertion/coverage code is provided in a separate VUnit file.
The VUnit can be either bound to a design module or an instance of a module. If bound to a module, it is linked to all the instances of that particular module.
The following is an example of how VUnit is specified.
Binding interface signals using VUnit At block level verification, we want to bind our interface signals to the boundary of a particular block. Use of VUnit simplifies this task when there are numerous instances of the same module. See below code which is written inside a VUnit for a module which is instantiated 10 times.
Output signal o_signal_a of each instance of module will be bound to signal_a of corresponding instance of intf.
Advantages of using VUnit
Since VUnit provides access to internal signals of the design, it provides an easier way to integrate our coverage and assertion with the design.
If we bind VUnit with the module name, we have to write the properties only once. They are automatically available for all the instances of that particular module that exist in the design
Use of VUnit eliminates the need of writing any complex glue logic around the port signals and deriving signals based on interface signals. This substantially reduces the chances of errors/subtle bugs which immensely increases the confidence on the assertion PASS and coverage data.
Useful in binding interface signals to the module ports.
Points to keep in mind
All the code written in VUnit is closely bound to the design. Users should take care while selecting the signals for coding coverage/assertion as we are relying on design. It is preferable to avoid using internal variables.
In order to include the VUnit files in compilation flow, we need to specify these files as additional property files to the tool. This may differ per tool. For Cadence Incisive Enterprise Simulator, use compile time option -propfile_vlog file_name
Using VUnits doesn't mean that VUnit code is permanently bound with the design. It can be easily excluded from simulation flow (using IFDEF, removing compile option -propfile_vlog, etc) as per requirements.
About the author Parth Parmar has a B.E. in Electronics & Communication from Gujarat Technological University and has worked as a Verification Engineer at eInfochips for more than 3 years in the ASIC Division.