From 11267fc80a400a8841c72d120993e7deb1383af9 Mon Sep 17 00:00:00 2001 From: Lars Asplund Date: Sun, 9 Jan 2022 23:49:27 +0100 Subject: [PATCH] Run different simulator threads in different directories. This avoids interference between different runner.cfg files. --- tests/unit/test_incisive_interface.py | 66 ++++++++++++++------------- tests/unit/test_test_runner.py | 33 +++++++++++++- tests/unit/test_test_suites.py | 54 +++++++++++++--------- vunit/persistent_tcl_shell.py | 16 +++---- vunit/sim_if/__init__.py | 17 +++++-- vunit/sim_if/activehdl.py | 31 ++++++------- vunit/sim_if/ghdl.py | 22 ++++----- vunit/sim_if/incisive.py | 7 +-- vunit/sim_if/rivierapro.py | 7 +++ vunit/sim_if/vsim_simulator_mixin.py | 33 ++++++-------- vunit/test/runner.py | 16 +++---- vunit/test/suites.py | 34 ++++++-------- 12 files changed, 189 insertions(+), 147 deletions(-) diff --git a/tests/unit/test_incisive_interface.py b/tests/unit/test_incisive_interface.py index ffcdbbad5..3aa854f1e 100644 --- a/tests/unit/test_incisive_interface.py +++ b/tests/unit/test_incisive_interface.py @@ -503,9 +503,9 @@ def test_simulate_vhdl(self, run_command, find_cds_root_irun, find_cds_root_virt simif.compile_project(project) config = make_config() - self.assertTrue(simif.simulate("suite_output_path", "test_suite_name", config)) - elaborate_args_file = str(Path("suite_output_path") / simif.name / "irun_elaborate.args") - simulate_args_file = str(Path("suite_output_path") / simif.name / "irun_simulate.args") + self.assertTrue(simif.simulate("suite_output_path", "simulator_output_path", "test_suite_name", config)) + elaborate_args_file = str(Path("simulator_output_path") / "irun_elaborate.args") + simulate_args_file = str(Path("simulator_output_path") / "irun_simulate.args") run_command.assert_has_calls( [ mock.call( @@ -541,7 +541,7 @@ def test_simulate_vhdl(self, run_command, find_cds_root_irun, find_cds_root_virt "-work work", '-nclibdirname "%s"' % str(Path(self.output_path) / "libraries"), '-cdslib "%s"' % str(Path(self.output_path) / "cds.lib"), - '-log "%s"' % str(Path("suite_output_path") / simif.name / "irun_elaborate.log"), + '-log "%s"' % str(Path("simulator_output_path") / "irun_elaborate.log"), "-quiet", '-reflib "lib_path"', "-access +r", @@ -565,7 +565,7 @@ def test_simulate_vhdl(self, run_command, find_cds_root_irun, find_cds_root_virt "-work work", '-nclibdirname "%s"' % str(Path(self.output_path) / "libraries"), '-cdslib "%s"' % str(Path(self.output_path) / "cds.lib"), - '-log "%s"' % str(Path("suite_output_path") / simif.name / "irun_simulate.log"), + '-log "%s"' % str(Path("simulator_output_path") / "irun_simulate.log"), "-quiet", '-reflib "lib_path"', "-access +r", @@ -591,9 +591,9 @@ def test_simulate_verilog(self, run_command, find_cds_root_irun, find_cds_root_v simif.compile_project(project) config = make_config(verilog=True) - self.assertTrue(simif.simulate("suite_output_path", "test_suite_name", config)) - elaborate_args_file = str(Path("suite_output_path") / simif.name / "irun_elaborate.args") - simulate_args_file = str(Path("suite_output_path") / simif.name / "irun_simulate.args") + self.assertTrue(simif.simulate("suite_output_path", "simulator_output_path", "test_suite_name", config)) + elaborate_args_file = str(Path("simulator_output_path") / "irun_elaborate.args") + simulate_args_file = str(Path("simulator_output_path") / "irun_simulate.args") run_command.assert_has_calls( [ mock.call( @@ -629,7 +629,7 @@ def test_simulate_verilog(self, run_command, find_cds_root_irun, find_cds_root_v "-work work", '-nclibdirname "%s"' % str(Path(self.output_path) / "libraries"), '-cdslib "%s"' % str(Path(self.output_path) / "cds.lib"), - '-log "%s"' % str(Path("suite_output_path") / simif.name / "irun_elaborate.log"), + '-log "%s"' % str(Path("simulator_output_path") / "irun_elaborate.log"), "-quiet", '-reflib "lib_path"', "-access +r", @@ -653,7 +653,7 @@ def test_simulate_verilog(self, run_command, find_cds_root_irun, find_cds_root_v "-work work", '-nclibdirname "%s"' % str(Path(self.output_path) / "libraries"), '-cdslib "%s"' % str(Path(self.output_path) / "cds.lib"), - '-log "%s"' % str(Path("suite_output_path") / simif.name / "irun_simulate.log"), + '-log "%s"' % str(Path("simulator_output_path") / "irun_simulate.log"), "-quiet", '-reflib "lib_path"', "-access +r", @@ -670,9 +670,9 @@ def test_simulate_extra_flags(self, run_command, find_cds_root_irun, find_cds_ro find_cds_root_virtuoso.return_value = None simif = IncisiveInterface(prefix="prefix", output_path=self.output_path) config = make_config(sim_options={"incisive.irun_sim_flags": ["custom", "flags"]}) - self.assertTrue(simif.simulate("suite_output_path", "test_suite_name", config)) - elaborate_args_file = str(Path("suite_output_path") / simif.name / "irun_elaborate.args") - simulate_args_file = str(Path("suite_output_path") / simif.name / "irun_simulate.args") + self.assertTrue(simif.simulate("suite_output_path", "simulator_output_path", "test_suite_name", config)) + elaborate_args_file = str(Path("simulator_output_path") / "irun_elaborate.args") + simulate_args_file = str(Path("simulator_output_path") / "irun_simulate.args") run_command.assert_has_calls( [ mock.call( @@ -708,9 +708,9 @@ def test_simulate_generics_and_parameters(self, run_command, find_cds_root_irun, find_cds_root_virtuoso.return_value = None simif = IncisiveInterface(prefix="prefix", output_path=self.output_path) config = make_config(verilog=True, generics={"genstr": "genval", "genint": 1, "genbool": True}) - self.assertTrue(simif.simulate("suite_output_path", "test_suite_name", config)) - elaborate_args_file = str(Path("suite_output_path") / simif.name / "irun_elaborate.args") - simulate_args_file = str(Path("suite_output_path") / simif.name / "irun_simulate.args") + self.assertTrue(simif.simulate("suite_output_path", "simulator_output_path", "test_suite_name", config)) + elaborate_args_file = str(Path("simulator_output_path") / "irun_elaborate.args") + simulate_args_file = str(Path("simulator_output_path") / "irun_simulate.args") run_command.assert_has_calls( [ mock.call( @@ -744,9 +744,9 @@ def test_simulate_hdlvar(self, run_command, find_cds_root_irun, find_cds_root_vi find_cds_root_virtuoso.return_value = None simif = IncisiveInterface(prefix="prefix", output_path=self.output_path, hdlvar="custom_hdlvar") config = make_config() - self.assertTrue(simif.simulate("suite_output_path", "test_suite_name", config)) - elaborate_args_file = str(Path("suite_output_path") / simif.name / "irun_elaborate.args") - simulate_args_file = str(Path("suite_output_path") / simif.name / "irun_simulate.args") + self.assertTrue(simif.simulate("suite_output_path", "simulator_output_path", "test_suite_name", config)) + elaborate_args_file = str(Path("simulator_output_path") / "irun_elaborate.args") + simulate_args_file = str(Path("simulator_output_path") / "irun_simulate.args") run_command.assert_has_calls( [ mock.call( @@ -778,8 +778,10 @@ def test_elaborate(self, run_command, find_cds_root_irun, find_cds_root_virtuoso find_cds_root_virtuoso.return_value = None simif = IncisiveInterface(prefix="prefix", output_path=self.output_path) config = make_config(verilog=True) - self.assertTrue(simif.simulate("suite_output_path", "test_suite_name", config, elaborate_only=True)) - elaborate_args_file = str(Path("suite_output_path") / simif.name / "irun_elaborate.args") + self.assertTrue( + simif.simulate("suite_output_path", "simulator_output_path", "test_suite_name", config, elaborate_only=True) + ) + elaborate_args_file = str(Path("simulator_output_path") / "irun_elaborate.args") run_command.assert_has_calls( [ mock.call( @@ -810,7 +812,7 @@ def test_elaborate(self, run_command, find_cds_root_irun, find_cds_root_virtuoso "-work work", '-nclibdirname "%s"' % str(Path(self.output_path) / "libraries"), '-cdslib "%s"' % str(Path(self.output_path) / "cds.lib"), - '-log "%s"' % str(Path("suite_output_path") / simif.name / "irun_elaborate.log"), + '-log "%s"' % str(Path("simulator_output_path") / "irun_elaborate.log"), "-quiet", "-access +r", '-input "@run"', @@ -826,8 +828,8 @@ def test_elaborate_fail(self, run_command, find_cds_root_irun, find_cds_root_vir find_cds_root_virtuoso.return_value = None simif = IncisiveInterface(prefix="prefix", output_path=self.output_path) config = make_config() - self.assertFalse(simif.simulate("suite_output_path", "test_suite_name", config)) - elaborate_args_file = str(Path("suite_output_path") / simif.name / "irun_elaborate.args") + self.assertFalse(simif.simulate("suite_output_path", "simulator_output_path", "test_suite_name", config)) + elaborate_args_file = str(Path("simulator_output_path") / "irun_elaborate.args") run_command.assert_has_calls( [ mock.call( @@ -850,9 +852,9 @@ def test_simulate_fail(self, run_command, find_cds_root_irun, find_cds_root_virt find_cds_root_virtuoso.return_value = None simif = IncisiveInterface(prefix="prefix", output_path=self.output_path) config = make_config() - self.assertFalse(simif.simulate("suite_output_path", "test_suite_name", config)) - elaborate_args_file = str(Path("suite_output_path") / simif.name / "irun_elaborate.args") - simulate_args_file = str(Path("suite_output_path") / simif.name / "irun_simulate.args") + self.assertFalse(simif.simulate("suite_output_path", "simulator_output_path", "test_suite_name", config)) + elaborate_args_file = str(Path("simulator_output_path") / "irun_elaborate.args") + simulate_args_file = str(Path("simulator_output_path") / "irun_simulate.args") run_command.assert_has_calls( [ mock.call( @@ -888,9 +890,9 @@ def test_simulate_gui(self, run_command, find_cds_root_irun, find_cds_root_virtu with mock.patch("vunit.sim_if.check_output", autospec=True, return_value="") as dummy: simif.compile_project(project) config = make_config() - self.assertTrue(simif.simulate("suite_output_path", "test_suite_name", config)) - elaborate_args_file = str(Path("suite_output_path") / simif.name / "irun_elaborate.args") - simulate_args_file = str(Path("suite_output_path") / simif.name / "irun_simulate.args") + self.assertTrue(simif.simulate("suite_output_path", "simulator_output_path", "test_suite_name", config)) + elaborate_args_file = str(Path("simulator_output_path") / "irun_elaborate.args") + simulate_args_file = str(Path("simulator_output_path") / "irun_simulate.args") run_command.assert_has_calls( [ mock.call( @@ -925,7 +927,7 @@ def test_simulate_gui(self, run_command, find_cds_root_irun, find_cds_root_virtu "-work work", '-nclibdirname "%s"' % str(Path(self.output_path) / "libraries"), '-cdslib "%s"' % str(Path(self.output_path) / "cds.lib"), - '-log "%s"' % str(Path("suite_output_path") / simif.name / "irun_elaborate.log"), + '-log "%s"' % str(Path("simulator_output_path") / "irun_elaborate.log"), "-quiet", '-reflib "lib_path"', "-access +rwc", @@ -949,7 +951,7 @@ def test_simulate_gui(self, run_command, find_cds_root_irun, find_cds_root_virtu "-work work", '-nclibdirname "%s"' % str(Path(self.output_path) / "libraries"), '-cdslib "%s"' % str(Path(self.output_path) / "cds.lib"), - '-log "%s"' % str(Path("suite_output_path") / simif.name / "irun_simulate.log"), + '-log "%s"' % str(Path("simulator_output_path") / "irun_simulate.log"), "-quiet", '-reflib "lib_path"', "-access +rwc", diff --git a/tests/unit/test_test_runner.py b/tests/unit/test_test_runner.py index bf712939f..943006026 100644 --- a/tests/unit/test_test_runner.py +++ b/tests/unit/test_test_runner.py @@ -11,6 +11,7 @@ from pathlib import Path import unittest from unittest import mock +from time import sleep from tests.common import with_tempdir from vunit.hashing import hash_string from vunit.test.runner import TestRunner @@ -44,6 +45,27 @@ def test_runs_testcases_in_order(self, tempdir): self.assertTrue(report.result_of("test1").passed) self.assertTrue(report.result_of("test2").failed) self.assertTrue(report.result_of("test3").passed) + self.assertEqual(test_case1.thread_id, 0) + self.assertEqual(test_case2.thread_id, 0) + self.assertEqual(test_case3.thread_id, 0) + + @with_tempdir + def test_runs_testcases_in_multiple_threads(self, tempdir): + report = TestReport() + runner = TestRunner(report, tempdir, num_threads=3) + + test_cases = [self.create_test(f"test{n}", True, delay=n / 2) for n in range(1, 6)] + test_list = TestList() + for test_case in test_cases: + test_list.add_test(test_case) + + runner.run(test_list) + + self.assertListEqual( + [test_case.thread_id for test_case in test_cases], + [1, 2, 0, 1, 2], + str([(test_case.name, test_case.thread_id) for test_case in test_cases]), + ) @with_tempdir def test_fail_fast(self, tempdir): @@ -68,6 +90,9 @@ def test_fail_fast(self, tempdir): self.assertEqual(order, ["test1", "test2"]) self.assertTrue(report.result_of("test1").passed) self.assertTrue(report.result_of("test2").failed) + self.assertEqual(test_case1.thread_id, 0) + self.assertEqual(test_case2.thread_id, 0) + self.assertEqual(test_case3.thread_id, None) @with_tempdir def test_handles_python_exeception(self, tempdir): @@ -196,7 +221,7 @@ def test_get_output_path_on_windows(self): ) @staticmethod - def create_test(name, passed, order=None): + def create_test(name, passed, order=None, delay=None): """ Utility function to create a mocked test with name that is either passed or failed @@ -208,6 +233,8 @@ def run_side_effect(*args, **kwargs): # pylint: disable=unused-argument """ if order is not None: order.append(name) + if delay is not None: + sleep(delay) return passed test_case = TestCaseMock(name=name, run_side_effect=run_side_effect) @@ -223,10 +250,11 @@ def __init__(self, name, run_side_effect): self.name = name self.output_path = None self.read_output = None + self.thread_id = None self.called = False self.run_side_effect = run_side_effect - def run(self, output_path, read_output): + def run(self, output_path, read_output, thread_id): """ Mock run method that just records the arguments """ @@ -234,4 +262,5 @@ def run(self, output_path, read_output): self.called = True self.output_path = output_path self.read_output = read_output + self.thread_id = thread_id return self.run_side_effect(output_path=output_path, read_output=read_output) diff --git a/tests/unit/test_test_suites.py b/tests/unit/test_test_suites.py index b82fd06d5..8000aac5c 100644 --- a/tests/unit/test_test_suites.py +++ b/tests/unit/test_test_suites.py @@ -194,37 +194,47 @@ def test_runner_cfg_location(self): with create_tempdir() as tempdir: design_unit = Entity("tb_entity", file_name=str(Path(tempdir) / "file.vhd")) design_unit.generic_names = ["runner_cfg"] - output_path = str(Path(__file__).parent / "sim_out") - renew_path(output_path) + vunit_output_path = str(Path(tempdir) / "vunit_out") + renew_path(vunit_output_path) + test_output_path = str(Path(vunit_output_path) / "test_out") + simulator_root = str(Path(vunit_output_path) / "my_simulator") class TestSimIf(SimulatorInterface): - def __init__(self, output_path, gui, expect_runner_cfg_generic): + def __init__(self, output_path, gui, expect_runner_cfg_generic, thread_id): super().__init__(output_path, gui) self._expect_runner_cfg_generic = expect_runner_cfg_generic + self._thread_id = thread_id - def simulate(self, output_path, test_suite_name, config, elaborate_only): + def simulate(self, output_path, simulator_output_path, test_suite_name, config, elaborate_only): tc = TestCase() + tc.assertEqual(Path(output_path), Path(test_output_path)) + tc.assertEqual(Path(simulator_output_path), Path(simulator_root) / str(self._thread_id)) if self._expect_runner_cfg_generic: tc.assertIn("runner_cfg", config.generics) - tc.assertFalse((Path(output_path) / "test_sim" / "runner.cfg").exists()) + tc.assertFalse((Path(simulator_output_path) / "runner.cfg").exists()) else: tc.assertNotIn("runner_cfg", config.generics) - tc.assertTrue((Path(output_path) / "test_sim" / "runner.cfg").exists()) - - def get_simulator_output_path(self, output_path): - return Path(output_path) / "test_sim" + tc.assertTrue((Path(simulator_output_path) / "runner.cfg").exists()) for expect_runner_cfg_generic in [False, True]: - config = Configuration( - "name", design_unit, vhdl_config_name=None if expect_runner_cfg_generic else "cfg" - ) - sim_if = TestSimIf(output_path, gui=False, expect_runner_cfg_generic=expect_runner_cfg_generic) - - run = TestRun( - simulator_if=sim_if, - config=config, - elaborate_only=False, - test_suite_name=None, - test_cases=["foo"], - ) - run._simulate(output_path) # pylint: disable=protected-access + for thread_id in range(2): + renew_path(test_output_path) + renew_path(simulator_root) + config = Configuration( + "name", design_unit, vhdl_config_name=None if expect_runner_cfg_generic else "cfg" + ) + sim_if = TestSimIf( + simulator_root, + gui=False, + expect_runner_cfg_generic=expect_runner_cfg_generic, + thread_id=thread_id, + ) + + test_run = TestRun( + simulator_if=sim_if, + config=config, + elaborate_only=False, + test_suite_name=None, + test_cases=["foo"], + ) + test_run.run(test_output_path, thread_id=thread_id, read_output=None) diff --git a/vunit/persistent_tcl_shell.py b/vunit/persistent_tcl_shell.py index 27d31b860..b3ec9768b 100644 --- a/vunit/persistent_tcl_shell.py +++ b/vunit/persistent_tcl_shell.py @@ -25,7 +25,7 @@ def __init__(self, create_process): self._lock = threading.Lock() self._create_process = create_process - def _process(self): + def _process(self, simulator_output_path): """ Create the vsim process """ @@ -39,7 +39,7 @@ def _process(self): except KeyError: pass - process = self._create_process(ident) + process = self._create_process(ident, simulator_output_path) self._processes[ident] = process process.writeline("puts #VUNIT_RETURN") @@ -53,27 +53,27 @@ def _process(self): raise return process - def execute(self, cmd): + def execute(self, cmd, simulator_output_path): """ Execute a command to the persistent TCL shell """ - process = self._process() + process = self._process(simulator_output_path) process.writeline(cmd) process.writeline("puts #VUNIT_RETURN") process.consume_output(output_consumer) - def read_var(self, varname): + def read_var(self, varname, simulator_output_path): """ Read a variable from the persistent TCL shell """ - process = self._process() + process = self._process(simulator_output_path) process.writeline(f"puts #VUNIT_READVAR=${varname!s}") consumer = ReadVarOutputConsumer() process.consume_output(consumer) return consumer.var - def read_bool(self, varname): - result = self.read_var(varname) + def read_bool(self, varname, simulator_output_path): + result = self.read_var(varname, simulator_output_path) assert result in ("true", "false") return result == "true" diff --git a/vunit/sim_if/__init__.py b/vunit/sim_if/__init__.py index 7aa2ce9a2..d50ce6952 100644 --- a/vunit/sim_if/__init__.py +++ b/vunit/sim_if/__init__.py @@ -50,11 +50,20 @@ class SimulatorInterface(object): # pylint: disable=too-many-public-methods supports_colors_in_gui = False def __init__(self, output_path, gui): + """ + Init function + + :param output_path: Directory dedicated to the simulator. + :param gui: True if running with GUI. + """ self._output_path = output_path self._gui = gui @property def output_path(self): + """ + Directory dedicated to the simulator. + """ return self._output_path @property @@ -203,13 +212,13 @@ def compile_project( self.setup_library_mapping(project) self.compile_source_files(project, printer, continue_on_error, target_files=target_files) - def simulate(self, output_path, test_suite_name, config, elaborate_only): + def simulate(self, output_path, simulator_output_path, test_suite_name, config, elaborate_only): """ Simulate - """ - def get_simulator_output_path(self, output_path): - "Get current working directory for simulation" + :param output_path: Directory dedicated for test input and output data. + :param simulator_output_path: Current working directory for simulation thread. + """ def setup_library_mapping(self, project): """ diff --git a/vunit/sim_if/activehdl.py b/vunit/sim_if/activehdl.py index 9563ae6d8..4a35ecbe8 100644 --- a/vunit/sim_if/activehdl.py +++ b/vunit/sim_if/activehdl.py @@ -362,7 +362,7 @@ def _create_batch_script(common_file_name, load_only=False): batch_do += "quit -code 0\n" return batch_do - def _create_gui_script(self, common_file_name, config): + def _create_gui_script(self, common_file_name, config, simulator_output_path): """ Create the user facing script which loads common functions and prints a help message """ @@ -376,7 +376,7 @@ def _create_gui_script(self, common_file_name, config): tcl += f"vmap {library.name!s} {fix_path(library.directory)!s}\n" tcl += "global sim_working_folder\n" - tcl += f"set sim_working_folder {fix_path(str(Path(common_file_name).parent / 'gui'))}\n" + tcl += f"set sim_working_folder {fix_path(str(simulator_output_path))}\n" tcl += "vunit_load\n" @@ -413,7 +413,7 @@ def _run_batch_file(self, batch_file_name, gui, cwd): return False return True - def simulate(self, output_path, test_suite_name, config, elaborate_only): + def simulate(self, output_path, simulator_output_path, test_suite_name, config, elaborate_only): """ Run a test bench """ @@ -422,31 +422,26 @@ def simulate(self, output_path, test_suite_name, config, elaborate_only): batch_file_name = script_path / "batch.tcl" gui_file_name = script_path / "gui.tcl" + library_cfg_path = simulator_output_path / "library.cfg" + library_cfg_path.write_text('$INCLUDE = ".."\n') write_file(common_file_name, self._create_common_script(config, output_path)) - write_file(gui_file_name, self._create_gui_script(str(common_file_name), config)) + write_file(gui_file_name, self._create_gui_script(str(common_file_name), config, simulator_output_path)) write_file( str(batch_file_name), self._create_batch_script(str(common_file_name), elaborate_only), ) if self._gui: - gui_path = str(script_path / "gui") - if (script_path / "gui" / "runner.cfg").exists(): - runner_cfg = (script_path / "gui" / "runner.cfg").read_text(encoding="utf-8") - renew_path(gui_path) - (script_path / "gui" / "runner.cfg").write_text(runner_cfg, encoding="utf-8") + if (simulator_output_path / "runner.cfg").exists(): + runner_cfg = (simulator_output_path / "runner.cfg").read_text(encoding="utf-8") + renew_path(str(simulator_output_path)) + (simulator_output_path / "runner.cfg").write_text(runner_cfg, encoding="utf-8") else: - renew_path(gui_path) + renew_path(str(simulator_output_path)) - return self._run_batch_file(str(gui_file_name), gui=True, cwd=gui_path) + return self._run_batch_file(str(gui_file_name), gui=True, cwd=str(simulator_output_path)) - return self._run_batch_file(str(batch_file_name), gui=False, cwd=str(Path(self._library_cfg).parent)) - - def get_simulator_output_path(self, output_path): - if self._gui: - return Path(output_path) / self.name / "gui" - - return Path(self._library_cfg).parent + return self._run_batch_file(str(batch_file_name), gui=False, cwd=str(simulator_output_path)) @total_ordering diff --git a/vunit/sim_if/ghdl.py b/vunit/sim_if/ghdl.py index 0a98863b8..7701ef68f 100644 --- a/vunit/sim_if/ghdl.py +++ b/vunit/sim_if/ghdl.py @@ -93,6 +93,7 @@ def __init__( # pylint: disable=too-many-arguments gtkwave_args="", backend="llvm", ): + SimulatorInterface.__init__(self, output_path, gui) self._prefix = prefix self._project = None @@ -265,7 +266,7 @@ def compile_vhdl_file_command(self, source_file): cmd += [source_file.name] return cmd - def _get_command(self, config, output_path, elaborate_only, ghdl_e, wave_file): # pylint: disable=too-many-branches + def _get_command(self, config, script_path, elaborate_only, ghdl_e, wave_file): # pylint: disable=too-many-branches """ Return GHDL simulation command """ @@ -278,7 +279,7 @@ def _get_command(self, config, output_path, elaborate_only, ghdl_e, wave_file): cmd += [f"--workdir={self._project.get_library(config.library_name).directory!s}"] cmd += [f"-P{lib.directory!s}" for lib in self._project.get_libraries()] - bin_path = str(Path(output_path) / f"{config.entity_name!s}-{config.architecture_name!s}") + bin_path = str(Path(script_path) / f"{config.entity_name!s}-{config.architecture_name!s}") if self._has_output_flag(): cmd += ["-o", bin_path] cmd += config.sim_options.get("ghdl.elab_flags", []) @@ -310,13 +311,13 @@ def _get_command(self, config, output_path, elaborate_only, ghdl_e, wave_file): cmd += ["--no-run"] else: try: - makedirs(output_path, mode=0o777) + makedirs(script_path, mode=0o777) except OSError: pass - with (Path(output_path) / "args.json").open("w", encoding="utf-8") as fname: + with (Path(script_path) / "args.json").open("w", encoding="utf-8") as fname: dump( { - "bin": str(Path(output_path) / f"{config.entity_name!s}-{config.architecture_name!s}"), + "bin": str(Path(script_path) / f"{config.entity_name!s}-{config.architecture_name!s}"), "build": cmd[1:], "sim": sim, }, @@ -325,7 +326,9 @@ def _get_command(self, config, output_path, elaborate_only, ghdl_e, wave_file): return cmd - def simulate(self, output_path, test_suite_name, config, elaborate_only): # pylint: disable=too-many-locals + def simulate( + self, output_path, simulator_output_path, test_suite_name, config, elaborate_only + ): # pylint: disable=too-many-locals """ Simulate with entity as top level using generics """ @@ -356,7 +359,7 @@ def simulate(self, output_path, test_suite_name, config, elaborate_only): # pyl self._coverage_test_dirs.add(coverage_dir) try: - proc = Process(cmd, env=gcov_env) + proc = Process(cmd, env=gcov_env, cwd=simulator_output_path) proc.consume_output() except Process.NonZeroExitCode: status = False @@ -369,13 +372,10 @@ def simulate(self, output_path, test_suite_name, config, elaborate_only): # pyl cmd += ["--script", str(Path(init_file).resolve())] stdout.write(" ".join(cmd) + "\n") - subprocess.call(cmd) + subprocess.call(cmd, cwd=simulator_output_path) return status - def get_simulator_output_path(self, output_path): - return Path(".") - def _compile_source_file(self, source_file, printer): """ Runs parent command for compilation, and moves any .gcno files to the compilation output diff --git a/vunit/sim_if/incisive.py b/vunit/sim_if/incisive.py index 42bbe4aa0..3acca17cf 100644 --- a/vunit/sim_if/incisive.py +++ b/vunit/sim_if/incisive.py @@ -286,13 +286,13 @@ def _select_vhdl_top(config): return f"{config.vhdl_config_name!s}" def simulate( - self, output_path, test_suite_name, config, elaborate_only=False + self, output_path, simulator_output_path, test_suite_name, config, elaborate_only=False ): # pylint: disable=too-many-locals,too-many-branches """ Elaborates and Simulates with entity as top level using generics """ - script_path = str(Path(output_path) / self.name) + script_path = str(simulator_output_path) launch_gui = self._gui is not False and not elaborate_only if elaborate_only: @@ -358,9 +358,6 @@ def simulate( return False return True - def get_simulator_output_path(self, output_path): - return Path(output_path) / self.name - def _hdlvar_args(self): """ Return hdlvar argument if available diff --git a/vunit/sim_if/rivierapro.py b/vunit/sim_if/rivierapro.py index 2f77b7c79..662aabc4b 100644 --- a/vunit/sim_if/rivierapro.py +++ b/vunit/sim_if/rivierapro.py @@ -240,6 +240,13 @@ def create_library(self, library_name, path, mapped_libraries=None): ) proc.consume_output(callback=None) + def simulate(self, output_path, simulator_output_path, test_suite_name, config, elaborate_only): + library_cfg_path = simulator_output_path / "library.cfg" + library_cfg_path.write_text('$INCLUDE = ".."\n') + return VsimSimulatorMixin.simulate( + self, output_path, simulator_output_path, test_suite_name, config, elaborate_only + ) + def _create_library_cfg(self): """ Create the library.cfg file if it does not exist diff --git a/vunit/sim_if/vsim_simulator_mixin.py b/vunit/sim_if/vsim_simulator_mixin.py index 383d8eec8..7c0ea1e1e 100644 --- a/vunit/sim_if/vsim_simulator_mixin.py +++ b/vunit/sim_if/vsim_simulator_mixin.py @@ -31,7 +31,7 @@ def __init__(self, prefix, persistent, sim_cfg_file_name): prefix = self._prefix # Avoid circular dependency inhibiting process destruction env = self.get_env() - def create_process(ident): + def create_process(ident, simulator_output_path): return Process( [ str(Path(prefix) / "vsim"), @@ -41,7 +41,7 @@ def create_process(ident): "-do", str((Path(__file__).parent / "tcl_read_eval_loop.tcl").resolve()), ], - cwd=str(Path(sim_cfg_file_name).parent), + cwd=str(simulator_output_path), env=env, ) @@ -263,7 +263,7 @@ def _create_gui_script(self, common_file_name, config): tcl += "}\n" return tcl - def _run_batch_file(self, batch_file_name, gui=False): + def _run_batch_file(self, batch_file_name, simulator_output_path, gui=False): """ Run a test bench in batch by invoking a new vsim process from the command line """ @@ -278,32 +278,32 @@ def _run_batch_file(self, batch_file_name, gui=False): f'source "{fix_path(batch_file_name)!s}"', ] - proc = Process(args, cwd=str(Path(self._sim_cfg_file_name).parent)) + proc = Process(args, cwd=simulator_output_path) proc.consume_output() except Process.NonZeroExitCode: return False return True - def _run_persistent(self, common_file_name, load_only=False): + def _run_persistent(self, common_file_name, simulator_output_path, load_only=False): """ Run a test bench using the persistent vsim process """ try: - self._persistent_shell.execute(f'source "{fix_path(common_file_name)!s}"') - self._persistent_shell.execute("set failed [vunit_load]") - if self._persistent_shell.read_bool("failed"): + self._persistent_shell.execute(f'source "{fix_path(common_file_name)!s}"', simulator_output_path) + self._persistent_shell.execute("set failed [vunit_load]", simulator_output_path) + if self._persistent_shell.read_bool("failed", simulator_output_path): return False run_ok = True if not load_only: - self._persistent_shell.execute("set failed [vunit_run]") - run_ok = not self._persistent_shell.read_bool("failed") - self._persistent_shell.execute("quit -sim") + self._persistent_shell.execute("set failed [vunit_run]", simulator_output_path) + run_ok = not self._persistent_shell.read_bool("failed", simulator_output_path) + self._persistent_shell.execute("quit -sim", simulator_output_path) return run_ok except Process.NonZeroExitCode: return False - def simulate(self, output_path, test_suite_name, config, elaborate_only): + def simulate(self, output_path, simulator_output_path, test_suite_name, config, elaborate_only): """ Run a test bench """ @@ -324,15 +324,12 @@ def simulate(self, output_path, test_suite_name, config, elaborate_only): ) if self._gui: - return self._run_batch_file(str(gui_file_name), gui=True) + return self._run_batch_file(str(gui_file_name), simulator_output_path, gui=True) if self._persistent_shell is not None: - return self._run_persistent(str(common_file_name), load_only=elaborate_only) + return self._run_persistent(str(common_file_name), simulator_output_path, load_only=elaborate_only) - return self._run_batch_file(str(batch_file_name)) - - def get_simulator_output_path(self, output_path): # pylint: disable=unused-argument - return Path(self._sim_cfg_file_name).parent + return self._run_batch_file(str(batch_file_name), simulator_output_path) def fix_path(path): diff --git a/vunit/test/runner.py b/vunit/test/runner.py index c9d988bab..4accfeda5 100644 --- a/vunit/test/runner.py +++ b/vunit/test/runner.py @@ -107,16 +107,16 @@ def run(self, test_suites): sys.stderr = ThreadLocalOutput(self._local, self._stdout) # Start P-1 worker threads - for _ in range(self._num_threads - 1): + for thread_id in range(1, self._num_threads): new_thread = threading.Thread( target=self._run_thread, - args=(write_stdout, scheduler, num_tests, False), + args=(write_stdout, scheduler, num_tests, False, thread_id), ) threads.append(new_thread) new_thread.start() # Run one worker in main thread such that P=1 is not multithreaded - self._run_thread(write_stdout, scheduler, num_tests, True) + self._run_thread(write_stdout, scheduler, num_tests, True, thread_id=0) scheduler.wait_for_finish() @@ -133,7 +133,7 @@ def run(self, test_suites): sys.stderr = self._stderr LOGGER.debug("TestRunner: Leaving") - def _run_thread(self, write_stdout, scheduler, num_tests, is_main): + def _run_thread(self, write_stdout, scheduler, num_tests, is_main, thread_id): """ Run worker thread """ @@ -152,7 +152,7 @@ def _run_thread(self, write_stdout, scheduler, num_tests, is_main): print(f"Starting {test_name!s}") print(f"Output file: {output_file_name!s}") - self._run_test_suite(test_suite, write_stdout, num_tests, output_path, output_file_name) + self._run_test_suite(test_suite, write_stdout, num_tests, output_path, output_file_name, thread_id) except StopIteration: return @@ -199,8 +199,8 @@ def _add_skipped_tests(self, test_suite, results, start_time, num_tests, output_ self._add_results(test_suite, results, start_time, num_tests, output_file_name) def _run_test_suite( # pylint: disable=too-many-locals - self, test_suite, write_stdout, num_tests, output_path, output_file_name - ): + self, test_suite, write_stdout, num_tests, output_path, output_file_name, thread_id + ): # pylint: disable=too-many-arguments """ Run the actual test suite """ @@ -241,7 +241,7 @@ def read_output(): output_file.seek(prev) return contents - results = test_suite.run(output_path=output_path, read_output=read_output) + results = test_suite.run(output_path=output_path, read_output=read_output, thread_id=thread_id) except KeyboardInterrupt as exk: self._add_skipped_tests(test_suite, results, start_time, num_tests, output_file_name) raise KeyboardInterrupt from exk diff --git a/vunit/test/suites.py b/vunit/test/suites.py index 76e532060..4d2bc6040 100644 --- a/vunit/test/suites.py +++ b/vunit/test/suites.py @@ -159,7 +159,7 @@ def __init__(self, simulator_if, config, elaborate_only, test_suite_name, test_c def set_test_cases(self, test_cases): self._test_cases = test_cases - def run(self, output_path, read_output): + def run(self, output_path, read_output, thread_id): """ Run selected test cases within the test suite @@ -169,13 +169,15 @@ def run(self, output_path, read_output): for name in self._test_cases: results[name] = FAILED - if not self._config.call_pre_config(output_path, self._simulator_if.output_path): + simulator_output_path = Path(self._simulator_if.output_path) / f"{thread_id}" + simulator_output_path.mkdir(parents=True, exist_ok=True) + if not self._config.call_pre_config(output_path, str(simulator_output_path)): return results # Ensure result file exists ostools.write_file(get_result_file_name(output_path), "") - sim_ok = self._simulate(output_path) + sim_ok = self._simulate(output_path, simulator_output_path) if self._elaborate_only: status = PASSED if sim_ok else FAILED @@ -211,7 +213,7 @@ def _check_results(self, results, sim_ok): return False, results - def _simulate(self, output_path): + def _simulate(self, output_path, simulator_output_path): """ Add runner_cfg generic values and run simulation """ @@ -231,26 +233,20 @@ def _simulate(self, output_path): "tb path": config.tb_path.replace("\\", "/") + "/", } - simulator_output_path = self._simulator_if.get_simulator_output_path(output_path) / "runner.cfg" + runner_cfg_path = simulator_output_path / "runner.cfg" if config.vhdl_config_name is not None: - simulator_output_path.parent.mkdir(parents=True, exist_ok=True) - simulator_output_path.write_text(encode_dict(runner_cfg)) + runner_cfg_path.write_text(encode_dict(runner_cfg)) else: # @TODO Warn if runner cfg already set? config.generics["runner_cfg"] = encode_dict(runner_cfg) - try: - return_value = self._simulator_if.simulate( - output_path=output_path, - test_suite_name=self._test_suite_name, - config=config, - elaborate_only=self._elaborate_only, - ) - finally: - if simulator_output_path.exists(): - simulator_output_path.unlink() - - return return_value + return self._simulator_if.simulate( + output_path=output_path, + simulator_output_path=simulator_output_path, + test_suite_name=self._test_suite_name, + config=config, + elaborate_only=self._elaborate_only, + ) def _read_test_results(self, file_name): # pylint: disable=too-many-branches """