Skip to content

Commit 8119bee

Browse files
committed
Refactoring and added tests
1 parent a23b53d commit 8119bee

File tree

3 files changed

+94
-20
lines changed

3 files changed

+94
-20
lines changed

tests/unit/test_modelsim_interface.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from vunit.sim_if.modelsim import ModelSimInterface
1919
from vunit.project import Project
2020
from vunit.ostools import renew_path, write_file
21+
from vunit.test.bench import Configuration
2122
from vunit.vhdl_standard import VHDL
2223

2324

@@ -316,14 +317,60 @@ def test_overwrites_modelsim_ini_file_from_user(self):
316317
with open(modelsim_ini, "r") as fptr:
317318
self.assertEqual(fptr.read(), "user")
318319

320+
@mock.patch("vunit.sim_if.modelsim.LOGGER", autospec=True)
321+
@mock.patch("vunit.sim_if.check_output", autospec=True, return_value="")
322+
@mock.patch("vunit.sim_if.modelsim.Process", autospec=True)
323+
@mock.patch("vunit.sim_if.vsim_simulator_mixin.Process", autospec=True)
324+
def test_optimize(self, vsim_simulator_mixin_process, modelsim_process, check_output, LOGGER):
325+
simif = ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False)
326+
project = Project()
327+
project.add_library("lib", str(Path(self.libraries_path) / "lib"))
328+
write_file("file.vhd", "")
329+
project.add_source_file("file.vhd", "lib", file_type="vhdl", vhdl_standard=VHDL.standard("2008"))
330+
simif.compile_project(project)
331+
config = make_config(sim_options={"modelsim.three_step_flow":True})
332+
333+
# First call should optimize design
334+
simif.simulate(self.simulation_output_path, "test_suite_name", config, False)
335+
design_to_optimize = "lib.tb(test)"
336+
expected_calls = [
337+
mock.call("%s scheduled for optimization.", design_to_optimize),
338+
mock.call("Acquired library lock for %s to optimize %s.", "lib", design_to_optimize),
339+
mock.call("Optimizing %s.", design_to_optimize),
340+
mock.call("%s optimization completed.", design_to_optimize),
341+
]
342+
self.assertEqual(LOGGER.debug.call_count, len(expected_calls))
343+
LOGGER.debug.assert_has_calls(expected_calls)
344+
345+
# Second call should reuse the already optimized design
346+
LOGGER.reset_mock()
347+
simif.simulate(self.simulation_output_path, "test_suite_name", config, False)
348+
LOGGER.debug.assert_called_once_with("Reusing optimized %s.", "lib.tb(test)")
349+
350+
# Fake that design is being optimized and that it is being waited for
351+
LOGGER.reset_mock()
352+
simif._optimized_designs[design_to_optimize]["optimized_design"] = None
353+
simif.simulate(self.simulation_output_path, "test_suite_name", config, False)
354+
expected_calls = [
355+
mock.call("Waiting for %s to be optimized.", design_to_optimize),
356+
mock.call("Done waiting for %s to be optimized.", design_to_optimize),
357+
]
358+
self.assertEqual(LOGGER.debug.call_count, len(expected_calls))
359+
LOGGER.debug.assert_has_calls(expected_calls)
360+
361+
319362
def setUp(self):
320363
self.test_path = str(Path(__file__).parent / "test_modelsim_out")
321364

322365
self.output_path = str(Path(self.test_path) / "modelsim")
323366
self.prefix_path = str(Path(self.test_path) / "prefix" / "bin")
367+
self.libraries_path = str(Path(self.output_path) / "libraries")
368+
self.simulation_output_path = str(Path(self.test_path) / "test_output" / "lib.tb")
324369
renew_path(self.test_path)
325370
renew_path(self.output_path)
326371
renew_path(self.prefix_path)
372+
renew_path(self.libraries_path)
373+
renew_path(self.simulation_output_path)
327374
installed_modelsim_ini = str(Path(self.prefix_path) / ".." / "modelsim.ini")
328375
write_file(installed_modelsim_ini, "[Library]")
329376
self.project = Project()
@@ -334,3 +381,23 @@ def tearDown(self):
334381
os.chdir(self.cwd)
335382
if Path(self.test_path).exists():
336383
rmtree(self.test_path)
384+
385+
def make_config(sim_options=None, generics=None, verilog=False):
386+
"""
387+
Utility to reduce boiler plate in tests
388+
"""
389+
cfg = mock.Mock(spec=Configuration)
390+
cfg.library_name = "lib"
391+
392+
if verilog:
393+
cfg.entity_name = "tb"
394+
cfg.architecture_name = None
395+
else:
396+
cfg.entity_name = "tb"
397+
cfg.architecture_name = "test"
398+
399+
cfg.sim_options = {} if sim_options is None else sim_options
400+
cfg.generics = {} if generics is None else generics
401+
cfg.vhdl_configuration_name = None
402+
cfg.vhdl_assert_stop_level = "error"
403+
return cfg

vunit/sim_if/modelsim.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,13 @@ def __init__(self, prefix, output_path, persistent=False, gui=False):
102102
self._coverage_files = set()
103103
assert not (persistent and gui)
104104
self._create_modelsim_ini()
105+
# Contains design already optimized, i.e. the optimized design can be reused
105106
self._optimized_designs = {}
107+
# Contains locks for each library. If locked, a design belonging to the library
108+
# is being optimized and no other design in that library can be optimized at the
109+
# same time (from another thread)
106110
self._library_locks = {}
111+
# Lock to access the two shared variables above
107112
self._shared_state_lock = Lock()
108113

109114
def _create_modelsim_ini(self):
@@ -216,7 +221,7 @@ def create_library(self, library_name, path, mapped_libraries=None):
216221
os.makedirs(apath)
217222

218223
if not file_exists(path):
219-
proc = Process([str(Path(self._prefix) / "vlib"), "-unix", "-type", "directory", path], env=self.get_env())
224+
proc = Process([str(Path(self._prefix) / "vlib"), "-unix", path], env=self.get_env())
220225
proc.consume_output(callback=None)
221226

222227
if library_name in mapped_libraries and mapped_libraries[library_name] == path:
@@ -238,7 +243,7 @@ def _get_mapped_libraries(self):
238243

239244
def _optimize_design(self, config):
240245
"""
241-
Return if design shall be optimized.
246+
Return True if design shall be optimized.
242247
"""
243248

244249
return config.sim_options.get("modelsim.three_step_flow", False)
@@ -309,7 +314,7 @@ def _create_optimize_function(self, config):
309314
return true
310315
}}
311316
312-
return False
317+
return false
313318
}}
314319
""".format(
315320
vopt_flags=" ".join(vopt_flags)
@@ -364,7 +369,7 @@ def _wait_for_file_lock(library):
364369
log_waiting = True
365370
while (Path(library.directory) / "_lock").exists():
366371
if log_waiting:
367-
LOGGER.debug("Waiting for %s to be removed", Path(library.directory) / "_lock")
372+
LOGGER.debug("Waiting for %s to be removed.", Path(library.directory) / "_lock")
368373
log_waiting = False
369374
sleep(0.05)
370375

@@ -376,13 +381,13 @@ def _acquire_library_lock(self, library, config, design_to_optimize):
376381
library_lock = self._library_locks[config.library_name]
377382

378383
if library_lock.locked():
379-
LOGGER.debug("Waiting for library lock for %s to optimize %s", config.library_name, design_to_optimize)
384+
LOGGER.debug("Waiting for library lock for %s to optimize %s.", config.library_name, design_to_optimize)
380385
# Do not completely block to allow for Ctrl+C
381386
while not library_lock.acquire(timeout=0.05):
382387
pass
383388

384389
self._wait_for_file_lock(library)
385-
LOGGER.debug("Acquired library lock for %s to optimize %s", config.library_name, design_to_optimize)
390+
LOGGER.debug("Acquired library lock for %s to optimize %s.", config.library_name, design_to_optimize)
386391

387392
def _release_library_lock(self, library, config):
388393
"""
@@ -393,6 +398,9 @@ def _release_library_lock(self, library, config):
393398
self._library_locks[config.library_name].release()
394399

395400
def _optimize(self, config, script_path):
401+
"""
402+
Optimize design and return simulation target or False if optimization failed.
403+
"""
396404
design_to_optimize = self._design_to_optimize(config)
397405

398406
libraries = {lib.name: lib for lib in self._libraries}
@@ -401,7 +409,7 @@ def _optimize(self, config, script_path):
401409
optimize = False
402410
with self._shared_state_lock:
403411
if design_to_optimize not in self._optimized_designs:
404-
LOGGER.debug("%s scheduled for optimization", design_to_optimize)
412+
LOGGER.debug("%s scheduled for optimization.", design_to_optimize)
405413
self._optimized_designs[design_to_optimize] = {
406414
"optimized_design": None,
407415
"optimization_completed": Event(),
@@ -416,7 +424,7 @@ def _optimize(self, config, script_path):
416424
if optimize:
417425
self._acquire_library_lock(library, config, design_to_optimize)
418426

419-
LOGGER.debug("Optimizing %s", design_to_optimize)
427+
LOGGER.debug("Optimizing %s.", design_to_optimize)
420428

421429
optimized_design = self._to_optimized_design(design_to_optimize)
422430

@@ -428,11 +436,11 @@ def _optimize(self, config, script_path):
428436

429437
else:
430438
tcl = f"""\
431-
onerror {{quit -code 1}}
432-
source "{fix_path(str(optimize_file_name))!s}"
433-
set failed [vunit_optimize]
434-
if {{$failed}} {{quit -code 1}}
435-
quit -code 0
439+
onerror {{quit -code 1}}
440+
source "{fix_path(str(optimize_file_name))!s}"
441+
set failed [vunit_optimize]
442+
if {{$failed}} {{quit -code 1}}
443+
quit -code 0
436444
"""
437445
batch_file_name = script_path / "batch_optimize.do"
438446
write_file(str(batch_file_name), tcl)
@@ -449,7 +457,7 @@ def _optimize(self, config, script_path):
449457

450458
with self._shared_state_lock:
451459
self._optimized_designs[design_to_optimize]["optimized_design"] = optimized_design
452-
self._optimized_designs[design_to_optimize]["optimization_completed"].set()
460+
self._optimized_designs[design_to_optimize]["optimization_completed"].set()
453461

454462
elif not optimized_design:
455463
LOGGER.debug("Waiting for %s to be optimized.", design_to_optimize)

vunit/sim_if/vsim_simulator_mixin.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -342,17 +342,16 @@ def _run_persistent(self, common_file_name, load_only=False):
342342
except Process.NonZeroExitCode:
343343
return False
344344

345-
def _optimize(self, config, script_path): # pylint: disable=unused-argument
345+
def _optimize_design(self, config): # pylint: disable=unused-argument
346346
"""
347-
Return simulation target or False if optimization failed.
347+
Return True if design shall be optimized.
348348
"""
349-
return "Optimize not supported"
349+
return False
350350

351-
def _optimize_design(self, config): # pylint: disable=unused-argument
351+
def _optimize(self, config, script_path): # pylint: disable=unused-argument
352352
"""
353-
Return if design shall be optimized.
353+
Optimize design and return simulation target or False if optimization failed.
354354
"""
355-
356355
return False
357356

358357
def simulate(self, output_path, test_suite_name, config, elaborate_only):

0 commit comments

Comments
 (0)