Repository navigation
GHDL: generics leak between configurations when ghdl.sim_flags is set #1223
Description
Description
With GHDL, a configuration can be simulated with generics set by another configuration. When ghdl.sim_flags is set, every simulation appends its -g<name>=<value> flags to one list shared by all configurations of the test bench. GHDL uses the last -g for each generic. So a configuration inherits any generic set by an earlier simulation that it doesn't set itself.
Which configurations are affected depends on the order in which they are simulated. Serial runs fail deterministically when a configuration that sets a generic runs before one that relies on its default. Parallel runs fail intermittently, and a test can pass while simulating the wrong configuration.
Reproduction
tb_generic_leak.vhd:
library vunit_lib;
context vunit_lib.vunit_context;
entity tb_generic_leak is
generic (
runner_cfg : string;
a : integer := 0;
b : integer := 0
);
end entity;
architecture tb of tb_generic_leak is
begin
main : process
begin
test_runner_setup(runner, runner_cfg);
info("a = " & integer'image(a) & ", b = " & integer'image(b));
test_runner_cleanup(runner);
end process;
end architecture;run.py:
from pathlib import Path
from vunit import VUnit
vu = VUnit.from_argv()
vu.add_vhdl_builtins()
lib = vu.add_library("lib")
lib.add_source_files(Path(__file__).parent / "tb_generic_leak.vhd")
vu.set_sim_option("ghdl.sim_flags", ["--ieee-asserts=disable"])
tb = lib.test_bench("tb_generic_leak")
# Configurations are simulated in the order they are added when running serially
tb.add_config(name="sets_b", generics=dict(b=1))
tb.add_config(name="sets_a", generics=dict(a=1)) # b is expected to keep its default (0)
vu.main()Run it with python run.py -p1 -v:
sets_b: INFO - a = 0, b = 1
sets_a: INFO - a = 1, b = 1 <- expected b = 0
The output is correct in either of these cases:
- The
set_sim_option("ghdl.sim_flags", ...)call is left out, because.get(..., [])then returns a new list each time. - The call is moved after the
add_config()calls, because each existing configuration then gets its own copy of the list. Only configurations added after the option is set share the default configuration's list.
Cause
In vunit/sim_if/ghdl.py, GHDLInterface._get_command() does this:
sim = config.sim_options.get("ghdl.sim_flags", [])
sim += hooks.get_flags(self, "run_flags")
for name, value in config.generics.items():
sim += [f"-g{name!s}={value!s}"]
sim += [f"--assert-level={config.vhdl_assert_stop_level!s}"]sim is the list object stored in sim_options, so += extends it in place. That list can be shared between configurations:
set_sim_option()copies the value once into each configuration that exists at that point, including the default configuration.Configuration.copy(), used byadd_config()and inIndependentSimTestCase._simulate(), makes only a shallow copy ofsim_options. So configurations added after the option is set share the default configuration's list.
The shared list therefore grows by one set of -g flags per simulation. Instrumenting _get_command() on a 16-test project showed the same list object for every configuration, and its length went 2 -> 12 -> 24 -> …
Fix
Copying the list before extending it worked in my case, but maybe there is a better/more general fix.
- sim = config.sim_options.get("ghdl.sim_flags", [])
+ sim = list(config.sim_options.get("ghdl.sim_flags", []))Environment
- VUnit 5.0.0.dev12 (master at af47432)
Activity
rustyqt commentedon Oct 14, 2026
I can confirm this independently with VUnit 5.0.0.dev7 and GHDL 6.0.0 (mcode backend). In a project that calls
set_sim_option("ghdl.sim_flags", ...)beforeadd_config(), configurations ran with generics of other configurations, depending on the run order; some of them passed while simulating the wrong case. GHDL applies the last-gof a name, so only the generics a configuration does not set itself leak, which is why it is easy to miss.I also checked the other simulator interfaces on master (b25ebc3): only
ghdl.pyextends the list stored insim_optionsin place.nvc.pyandincisive.pyextend their local command list, andmodelsim.py,rivierapro.pyandactivehdl.pybuild a new list with+. So the one-line change of #1224 covers it.#1224 has no test yet. This unit test for
tests/unit/test_ghdl_interface.pyfails on master and passes with #1224 (tests/unit/test_ghdl_interface.pyandtests/unit/test_configuration.py: 38 passed). Feel free to take it into #1224:On master it fails with:
A news fragment would complete it, for example
docs/news.d/1223.bugfix.rst: "GHDL: a configuration no longer inherits the generics of other configurations through a sharedghdl.sim_flagslist."prds-code commentedon Oct 15, 2026
I've added the test and news fragment to #1224 as suggested. Sorry for opening the PR without them.