VUnit Packages¶
HDL code can be distributed as a Python package and added to a VUnit project
with add_package().
A VUnit package must be installed and importable, and its root directory must
contain a vunit_pkg.toml manifest. Namespace packages are not currently
supported.
To use a package, add it in the project’s run script:
from vunit import VUnit
vu = VUnit.from_argv()
vu.add_vhdl_builtins()
vu.add_package("foo")
vu.main()
The manifest specifies the package’s source files, version requirements, and compilation defaults. For example:
[package]
requires-vunit = ">=5.0.0.dev14"
requires-vhdl = ">=2008,<2019"
library = "foo_lib"
compile_option = [["<option-name>", ["<value>"]]]
[[package.sources]]
include = ["hdl/src/*.vhd"]
[[package.sources]]
include = ["hdl/compat/*.vhd"]
library = "foo_compat"
compile_option = [["<option-name>", ["<source-value>"]]]
Each [[package.sources]] table defines a source entry. Its include
patterns are relative to the package root. In this example, files matching
hdl/src/*.vhd use the default library, foo_lib, while files matching
hdl/compat/*.vhd use foo_compat.
Replace the compile-option placeholders with names and values supported by the project. Each option is a two-element array containing the option name and an array of string values. Package-level options apply to every source entry. A source-level option overrides the package-level option with the same name for that entry.
The supported manifest fields are:
Field |
Location |
Meaning |
|---|---|---|
|
|
VUnit version constraints that the project must satisfy. |
|
|
Constraints on the VHDL standards supported by the package. |
|
|
Default library for package sources. Required when |
|
|
Repeated source tables. Each table must contain |
|
|
Optional library override for this source entry. VUnit creates the library if needed. |
|
|
Array of option-name/value-array pairs. Source-level options override package-level options with the same name. |
|
|
Optional Python setup function, specified as |
The requires-vunit and requires-vhdl fields accept comma-separated
comparisons using <=, <, !=, ==, >=, or >. All
comparisons must be satisfied. For example, ">=2008,<2019" allows
VHDL 2008 but excludes VHDL 2019 and later standards.
If the project’s VHDL standard does not satisfy requires-vhdl, VUnit
selects a compatible supported standard and issues a warning. If no such
standard is available, adding the package fails. A VUnit version that does
not satisfy requires-vunit always causes an error.
Manifest validation is strict: unknown fields and values of the wrong type are rejected.
Publishing¶
It is recommended to publish VUnit packages on PyPI and add the vunitpkg
keyword to the package metadata in pyproject.toml. This makes VUnit
packages easier to identify and discover:
[project]
keywords = ["vunitpkg"]
Setup Function¶
Packages can be defined entirely in vunit_pkg.toml. For packages that need additional setup, such as building a native library, generating sources, or configuring simulator options, the optional setup key specifies a Python function in the format “module:function”:
[package]
library = "foo_lib"
setup = "foo.vunit_setup:setup"
[[package.sources]]
include = ["hdl/src/*.vhd"]
The project must explicitly allow the setup function to run:
vu.add_package("foo", allow_setup=True)
If a package declares a setup function and allow_setup=True is not
provided, adding the package fails with an error identifying the function
that would have run. This makes permission to execute package setup code
explicit in the project’s run script. Packages that only list HDL sources
do not need a setup function.
After adding the sources listed in the manifest, VUnit imports the specified
module and calls the setup function with a
PackageContext:
def setup(context):
build_dir = context.output_path / "foo"
build_native_library(context.package_root / "c", build_dir)
context.add_source_files("foo_lib", build_dir / "*.vhd")
The context provides the package root, the default library created for the package, the VHDL standard used for its sources, the VUnit output path, the run script path, and information about the selected simulator. Exceptions raised by the setup function are reported as package errors.
A package that builds against a simulator installation needs information about that installation during setup, before VUnit creates the simulator interface. The context exposes this information through the following attributes:
context.simulator_classis the selected simulator interface class.context.simulator_nameis the selected simulator’s name.context.simulator_prefixis the directory containing the selected simulator’s executables.context.simulator_backendidentifies the installation’s backend, where applicable. For GHDL, this is the code generator:"mcode","llvm","llvm-jit", or"gcc". For simulators without a backend distinction, it isNone.
Both simulator_class and simulator_name are None when no simulator
is found.
Simulator Hooks¶
A package’s HDL sources may depend on a native library or generated files. The simulator may need additional flags or environment variables to locate and use these dependencies.
Use context.register_simulator_hooks(simulator_name, ...) to register
functions that supply these settings for a particular simulator. Hooks
receive the simulator interface, so they can adapt their results to its
configuration. They apply only to the project in which they are registered.
The following example builds a native library and registers hooks to load it with NVC or link it with GHDL:
def setup(context):
library = build_native_library(
context.package_root / "c",
context.output_path / "foo",
)
context.register_simulator_hooks(
"nvc",
run_flags=lambda simulator_interface: [f"--load={library}"],
)
context.register_simulator_hooks(
"ghdl",
elab_flags=lambda simulator_interface: [f"-Wl,{library}"],
)
The GHDL hook above assumes a backend that links native libraries during elaboration. Backend-specific hooks are described below.
Four hook types are available, with support depending on the simulator:
Hook |
Return value |
Supported simulators |
|---|---|---|
|
Additional flags for test elaboration. |
GHDL, NVC, and ModelSim/Questa. ModelSim/Questa passes these flags
to |
|
Additional flags for test simulation. |
All simulators. For simulators based on |
|
Additional command-line flags for the simulator process started by VUnit. |
ModelSim/Questa and Riviera-PRO, for both persistent and batch
|
|
The simulation environment, based on the environment supplied
in |
GHDL and NVC, and the |
For simulators based on vsim, run_flags and process_flags target
different commands:
run_flagsadds options to thevsimcommand inside the generated do-file. Use it for options that control what is loaded into the simulation.process_flagsadds options to the command line used to start thevsimprocess. Use it for options that configure the process itself.
For example, Questa’s -noautoldlibpath option prevents bundled runtime
libraries from being added to the dynamic library search path. It is only
honoured on the process command line, so it belongs in process_flags.
VUnit evaluates process_flags and run_env when it constructs the
simulator interface. These hooks must therefore be registered before the
interface is created. Registering them in a package setup function meets
this requirement: setup runs during
add_package(), before
main() creates the interface.
Hooks can also inspect the simulator installation through the interface
they receive. The simulator_interface.prefix attribute contains the
directory of the simulator executables, matching
context.simulator_prefix from the setup function.
For GHDL, GHDLInterface.backend identifies the code generator:
"mcode", "llvm", "llvm-jit", or "gcc". This determines how
native libraries are bound to the design. The "llvm" and "gcc"
backends link them during elaboration; the other backends load them at
runtime.
A hook can use this information to return flags only for the relevant
backends. For example, with library referring to the built native
library, this hook adds a linker search path only for "llvm" and
"gcc":
def elab_flags(simulator_interface):
if simulator_interface.backend not in ("llvm", "gcc"):
return []
return [f"-Wl,-L{library.parent}"]