Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/news.d/1196.misc.rst
Original file line number Diff line number Diff line change
@@ -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.
3 changes: 3 additions & 0 deletions docs/news.d/1198.feature.rst
Original file line number Diff line number Diff line change
@@ -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.
13 changes: 8 additions & 5 deletions examples/vhdl/array/src/test/tb_sobel_x.vhd
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[build-system]
requires = ["setuptools>=61"]
requires = ["setuptools>=77"]
build-backend = "setuptools.build_meta"

[project]
Expand Down
76 changes: 55 additions & 21 deletions tests/unit/test_modelsim_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 """\
Expand All @@ -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 """\
Expand All @@ -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 <modelsim.ini> Specify path to the modelsim.ini file
-ini <questa.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)
Expand Down Expand Up @@ -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)
Expand Down
77 changes: 68 additions & 9 deletions vunit/sim_if/modelsim.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -71,24 +71,84 @@ 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):
"""
Find first valid Modelsim/Questa toolchain prefix
"""

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])

Expand Down Expand Up @@ -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 = []
Expand All @@ -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())

Expand Down
Loading