VUnit is an open-source unit testing framework for VHDL/SystemVerilog that helps to write, and automatically run, tests. The VUnit project was started by Lars Asplund from Synective Labs and Olof Kraigher . The source code is hosted at GitHub . VUnit automatically scans your projects for unit tests (aka test benches), runs them with your favorite simulator and reports the results. This automation helps to run tests frequently and iterate faster.
Installing VUnit
VUnit scripts are written in Python, so first make sure Python is installed on your system. A recent version of Python 3 is recommended. Windows users must install a full version of Python , not from the Windows Store.
The recommended way to install the latest stable release of VUnit is via pip
or pip3
. On most systems running the following command should do the trick:
pip install vunit_hdl
Make sure you use vunit_hdl
and not vunit
. The latter is a completely different Python library.
You can check that VUnit is installed correctly by for example running:
pip freeze
In the list of installed Python packages you will find a vunit-hdl
entry and its version number.
Using VUnit
Running VUnit tests requires two elements: VHDL or SystemVerilog
testbenches and a Python script run.py
which controls the
execution.
VUnit will recognize a VHDL entity or a SystemVerilog module as a
testbench if it has a runner_cfg
generic (VHDL) or parameter
(SV). Testbench entity and module names should have either a tb_
prefix or a _tb
suffix. VUnit will warn if that is not the case.
A VHDL VUnit test bench has the following structure. Note that VHDL 2008 is required, a.o. for support of a VHDL context. The main process has function calls to set up and terminate the simulation. Between these function calls, one or more tests may be run.
library vunit_lib; -- VUnit library and context (VHDL2008 required!)
context vunit_lib.vunit_context;
entity tb_example is -- Entity name with tb_ prefix
generic (runner_cfg : string); -- Generic indicates that this is a VUnit testbench
end entity;
architecture tb of tb_example is
begin
main : process
begin
test_runner_setup(runner, runner_cfg); -- Set up and start simulation
report "This test will pass!"; -- Single, trivial test in this example
test_runner_cleanup(runner); -- Simulation ends
end process;
end architecture;
In SystemVerilog it looks a bit different. The runner_cfg
parameter, which
marks the module as a VUnit testbench, is defined in TEST_SUITE
.
`include "vunit_defines.svh" // Include VUnit definitions
module tb_sv_example;
`TEST_SUITE begin // Includes the definition of parameter runner_cfg
// which makes this module a VUnit testbench
// Note: Do not place any code here.
`TEST_SUITE_SETUP begin
$display("Running test suite setup code");
// Start clocks, assert resets etc. here
end
`TEST_CASE_SETUP begin
$display("Running test case setup code");
// Leave empty for simple cases
end
`TEST_CASE("Test that a successful test case passes") begin
$display("This test case is expected to pass");
`CHECK_EQUAL(1, 1);
end
`TEST_CASE("Test that a failing test case actually fails") begin
$display("This test case is expected to fail");
`CHECK_EQUAL(0, 1, "You may also optionally add a diagnostic message to CHECK_EQUAL");
end
`TEST_CASE_CLEANUP begin
$display("Cleaning up after a test case");
// Leave empty for simple cases
end
`TEST_SUITE_CLEANUP begin
$display("Cleaning up after running the complete test suite");
// Leave empty for simple cases
end
end;
// Optional, but recommended.
`WATCHDOG(1ns);
endmodule
Note the presence of the watchdog near the end of the above example. The watchdog ensures that hanging tests terminate as failed rather than keep on running forever.
Finally, a Python script run.py
is required to control the VUnit
tests. In it simplest form, run.py
for a VHDL project could look
like this. The script creates a VUnit instance, adds libraries and
design files to it, and runs the tests.
# The following line works for VHDL
from vunit import VUnit
# If your project is SystemVerilog, use the following one instead:
# from vunit.verilog import VUnit
# Create VUnit instance by parsing command line arguments
vu = VUnit.from_argv()
# Create library 'lib'
lib = vu.add_library("lib")
# Add all files ending in .vhd in current working directory to library
lib.add_source_files("*.vhd")
# Run vunit tests
vu.main()
Note that VUnit does not support a library named work
. In VHDL,
work
is a keyword which means the same library where this element
is defined. While in many VHDL projects, the work library is called
work
, a different name must be chosen in VUnit. In the above
example, the work library is named lib
. Given the special meaning of
work
, this won’t break any references to work
in the VHDL code.
VUnit’s user guide offers further details on how to use VUnit.
If your project has multiple source code directories and/or libraries,
run.py
will need to reflect that. run.py
is meant to be stored in
revision control. For that reason, generating run.py
(e.g. from
other scripts) is discouraged. However, the full power of Python is
available to configure VUnit’s libraries and source files from within
run.py
. We’ll give a few examples later.
Using VUnit in Sigasi Visual HDL (SVH)
SVH can help you manage VUnit projects and enables you to run and inspect test results straight from the IDE. VUnit support in SVH requires a Sigasi Visual HDL Professional Edition license.
Details of how to run VUnit tests in SVH can be found in the Sigasi manual. Once your tests are running, SVH opens the VUnit view from where you can see test results and inspect the details.
Complex VUnit projects: Python to the rescue
VUnit makes use of a single run.py
script which configures libraries
and controls the execution. It is not desirable to duplicate the
library configuration from other tools in run.py
. First of all,
the library configuration would need regular updates as the project
evolves, which means additional work for the designers and a
possible source of errors. Generating run.py
from other scripts or
tools is not recommended either, because run.py
is meant to be
kept in revision control.
Fortunately, the full strength of the Python programming language is available to help manage VUnit’s configuration. We present a few examples of how Python can be used to automate the VUnit setup.
Determine libraries from folder names
In this example, we have a VHDL project with one level of subfolders. Each subfolder is mapped to a library with the same name. Over the course of the project, subfolders may be added or renamed.
The following run.py
script extracts the libraries from the folder
names. The designer doesn’t need to change anything to the script when
the folder / library structure is changed.
from vunit import VUnit
import os
# Create VUnit instance by parsing command line arguments
vu = VUnit.from_argv()
# For each folder, create a library with the same name as the folder.
# Map all VHDL files in the folder into the library.
for fpath in os.scandir('.'):
if fpath.is_dir() and not fpath.name.startswith('.'):
vu.add_library(fpath.name).add_source_files(os.path.join(fpath.path, "*.vhd"))
# Add all files ending in .vhd in the top level directory to library "libwork"
vu.add_library("libwork").add_source_files("*.vhd")
# Run vunit function
vu.main()
Find all SystemVerilog files in a folder hierarchy
In this example, libraries are not the problem because SystemVerilog uses one single library. The difficulty here is that design files are present at various depths in the file hierarchy.
Again, Python proves to be a powerful tool to find the design files and configure VUnit tests.
from vunit.verilog import VUnit
import glob
# Create VUnit instance by parsing command line arguments
vu = VUnit.from_argv()
# Create library 'lib'
lib = vu.add_library("lib")
# Hierarchically add all files ending in .v and .sv in current working directory to library
for file in glob.glob("**/*.v", recursive=True):
lib.add_source_files(file)
for file in glob.glob("**/*.sv", recursive=True):
lib.add_source_files(file)
# Run vunit function
vu.main()
Obviously, the example could be extended. For example, one could read a list with files to exclude from a file, and not add those files to the library.
Read VUnit configuration from SVH project files
Update: Since Sigasi Studio 4.14, SVH uses the information from run.py
to automatically maintain the library mapping.
The approach described below in this section is no longer appropriate.
Oftentimes, information about design files, libraries, excludes
etc. is available in other design tools, e.g. SVH. With some
clever use of Python, run.py
can retrieve this information from
SVH’s project files, making the configuration of VUnit
tests entirely automatic. Just use this
run.py
in your project,
sit back and run your tests.
See also
- How to configure VUnit using the Sigasi VS Code extension (knowledge)
- Using VUnit for Real (blog post)
- Using VUnit in a GitLab CI Verification Environment (blog post)
- VUnit: managing input files and compile order (blog post)
- Running UVM tests in VUnit (blog post)