rules_vunit

Bazel rules that run VUnit testbenches against VHDL (and SystemVerilog where the simulator supports it) modules using HDL simulators — primarily GHDL among the open-source set, with infrastructure for NVC and the major commercial simulators (ModelSim/Questa, Riviera-PRO, Active-HDL).

Overview

A vunit_test is a Bazel test target that pairs:

  • one or more HDL *_library targets (from rules_vhdl and/or rules_verilog) keyed by the VUnit library name they should be added under,
  • a vunit_toolchain that picks the simulator and supplies the orchestration run.py.

The two top-level rules are documented under Rules; the per-simulator integrations live under Simulators.

End-to-end example

The walkthrough below builds a tiny 8-bit VHDL adder and exercises it with a VUnit testbench under GHDL.

MODULE.bazel

bazel_dep(name = "rules_vhdl", version = "0.1.1")
bazel_dep(name = "rules_vunit", version = "{version}")

rules_vunit registers an in-tree toolchain (//vunit/toolchain:toolchain) wired to GHDL for its own test suite, but downstream projects should define their own — see the example for the minimum wiring.

adder.vhd

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity adder is
    port (
        x                : in  std_logic_vector(7 downto 0);
        y                : in  std_logic_vector(7 downto 0);
        carry_in         : in  std_logic;
        sum              : out std_logic_vector(7 downto 0);
        carry_output_bit : out std_logic
    );
end entity adder;

architecture rtl of adder is
    signal result : unsigned(8 downto 0);
begin
    result <= ('0' & unsigned(x)) + ('0' & unsigned(y)) + ("00000000" & carry_in);
    sum              <= std_logic_vector(result(7 downto 0));
    carry_output_bit <= result(8);
end architecture rtl;

tb_adder.vhd

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

library vunit_lib;
context vunit_lib.vunit_context;

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

architecture tb of tb_adder is
    signal x, y, sum     : std_logic_vector(7 downto 0) := (others => '0');
    signal carry_in, cout : std_logic := '0';
begin
    dut : entity work.adder
        port map (x, y, carry_in, sum, cout);

    main : process
    begin
        test_runner_setup(runner, runner_cfg);
        while test_suite loop
            if run("test_basic_add") then
                x <= x"03"; y <= x"04"; carry_in <= '0';
                wait for 1 ns;
                check_equal(sum, std_logic_vector'(x"07"));
            end if;
        end loop;
        test_runner_cleanup(runner);
    end process;
end architecture tb;

BUILD.bazel

load("@rules_vhdl//vhdl:defs.bzl", "vhdl_library")
load("@rules_vunit//vunit:vunit_test.bzl", "vunit_test")

vhdl_library(name = "adder", srcs = ["adder.vhd"])
vhdl_library(name = "tb_adder", srcs = ["tb_adder.vhd"], deps = [":adder"])

vunit_test(
    name = "adder_test",
    libraries = {
        ":tb_adder": "tb_lib",
        ":adder":    "work",
    },
    sim = "ghdl",
)

Run it

$ bazel test //path/to:adder_test
//path/to:adder_test                                                     PASSED

Going further

  • The vunit_test reference covers the full attribute set (sim_opts, env, data, etc.).
  • The vunit_toolchain reference shows how to define a custom toolchain — selecting a different default simulator, overriding run.py, or wiring in bring-your-own-install vunit_*_sim rules.
  • The Simulators section lists every built-in simulator integration and per-simulator status.

Rules

vunit_test

Rules

vunit_test

load("@rules_vunit//vunit:vunit_test.bzl", "vunit_test")

vunit_test(name, deps, data, env, module, precompiled_libs, sim, sim_opts)

Run a VUnit test over the given HDL libraries.

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
depsExtra Python dependencies merged into the wrapper's venv. Use this to make helper packages importable from a custom toolchain run.py.List of labelsoptional[]
dataAdditional runtime data used by the test.List of labelsoptional[]
envEnvironment variables to set for the test.Dictionary: String -> Stringoptional{}
moduleRoot HDL library target whose transitive VhdlInfo/VerilogInfo deps are walked to discover every source the test should compile. Each reachable VhdlInfo contributes its sources under its own library field (fallback "work"). Verilog sources land in the module's own VHDL library when module is a vhdl_library (mixed-language testbench → one shared library by default), otherwise "work". VhdlInfo.deps / VerilogInfo.deps are already transitive depsets, so no aspect or extra walking machinery is involved — the provider IS the DAG.Labelrequired
precompiled_libsPrecompiled simulator library sets to link before the test's own compile step. Each target must produce a VUnitPrecompiledLibraryInfo whose simulator field matches this test's resolved sim (analysis-time check). At runtime the per-format runner patch in vunit_process_wrapper emits the vendor's link directive (vmap -link for Aldec, etc.) so HDL that references libraries inside the precompiled set (e.g. Xilinx's xil_defaultlib, unisim) resolves.List of labelsoptional[]
simThe name of the simulator to use. Must match a key in the vunit_toolchain's simulators dict.Stringoptional""
sim_optsAdditional command line arguments to forward to the simulator via VUnit.List of stringsoptional[]

vunit_toolchain

Rules

vunit_toolchain

load("@rules_vunit//vunit:vunit_toolchain.bzl", "vunit_toolchain")

vunit_toolchain(name, default_sim, env, run_py, simulators, vunit)

Define a toolchain for vunit_* rules.

The toolchain bundles a vunit Python library, a set of HDL simulators that vunit_test targets can select between via their sim attribute, and the orchestration run.py the rule invokes at test time. Register an instance with the standard toolchain(...) wrapper to make it discoverable.

Registering simulators

Each entry in simulators is a vunit_*_sim target keyed by the string name that vunit_test(sim = ...) will use to select it. Names are user-chosen — common conventions are "ghdl", "nvc", "modelsim", "questa". Each name must be unique within the dict.

load("@rules_vunit//vunit:vunit_ghdl_sim.bzl", "vunit_ghdl_sim")
load("@rules_vunit//vunit:vunit_toolchain.bzl", "vunit_toolchain")

vunit_ghdl_sim(
    name = "vunit_ghdl",
    ghdl = "@ghdl",
    vhdl_libs = ["@ghdl//:vhdl_libs_v08"],
)

vunit_toolchain(
    name = "my_vunit_toolchain",
    vunit = "//path/to:vunit_py_library",
    default_sim = "ghdl",
    simulators = {":vunit_ghdl": "ghdl"},
    # run_py defaults to //tools/rules_vunit_run:rules_vunit_run.py.
)

toolchain(
    name = "my_toolchain",
    toolchain = ":my_vunit_toolchain",
    toolchain_type = "@rules_vunit//vunit:toolchain_type",
)

Add register_toolchains("//path/to:my_toolchain") to MODULE.bazel ahead of //vunit/toolchain to override the default. default_sim chooses which simulator runs when a vunit_test omits its own sim attribute; it's optional, but if set must name one of the keys in simulators.

Customising run.py

The default run.py (shipped at //tools/rules_vunit_run:rules_vunit_run.py) reads a JSON descriptor from VUNIT_LIBRARIES_JSON, declares each library with vu.add_library(...) / lib.add_source_file(...), and calls vu.main(). Override it via the run_py attribute when you need extra configuration — custom test attributes, set_sim_option(...) calls, post_run hooks, etc. The wrapper always sets:

  • VUNIT_LIBRARIES_JSON — path to the resolved library/sources JSON.
  • VUNIT_OUTPUT_PATH — sandboxed directory to pass as -o.
  • VUNIT_XUNIT_XML — path to write the xunit XML to (pass as -x).

The script runs inside the venv composed by the vunit_test rule: toolchain.vunit is on sys.path, plus anything the test pulls in via its own deps attribute. So a custom run.py just needs to import vunit and any helper packages declared on the test target.

The default driver's plumbing is also exposed as a Python library — from rules_vunit_run import load_manifest, ensure_vunit_verilog_path_is_plus_free, vunit_builtin_verilog_include_dir, configure_coverage — so a custom run.py can reuse the manifest loader, the bzlmod +-in-path workaround for Aldec, and the coverage hook instead of vendoring them. Add @rules_vunit//tools/rules_vunit_run to the test target's deps to make the import resolve.

Per-simulator API and wiring details live in the Simulators section.

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
default_simAn optional default simulator to use.Stringoptional""
envEnvironment variables to set whenever the toolchain invokes a simulator. Applied to every sim registered in simulators; for sim-specific vars (license server, install root, etc.) prefer the per-vunit_*_sim env attr instead. Precedence at test time: toolchain env < sim env < rule-level vunit_test(env = ...).Dictionary: String -> Stringoptional{}
run_pyThe Python orchestration script the vunit_test rule invokes. Defaults to a shipped driver that wires VUnit up from a JSON library descriptor; override with your own .py when you need custom orchestration (hooks, per-test attributes, etc.). Python deps for the script come from the toolchain's vunit py_library plus the test's deps — both are stitched into the venv that runs the script.Labeloptional"@rules_vunit//tools/rules_vunit_run:rules_vunit_run.py"
simulatorsA mapping of vunit_*_sim targets to their matching simulator names. Every target must provide VUnitSimInfo.Dictionary: Label -> Stringrequired
vunitThe vunit python library.Labelrequired

Simulators

A simulator integration is a Bazel rule that wires a specific HDL simulator into the VUnit test harness. vunit_test doesn't talk to simulators directly — it picks one of the simulators registered in the active vunit_toolchain by name (via its sim attribute), and the toolchain dispatches source staging through the matching vunit_*_sim target.

Each vunit_*_sim rule produces a VUnitSimInfo provider that the shared vunit_test rule consumes. New simulator integrations can be added by writing a rule that returns the same provider and registering it in a vunit_toolchain.simulators dict.

Built-in integrations

RuleHDL languagesSourceCI-verified
vunit_ghdl_simVHDLBCR ghdl
vunit_nvc_simVHDLbring-your-own (no BCR module yet)
vunit_modelsim_simVerilog / SystemVerilog / VHDLbring-your-own (commercial)
vunit_questa_simVerilog / SystemVerilog / VHDLbring-your-own (commercial; same VUnit backend as ModelSim)
vunit_riviera_simVerilog / SystemVerilog / VHDLbring-your-own (commercial)
vunit_activehdl_simVerilog / SystemVerilog / VHDLbring-your-own (commercial)

The in-tree toolchain (//vunit/toolchain:toolchain) registers only the CI-verified row — GHDL. For any other simulator, define your own vunit_toolchain that wires the relevant vunit_*_sim to your install and register it ahead of the default.

The "CI-verified" column is what rules_vunit's own //tests/... exercises. The other rules are wired correctly against VUnit's simulator backends, but rules_vunit can't ship the binaries (commercial license or no BCR module yet), so we can't validate them ourselves — please file issues if you hit problems wiring one up.

Per-simulator status, known limitations, and any downstream-wiring patterns are inline on each simulator's reference page.

vunit_ghdl_sim

Rules

vunit_ghdl_sim

load("@rules_vunit//vunit:vunit_ghdl_sim.bzl", "vunit_ghdl_sim")

vunit_ghdl_sim(name, env, ghdl, ghdl_prefix, vhdl_libs)

A simulator configuration for running GHDL simulations in VUnit tests.

Status

Working against the BCR ghdl module from version 6.0.0.bcr.1 onward. The backend is selectable with --@ghdl//:backend={mcode, llvm-jit} (default mcode on x86_64, llvm-jit elsewhere).

Notes

GHDL only supports VHDL (VhdlInfo); pair it with vhdl_library targets in the libraries map of vunit_test. VUnit drives ghdl directly via its vu.main() at test time using sources surfaced through runfiles.

The vhdl_libs attribute should point at the pre-compiled IEEE/std libraries from the BCR ghdl module — most projects want ["@ghdl//:vhdl_libs_v08"].

Coverage

GHDL only emits coverage when built with the gcc backend; the BCR ghdl module ships only mcode and llvm-jit, neither of which support it. Wiring up coverage flags here would be a no-op (or runtime error) against a stock BCR build, so this rule omits the sim-side coverage path. The generic VUnitSimInfo.coverage field remains available for downstream toolchains that point vunit_ghdl_sim(ghdl = ...) at a GHDL+gcc binary and want to thread gcov output through bazel coverage.

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
envEnvironment variables to set when the vunit runner invokes this simulator at test time (license-server pointers, install root vars, …). Precedence: toolchain env < sim env < rule-level vunit_test(env = ...).Dictionary: String -> Stringoptional{}
ghdlThe ghdl binary.Labelrequired
ghdl_prefixLiteral value to set as GHDL_PREFIX at simulation time. Use this for ghdl installs where the prefix is a stable absolute path on disk — typically a system/.deb/Homebrew install (e.g. "/usr/lib/ghdl").

When unset (default), GHDL_PREFIX is auto-derived from vhdl_libs if it's populated (BCR-shaped layout), otherwise left unset so ghdl falls back to its own compiled-in search paths.
Stringoptional""
vhdl_libsPre-compiled VHDL standard libraries (e.g. @ghdl//:vhdl_libs_v08). Assumes the BCR ghdl module's layout<root>/{std,ieee}/v<XX>/<name>-objXX.cf — from which GHDL_PREFIX is derived. If you ship ghdl differently and the IEEE/std libs are already discoverable by the binary, leave this empty and (optionally) set ghdl_prefix to a literal path, or skip both for a system install that uses its compiled-in defaults.List of labelsoptional[]

vunit_nvc_sim

Rules

vunit_nvc_sim

load("@rules_vunit//vunit:vunit_nvc_sim.bzl", "vunit_nvc_sim")

vunit_nvc_sim(name, env, nvc, nvc_libpath, vhdl_libs)

A simulator configuration for running NVC (open-source VHDL simulator) under VUnit.

Status

Working against the BCR nvc module from version 1.21.0 onward.

Notes

NVC only supports VHDL (VhdlInfo); pair it with vhdl_library targets in the libraries map of vunit_test. VUnit drives nvc directly via its vu.main() at test time using sources surfaced through runfiles.

vhdl_libs accepts a list of per-library TreeArtifact targets — typically ["@nvc//:vhdl_libs"], the BCR nvc module's filegroup of per-library directories laid out exactly like a system install under <prefix>/lib/nvc/<library>/. The rule's implementation stages those TreeArtifacts side-by-side under a single root and points NVC_LIBPATH at it so nvc finds every shipped library (STD, IEEE, NVC, plus the SYNOPSYS / VITAL extras inside IEEE) at simulation time.

Coverage

VUnit has no NVC coverage integration upstream — vu.set_sim_option exposes nvc.elab_flags/nvc.sim_flags/nvc.a_flags, but there's no shipped recipe for routing NVC's --cover instrumentation through those, and nvc --cover-export only emits Cobertura. Rather than invent a sim-side path here, this rule omits coverage. The generic VUnitSimInfo.coverage field stays available for the commercial simulators (Mentor UCDB, Aldec ACDB) where a real bridge exists.

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
envEnvironment variables to set when the vunit runner invokes this simulator at test time (license-server pointers, install root vars, …). Precedence: toolchain env < sim env < rule-level vunit_test(env = ...).Dictionary: String -> Stringoptional{}
nvcThe nvc simulator binary.Labelrequired
nvc_libpathLiteral value to set as NVC_LIBPATH at simulation time. Use this for nvc installs where the prefix is a stable absolute path on disk — typically a system / .deb / Homebrew install (e.g. "/usr/local/lib/nvc").

When unset (default), NVC_LIBPATH is auto-derived from vhdl_libs if it's populated (per-library TreeArtifacts staged into one root by this rule), otherwise left unset so nvc falls back to its own compiled-in search paths.
Stringoptional""
vhdl_libsPer-library TreeArtifact targets to stage side-by-side under a single NVC_LIBPATH root. Typically ["@nvc//:vhdl_libs"] — the BCR nvc module's filegroup of per-library directories. Empty (the default) leaves NVC_LIBPATH unset.List of labelsoptional[]

vunit_modelsim_sim

Rules

vunit_modelsim_sim

load("@rules_vunit//vunit:vunit_modelsim_sim.bzl", "vunit_modelsim_sim")

vunit_modelsim_sim(name, env, vcom, vlib, vlog, vsim)

A simulator configuration for running Mentor/Siemens EDA ModelSim under VUnit.

VUnit shares the modelsim backend for both ModelSim and Questa; use vunit_questa_sim if you prefer that terminology — they wire up the same VUnit backend.

Status

Infrastructure only. ModelSim is commercial, with no BCR module and no redistributable binary; rules_vunit cannot validate this rule in CI. Wire it up downstream by pointing the binary attrs at your own install and registering a vunit_toolchain that routes sim = "modelsim" through this rule.

Notes

ModelSim accepts both Verilog/SystemVerilog (VerilogInfo) and VHDL (VhdlInfo) modules. VUnit drives vlib / vlog / vcom / vsim via its own runner — the rule just stages sources and surfaces the binaries on PATH.

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
envEnvironment variables to set when the vunit runner invokes this simulator at test time (license-server pointers, install root vars, …). Precedence: toolchain env < sim env < rule-level vunit_test(env = ...).Dictionary: String -> Stringoptional{}
vcomThe vcom VHDL compiler binary.Labelrequired
vlibThe vlib library manager binary.Labelrequired
vlogThe vlog Verilog/SystemVerilog compiler binary.Labelrequired
vsimThe vsim simulator binary.Labelrequired

vunit_questa_sim

Rules

vunit_questa_sim

load("@rules_vunit//vunit:vunit_questa_sim.bzl", "vunit_questa_sim")

vunit_questa_sim(name, env, vcom, vlib, vlog, vsim)

A simulator configuration for running Mentor/Siemens EDA Questa under VUnit.

Functionally identical to vunit_modelsim_sim — VUnit shares one backend for both products. Use whichever name suits your team's terminology.

Status

Infrastructure only. Questa is commercial, with no BCR module and no redistributable binary; rules_vunit cannot validate this rule in CI.

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
envEnvironment variables to set when the vunit runner invokes this simulator at test time (license-server pointers, install root vars, …). Precedence: toolchain env < sim env < rule-level vunit_test(env = ...).Dictionary: String -> Stringoptional{}
vcomThe vcom VHDL compiler binary.Labelrequired
vlibThe vlib library manager binary.Labelrequired
vlogThe vlog Verilog/SystemVerilog compiler binary.Labelrequired
vsimThe vsim simulator binary.Labelrequired

vunit_riviera_sim

Rules

vunit_riviera_sim

load("@rules_vunit//vunit:vunit_riviera_sim.bzl", "vunit_riviera_sim")

vunit_riviera_sim(name, coverage_args, coverage_data_glob, coverage_tool, env, vcom, vlib, vlist,
                  vlog, vmap, vsim, vsimsa)

A simulator configuration for running Aldec Riviera-PRO under VUnit.

Status

Infrastructure only. Riviera-PRO is commercial, with no BCR module and no redistributable binary; rules_vunit cannot validate this rule in CI.

Notes

Riviera-PRO accepts both Verilog/SystemVerilog (VerilogInfo) and VHDL (VhdlInfo) modules. Sets VUNIT_SIMULATOR=rivierapro.

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
coverage_argsTemplate fragments forwarded to coverage_tool after the test runs under bazel coverage. {output} is substituted with $COVERAGE_OUTPUT_FILE; {data_files} is expanded into one positional arg per file matched by coverage_data_glob. Only meaningful when coverage_tool is set.List of stringsoptional[]
coverage_data_globShell glob (relative to the VUnit output dir) selecting the raw coverage data files coverage_tool consumes. For Riviera-PRO, **/coverage.acdb matches every per-testcase ACDB the run produces. Only meaningful when coverage_tool is set.Stringoptional""
coverage_toolOptional Riviera coverage post-processor. When set AND the test runs under bazel coverage (Bazel populates $COVERAGE_OUTPUT_FILE), the rules_vunit process wrapper invokes this binary after the sim exits with the coverage_args template (substituting {output} and {data_files}) so it can translate the merged ACDB into lcov at $COVERAGE_OUTPUT_FILE. Leave unset to ship raw ACDBs under the test outputs dir without an lcov roll-up.LabeloptionalNone
envEnvironment variables to set when the vunit runner invokes this simulator at test time (license-server pointers, install root vars, …). Precedence: toolchain env < sim env < rule-level vunit_test(env = ...).Dictionary: String -> Stringoptional{}
vcomThe vcom VHDL compiler binary.Labelrequired
vlibThe vlib library manager binary.Labelrequired
vlistThe vlist library lister binary. Invoked by vu.main() to enumerate compiled units in the library.cfg.Labelrequired
vlogThe vlog Verilog/SystemVerilog compiler binary.Labelrequired
vmapThe vmap library-mapping binary. Invoked when external (precompiled) libraries are linked into the test's library.cfg.Labelrequired
vsimThe vsim simulator binary.Labelrequired
vsimsaThe vsimsa batch-shell binary. Required: VUnit's RivieraProInterface.find_prefix_from_path() rejects a toolchain directory unless BOTH vsim AND vsimsa resolve in the same dir on PATH. Omitting vsimsa produces a confusing No available simulator detected error at test time.Labelrequired

vunit_activehdl_sim

Rules

vunit_activehdl_sim

load("@rules_vunit//vunit:vunit_activehdl_sim.bzl", "vunit_activehdl_sim")

vunit_activehdl_sim(name, env, vcom, vlib, vlog, vsim)

A simulator configuration for running Aldec Active-HDL under VUnit.

Status

Infrastructure only. Active-HDL is commercial, with no BCR module and no redistributable binary; rules_vunit cannot validate this rule in CI.

Notes

Active-HDL accepts both Verilog/SystemVerilog (VerilogInfo) and VHDL (VhdlInfo) modules. Sets VUNIT_SIMULATOR=activehdl.

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
envEnvironment variables to set when the vunit runner invokes this simulator at test time (license-server pointers, install root vars, …). Precedence: toolchain env < sim env < rule-level vunit_test(env = ...).Dictionary: String -> Stringoptional{}
vcomThe vcom VHDL compiler binary.Labelrequired
vlibThe vlib library manager binary.Labelrequired
vlogThe vlog Verilog/SystemVerilog compiler binary.Labelrequired
vsimThe vsim simulator binary.Labelrequired