User Guide

Introduction

VUnit is invoked by a user-defined project specified in a Python script. At minimum, a VUnit project consists of a set of HDL source files mapped to libraries. The project serves as single point of entry for compiling and running all tests within an HDL project. VUnit provides automatic scanning for unit tests (test benches), automatic determination of compilation order, and incremental recompilation of modified sources.

The top level Python script is typically named run.py. This script is the entry point for executing VUnit. The script defines the location for each HDL source file in the project, the library that each source file belongs to, any external (pre-compiled) libraries, and special settings that may be required in order to compile or simulate the project source files. The VUnit Python interface is used to create and manipulate the VUnit project within the run.py file.

Since the VUnit project is defined by a Python script, the full functionality of Python is available to create dynamic rules to specify the files that should be included in the project. For example, HDL files may be recursively included from a directory structure based on a filename pattern. Other Python packages or modules may be imported in order to setup the project.

Once the files for a project have been included, the command line interface can then be used to perform a variety of actions on the project. For example, listing all tests discovered, or running individual tests matching a wildcard pattern. The Python interface also supports running a test bench or test for many different combinations of generic values.

Example run.py file.
from vunit 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 function
vu.main()

Test benches are written using supporting libraries in VHDL and SystemVerilog respectively. A test bench can in itself be a single unnamed test or contain multiple named test cases.

There are many example projects demonstrating the usage and capabilities of VUnit.

VUnit supports many simulators. Read about how they are detected and how to choose which one to use here.

VHDL Test Benches

In its simplest form a VUnit VHDL test bench looks like this:

Simplest VHDL test bench: tb_example.vhd
library vunit_lib;
context vunit_lib.vunit_context;

entity tb_example is
  generic (runner_cfg : string);
end entity;

architecture tb of tb_example is
begin
  main : process
  begin
    test_runner_setup(runner, runner_cfg);
    report "Hello world!";
    test_runner_cleanup(runner); -- Simulation ends here
  end process;
end architecture;

From tb_example.vhd a single test case named lib.tb_example.all is created.

This example also outlines what you have to do with existing testbenches to make them VUnit compatible. Include the VUnit context, add the runner_cfg generic, and wrap your existing code in your main controlling process with the calls to test_runner_setup and test_runner_cleanup. Remember to remove your testbench termination code, for example calls to std.env.finish, end of simulation asserts, or similar. A VUnit testbench must be terminated with the test_runner_cleanup call.

It is also possible to put multiple tests in a single test bench that are each run in individual, independent, simulations. Putting multiple tests in the same test bench is a good way to share a common test environment.

VHDL test bench with multiple tests: tb_example_many.vhd
library vunit_lib;
context vunit_lib.vunit_context;

entity tb_example_many is
  generic (runner_cfg : string);
end entity;

architecture tb of tb_example_many is
begin
  main : process
  begin
    test_runner_setup(runner, runner_cfg);

    while test_suite loop

      if run("test_pass") then
        report "This will pass";

      elsif run("test_fail") then
        assert false report "It fails";

      end if;
    end loop;

    test_runner_cleanup(runner);
  end process;
end architecture;

From tb_example_many.vhd‘s run() calls, two test cases are created:

  • lib.tb_example_many.test_pass
  • lib.tb_example_many.test_fail

The above example code can be found in vhdl/user_guide.

SystemVerilog Test Benches

In its simplest form a VUnit SystemVerilog test bench looks like this:

SystemVerilog test bench: tb_example.sv
// You do not need to worry about adding vunit_defines.svh to your
// include path, VUnit will automatically do that for you if VUnit is
// correctly installed (and your python run-script is correct).
`include "vunit_defines.svh"

module tb_example;
   `TEST_SUITE begin
      // Note: Do not place any code here (unless you are debugging
      // VUnit internals).

      `TEST_SUITE_SETUP begin
         // Here you will typically place things that are common to
         // all tests, such as asserting the reset signal and starting
         // the clock(s).
         $display("Running test suite setup code");
      end

      `TEST_CASE_SETUP begin
	 // By default VUnit will run each test separately. However,
	 // advanced users may want to run tests consecutively rather
	 // than in separate instances of the HDL-simulator. In that
	 // case the code placed in a TEST_CASE_SETUP block should
	 // restore the unit under test to the state expected by the
	 // test cases below. In many cases this block would only
	 // assert/deassert the reset signal for a couple of
	 // clock-cycles.
	 //
	 // When trying out VUnit for the first time this section
	 // should probably be left empty.
         $display("Running test case setup code");
      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");
	 // Note: A test case will also be marked as failing if the
	 // simulator stops for other reasons before the end of the
	 // TEST_SUITE block is reached. This means that you don't
	 // need to use CHECK_EQUAL if the testbench you want to
	 // convert to VUnit already contains code that for example
	 // calls $stop if an error-condition is detected.
      end

      `TEST_CASE("Test that a test case that takes too long time fails with a timeout") begin
         $display("This test is expected to timeout because of the watch dog below.");
         #2ns; //
      end

      `TEST_CASE_CLEANUP begin
	 // This section will run after the end of a test case. In
	 // many cases this section will not be needed.
         $display("Cleaning up after a test case");
      end

      `TEST_SUITE_CLEANUP begin
	 // This section will run last before the TEST_SUITE block
	 // exits. In many cases this section will not be needed.
         $display("Cleaning up after running the complete test suite");
      end
   end;

   // The watchdog macro is optional, but recommended. If present, it
   // must not be placed inside any initial or always-block.
   `WATCHDOG(1ns);
endmodule

From tb_example.sv‘s `TEST_CASE() macros, three test cases are created:

  • lib.tb_example.Test that pass
  • lib.tb_example.Test that fail
  • lib.tb_example.Test that timeouts

Each test is run in an individual simulation. Putting multiple tests in the same test bench is a good way to share a common test environment.

The above example code can be found in verilog/user_guide.

Scanning for Test Benches

VUnit will recognize a module or entity as a test bench and run it if it has a runner_cfg generic or parameter. A SystemVerilog test bench using the TEST_SUITE macro will have a runner_cfg parameter created by the macro and thus match the criteria.

A warning will be given if the test bench entity or module name does not match the pattern tb_* or *_tb.

A warning will be given if the name does match the above pattern but lacks a runner_cfg generic or parameter preventing it to be run by VUnit.

Special generics/parameters

A VUnit test bench can have several special generics/parameters. Optional generics are filled in automatically by VUnit if detected on the test bench.

  • runner_cfg : string

    Required by VUnit to pass private information between Python and the HDL test runner

  • output_path : string

    Optional path to the output directory of the current test. This is useful to create additional output files that can be checked after simulation by a post_check Python function.

  • tb_path : string

    Optional path to the directory containing the test bench. This is useful to read input data with a known location relative to the test bench location.

Examples

There are many examples demonstrating more specific usage of VUnit listed below:

VHDL User Guide Example
The most minimal VUnit VHDL project covering the basics of this user guide.
SystemVerilog User Guide Example
The most minimal VUnit SystemVerilog project covering the basics of this user guide.
VHDL UART Example
A more realistic test bench of an UART to show VUnit VHDL usage on a typical module. In addition to the normal run.py it also contains a run_with_preprocessing.py to demonstrate the benefit of location and check preprocessing.
SystemVerilog UART Example
A more realistic test bench of an UART to show VUnit SystemVerilog usage on a typical module.
Check Example
Demonstrates the VUnit check library.
Logging Example
Demonstrates VUnit’s support for logging.
Array Example
Demonstrates the array_t data type of array_pkg.vhd which can be used to handle dynamically sized 1D, 2D and 3D data as well as storing and loading it from csv and raw files.
Generating tests
Demonstrates generating multiple test runs of the same test bench with different generic values. Also demonstrates use of output_path generic to create test bench output files in location specified by VUnit python runner.
Vivado IP example
Demonstrates compiling and performing behavioral simulation of Vivado IPs with VUnit.
Communication library example
Demonstrates the com message passing package which can be used to communicate arbitrary objects between processes. Further reading can be found in the com user guide