From 3227b36e7f8783504706532b251cffcef86e0adf Mon Sep 17 00:00:00 2001 From: Lars Asplund Date: Thu, 1 Oct 2026 22:43:46 +0200 Subject: [PATCH 1/4] Added support for multiple libraries in a package. --- tests/unit/test_builtins.py | 65 +++++++++++++++++++++++++++++++++++++ vunit/builtins.py | 28 ++++++++++++---- 2 files changed, 87 insertions(+), 6 deletions(-) diff --git a/tests/unit/test_builtins.py b/tests/unit/test_builtins.py index bd58822a9..86f9036c6 100644 --- a/tests/unit/test_builtins.py +++ b/tests/unit/test_builtins.py @@ -332,6 +332,71 @@ def test_adding_package_to_library(self): ] ) + def test_source_library_overrides_package_library(self): + libraries = {"bar": mock.Mock(), "baz": mock.Mock()} + + def add_library(name): + self.vu._project._libraries.append(name) + return libraries[name] + + self.vu.add_library.side_effect = add_library + self.vu.add_library.reset_mock() + + with create_tempdir() as tempdir, pkg_env(tempdir): + self._write_toml( + tempdir, + """\ +[package] +library = "bar" +[[package.sources]] +include=["hdl/default/*.vhd"] +[[package.sources]] +library = "baz" +include=["hdl/override1/*.vhd"] +[[package.sources]] +library = "baz" +include=["hdl/override2/*.vhd"] +""", + ) + + self.builtins.add_package("foo") + + self.vu.add_library.assert_has_calls([mock.call("bar"), mock.call("baz")]) + self.assertEqual(self.vu.add_library.call_count, 2) + libraries["bar"].add_source_files.assert_called_once_with(tempdir / "hdl/default/*.vhd", vhdl_standard=None) + libraries["baz"].add_source_files.assert_has_calls( + [ + mock.call(tempdir / "hdl/override1/*.vhd", vhdl_standard=None), + mock.call(tempdir / "hdl/override2/*.vhd", vhdl_standard=None), + ] + ) + + def test_raises_if_source_library_is_not_a_string(self): + with ( + create_tempdir() as tempdir, + pkg_env(tempdir), + self.assertRaisesRegex(RuntimeError, re.escape("Invalid vunit_pkg.toml: 1 error(s) found.")), + self.assertLogs("vunit.builtins", "ERROR") as mock_error, + ): + self._write_toml( + tempdir, + """\ +[package] +library = "bar" +[[package.sources]] +library = 17 +include=["hdl/src1/*.vhd"] +""", + ) + try: + self.builtins.add_package("foo") + finally: + self._assertLogContent( + mock_error, + "ERROR", + "package.sources[0].library: 'library' must be a string.", + ) + def test_raises_if_missing_library(self): with ( create_tempdir() as tempdir, diff --git a/vunit/builtins.py b/vunit/builtins.py index 799db1b21..71b1c8836 100755 --- a/vunit/builtins.py +++ b/vunit/builtins.py @@ -30,7 +30,6 @@ from vunit.about import version, VUnitVersion from vunit.package_context import PackageContext - LOGGER = logging.getLogger(__name__) VHDL_PATH = (Path(__file__).parent / "vhdl").resolve() @@ -196,6 +195,13 @@ def _validate_toml(self, data: dict) -> None: errors.extend(self._check_mandatory_keys(package, mandatory={"library": str}, path="package")) for idx, source in enumerate(package["sources"]): + errors.extend( + self._check_valid_keys( + source, + valid={"include": list, "library": str}, + path=f"package.sources[{idx}]", + ) + ) errors.extend( self._check_mandatory_keys(source, mandatory={"include": list}, path=f"package.sources[{idx}]") ) @@ -294,9 +300,21 @@ def add_package(self, package_name: str, allow_setup: bool = False) -> None: if library is None: return + libraries = {library_name.lower(): library} for source in sources: + source_library_name = source.get("library", library_name) + source_library = libraries.get(source_library_name.lower()) + if source_library is None: + source_library = self._add_library_if_not_exist( + source_library_name, + f"Library {source_library_name} previously defined. Skipping addition of {package_name}.", + ) + if source_library is None: + return + libraries[source_library_name.lower()] = source_library + for include in source["include"]: - library.add_source_files(package_root / include, vhdl_standard=use_vhdl_standard) + source_library.add_source_files(package_root / include, vhdl_standard=use_vhdl_standard) setup = package.get("setup") if setup: @@ -487,14 +505,12 @@ def _add_osvvm(self): supports_vhdl_package_generics = self._simulator_class.supports_vhdl_package_generics() if not osvvm_is_installed(): - raise RuntimeError( - """ + raise RuntimeError(""" Found no OSVVM VHDL files. Did you forget to run git submodule update --init --recursive -in your VUnit Git repository? You have to do this first if installing using setup.py.""" - ) +in your VUnit Git repository? You have to do this first if installing using setup.py.""") for file_name in glob(str(VHDL_PATH / "osvvm" / "*.vhd")): bname = Path(file_name).name From 76db742a1e39ffe1ebc6cf8b5f8d65b6e2018759 Mon Sep 17 00:00:00 2001 From: Lars Asplund Date: Fri, 2 Oct 2026 19:21:09 +0200 Subject: [PATCH 2/4] Added TOML support for source files compile options in VUnit packages. --- tests/unit/test_builtins.py | 81 +++++++++++++++++++++++++++++++++++++ vunit/builtins.py | 61 +++++++++++++++++++++++++++- 2 files changed, 140 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_builtins.py b/tests/unit/test_builtins.py index 86f9036c6..abc33ec56 100644 --- a/tests/unit/test_builtins.py +++ b/tests/unit/test_builtins.py @@ -142,6 +142,87 @@ def test_accepts_empty_toml_package(self): ) self.builtins.add_package("foo") + def test_applies_package_compile_options_and_source_overrides(self): + with create_tempdir() as tempdir, pkg_env(tempdir): + default_source_files = mock.Mock() + overridden_source_files = mock.Mock() + self.library_mock.add_source_files.side_effect = [default_source_files, overridden_source_files] + self._write_toml( + tempdir, + """\ +[package] +library = "bar" +compile_option = [["package.option", ["--package-flag"]], ["shared.option", ["--default"]]] +[[package.sources]] +include = ["hdl/default/*.vhd"] +[[package.sources]] +include = ["hdl/override/*.vhd"] +compile_option = [["shared.option", ["--override"]]] +""", + ) + + self.builtins.add_package("foo") + + default_source_files.set_compile_option.assert_has_calls( + [ + mock.call("package.option", ["--package-flag"]), + mock.call("shared.option", ["--default"]), + ] + ) + overridden_source_files.set_compile_option.assert_has_calls( + [ + mock.call("package.option", ["--package-flag"]), + mock.call("shared.option", ["--override"]), + ] + ) + + def test_rejects_invalid_compile_option_shapes_at_package_and_source_levels(self): + invalid_options = [ + ('[["name"]]', "[0]", "Compile option must be an array containing a string and an array of strings."), + ("[[17, []]]", "[0][0]", "Compile option name must be a string."), + ('[["name", "value"]]', "[0][1]", "Compile option value must be an array of strings."), + ( + '[["name", ["value", 17]]]', + "[0][1][1]", + "Compile option value must be a string.", + ), + ] + + for location in ("package", "package.sources[0]"): + for options, error_suffix, error_message in invalid_options: + with self.subTest(location=location, options=options): + if location == "package": + manifest = f"""\ +[package] +compile_option = {options} +""" + else: + manifest = f"""\ +[package] +library = "bar" +[[package.sources]] +include = ["hdl/src/*.vhd"] +compile_option = {options} +""" + + with ( + create_tempdir() as tempdir, + pkg_env(tempdir), + self.assertRaisesRegex( + RuntimeError, + re.escape("Invalid vunit_pkg.toml: 1 error(s) found."), + ), + self.assertLogs("vunit.builtins", "ERROR") as mock_error, + ): + self._write_toml(tempdir, manifest) + self.builtins.add_package("foo") + + self._assertLogContent( + mock_error, + "ERROR", + f"{location}.compile_option{error_suffix}: {error_message}", + ) + def test_raises_if_incompatible_vunit_version(self): with ( create_tempdir() as tempdir, diff --git a/vunit/builtins.py b/vunit/builtins.py index 71b1c8836..82e97b29b 100755 --- a/vunit/builtins.py +++ b/vunit/builtins.py @@ -158,6 +158,48 @@ def _check_mandatory_keys( return errors + @staticmethod + def _check_compile_options(compile_options: list, path: str) -> list[ValidationError]: + """Check that compile options are pairs of a name and a list of strings.""" + errors = [] + for idx, option in enumerate(compile_options): + option_path = f"{path}[{idx}]" + if not isinstance(option, list) or len(option) != 2: + errors.append( + ValidationError( + path=option_path, + message="Compile option must be an array containing a string and an array of strings.", + ) + ) + continue + + name, values = option + if not isinstance(name, str): + errors.append( + ValidationError( + path=f"{option_path}[0]", + message="Compile option name must be a string.", + ) + ) + if not isinstance(values, list): + errors.append( + ValidationError( + path=f"{option_path}[1]", + message="Compile option value must be an array of strings.", + ) + ) + else: + for value_idx, value in enumerate(values): + if not isinstance(value, str): + errors.append( + ValidationError( + path=f"{option_path}[1][{value_idx}]", + message="Compile option value must be a string.", + ) + ) + + return errors + @staticmethod def _log_validation_errors(errors: list[ValidationError]) -> None: """Log validation error path and message.""" @@ -186,11 +228,15 @@ def _validate_toml(self, data: dict) -> None: "library": str, "sources": list, "setup": str, + "compile_option": list, }, path="package", ) ) + if isinstance(package.get("compile_option"), list): + errors.extend(self._check_compile_options(package["compile_option"], "package.compile_option")) + if "sources" in package: errors.extend(self._check_mandatory_keys(package, mandatory={"library": str}, path="package")) @@ -198,10 +244,14 @@ def _validate_toml(self, data: dict) -> None: errors.extend( self._check_valid_keys( source, - valid={"include": list, "library": str}, + valid={"include": list, "library": str, "compile_option": list}, path=f"package.sources[{idx}]", ) ) + if isinstance(source.get("compile_option"), list): + errors.extend( + self._check_compile_options(source["compile_option"], f"package.sources[{idx}].compile_option") + ) errors.extend( self._check_mandatory_keys(source, mandatory={"include": list}, path=f"package.sources[{idx}]") ) @@ -291,6 +341,7 @@ def add_package(self, package_name: str, allow_setup: bool = False) -> None: library = None sources = package.get("sources", []) if sources: + package_compile_options = dict(package.get("compile_option", [])) library_name = package.get("library") library = self._add_library_if_not_exist( library_name, @@ -313,8 +364,14 @@ def add_package(self, package_name: str, allow_setup: bool = False) -> None: return libraries[source_library_name.lower()] = source_library + compile_options = package_compile_options.copy() + compile_options.update(source.get("compile_option", [])) for include in source["include"]: - source_library.add_source_files(package_root / include, vhdl_standard=use_vhdl_standard) + source_files = source_library.add_source_files( + package_root / include, vhdl_standard=use_vhdl_standard + ) + for name, value in compile_options.items(): + source_files.set_compile_option(name, value) setup = package.get("setup") if setup: From bb572c0867ada64ee7ff0010d673d501971070db Mon Sep 17 00:00:00 2001 From: Lars Asplund Date: Sat, 3 Oct 2026 17:35:31 +0200 Subject: [PATCH 3/4] Updated VUnit package documentation. --- docs/hdl_libraries.rst | 159 ------------------- docs/index.rst | 1 + docs/news.d/1228.feature.rst | 2 + docs/vunit_packages.rst | 287 +++++++++++++++++++++++++++++++++++ vunit/package_context.py | 2 +- vunit/ui/__init__.py | 8 +- 6 files changed, 294 insertions(+), 165 deletions(-) create mode 100644 docs/news.d/1228.feature.rst create mode 100644 docs/vunit_packages.rst diff --git a/docs/hdl_libraries.rst b/docs/hdl_libraries.rst index 6746ed39b..a613755f6 100644 --- a/docs/hdl_libraries.rst +++ b/docs/hdl_libraries.rst @@ -72,165 +72,6 @@ Moreover, multiple approaches are supported for using `OSVVMLibraries `. -.. _packages: - -Packages -======== - -HDL code can also be distributed as a Python package that VUnit adds with -:meth:`add_package() `. -A package is an installed Python package containing a ``vunit_pkg.toml`` file in its root directory: - -.. code-block:: toml - :caption: vunit_pkg.toml - - [package] - requires-vunit = ">=5.0.0" - requires-vhdl = ">=2008" - library = "foo_lib" - - [[package.sources]] - include = ["hdl/src/*.vhd"] - -The sources are compiled into the library named by ``library``, which is created by VUnit and owned by -the package. -``requires-vunit`` and ``requires-vhdl`` state the VUnit versions and the VHDL standards the package -supports. - -Setup Function --------------- - -Not everything a package needs to do can be expressed with a list of sources. -A package building a native library, generating sources or depending on simulator options -adds a ``setup`` key naming a ``"module:function"`` setup function: - -.. code-block:: toml - :caption: vunit_pkg.toml - - [package] - library = "foo_lib" - setup = "foo.vunit_setup:setup" - - [[package.sources]] - include = ["hdl/src/*.vhd"] - -Running the function takes both the package declaring it and the project allowing it: - -.. code-block:: python - - vu.add_package("foo", allow_setup=True) - -Adding a package declaring a setup function without ``allow_setup=True`` is an error naming the -function that would have run. -A package of nothing but VHDL never runs code of its own, and the Python code a project does allow -to run is visible in the run script rather than in a manifest the project never reads. - -VUnit imports the module and calls the function with a :class:`PackageContext ` -once the sources listed in the manifest have been added: - -.. code-block:: python - :caption: foo/vunit_setup.py - - 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 library created for the package, the VHDL standard used for -its sources, the VUnit output path, the path of the run script and the selected simulator. -Errors raised by the setup function are reported as an error for the package. - -A package building something against the simulator installation itself has to know which one at setup -time, before any simulator interface exists to ask. -``context.simulator_class`` is the class of the selected simulator interface and -``context.simulator_name`` its name, both ``None`` when no simulator was found. -``context.simulator_prefix`` is the path the executables of that simulator were found in and -``context.simulator_backend`` says how the installation found there was built, which is simulator -specific: for GHDL it is its code generator, ``"mcode"``, ``"llvm"``, ``"llvm-jit"`` or ``"gcc"``, -and for a simulator with no such notion it is ``None``. - -Simulator Hooks ---------------- - -The HDL code of a package can depend on something built outside the simulator, a native library or -generated files, and then the simulator typically has to be given extra options to find it: flags for -the elaboration of a test, flags for its simulation, flags for the simulator process VUnit starts or -environment variables. -``context.register_simulator_hooks(simulator_name, ...)`` registers, for one simulator, functions -returning those extras. -The hooks are called with the simulator interface such that what they return can depend on how the -simulator was configured and they only apply to the project they were registered for. -Here the flags give the simulator the search path of a library the package built and ask it to load -it: - -.. code-block:: python - :caption: foo/vunit_setup.py - - 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}"], - ) - -There are four kinds of hooks, each used by the simulators for which it makes sense: - -.. list-table:: - :header-rows: 1 - :widths: 20 45 35 - - * - Hook - - What it returns - - Used by - * - ``elab_flags(simulator_interface)`` - - Extra flags for the elaboration of a test. - - GHDL, NVC and ModelSim/Questa, where they are given to ``vopt``. - * - ``run_flags(simulator_interface)`` - - Extra flags for the simulation of a test. - - All simulators. For the vsim based ones these reach the ``vsim`` command of the generated - do-file, not the ``vsim`` process itself. - * - ``process_flags(simulator_interface)`` - - Extra flags for the simulator process VUnit starts. - - ModelSim/Questa and Riviera-PRO, for both the persistent and the batch ``vsim`` process. - * - ``run_env(simulator_interface, env)`` - - The environment of the simulation, given the environment it would otherwise have. - - GHDL, NVC and, for the ``vsim`` process, ModelSim/Questa and Riviera-PRO. - -``run_flags`` and ``process_flags`` are separate because a vsim based simulator runs the simulation -from a script: ``run_flags`` extends the ``vsim`` command in that script, which is what options -selecting what to load into the simulation belong on, while ``process_flags`` extends the command line -of the ``vsim`` process VUnit starts, which is where an option affecting how that process itself is -set up has to go. Questa's ``-noautoldlibpath``, which keeps the bundled runtime libraries out of the -dynamic library search path, is only honoured there. - -``process_flags`` and ``run_env`` are read when VUnit constructs the simulator interface, so they have -to be registered before that happens. A package registering them from its setup function is in time: -the setup function runs when :meth:`add_package() ` is called on the VUnit -object, which is before :meth:`main() ` creates the interface. - -A hook can also look up where and how the simulator was installed through the simulator interface it -is called with. -``simulator_interface.prefix`` is the directory of the simulator executables, the same path the setup -function got as ``context.simulator_prefix``. -``GHDLInterface.backend`` is the code generator of the GHDL used, ``"mcode"``, ``"llvm"``, -``"llvm-jit"`` or ``"gcc"``, which decides how a native library is bound to the design: -``"llvm"`` and ``"gcc"`` link it at elaboration while the others load it at run time. -A hook picks what to return from it: - -.. code-block:: python - :caption: foo/vunit_setup.py - - def elab_flags(simulator_interface): - if simulator_interface.backend not in ("llvm", "gcc"): - return [] - return [f"-Wl,-L{library.parent}"] - System Verilog ============== diff --git a/docs/index.rst b/docs/index.rst index 063d1eb44..60e19b9a7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -48,6 +48,7 @@ often"* approach through automation. :ref:`Read more ` cli py/ui hdl_libraries + vunit_packages examples .. toctree:: diff --git a/docs/news.d/1228.feature.rst b/docs/news.d/1228.feature.rst new file mode 100644 index 000000000..d51e8d3ba --- /dev/null +++ b/docs/news.d/1228.feature.rst @@ -0,0 +1,2 @@ +VUnit packages can now assign source entries to multiple libraries and define package-wide as well as per-source +compile options in ``vunit_pkg.toml``. \ No newline at end of file diff --git a/docs/vunit_packages.rst b/docs/vunit_packages.rst new file mode 100644 index 000000000..87b3c770e --- /dev/null +++ b/docs/vunit_packages.rst @@ -0,0 +1,287 @@ +.. _vunit_packages: + +VUnit Packages +############## + +HDL code can be distributed as a Python package and added to a VUnit project +with :meth:`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: + +.. code-block:: python + + 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: + +.. code-block:: toml + :caption: vunit_pkg.toml + + [package] + requires-vunit = ">=5.0.0.dev14" + requires-vhdl = ">=2008,<2019" + library = "foo_lib" + compile_option = [["", [""]]] + + [[package.sources]] + include = ["hdl/src/*.vhd"] + + [[package.sources]] + include = ["hdl/compat/*.vhd"] + library = "foo_compat" + compile_option = [["", [""]]] + +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: + +.. list-table:: + :header-rows: 1 + :widths: 24 20 56 + + * - Field + - Location + - Meaning + * - ``requires-vunit`` + - ``[package]`` + - VUnit version constraints that the project must satisfy. + * - ``requires-vhdl`` + - ``[package]`` + - Constraints on the VHDL standards supported by the package. + * - ``library`` + - ``[package]`` + - Default library for package sources. Required when ``sources`` is + present. VUnit creates the library for the package. + * - ``sources`` + - ``[[package.sources]]`` + - Repeated source tables. Each table must contain ``include``, an array + of source patterns relative to the package root. + * - ``library`` + - ``[[package.sources]]`` + - Optional library override for this source entry. VUnit creates the + library if needed. + * - ``compile_option`` + - ``[package]`` or ``[[package.sources]]`` + - Array of option-name/value-array pairs. Source-level options override + package-level options with the same name. + * - ``setup`` + - ``[package]`` + - Optional Python setup function, specified as ``"module:function"``. + See :ref:`package_setup_function`. + +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: + +.. code-block:: toml + + [project] + keywords = ["vunitpkg"] + +.. _package_setup_function: + +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"`: + +.. code-block:: toml + :caption: vunit_pkg.toml + + [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: + +.. code-block:: python + + 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 +:class:`PackageContext `: + +.. code-block:: python + :caption: foo/vunit_setup.py + + 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_class`` is the selected simulator interface class. +* ``context.simulator_name`` is the selected simulator's name. +* ``context.simulator_prefix`` is the directory containing the selected + simulator's executables. +* ``context.simulator_backend`` identifies 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 is ``None``. + +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: + +.. code-block:: python + :caption: foo/vunit_setup.py + + 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: + +.. list-table:: + :header-rows: 1 + :widths: 20 45 35 + + * - Hook + - Return value + - Supported simulators + * - ``elab_flags(simulator_interface)`` + - Additional flags for test elaboration. + - GHDL, NVC, and ModelSim/Questa. ModelSim/Questa passes these flags + to ``vopt``. + * - ``run_flags(simulator_interface)`` + - Additional flags for test simulation. + - All simulators. For simulators based on ``vsim``, these flags are + added to the ``vsim`` command in the generated do-file. + * - ``process_flags(simulator_interface)`` + - Additional command-line flags for the simulator process started + by VUnit. + - ModelSim/Questa and Riviera-PRO, for both persistent and batch + ``vsim`` processes. + * - ``run_env(simulator_interface, env)`` + - The simulation environment, based on the environment supplied + in ``env``. + - GHDL and NVC, and the ``vsim`` process for ModelSim/Questa and + Riviera-PRO. + +For simulators based on ``vsim``, ``run_flags`` and ``process_flags`` target +different commands: + +* ``run_flags`` adds options to the ``vsim`` command inside the generated + do-file. Use it for options that control what is loaded into the simulation. +* ``process_flags`` adds options to the command line used to start the + ``vsim`` process. 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 +:meth:`add_package() `, before +:meth:`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"``: + +.. code-block:: python + :caption: foo/vunit_setup.py + + def elab_flags(simulator_interface): + if simulator_interface.backend not in ("llvm", "gcc"): + return [] + return [f"-Wl,-L{library.parent}"] \ No newline at end of file diff --git a/vunit/package_context.py b/vunit/package_context.py index ff90f6d3a..e8b05d1bb 100644 --- a/vunit/package_context.py +++ b/vunit/package_context.py @@ -43,7 +43,7 @@ class PackageContext: simulator_class: Any _vunit_obj: Any - #: Register hooks extending what a simulator does for a test, see :ref:`packages`. + #: Register hooks extending what a simulator does for a test, see :ref:`vunit_packages`. register_simulator_hooks = staticmethod(register_hooks) @property diff --git a/vunit/ui/__init__.py b/vunit/ui/__init__.py index b245d302d..28943a3c5 100644 --- a/vunit/ui/__init__.py +++ b/vunit/ui/__init__.py @@ -1298,7 +1298,7 @@ def add_package(self, package_name: str, allow_setup: bool = False) -> None: """ Add a VUnit package, that is an installed Python package providing HDL code. - See :ref:`packages` for how a package is created. + See :ref:`vunit_packages` for how a package is created. :param package_name: The name of the Python package. Dashes and dots are, just like for PyPI package names, equivalent to underscores. @@ -1349,8 +1349,7 @@ def add_json4vhdl(self): """ Removed json4vhdl add-on. """ - raise RuntimeError( - """\ + raise RuntimeError("""\ add_json4vhdl() has been removed. JSON-for-VHDL support is now provided through a separate package. Install it with: @@ -1358,8 +1357,7 @@ def add_json4vhdl(self): pip install vunit-json-for-vhdl Then replace the add_json4vhdl() call with add_package("vunit-json-for-vhdl"). -""" - ) +""") def update_test_pattern( self, From dd07a769b88b048cfbcdfd6551d9848c51abd553 Mon Sep 17 00:00:00 2001 From: Lars Asplund Date: Sat, 3 Oct 2026 17:51:42 +0200 Subject: [PATCH 4/4] Fixed linting issues. --- vunit/builtins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vunit/builtins.py b/vunit/builtins.py index 82e97b29b..240177e70 100755 --- a/vunit/builtins.py +++ b/vunit/builtins.py @@ -306,7 +306,7 @@ def _find_package(package_name: str) -> Path: raise RuntimeError(f"Failed to find location of package {package_name}.") - def add_package(self, package_name: str, allow_setup: bool = False) -> None: + def add_package(self, package_name: str, allow_setup: bool = False) -> None: # pylint: disable=too-many-locals """ Add a VUnit package and, if allowed, run its setup function.