diff --git a/tests/unit/test_modelsim_interface.py b/tests/unit/test_modelsim_interface.py index be392594a..24c28aa52 100644 --- a/tests/unit/test_modelsim_interface.py +++ b/tests/unit/test_modelsim_interface.py @@ -22,14 +22,27 @@ from vunit.vhdl_standard import VHDL +def _get_test_path(): + return Path(__file__).parent / "test_modelsim_out" + + +def _get_prefix_path(): + return _get_test_path() / "prefix" / "bin" + + +def _get_installed_modelsim_ini(): + return (_get_prefix_path() / ".." / "modelsim.ini").resolve() + + class TestModelSimInterface(unittest.TestCase): """ Test the ModelSim interface """ + @mock.patch("vunit.sim_if.modelsim.ModelSimInterface._get_modelsim_ini_from_vmap", return_value=_get_installed_modelsim_ini()) @mock.patch("vunit.sim_if.check_output", autospec=True, return_value="") @mock.patch("vunit.sim_if.modelsim.Process", autospec=True) - def test_compile_project_vhdl_2008(self, process, check_output): + def test_compile_project_vhdl_2008(self, process, check_output, get_modelsim_ini): simif = ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False) project = Project() project.add_library("lib", "lib_path") @@ -50,9 +63,10 @@ def test_compile_project_vhdl_2008(self, process, check_output): ] check_output.assert_called_once_with(check_args, env=simif.get_env()) + @mock.patch("vunit.sim_if.modelsim.ModelSimInterface._get_modelsim_ini_from_vmap", return_value=_get_installed_modelsim_ini()) @mock.patch("vunit.sim_if.check_output", autospec=True, return_value="") @mock.patch("vunit.sim_if.modelsim.Process", autospec=True) - def test_compile_project_vhdl_2002(self, process, check_output): + def test_compile_project_vhdl_2002(self, process, check_output, get_modelsim_ini): simif = ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False) project = Project() project.add_library("lib", "lib_path") @@ -73,9 +87,10 @@ def test_compile_project_vhdl_2002(self, process, check_output): ] check_output.assert_called_once_with(check_args, env=simif.get_env()) + @mock.patch("vunit.sim_if.modelsim.ModelSimInterface._get_modelsim_ini_from_vmap", return_value=_get_installed_modelsim_ini()) @mock.patch("vunit.sim_if.check_output", autospec=True, return_value="") @mock.patch("vunit.sim_if.modelsim.Process", autospec=True) - def test_compile_project_vhdl_93(self, process, check_output): + def test_compile_project_vhdl_93(self, process, check_output, get_modelsim_ini): simif = ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False) project = Project() project.add_library("lib", "lib_path") @@ -96,9 +111,10 @@ def test_compile_project_vhdl_93(self, process, check_output): ] check_output.assert_called_once_with(check_args, env=simif.get_env()) + @mock.patch("vunit.sim_if.modelsim.ModelSimInterface._get_modelsim_ini_from_vmap", return_value=_get_installed_modelsim_ini()) @mock.patch("vunit.sim_if.check_output", autospec=True, return_value="") @mock.patch("vunit.sim_if.modelsim.Process", autospec=True) - def test_compile_project_vhdl_extra_flags(self, process, check_output): + def test_compile_project_vhdl_extra_flags(self, process, check_output, get_modelsim_ini): simif = ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False) project = Project() project.add_library("lib", "lib_path") @@ -122,9 +138,10 @@ def test_compile_project_vhdl_extra_flags(self, process, check_output): ] check_output.assert_called_once_with(check_args, env=simif.get_env()) + @mock.patch("vunit.sim_if.modelsim.ModelSimInterface._get_modelsim_ini_from_vmap", return_value=_get_installed_modelsim_ini()) @mock.patch("vunit.sim_if.check_output", autospec=True, return_value="") @mock.patch("vunit.sim_if.modelsim.Process", autospec=True) - def test_compile_project_verilog(self, process, check_output): + def test_compile_project_verilog(self, process, check_output, get_modelsim_ini): simif = ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False) project = Project() project.add_library("lib", "lib_path") @@ -146,9 +163,10 @@ def test_compile_project_verilog(self, process, check_output): ] check_output.assert_called_once_with(check_args, env=simif.get_env()) + @mock.patch("vunit.sim_if.modelsim.ModelSimInterface._get_modelsim_ini_from_vmap", return_value=_get_installed_modelsim_ini()) @mock.patch("vunit.sim_if.check_output", autospec=True, return_value="") @mock.patch("vunit.sim_if.modelsim.Process", autospec=True) - def test_compile_project_system_verilog(self, process, check_output): + def test_compile_project_system_verilog(self, process, check_output, get_modelsim_ini): simif = ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False) project = Project() project.add_library("lib", "lib_path") @@ -171,9 +189,10 @@ def test_compile_project_system_verilog(self, process, check_output): ] check_output.assert_called_once_with(check_args, env=simif.get_env()) + @mock.patch("vunit.sim_if.modelsim.ModelSimInterface._get_modelsim_ini_from_vmap", return_value=_get_installed_modelsim_ini()) @mock.patch("vunit.sim_if.check_output", autospec=True, return_value="") @mock.patch("vunit.sim_if.modelsim.Process", autospec=True) - def test_compile_project_verilog_extra_flags(self, process, check_output): + def test_compile_project_verilog_extra_flags(self, process, check_output, get_modelsim_ini): simif = ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False) project = Project() project.add_library("lib", "lib_path") @@ -198,9 +217,10 @@ def test_compile_project_verilog_extra_flags(self, process, check_output): ] check_output.assert_called_once_with(check_args, env=simif.get_env()) + @mock.patch("vunit.sim_if.modelsim.ModelSimInterface._get_modelsim_ini_from_vmap", return_value=_get_installed_modelsim_ini()) @mock.patch("vunit.sim_if.check_output", autospec=True, return_value="") @mock.patch("vunit.sim_if.modelsim.Process", autospec=True) - def test_compile_project_verilog_include(self, process, check_output): + def test_compile_project_verilog_include(self, process, check_output, get_modelsim_ini): simif = ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False) project = Project() project.add_library("lib", "lib_path") @@ -223,9 +243,10 @@ def test_compile_project_verilog_include(self, process, check_output): ] check_output.assert_called_once_with(check_args, env=simif.get_env()) + @mock.patch("vunit.sim_if.modelsim.ModelSimInterface._get_modelsim_ini_from_vmap", return_value=_get_installed_modelsim_ini()) @mock.patch("vunit.sim_if.check_output", autospec=True, return_value="") @mock.patch("vunit.sim_if.modelsim.Process", autospec=True) - def test_compile_project_verilog_define(self, process, check_output): + def test_compile_project_verilog_define(self, process, check_output, get_modelsim_ini): simif = ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False) project = Project() project.add_library("lib", "lib_path") @@ -251,11 +272,12 @@ def test_compile_project_verilog_define(self, process, check_output): def _get_inis(self): return ( str(Path(self.output_path) / "modelsim.ini"), - str(Path(self.prefix_path) / ".." / "modelsim.ini"), + str(_get_installed_modelsim_ini()), str(Path(self.test_path) / "my_modelsim.ini"), ) - def test_copies_modelsim_ini_file_from_install(self): + @mock.patch("vunit.sim_if.modelsim.ModelSimInterface._get_modelsim_ini_from_vmap", return_value=_get_installed_modelsim_ini()) + def test_copies_modelsim_ini_file_from_install(self, get_modelsim_ini): (modelsim_ini, installed_modelsim_ini, user_modelsim_ini) = self._get_inis() with open(installed_modelsim_ini, "w") as fptr: @@ -268,7 +290,8 @@ def test_copies_modelsim_ini_file_from_install(self): with open(modelsim_ini, "r") as fptr: self.assertEqual(fptr.read(), "installed") - def test_copies_modelsim_ini_file_from_user(self): + @mock.patch("vunit.sim_if.modelsim.ModelSimInterface._get_modelsim_ini_from_vmap", return_value=_get_installed_modelsim_ini()) + def test_copies_modelsim_ini_file_from_user(self, get_modelsim_ini): (modelsim_ini, installed_modelsim_ini, user_modelsim_ini) = self._get_inis() with open(installed_modelsim_ini, "w") as fptr: @@ -283,7 +306,8 @@ def test_copies_modelsim_ini_file_from_user(self): with open(modelsim_ini, "r") as fptr: self.assertEqual(fptr.read(), "user") - def test_overwrites_modelsim_ini_file_from_install(self): + @mock.patch("vunit.sim_if.modelsim.ModelSimInterface._get_modelsim_ini_from_vmap", return_value=_get_installed_modelsim_ini()) + def test_overwrites_modelsim_ini_file_from_install(self, get_modelsim_ini): (modelsim_ini, installed_modelsim_ini, user_modelsim_ini) = self._get_inis() with open(modelsim_ini, "w") as fptr: @@ -299,7 +323,8 @@ def test_overwrites_modelsim_ini_file_from_install(self): with open(modelsim_ini, "r") as fptr: self.assertEqual(fptr.read(), "installed") - def test_overwrites_modelsim_ini_file_from_user(self): + @mock.patch("vunit.sim_if.modelsim.ModelSimInterface._get_modelsim_ini_from_vmap", return_value=_get_installed_modelsim_ini()) + def test_overwrites_modelsim_ini_file_from_user(self, get_modelsim_ini): (modelsim_ini, installed_modelsim_ini, user_modelsim_ini) = self._get_inis() with open(modelsim_ini, "w") as fptr: @@ -318,10 +343,11 @@ def test_overwrites_modelsim_ini_file_from_user(self): self.assertEqual(fptr.read(), "user") @mock.patch("vunit.sim_if.modelsim.LOGGER", autospec=True) + @mock.patch("vunit.sim_if.modelsim.ModelSimInterface._get_modelsim_ini_from_vmap", return_value=_get_installed_modelsim_ini()) @mock.patch("vunit.sim_if.check_output", autospec=True, return_value="") @mock.patch("vunit.sim_if.modelsim.Process", autospec=True) @mock.patch("vunit.sim_if.vsim_simulator_mixin.Process", autospec=True) - def test_optimize(self, vsim_simulator_mixin_process, modelsim_process, check_output, LOGGER): + def test_optimize(self, vsim_simulator_mixin_process, modelsim_process, check_output, get_modelsim_ini, LOGGER): simif = ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False) project = Project() project.add_library("lib", str(Path(self.libraries_path) / "lib")) @@ -361,10 +387,10 @@ def test_optimize(self, vsim_simulator_mixin_process, modelsim_process, check_ou LOGGER.error.assert_has_calls(expected_error_calls) def setUp(self): - self.test_path = str(Path(__file__).parent / "test_modelsim_out") + self.test_path = str(_get_test_path()) self.output_path = str(Path(self.test_path) / "modelsim") - self.prefix_path = str(Path(self.test_path) / "prefix" / "bin") + self.prefix_path = str(_get_prefix_path) self.libraries_path = str(Path(self.output_path) / "libraries") self.simulation_output_path = str(Path(self.test_path) / "test_output" / "lib.tb") renew_path(self.test_path) @@ -372,7 +398,7 @@ def setUp(self): renew_path(self.prefix_path) renew_path(self.libraries_path) renew_path(self.simulation_output_path) - installed_modelsim_ini = str(Path(self.prefix_path) / ".." / "modelsim.ini") + installed_modelsim_ini = str(_get_installed_modelsim_ini()) write_file(installed_modelsim_ini, "[Library]") self.project = Project() self.cwd = os.getcwd() diff --git a/vunit/sim_if/modelsim.py b/vunit/sim_if/modelsim.py index 3079e64a9..c0fb24680 100644 --- a/vunit/sim_if/modelsim.py +++ b/vunit/sim_if/modelsim.py @@ -10,6 +10,7 @@ from pathlib import Path import os +import re import logging from threading import Lock, Event from time import sleep @@ -84,11 +85,11 @@ def find_prefix_from_path(cls): """ Find first valid modelsim toolchain prefix """ - - def has_modelsim_ini(path): - return os.path.isfile(str(Path(path).parent / "modelsim.ini")) - - return cls.find_toolchain(["vsim"], constraints=[has_modelsim_ini]) + def not_aldec_simulator(path): + is_active_hdl = cls.find_toolchain(["vsim", "avhdl"]) is not None + is_riviera = cls.find_toolchain(["vsim", "vsimsa"]) is not None + return not (is_active_hdl or is_riviera) + return cls.find_toolchain(["vsim"], constraints=[not_aldec_simulator]) @classmethod def supports_vhdl_package_generics(cls): @@ -126,6 +127,32 @@ def __init__(self, prefix, output_path, *, persistent=False, gui=False, debugger # Lock to access the two shared variables above self._shared_state_lock = Lock() + def _get_modelsim_ini_from_vmap(self): + """ + Get the path to modelsim.ini, as used by vmap. + + This means it listens to the MODELSIM environment variable, allowing + both vunit and other code/scripts to use the same modelsim.ini. + """ + vmap_output = [] + proc = Process([str(Path(self._prefix) / "vmap")]) + try: + proc.consume_output(callback=lambda line: vmap_output.append(line)) + except Process.NonZeroExitCode: + # The responsibility of this code is only detecting where + # modelsim.ini is, not to check that all libraries defined in it + # exist. So suppress non-zero exit codes. + pass + for line in vmap_output: + m = re.match(r"Reading (.*modelsim\.ini)", line) + if m is None: + continue + modelsim_ini = Path(m.group(1)).resolve() + if not modelsim_ini.exists(): + raise FileNotFoundError(modelsim_ini) + return modelsim_ini + raise Exception("Failed to get the path to modelsim.ini from vmap") + def _create_modelsim_ini(self): """ Create the modelsim.ini file @@ -134,7 +161,11 @@ def _create_modelsim_ini(self): if not file_exists(parent): os.makedirs(parent) - original_modelsim_ini = os.environ.get("VUNIT_MODELSIM_INI", str(Path(self._prefix).parent / "modelsim.ini")) + # Try vunit specific environment variable first, then query vmap, which + # reads the MODELSIM environment variable if it exists, then checks the + # current working directory and eventually falls back to modelsim.ini + # bundled with ModelSim. + original_modelsim_ini = os.environ.get("VUNIT_MODELSIM_INI", str(self._get_modelsim_ini_from_vmap())) with Path(original_modelsim_ini).open("rb") as fread: with Path(self._sim_cfg_file_name).open("wb") as fwrite: fwrite.write(fread.read())