From b40f6ceb15e7327847622194183d5dcd2e671fbc Mon Sep 17 00:00:00 2001 From: Lars Asplund Date: Mon, 13 Apr 2026 20:54:18 +0200 Subject: [PATCH 1/3] Bumped required setuptools version. The higher version is required to support the license and license files formats. --- docs/news.d/1196.misc.rst | 2 ++ pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 docs/news.d/1196.misc.rst diff --git a/docs/news.d/1196.misc.rst b/docs/news.d/1196.misc.rst new file mode 100644 index 000000000..b9c626647 --- /dev/null +++ b/docs/news.d/1196.misc.rst @@ -0,0 +1,2 @@ +Bumped required setuptools version to >=77. The higher version is required to support the license and license files +formats in pyproject.toml. \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 6e911e28e..617e2a666 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=61"] +requires = ["setuptools>=77"] build-backend = "setuptools.build_meta" [project] From bd5419826dfb50208cd0494a23b97fb20f7deec4 Mon Sep 17 00:00:00 2001 From: Lars Asplund Date: Fri, 17 Apr 2026 22:34:03 +0200 Subject: [PATCH 2/3] Improved support for automatically finding Modelsim/Questa INI files. If modelsim.ini or questa.ini isn't found, it will look for any INI file containing a library section. --- docs/news.d/1198.feature.rst | 3 ++ tests/unit/test_modelsim_interface.py | 76 ++++++++++++++++++-------- vunit/sim_if/modelsim.py | 77 +++++++++++++++++++++++---- 3 files changed, 126 insertions(+), 30 deletions(-) create mode 100644 docs/news.d/1198.feature.rst diff --git a/docs/news.d/1198.feature.rst b/docs/news.d/1198.feature.rst new file mode 100644 index 000000000..73a4fbb26 --- /dev/null +++ b/docs/news.d/1198.feature.rst @@ -0,0 +1,3 @@ +Improved the strategy for locating the INI file in Questa / ModelSim installations that do not use the standard file +names ``questa.ini`` or ``modelsim.ini``. The update handles known naming deviations and also supports other INI file +names, provided their contents appear to be valid simulator configuration files. diff --git a/tests/unit/test_modelsim_interface.py b/tests/unit/test_modelsim_interface.py index f382095a4..248d03126 100644 --- a/tests/unit/test_modelsim_interface.py +++ b/tests/unit/test_modelsim_interface.py @@ -269,73 +269,73 @@ def test_copies_modelsim_ini_file_from_install(self, _check_output): (modelsim_ini, installed_modelsim_ini, user_modelsim_ini) = self._get_inis() with open(installed_modelsim_ini, "w") as fptr: - fptr.write("installed") + fptr.write("[Library]\ninstalled=installed") with open(user_modelsim_ini, "w") as fptr: - fptr.write("user") + fptr.write("[Library]\nuser=user") ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False) with open(modelsim_ini, "r") as fptr: - self.assertEqual(fptr.read(), "installed") + self.assertEqual(fptr.read(), "[Library]\ninstalled=installed") @mock.patch("vunit.sim_if.modelsim.check_output", autospec=True, return_value="") def test_copies_modelsim_ini_file_from_user(self, _check_output): (modelsim_ini, installed_modelsim_ini, user_modelsim_ini) = self._get_inis() with open(installed_modelsim_ini, "w") as fptr: - fptr.write("installed") + fptr.write("[Library]\ninstalled=installed") with open(user_modelsim_ini, "w") as fptr: - fptr.write("user") + fptr.write("[Library]\nuser=user") with set_env(VUNIT_MODELSIM_INI=user_modelsim_ini): ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False) with open(modelsim_ini, "r") as fptr: - self.assertEqual(fptr.read(), "user") + self.assertEqual(fptr.read(), "[Library]\nuser=user") @mock.patch("vunit.sim_if.modelsim.check_output", autospec=True, return_value="") def test_overwrites_modelsim_ini_file_from_install(self, _check_output): (modelsim_ini, installed_modelsim_ini, user_modelsim_ini) = self._get_inis() with open(modelsim_ini, "w") as fptr: - fptr.write("existing") + fptr.write("[Library]\nexisting=existing") with open(installed_modelsim_ini, "w") as fptr: - fptr.write("installed") + fptr.write("[Library]\ninstalled=installed") with open(user_modelsim_ini, "w") as fptr: - fptr.write("user") + fptr.write("[Library]\nuser=user") ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False) with open(modelsim_ini, "r") as fptr: - self.assertEqual(fptr.read(), "installed") + self.assertEqual(fptr.read(), "[Library]\ninstalled=installed") @mock.patch("vunit.sim_if.modelsim.check_output", autospec=True, return_value="") def test_overwrites_modelsim_ini_file_from_user(self, _check_output): (modelsim_ini, installed_modelsim_ini, user_modelsim_ini) = self._get_inis() with open(modelsim_ini, "w") as fptr: - fptr.write("existing") + fptr.write("[Library]\nexisting=existing") with open(installed_modelsim_ini, "w") as fptr: - fptr.write("installed") + fptr.write("[Library]\ninstalled=installed") with open(user_modelsim_ini, "w") as fptr: - fptr.write("user") + fptr.write("[Library]\nuser=user") with set_env(VUNIT_MODELSIM_INI=user_modelsim_ini): ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False) with open(modelsim_ini, "r") as fptr: - self.assertEqual(fptr.read(), "user") + self.assertEqual(fptr.read(), "[Library]\nuser=user") @mock.patch("vunit.sim_if.vsim_simulator_mixin.Process", autospec=True) def test_modelsim_ini_file_detection(self, vsim_simulator_mixin_process): - (_, _, user_modelsim_ini) = self._get_inis("questa") + (modelsim_ini, _, user_modelsim_ini) = self._get_inis() with open(user_modelsim_ini, "w") as fptr: - fptr.write("user") + fptr.write("[Library]\nuser=user") def check_output(*args, **kwargs): return """\ @@ -352,14 +352,18 @@ def check_output(*args, **kwargs): ): simif = ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False) self.assertEqual(simif._ini_flag, "-modelsimini") - self.assertEqual(simif._ini_file, "modelsim.ini") + self.assertEqual(str(simif._ini_file_path), user_modelsim_ini) + self.assertEqual(simif._sim_cfg_file_name, modelsim_ini) @mock.patch("vunit.sim_if.vsim_simulator_mixin.Process", autospec=True) def test_questa_ini_file_detection(self, vsim_simulator_mixin_process): - (_, _, user_questa_ini) = self._get_inis("questa") + (questa_ini, installed_questa_ini, user_questa_ini) = self._get_inis("questa") with open(user_questa_ini, "w") as fptr: - fptr.write("user") + fptr.write("[Library]\nuser=user") + + with open(installed_questa_ini, "w") as fptr: + fptr.write("[Library]\ninstalled=installed") def check_output(*args, **kwargs): return """\ @@ -374,7 +378,37 @@ def check_output(*args, **kwargs): ): simif = ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False) self.assertEqual(simif._ini_flag, "-ini") - self.assertEqual(simif._ini_file, "questa.ini") + self.assertEqual(str(simif._ini_file_path), user_questa_ini) + self.assertEqual(simif._sim_cfg_file_name, questa_ini) + + @mock.patch("vunit.sim_if.vsim_simulator_mixin.Process", autospec=True) + def test_non_standard_ini_name(self, vsim_simulator_mixin_process): + # We need to remove the INI file created in setUp since we testing a non-standard INI file name. + (Path(self.prefix_path) / ".." / "modelsim.ini").unlink() + + (questasim_ini, installed_questasim_ini, user_questasim_ini) = self._get_inis("questasim") + + with open(user_questasim_ini, "w") as fptr: + fptr.write("[Library]\nuser=user") + + with open(installed_questasim_ini, "w") as fptr: + fptr.write("[Library]\ninstalled=installed") + + def check_output(*args, **kwargs): + return """\ +-modelsimini Specify path to the modelsim.ini file +-ini Specify path to the questa.ini file +""" + + with ( + set_env(VUNIT_MODELSIM_INI=user_questasim_ini), + mock.patch("vunit.sim_if.vsim_simulator_mixin.Process", return_value=None), + mock.patch("vunit.sim_if.modelsim.check_output", side_effect=check_output), + ): + simif = ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False) + self.assertEqual(simif._ini_flag, "-ini") + self.assertEqual(str(simif._ini_file_path), user_questasim_ini) + self.assertEqual(simif._sim_cfg_file_name, questasim_ini) @mock.patch("vunit.sim_if.modelsim.check_output", autospec=True, return_value="") @mock.patch("vunit.sim_if.modelsim.LOGGER", autospec=True) @@ -435,7 +469,7 @@ def setUp(self): renew_path(self.libraries_path) renew_path(self.simulation_output_path) installed_modelsim_ini = str(Path(self.prefix_path) / ".." / "modelsim.ini") - write_file(installed_modelsim_ini, "[Library]") + write_file(installed_modelsim_ini, "[Library]\ninstalled=installed") self.project = Project() self.cwd = os.getcwd() os.chdir(self.test_path) diff --git a/vunit/sim_if/modelsim.py b/vunit/sim_if/modelsim.py index df07e8a1e..d0ee6a13a 100644 --- a/vunit/sim_if/modelsim.py +++ b/vunit/sim_if/modelsim.py @@ -13,7 +13,7 @@ import logging from threading import Lock, Event from time import sleep -from configparser import RawConfigParser +from configparser import RawConfigParser, ParsingError from ..exceptions import CompileError from ..ostools import write_file, Process, file_exists from ..vhdl_standard import VHDL @@ -71,14 +71,76 @@ def from_args(cls, args, output_path, **kwargs): """ persistent = not (args.unique_sim or args.gui) + prefix = cls.find_prefix() + try: + Path(prefix) + except TypeError as exc: + raise FileNotFoundError( + "Modelsim/Questa executable not found. Please set the VUNIT_MODELSIM_PATH environment variable." + ) from exc + return cls( - prefix=cls.find_prefix(), + prefix=prefix, output_path=output_path, persistent=persistent, gui=args.gui, debugger=args.debugger, ) + @staticmethod + def _find_any_ini_file(root: Path) -> Path | None: + """ + Find any INI file in the given directory that resembles a ModelSim/Questa INI file. + """ + possible_ini_files = list(root.glob("*.ini")) + for ini_file in possible_ini_files: + try: + cfg = RawConfigParser() + with Path(ini_file).open("r", encoding="utf-8") as fptr: + cfg.read_file(fptr) + + for name in ["Library", "library"]: + if cfg.has_section(name): + return ini_file + + except ParsingError: + continue + + return None + + def _find_ini_file(self, prefix: str, support_ini_flag: bool) -> tuple[Path, str] | None: + """ + Find the INI file to use for the simulation and the name of the copy to be used for simulation. + """ + + # The standard simulation INI file name is based on the name of the INI flag option. If such a file + # doesn't exist in the installation but there is another similar INI file, the simulation name + # is based on that file instead. We only revert to finding any INI file if the standard names + # can't be found. The reason is that the non-standard approaches are a somewhat unknown territory + # and this way we avoid discarding a standard name if there are several INI files in the installation + # directory. + parent_dir = Path(prefix).parent + installation_ini_name = "questa.ini" if support_ini_flag else "modelsim.ini" + standard_installation_ini_file = parent_dir / installation_ini_name + has_standard_ini_file = standard_installation_ini_file.is_file() + + first_installation_ini_file = self._find_any_ini_file(parent_dir) + if not has_standard_ini_file and first_installation_ini_file: + installation_ini_name = first_installation_ini_file.name + + if "VUNIT_MODELSIM_INI" in os.environ: + return Path(os.environ["VUNIT_MODELSIM_INI"]), installation_ini_name + + if has_standard_ini_file: + return standard_installation_ini_file, installation_ini_name + + if first_installation_ini_file: + return first_installation_ini_file, first_installation_ini_file.name + + raise RuntimeError( + "Failed to find an INI file for ModelSim/Questa. Please set the VUNIT_MODELSIM_INI environment variable." + ) + @classmethod def find_prefix_from_path(cls): """ @@ -86,9 +148,7 @@ def find_prefix_from_path(cls): """ def has_ini(path): - return os.path.isfile(str(Path(path).parent / "modelsim.ini")) or os.path.isfile( - str(Path(path).parent / "questa.ini") - ) + return cls._find_any_ini_file(Path(path).parent) is not None return cls.find_toolchain(["vsim"], constraints=[has_ini]) @@ -127,14 +187,14 @@ def __init__(self, prefix, output_path, *, persistent=False, gui=False, debugger self._supports_vhdl_2019 = self._find_in_help(prefix, "vcom", "-2019") support_ini_flag = self._find_in_help(prefix, "vcom", "-ini") self._ini_flag = "-ini" if support_ini_flag else "-modelsimini" - self._ini_file = "questa.ini" if support_ini_flag else "modelsim.ini" + self._ini_file_path, simulation_ini_file_name = self._find_ini_file(prefix, support_ini_flag) SimulatorInterface.__init__(self, output_path, gui) VsimSimulatorMixin.__init__( self, prefix, persistent, - sim_cfg_file_name=str(Path(output_path) / self._ini_file), + sim_cfg_file_name=str(Path(output_path) / simulation_ini_file_name), ) self._libraries = [] @@ -160,8 +220,7 @@ def _create_ini(self): if not file_exists(parent): os.makedirs(parent) - original_ini = os.environ.get("VUNIT_MODELSIM_INI", str(Path(self._prefix).parent / self._ini_file)) - with Path(original_ini).open("rb") as fread: + with self._ini_file_path.open("rb") as fread: with Path(self._sim_cfg_file_name).open("wb") as fwrite: fwrite.write(fread.read()) From f3f6976c811f459b67c38065b627bac607b15770 Mon Sep 17 00:00:00 2001 From: Lars Asplund Date: Tue, 21 Apr 2026 20:53:38 +0200 Subject: [PATCH 3/3] Worked around compilation issue in nvc 1.20.0. --- examples/vhdl/array/src/test/tb_sobel_x.vhd | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/examples/vhdl/array/src/test/tb_sobel_x.vhd b/examples/vhdl/array/src/test/tb_sobel_x.vhd index 085edbaef..3129dbce3 100644 --- a/examples/vhdl/array/src/test/tb_sobel_x.vhd +++ b/examples/vhdl/array/src/test/tb_sobel_x.vhd @@ -40,15 +40,18 @@ begin impure function sobel_x ( constant image : integer_array_t ) return integer_array_t is + constant image_width : natural := width(image); + constant image_height : natural := height(image); + constant image_bit_width : natural := bit_width(image); variable result: integer_array_t := new_2d( - width => width(image), - height => height(image), - bit_width => bit_width(image)+1, + width => image_width, + height => image_height, + bit_width => image_bit_width+1, is_signed => true ); begin - for y in 0 to height(image)-1 loop - for x in 0 to width(image)-1 loop + for y in 0 to image_height-1 loop + for x in 0 to image_width-1 loop set( result, x => x,