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
*_librarytargets (fromrules_vhdland/orrules_verilog) keyed by the VUnit library name they should be added under, - a
vunit_toolchainthat picks the simulator and supplies the orchestrationrun.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_testreference covers the full attribute set (sim_opts,env,data, etc.). - The
vunit_toolchainreference shows how to define a custom toolchain — selecting a different default simulator, overridingrun.py, or wiring in bring-your-own-installvunit_*_simrules. - 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
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| deps | Extra Python dependencies merged into the wrapper's venv. Use this to make helper packages importable from a custom toolchain run.py. | List of labels | optional | [] |
| data | Additional runtime data used by the test. | List of labels | optional | [] |
| env | Environment variables to set for the test. | Dictionary: String -> String | optional | {} |
| module | Root 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. | Label | required | |
| precompiled_libs | Precompiled 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 labels | optional | [] |
| sim | The name of the simulator to use. Must match a key in the vunit_toolchain's simulators dict. | String | optional | "" |
| sim_opts | Additional command line arguments to forward to the simulator via VUnit. | List of strings | optional | [] |
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
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| default_sim | An optional default simulator to use. | String | optional | "" |
| env | Environment 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 -> String | optional | {} |
| run_py | The 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. | Label | optional | "@rules_vunit//tools/rules_vunit_run:rules_vunit_run.py" |
| simulators | A mapping of vunit_*_sim targets to their matching simulator names. Every target must provide VUnitSimInfo. | Dictionary: Label -> String | required | |
| vunit | The vunit python library. | Label | required |
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
| Rule | HDL languages | Source | CI-verified |
|---|---|---|---|
vunit_ghdl_sim | VHDL | BCR ghdl | ✅ |
vunit_nvc_sim | VHDL | bring-your-own (no BCR module yet) | — |
vunit_modelsim_sim | Verilog / SystemVerilog / VHDL | bring-your-own (commercial) | — |
vunit_questa_sim | Verilog / SystemVerilog / VHDL | bring-your-own (commercial; same VUnit backend as ModelSim) | — |
vunit_riviera_sim | Verilog / SystemVerilog / VHDL | bring-your-own (commercial) | — |
vunit_activehdl_sim | Verilog / SystemVerilog / VHDL | bring-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
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| env | Environment 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 -> String | optional | {} |
| ghdl | The ghdl binary. | Label | required | |
| ghdl_prefix | Literal 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. | String | optional | "" |
| vhdl_libs | Pre-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 labels | optional | [] |
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
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| env | Environment 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 -> String | optional | {} |
| nvc | The nvc simulator binary. | Label | required | |
| nvc_libpath | Literal 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. | String | optional | "" |
| vhdl_libs | Per-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 labels | optional | [] |
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
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| env | Environment 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 -> String | optional | {} |
| vcom | The vcom VHDL compiler binary. | Label | required | |
| vlib | The vlib library manager binary. | Label | required | |
| vlog | The vlog Verilog/SystemVerilog compiler binary. | Label | required | |
| vsim | The vsim simulator binary. | Label | required |
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
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| env | Environment 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 -> String | optional | {} |
| vcom | The vcom VHDL compiler binary. | Label | required | |
| vlib | The vlib library manager binary. | Label | required | |
| vlog | The vlog Verilog/SystemVerilog compiler binary. | Label | required | |
| vsim | The vsim simulator binary. | Label | required |
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
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| coverage_args | Template 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 strings | optional | [] |
| coverage_data_glob | Shell 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. | String | optional | "" |
| coverage_tool | Optional 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. | Label | optional | None |
| env | Environment 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 -> String | optional | {} |
| vcom | The vcom VHDL compiler binary. | Label | required | |
| vlib | The vlib library manager binary. | Label | required | |
| vlist | The vlist library lister binary. Invoked by vu.main() to enumerate compiled units in the library.cfg. | Label | required | |
| vlog | The vlog Verilog/SystemVerilog compiler binary. | Label | required | |
| vmap | The vmap library-mapping binary. Invoked when external (precompiled) libraries are linked into the test's library.cfg. | Label | required | |
| vsim | The vsim simulator binary. | Label | required | |
| vsimsa | The 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. | Label | required |
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
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| env | Environment 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 -> String | optional | {} |
| vcom | The vcom VHDL compiler binary. | Label | required | |
| vlib | The vlib library manager binary. | Label | required | |
| vlog | The vlog Verilog/SystemVerilog compiler binary. | Label | required | |
| vsim | The vsim simulator binary. | Label | required |