Skip to content

Commit

Permalink
sim_if/modelsim: use vmap to find modelsim.ini
Browse files Browse the repository at this point in the history
* This creates a single source of truth for finding modelsim.ini. It is
  now possible to use MODELSIM=/path/to/custom/modelsim.ini and both
  ModelSim and vunit will use that. (The VUNIT_MODELSIM_INI environment
  variable can still be used to override modelsim.ini only for vunit.)

* This fixes detecting modelsim.ini in the quartus-prime-lite package
  from https://github.com/nixos/nixpkgs, which exposes binaries at
  $prefix/bin, but has no $prefix/modelsim.ini, because the latter would
  be an FHS violation.
  • Loading branch information
bjornfor committed Dec 18, 2024
1 parent 35d1aa6 commit a2e423d
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 24 deletions.
62 changes: 44 additions & 18 deletions tests/unit/test_modelsim_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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"))
Expand Down Expand Up @@ -361,18 +387,18 @@ 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)
renew_path(self.output_path)
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()
Expand Down
43 changes: 37 additions & 6 deletions vunit/sim_if/modelsim.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from pathlib import Path
import os
import re
import logging
from threading import Lock, Event
from time import sleep
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand All @@ -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())
Expand Down

0 comments on commit a2e423d

Please sign in to comment.