From c30232998448f07ad951d646399dbf9db34c5e1f Mon Sep 17 00:00:00 2001 From: Lars Asplund Date: Sat, 25 Dec 2021 18:00:04 +0100 Subject: [PATCH 01/13] Allow test_runner_setup to read runner_cfg from file. --- .../vhdl/tb_reading_runner_cfg_from_file.vhd | 41 ++++++++++ tests/acceptance/test_artificial.py | 4 + vunit/vhdl/run/src/run.vhd | 75 +++++++++++-------- vunit/vhdl/run/src/run_types.vhd | 1 + vunit/vhdl/run/test/run_tests.vhd | 19 +++++ 5 files changed, 110 insertions(+), 30 deletions(-) create mode 100644 tests/acceptance/artificial/vhdl/tb_reading_runner_cfg_from_file.vhd diff --git a/tests/acceptance/artificial/vhdl/tb_reading_runner_cfg_from_file.vhd b/tests/acceptance/artificial/vhdl/tb_reading_runner_cfg_from_file.vhd new file mode 100644 index 000000000..a8e68cf42 --- /dev/null +++ b/tests/acceptance/artificial/vhdl/tb_reading_runner_cfg_from_file.vhd @@ -0,0 +1,41 @@ +-- This Source Code Form is subject to the terms of the Mozilla Public +-- License, v. 2.0. If a copy of the MPL was not distributed with this file, +-- You can obtain one at http://mozilla.org/MPL/2.0/. +-- +-- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com + +library vunit_lib; +context vunit_lib.vunit_context; + +use std.textio.all; + +entity tb_reading_runner_cfg_from_file is + generic(runner_cfg : string); +end entity; + +architecture tb of tb_reading_runner_cfg_from_file is +begin + test_runner : process + file runner_cfg_fptr : text; + variable runner_cfg_line : line; + begin + file_open(runner_cfg_fptr, "runner.cfg", write_mode); + write( + runner_cfg_line, + string'("active python runner : true,enabled_test_cases : ,output path : " & + replace(output_path(runner_cfg), ":", "::") & ",tb path : " & + replace(tb_path(runner_cfg), ":", "::") & ",use_color : true") + ); + writeline(runner_cfg_fptr, runner_cfg_line); + file_close(runner_cfg_fptr); + + test_runner_setup(runner, null_runner_cfg); + check_true(active_python_runner(null_runner_cfg)); + check(output_path(null_runner_cfg) /= ""); + check(enabled_test_cases(null_runner_cfg) /= "__all__"); + check(tb_path(null_runner_cfg) /= ""); + + test_runner_cleanup(runner); + wait; + end process; +end architecture; diff --git a/tests/acceptance/test_artificial.py b/tests/acceptance/test_artificial.py index bc6d28093..0895f613b 100644 --- a/tests/acceptance/test_artificial.py +++ b/tests/acceptance/test_artificial.py @@ -245,4 +245,8 @@ def test_exit_0_flag(self): "failed", "lib.tb_assert_stop_level.Report failure when VHDL assert stop level = failure", ), + ( + "passed", + "lib.tb_reading_runner_cfg_from_file.all", + ), ) diff --git a/vunit/vhdl/run/src/run.vhd b/vunit/vhdl/run/src/run.vhd index b3ed6891f..ef2ba8b89 100644 --- a/vunit/vhdl/run/src/run.vhd +++ b/vunit/vhdl/run/src/run.vhd @@ -21,30 +21,49 @@ use work.checker_pkg.all; use work.dict_pkg.all; package body run_pkg is - procedure test_runner_setup( + impure function resolve_runner_cfg(runner_cfg : string) return string is + file runner_cfg_fptr : text; + variable status : file_open_status; + variable runner_cfg_line : line; + + begin + if runner_cfg /= null_runner_cfg then + return runner_cfg; + end if; + + file_open(status, runner_cfg_fptr, "runner.cfg", read_mode); + assert status = OPEN_OK report "Failed to find runner configuration" severity failure; + readline(runner_cfg_fptr, runner_cfg_line); + return runner_cfg_line.all; + end; + + procedure test_runner_setup ( signal runner : inout runner_sync_t; constant runner_cfg : in string := runner_cfg_default) is + constant resolved_runner_cfg : string := resolve_runner_cfg(runner_cfg); variable test_case_candidates : lines_t; variable selected_enabled_test_cases : line; begin - -- fake active python runner key is only used during testing in tb_run.vhd -- to avoid creating vunit_results file set_active_python_runner(runner_state, - (active_python_runner(runner_cfg) and not has_key(runner_cfg, "fake active python runner"))); + (active_python_runner(resolved_runner_cfg) and not has_key(resolved_runner_cfg, "fake active python runner"))); + + if has_active_python_runner(runner_state) then + core_pkg.setup(output_path(resolved_runner_cfg) & "vunit_results"); + end if; if has_active_python_runner(runner_state) then - core_pkg.setup(output_path(runner_cfg) & "vunit_results"); hide(runner_trace_logger, display_handler, info); end if; - if has_key(runner_cfg, "use_color") and boolean'value(get(runner_cfg, "use_color")) then + if has_key(resolved_runner_cfg, "use_color") and boolean'value(get(resolved_runner_cfg, "use_color")) then enable_colors; end if; - set_string(run_db, "output_path", output_path(runner_cfg)); + set_string(run_db, "output_path", output_path(resolved_runner_cfg)); - if not active_python_runner(runner_cfg) then + if not active_python_runner(resolved_runner_cfg) then set_stop_level(failure); end if; @@ -59,14 +78,14 @@ package body run_pkg is deallocate(selected_enabled_test_cases); end if; - if has_key(runner_cfg, "enabled_test_cases") then - write(selected_enabled_test_cases, get(runner_cfg, "enabled_test_cases")); + if has_key(resolved_runner_cfg, "enabled_test_cases") then + write(selected_enabled_test_cases, get(resolved_runner_cfg, "enabled_test_cases")); else write(selected_enabled_test_cases, string'("__all__")); end if; test_case_candidates := split(replace(selected_enabled_test_cases.all, ",,", "__comma__"), ","); - set_cfg(runner_state, runner_cfg); + set_cfg(runner_state, resolved_runner_cfg); set_run_all(runner_state, strip(test_case_candidates(0).all) = "__all__"); if get_run_all(runner_state) then @@ -476,45 +495,41 @@ package body run_pkg is notify(runner(runner_phase_idx to runner_phase_idx + basic_event_length - 1)); end procedure exit_gate; - impure function active_python_runner( - constant runner_cfg : string) - return boolean is + impure function active_python_runner(constant runner_cfg : string) return boolean is + constant resolved_runner_cfg : string := resolve_runner_cfg(runner_cfg); begin - if has_key(runner_cfg, "active python runner") then - return get(runner_cfg, "active python runner") = "true"; + if has_key(resolved_runner_cfg, "active python runner") then + return get(resolved_runner_cfg, "active python runner") = "true"; else return false; end if; end; - impure function output_path( - constant runner_cfg : string) - return string is + impure function output_path(constant runner_cfg : string) return string is + constant resolved_runner_cfg : string := resolve_runner_cfg(runner_cfg); begin - if has_key(runner_cfg, "output path") then - return get(runner_cfg, "output path"); + if has_key(resolved_runner_cfg, "output path") then + return get(resolved_runner_cfg, "output path"); else return ""; end if; end; - impure function enabled_test_cases( - constant runner_cfg : string) - return test_cases_t is + impure function enabled_test_cases(constant runner_cfg : string) return test_cases_t is + constant resolved_runner_cfg : string := resolve_runner_cfg(runner_cfg); begin - if has_key(runner_cfg, "enabled_test_cases") then - return get(runner_cfg, "enabled_test_cases"); + if has_key(resolved_runner_cfg, "enabled_test_cases") then + return get(resolved_runner_cfg, "enabled_test_cases"); else return "__all__"; end if; end; - impure function tb_path( - constant runner_cfg : string) - return string is + impure function tb_path(constant runner_cfg : string) return string is + constant resolved_runner_cfg : string := resolve_runner_cfg(runner_cfg); begin - if has_key(runner_cfg, "tb path") then - return get(runner_cfg, "tb path"); + if has_key(resolved_runner_cfg, "tb path") then + return get(resolved_runner_cfg, "tb path"); else return ""; end if; diff --git a/vunit/vhdl/run/src/run_types.vhd b/vunit/vhdl/run/src/run_types.vhd index 247537257..58c5b95fd 100644 --- a/vunit/vhdl/run/src/run_types.vhd +++ b/vunit/vhdl/run/src/run_types.vhd @@ -23,6 +23,7 @@ package run_types_pkg is subtype runner_cfg_t is string; -- Subtype deprecated, use string instead constant runner_cfg_default : string := "enabled_test_cases : __all__, output path : , active python runner : false"; + constant null_runner_cfg : string := "is_null : true"; subtype test_cases_t is string; type runner_phase_t is (test_runner_entry, test_runner_setup, test_suite_setup, test_case_setup, diff --git a/vunit/vhdl/run/test/run_tests.vhd b/vunit/vhdl/run/test/run_tests.vhd index 4a24c1910..9ac393c78 100644 --- a/vunit/vhdl/run/test/run_tests.vhd +++ b/vunit/vhdl/run/test/run_tests.vhd @@ -206,6 +206,8 @@ begin constant test_case_setup_entry_key : key_t := get_entry_key(test_case_setup); constant test_case_setup_exit_key : key_t := get_exit_key(test_case_setup); + file runner_cfg_fptr : text; + variable runner_cfg_line : line; begin banner("Should extract single enabled test case from input string"); test_case_setup; @@ -297,6 +299,23 @@ begin check(c, enabled("Should three"), "Expected ""Should three"" test case to be enabled"); test_case_cleanup; + --------------------------------------------------------------------------- + banner("Should read from file on null"); + test_case_setup; + file_open(runner_cfg_fptr, "runner.cfg", write_mode); + write( + runner_cfg_line, + string'("active python runner : false,enabled_test_cases : __all__,output path : path1,tb path : path2") + ); + writeline(runner_cfg_fptr, runner_cfg_line); + file_close(runner_cfg_fptr); + test_runner_setup(runner, null_runner_cfg); + check_false(c, active_python_runner(null_runner_cfg)); + check(c, vunit_lib.run_pkg.output_path(null_runner_cfg) = "path1"); + check(c, enabled_test_cases(null_runner_cfg) = "__all__"); + check(c, tb_path(null_runner_cfg) = "path2"); + test_case_cleanup; + --------------------------------------------------------------------------- banner("Mocked logger should cause failure on test_runner_cleanup"); test_case_setup; From 1279d49e28ca380534b2cf54693bf13f79cbc9ab Mon Sep 17 00:00:00 2001 From: Lars Asplund Date: Mon, 27 Dec 2021 09:38:12 +0100 Subject: [PATCH 02/13] Write runner_cfg to file. --- .../vhdl/tb_reading_runner_cfg_from_file.vhd | 13 +------------ vunit/sim_if/__init__.py | 3 +++ vunit/sim_if/activehdl.py | 17 ++++++++++++++++- vunit/sim_if/ghdl.py | 3 +++ vunit/sim_if/incisive.py | 3 +++ vunit/sim_if/vsim_simulator_mixin.py | 3 +++ vunit/test/suites.py | 5 +++++ 7 files changed, 34 insertions(+), 13 deletions(-) diff --git a/tests/acceptance/artificial/vhdl/tb_reading_runner_cfg_from_file.vhd b/tests/acceptance/artificial/vhdl/tb_reading_runner_cfg_from_file.vhd index a8e68cf42..49aece187 100644 --- a/tests/acceptance/artificial/vhdl/tb_reading_runner_cfg_from_file.vhd +++ b/tests/acceptance/artificial/vhdl/tb_reading_runner_cfg_from_file.vhd @@ -16,20 +16,9 @@ end entity; architecture tb of tb_reading_runner_cfg_from_file is begin test_runner : process - file runner_cfg_fptr : text; - variable runner_cfg_line : line; begin - file_open(runner_cfg_fptr, "runner.cfg", write_mode); - write( - runner_cfg_line, - string'("active python runner : true,enabled_test_cases : ,output path : " & - replace(output_path(runner_cfg), ":", "::") & ",tb path : " & - replace(tb_path(runner_cfg), ":", "::") & ",use_color : true") - ); - writeline(runner_cfg_fptr, runner_cfg_line); - file_close(runner_cfg_fptr); - test_runner_setup(runner, null_runner_cfg); + check_true(active_python_runner(null_runner_cfg)); check(output_path(null_runner_cfg) /= ""); check(enabled_test_cases(null_runner_cfg) /= "__all__"); diff --git a/vunit/sim_if/__init__.py b/vunit/sim_if/__init__.py index 836fbea99..7a571caa2 100644 --- a/vunit/sim_if/__init__.py +++ b/vunit/sim_if/__init__.py @@ -215,6 +215,9 @@ def simulate(self, output_path, test_suite_name, config, elaborate_only): Simulate """ + def get_simulator_output_path(self, output_path): + "Get current working directory for simulation" + def setup_library_mapping(self, project): """ Implemented by specific simulators diff --git a/vunit/sim_if/activehdl.py b/vunit/sim_if/activehdl.py index d73f6d175..dd9be1668 100644 --- a/vunit/sim_if/activehdl.py +++ b/vunit/sim_if/activehdl.py @@ -375,6 +375,9 @@ def _create_gui_script(self, common_file_name, config): for library in self._libraries: 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 += "vunit_load\n" init_file = config.sim_options.get(self.name + ".init_file.gui", None) @@ -428,11 +431,23 @@ def simulate(self, output_path, test_suite_name, config, elaborate_only): if self._gui: gui_path = str(script_path / "gui") - renew_path(gui_path) + if (script_path / "gui" / "runner.cfg").exists(): + runner_cfg = (script_path / "gui" / "runner.cfg").read_text() + renew_path(gui_path) + (script_path / "gui" / "runner.cfg").write_text(runner_cfg) + else: + renew_path(gui_path) + return self._run_batch_file(str(gui_file_name), gui=True, cwd=gui_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 + @total_ordering class Version(object): diff --git a/vunit/sim_if/ghdl.py b/vunit/sim_if/ghdl.py index 13f36f7d1..650c9a8c8 100644 --- a/vunit/sim_if/ghdl.py +++ b/vunit/sim_if/ghdl.py @@ -363,6 +363,9 @@ def simulate(self, output_path, test_suite_name, config, elaborate_only): # pyl 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 ede8fce32..db56f0fa4 100644 --- a/vunit/sim_if/incisive.py +++ b/vunit/sim_if/incisive.py @@ -347,6 +347,9 @@ def simulate(self, output_path, test_suite_name, config, elaborate_only=False): 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/vsim_simulator_mixin.py b/vunit/sim_if/vsim_simulator_mixin.py index a461789db..5a35c5138 100644 --- a/vunit/sim_if/vsim_simulator_mixin.py +++ b/vunit/sim_if/vsim_simulator_mixin.py @@ -331,6 +331,9 @@ def simulate(self, output_path, test_suite_name, config, elaborate_only): return self._run_batch_file(str(batch_file_name)) + def get_simulator_output_path(self, output_path): + return Path(self._sim_cfg_file_name).parent + def fix_path(path): """ diff --git a/vunit/test/suites.py b/vunit/test/suites.py index cd650a480..ec9205aa5 100644 --- a/vunit/test/suites.py +++ b/vunit/test/suites.py @@ -231,6 +231,11 @@ def _simulate(self, output_path): "tb path": config.tb_path.replace("\\", "/") + "/", } + # TODO: remove file after? + simulator_output_path = self._simulator_if.get_simulator_output_path(output_path) / "runner.cfg" + simulator_output_path.parent.mkdir(parents=True, exist_ok=True) + simulator_output_path.write_text(encode_dict(runner_cfg)) + # @TODO Warn if runner cfg already set? config.generics["runner_cfg"] = encode_dict(runner_cfg) From 98eb4a47ce164731b16f17da901d9e7c3725a8d2 Mon Sep 17 00:00:00 2001 From: Lars Asplund Date: Mon, 27 Dec 2021 18:58:08 +0100 Subject: [PATCH 03/13] Added support for manual addition of VHDL configurations. --- tests/acceptance/artificial/vhdl/cfg1.vhd | 18 +++++++ tests/acceptance/artificial/vhdl/cfg2.vhd | 18 +++++++ tests/acceptance/artificial/vhdl/ent.vhd | 9 ++++ tests/acceptance/artificial/vhdl/run.py | 17 ++++++ .../vhdl/tb_reading_runner_cfg_from_file.vhd | 30 ----------- .../vhdl/tb_with_vhdl_configuration.vhd | 52 +++++++++++++++++++ tests/acceptance/test_artificial.py | 6 ++- tests/unit/test_configuration.py | 9 +++- tests/unit/test_incisive_interface.py | 18 +++++++ tests/unit/test_test_suites.py | 42 +++++++++++++++ vunit/configuration.py | 28 +++++++++- vunit/sim_if/activehdl.py | 13 +++-- vunit/sim_if/ghdl.py | 6 ++- vunit/sim_if/incisive.py | 15 +++++- vunit/sim_if/modelsim.py | 8 ++- vunit/sim_if/rivierapro.py | 15 ++++-- vunit/sim_if/vsim_simulator_mixin.py | 2 +- vunit/test/suites.py | 30 ++++++----- vunit/ui/testbench.py | 2 + 19 files changed, 278 insertions(+), 60 deletions(-) create mode 100644 tests/acceptance/artificial/vhdl/cfg1.vhd create mode 100644 tests/acceptance/artificial/vhdl/cfg2.vhd create mode 100644 tests/acceptance/artificial/vhdl/ent.vhd delete mode 100644 tests/acceptance/artificial/vhdl/tb_reading_runner_cfg_from_file.vhd create mode 100644 tests/acceptance/artificial/vhdl/tb_with_vhdl_configuration.vhd diff --git a/tests/acceptance/artificial/vhdl/cfg1.vhd b/tests/acceptance/artificial/vhdl/cfg1.vhd new file mode 100644 index 000000000..da4fbdcb5 --- /dev/null +++ b/tests/acceptance/artificial/vhdl/cfg1.vhd @@ -0,0 +1,18 @@ +-- This Source Code Form is subject to the terms of the Mozilla Public +-- License, v. 2.0. If a copy of the MPL was not distributed with this file, +-- You can obtain one at http://mozilla.org/MPL/2.0/. +-- +-- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com + +architecture arch1 of ent is +begin + arch <= "arch1"; +end; + +configuration cfg1 of tb_with_vhdl_configuration is + for tb + for ent_inst : ent + use entity work.ent(arch1); + end for; + end for; +end; \ No newline at end of file diff --git a/tests/acceptance/artificial/vhdl/cfg2.vhd b/tests/acceptance/artificial/vhdl/cfg2.vhd new file mode 100644 index 000000000..45f4f537a --- /dev/null +++ b/tests/acceptance/artificial/vhdl/cfg2.vhd @@ -0,0 +1,18 @@ +-- This Source Code Form is subject to the terms of the Mozilla Public +-- License, v. 2.0. If a copy of the MPL was not distributed with this file, +-- You can obtain one at http://mozilla.org/MPL/2.0/. +-- +-- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com + +architecture arch2 of ent is +begin + arch <= "arch2"; +end; + +configuration cfg2 of tb_with_vhdl_configuration is + for tb + for ent_inst : ent + use entity work.ent(arch2); + end for; + end for; +end; \ No newline at end of file diff --git a/tests/acceptance/artificial/vhdl/ent.vhd b/tests/acceptance/artificial/vhdl/ent.vhd new file mode 100644 index 000000000..3dcfeb634 --- /dev/null +++ b/tests/acceptance/artificial/vhdl/ent.vhd @@ -0,0 +1,9 @@ +-- This Source Code Form is subject to the terms of the Mozilla Public +-- License, v. 2.0. If a copy of the MPL was not distributed with this file, +-- You can obtain one at http://mozilla.org/MPL/2.0/. +-- +-- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com + +entity ent is + port(arch : out string(1 to 5)); +end; \ No newline at end of file diff --git a/tests/acceptance/artificial/vhdl/run.py b/tests/acceptance/artificial/vhdl/run.py index b0699e7fa..544fd5086 100644 --- a/tests/acceptance/artificial/vhdl/run.py +++ b/tests/acceptance/artificial/vhdl/run.py @@ -94,10 +94,27 @@ def configure_tb_assert_stop_level(ui): test.set_sim_option("vhdl_assert_stop_level", vhdl_assert_stop_level) +def configure_tb_with_vhdl_configuration(ui): + def make_post_check(expected_arch): + def post_check(output_path): + arch = (Path(output_path) / "result.txt").read_text() + if arch[:-1] != expected_arch: + raise RuntimeError(f"Error! Got {arch[: -1]}, expected {expected_arch}") + + return True + + return post_check + + tb = ui.library("lib").entity("tb_with_vhdl_configuration") + tb.add_config(name="cfg1", post_check=make_post_check("arch1"), vhdl_configuration_name="cfg1") + tb.add_config(name="cfg2", post_check=make_post_check("arch2"), vhdl_configuration_name="cfg2") + + configure_tb_with_generic_config() configure_tb_same_sim_all_pass(vu) configure_tb_set_generic(vu) configure_tb_assert_stop_level(vu) +configure_tb_with_vhdl_configuration(vu) lib.entity("tb_no_generic_override").set_generic("g_val", False) lib.entity("tb_ieee_warning").test("pass").set_sim_option("disable_ieee_warnings", True) lib.entity("tb_other_file_tests").scan_tests_from_file(str(root / "other_file_tests.vhd")) diff --git a/tests/acceptance/artificial/vhdl/tb_reading_runner_cfg_from_file.vhd b/tests/acceptance/artificial/vhdl/tb_reading_runner_cfg_from_file.vhd deleted file mode 100644 index 49aece187..000000000 --- a/tests/acceptance/artificial/vhdl/tb_reading_runner_cfg_from_file.vhd +++ /dev/null @@ -1,30 +0,0 @@ --- This Source Code Form is subject to the terms of the Mozilla Public --- License, v. 2.0. If a copy of the MPL was not distributed with this file, --- You can obtain one at http://mozilla.org/MPL/2.0/. --- --- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com - -library vunit_lib; -context vunit_lib.vunit_context; - -use std.textio.all; - -entity tb_reading_runner_cfg_from_file is - generic(runner_cfg : string); -end entity; - -architecture tb of tb_reading_runner_cfg_from_file is -begin - test_runner : process - begin - test_runner_setup(runner, null_runner_cfg); - - check_true(active_python_runner(null_runner_cfg)); - check(output_path(null_runner_cfg) /= ""); - check(enabled_test_cases(null_runner_cfg) /= "__all__"); - check(tb_path(null_runner_cfg) /= ""); - - test_runner_cleanup(runner); - wait; - end process; -end architecture; diff --git a/tests/acceptance/artificial/vhdl/tb_with_vhdl_configuration.vhd b/tests/acceptance/artificial/vhdl/tb_with_vhdl_configuration.vhd new file mode 100644 index 000000000..5dc7dfd0f --- /dev/null +++ b/tests/acceptance/artificial/vhdl/tb_with_vhdl_configuration.vhd @@ -0,0 +1,52 @@ +-- This Source Code Form is subject to the terms of the Mozilla Public +-- License, v. 2.0. If a copy of the MPL was not distributed with this file, +-- You can obtain one at http://mozilla.org/MPL/2.0/. +-- +-- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com + +library vunit_lib; +context vunit_lib.vunit_context; + +use std.textio.all; + +entity tb_with_vhdl_configuration is + generic(runner_cfg : string := null_runner_cfg); +end entity; + +architecture tb of tb_with_vhdl_configuration is + component ent + port(arch : out string(1 to 5)); + end component; + + signal arch : string(1 to 5); + +begin + test_runner : process + file result_fptr : text; + variable result_line : line; + begin + test_runner_setup(runner, runner_cfg); + + check_equal(runner_cfg, null_runner_cfg); + check_true(active_python_runner(runner_cfg)); + check(output_path(runner_cfg) /= ""); + check(enabled_test_cases(runner_cfg) /= "__all__"); + check(tb_path(runner_cfg) /= ""); + + file_open(result_fptr, join(output_path(runner_cfg), "result.txt"), write_mode); + write(result_line, arch); + writeline(result_fptr, result_line); + file_close(result_fptr); + + info(arch); + + test_runner_cleanup(runner); + wait; + end process; + + ent_inst : ent + port map( + arch => arch + ); + +end architecture; diff --git a/tests/acceptance/test_artificial.py b/tests/acceptance/test_artificial.py index 0895f613b..8ac0b0217 100644 --- a/tests/acceptance/test_artificial.py +++ b/tests/acceptance/test_artificial.py @@ -247,6 +247,10 @@ def test_exit_0_flag(self): ), ( "passed", - "lib.tb_reading_runner_cfg_from_file.all", + "lib.tb_with_vhdl_configuration.cfg1", + ), + ( + "passed", + "lib.tb_with_vhdl_configuration.cfg2", ), ) diff --git a/tests/unit/test_configuration.py b/tests/unit/test_configuration.py index 8d9b822bf..6f4831573 100644 --- a/tests/unit/test_configuration.py +++ b/tests/unit/test_configuration.py @@ -17,7 +17,7 @@ from unittest import mock from tests.common import with_tempdir, create_tempdir from tests.unit.test_test_bench import Entity -from vunit.configuration import Configuration, AttributeException +from vunit.configuration import Configuration, AttributeException, GenericAndVHDLConfigurationException class TestConfiguration(unittest.TestCase): @@ -45,6 +45,13 @@ def test_error_on_setting_illegal_value_sim_option(self): with _create_config() as config: self.assertRaises(ValueError, config.set_sim_option, "vhdl_assert_stop_level", "illegal") + def test_error_on_both_generics_and_vhdl_configuration(self): + with _create_config(vhdl_configuration_name="cfg") as config: + self.assertRaises(GenericAndVHDLConfigurationException, config.set_generic, "foo", "bar") + + with _create_config(generics=dict(foo=17)) as config: + self.assertRaises(GenericAndVHDLConfigurationException, config.set_vhdl_configuration_name, "bar") + def test_sim_option_is_not_mutated(self): with _create_config() as config: options = ["--foo"] diff --git a/tests/unit/test_incisive_interface.py b/tests/unit/test_incisive_interface.py index fe53217ea..ffbf5b72c 100644 --- a/tests/unit/test_incisive_interface.py +++ b/tests/unit/test_incisive_interface.py @@ -21,6 +21,8 @@ from vunit.ostools import renew_path, write_file, read_file from vunit.test.bench import Configuration from vunit.vhdl_standard import VHDL +from tests.common import create_tempdir +from tests.unit.test_test_bench import Entity class TestIncisiveInterface(unittest.TestCase): @@ -956,6 +958,21 @@ def test_simulate_gui(self, run_command, find_cds_root_irun, find_cds_root_virtu ], ) + @mock.patch("vunit.sim_if.incisive.IncisiveInterface.find_cds_root_virtuoso") + @mock.patch("vunit.sim_if.incisive.IncisiveInterface.find_cds_root_irun") + def test_configuration_and_entity_selection(self, find_cds_root_irun, find_cds_root_virtuoso): + find_cds_root_irun.return_value = "cds_root_irun" + find_cds_root_virtuoso.return_value = None + + with create_tempdir() as tempdir: + design_unit = Entity("tb_entity", file_name=str(Path(tempdir) / "file.vhd")) + design_unit.generic_names = ["runner_cfg"] + config = Configuration("name", design_unit, vhdl_configuration_name="cfg") + simif = IncisiveInterface(prefix="prefix", output_path=self.output_path) + self.assertEqual(simif._select_vhdl_top(config), "cfg") # pylint: disable=protected-access + config = Configuration("name", design_unit) + self.assertEqual(simif._select_vhdl_top(config), "lib.tb_entity:arch") # pylint: disable=protected-access + def setUp(self): self.output_path = str(Path(__file__).parent / "test_incisive_out") renew_path(self.output_path) @@ -985,4 +1002,5 @@ def make_config(sim_options=None, generics=None, verilog=False): cfg.sim_options = {} if sim_options is None else sim_options cfg.generics = {} if generics is None else generics + cfg.vhdl_configuration_name = None return cfg diff --git a/tests/unit/test_test_suites.py b/tests/unit/test_test_suites.py index f260e4d42..33ea11787 100644 --- a/tests/unit/test_test_suites.py +++ b/tests/unit/test_test_suites.py @@ -14,6 +14,9 @@ from vunit.test.suites import TestRun from vunit.test.report import PASSED, SKIPPED, FAILED from vunit.sim_if import SimulatorInterface +from vunit.test.bench import Configuration +from vunit.ostools import renew_path +from tests.unit.test_test_bench import Entity class TestTestSuites(TestCase): @@ -186,3 +189,42 @@ def func(): run._check_results(results, sim_ok), # pylint: disable=protected-access (waschecked, expected), ) + + 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) + + class TestSimIf(SimulatorInterface): + def __init__(self, output_path, gui, expect_runner_cfg_generic): + super().__init__(output_path, gui) + self._expect_runner_cfg_generic = expect_runner_cfg_generic + + def simulate(self, output_path, test_suite_name, config, elaborate_only): + tc = TestCase() + if self._expect_runner_cfg_generic: + tc.assertIn("runner_cfg", config.generics) + tc.assertFalse((Path(output_path) / "test_sim" / "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" + + for expect_runner_cfg_generic in [False, True]: + config = Configuration( + "name", design_unit, vhdl_configuration_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 diff --git a/vunit/configuration.py b/vunit/configuration.py index bfccbfe2d..663aeec90 100644 --- a/vunit/configuration.py +++ b/vunit/configuration.py @@ -25,6 +25,10 @@ class AttributeException(Exception): pass +class GenericAndVHDLConfigurationException(Exception): + pass + + class Configuration(object): # pylint: disable=too-many-instance-attributes """ Represents a configuration of a test bench @@ -39,12 +43,14 @@ def __init__( # pylint: disable=too-many-arguments pre_config=None, post_check=None, attributes=None, + vhdl_configuration_name=None, ): self.name = name self._design_unit = design_unit self.generics = {} if generics is None else generics self.sim_options = {} if sim_options is None else sim_options self.attributes = {} if attributes is None else attributes + self.vhdl_configuration_name = vhdl_configuration_name self.tb_path = str(Path(design_unit.original_file_name).parent) @@ -64,6 +70,7 @@ def copy(self): pre_config=self.pre_config, post_check=self.post_check, attributes=self.attributes.copy(), + vhdl_configuration_name=self.vhdl_configuration_name, ) @property @@ -102,10 +109,21 @@ def set_attribute(self, name, value): else: raise AttributeException + def set_vhdl_configuration_name(self, name): + """ + Set VHDL configuration name + """ + if self.generics: + raise GenericAndVHDLConfigurationException + + self.vhdl_configuration_name = name + def set_generic(self, name, value): """ Set generic """ + if self.vhdl_configuration_name: + raise GenericAndVHDLConfigurationException if name not in self._design_unit.generic_names: LOGGER.warning( "Generic '%s' set to value '%s' not found in %s '%s.%s'. Possible values are [%s]", @@ -240,7 +258,7 @@ def set_post_check(self, value): for config in configs.values(): config.post_check = value - def add_config( # pylint: disable=too-many-arguments + def add_config( # pylint: disable=too-many-arguments, too-many-branches self, name, generics=None, @@ -248,6 +266,7 @@ def add_config( # pylint: disable=too-many-arguments post_check=None, sim_options=None, attributes=None, + vhdl_configuration_name=None, ): """ Add a configuration copying unset fields from the default configuration: @@ -272,6 +291,8 @@ def add_config( # pylint: disable=too-many-arguments config.post_check = post_check if generics is not None: + if config.vhdl_configuration_name: + raise GenericAndVHDLConfigurationException config.generics.update(generics) if sim_options is not None: @@ -283,4 +304,9 @@ def add_config( # pylint: disable=too-many-arguments raise AttributeException config.attributes.update(attributes) + if vhdl_configuration_name is not None: + if config.generics: + raise GenericAndVHDLConfigurationException + config.vhdl_configuration_name = vhdl_configuration_name + configs[config.name] = config diff --git a/vunit/sim_if/activehdl.py b/vunit/sim_if/activehdl.py index dd9be1668..4ad8cc4ae 100644 --- a/vunit/sim_if/activehdl.py +++ b/vunit/sim_if/activehdl.py @@ -239,11 +239,14 @@ def _create_load_function(self, config, output_path): set_generic_name_str, "-lib", config.library_name, - config.entity_name, ] - if config.architecture_name is not None: - vsim_flags.append(config.architecture_name) + if config.vhdl_configuration_name is None: + vsim_flags.append(config.entity_name) + if config.architecture_name is not None: + vsim_flags.append(config.architecture_name) + else: + vsim_flags.append(config.vhdl_configuration_name) if config.sim_options.get("enable_coverage", False): coverage_file_path = str(Path(output_path) / "coverage.acdb") @@ -432,9 +435,9 @@ def simulate(self, output_path, test_suite_name, config, 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() + 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) + (script_path / "gui" / "runner.cfg").write_text(runner_cfg, encoding="utf-8") else: renew_path(gui_path) diff --git a/vunit/sim_if/ghdl.py b/vunit/sim_if/ghdl.py index 650c9a8c8..39435ed84 100644 --- a/vunit/sim_if/ghdl.py +++ b/vunit/sim_if/ghdl.py @@ -279,7 +279,11 @@ def _get_command(self, config, output_path, elaborate_only, ghdl_e, wave_file): if config.sim_options.get("enable_coverage", False): # Enable coverage in linker cmd += ["-Wl,-lgcov"] - cmd += [config.entity_name, config.architecture_name] + + if config.vhdl_configuration_name is not None: + cmd += [config.vhdl_configuration_name] + else: + cmd += [config.entity_name, config.architecture_name] sim = config.sim_options.get("ghdl.sim_flags", []) for name, value in config.generics.items(): diff --git a/vunit/sim_if/incisive.py b/vunit/sim_if/incisive.py index db56f0fa4..4d06b2da0 100644 --- a/vunit/sim_if/incisive.py +++ b/vunit/sim_if/incisive.py @@ -277,7 +277,17 @@ def _get_mapped_libraries(self): cds = CDSFile.parse(self._cdslib) return cds - def simulate(self, output_path, test_suite_name, config, elaborate_only=False): # pylint: disable=too-many-locals + @staticmethod + def _select_vhdl_top(config): + "Select VHDL configuration or entity as top." + if config.vhdl_configuration_name is None: + return f"{config.library_name!s}.{config.entity_name!s}:{config.architecture_name!s}" + + return f"{config.vhdl_configuration_name!s}" + + def simulate( + self, 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 """ @@ -336,7 +346,8 @@ def simulate(self, output_path, test_suite_name, config, elaborate_only=False): args += [f"-top {config.library_name!s}.{config.entity_name!s}:sv"] else: # we have a VHDL toplevel: - args += [f"-top {config.library_name!s}.{config.entity_name!s}:{config.architecture_name!s}"] + args += [f"-top {self._select_vhdl_top(config)}"] + argsfile = f"{script_path!s}/irun_{step!s}.args" write_file(argsfile, "\n".join(args)) if not run_command( diff --git a/vunit/sim_if/modelsim.py b/vunit/sim_if/modelsim.py index 0acd589c3..c2fd3d309 100644 --- a/vunit/sim_if/modelsim.py +++ b/vunit/sim_if/modelsim.py @@ -258,6 +258,12 @@ def _create_load_function(self, test_suite_name, config, output_path): coverage_save_cmd = "" coverage_args = "" + simulation_target = ( + config.library_name + "." + config.entity_name + architecture_suffix + if config.vhdl_configuration_name is None + else config.library_name + "." + config.vhdl_configuration_name + ) + vsim_flags = [ f"-wlf {{{fix_path(str(Path(output_path) / 'vsim.wlf'))!s}}}", "-quiet", @@ -266,7 +272,7 @@ def _create_load_function(self, test_suite_name, config, output_path): "-onfinish stop", pli_str, set_generic_str, - config.library_name + "." + config.entity_name + architecture_suffix, + simulation_target, coverage_args, self._vsim_extra_args(config), ] diff --git a/vunit/sim_if/rivierapro.py b/vunit/sim_if/rivierapro.py index cde7dd85b..07015762c 100644 --- a/vunit/sim_if/rivierapro.py +++ b/vunit/sim_if/rivierapro.py @@ -306,12 +306,17 @@ def _create_load_function(self, test_suite_name, config, output_path): # pylint if config.sim_options.get("disable_ieee_warnings", False): vsim_flags.append("-ieee_nowarn") - # Add the the testbench top-level unit last as coverage is - # only collected for the top-level unit specified last - vsim_flags += ["-lib", config.library_name, config.entity_name] + vsim_flags += ["-lib", config.library_name] - if config.architecture_name is not None: - vsim_flags.append(config.architecture_name) + if config.vhdl_configuration_name is None: + # Add the the testbench top-level unit last as coverage is + # only collected for the top-level unit specified last + vsim_flags += [config.entity_name] + + if config.architecture_name is not None: + vsim_flags.append(config.architecture_name) + else: + vsim_flags += [config.vhdl_configuration_name] tcl = """ proc vunit_load {{}} {{ diff --git a/vunit/sim_if/vsim_simulator_mixin.py b/vunit/sim_if/vsim_simulator_mixin.py index 5a35c5138..e2c1fbc94 100644 --- a/vunit/sim_if/vsim_simulator_mixin.py +++ b/vunit/sim_if/vsim_simulator_mixin.py @@ -331,7 +331,7 @@ def simulate(self, output_path, test_suite_name, config, elaborate_only): return self._run_batch_file(str(batch_file_name)) - def get_simulator_output_path(self, output_path): + def get_simulator_output_path(self, output_path): # pylint: disable=unused-argument return Path(self._sim_cfg_file_name).parent diff --git a/vunit/test/suites.py b/vunit/test/suites.py index ec9205aa5..c058f65db 100644 --- a/vunit/test/suites.py +++ b/vunit/test/suites.py @@ -231,20 +231,26 @@ def _simulate(self, output_path): "tb path": config.tb_path.replace("\\", "/") + "/", } - # TODO: remove file after? simulator_output_path = self._simulator_if.get_simulator_output_path(output_path) / "runner.cfg" - simulator_output_path.parent.mkdir(parents=True, exist_ok=True) - simulator_output_path.write_text(encode_dict(runner_cfg)) - - # @TODO Warn if runner cfg already set? - config.generics["runner_cfg"] = encode_dict(runner_cfg) + if config.vhdl_configuration_name is not None: + simulator_output_path.parent.mkdir(parents=True, exist_ok=True) + simulator_output_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 self._simulator_if.simulate( - output_path=output_path, - test_suite_name=self._test_suite_name, - config=config, - elaborate_only=self._elaborate_only, - ) + return return_value def _read_test_results(self, file_name): # pylint: disable=too-many-branches """ diff --git a/vunit/ui/testbench.py b/vunit/ui/testbench.py index 3ca643063..efb2fc7de 100644 --- a/vunit/ui/testbench.py +++ b/vunit/ui/testbench.py @@ -130,6 +130,7 @@ def add_config( # pylint: disable=too-many-arguments post_check=None, sim_options=None, attributes=None, + vhdl_configuration_name=None, ): """ Add a configuration of this test bench or to all test cases within it by copying the default configuration. @@ -181,6 +182,7 @@ def add_config( # pylint: disable=too-many-arguments post_check=post_check, sim_options=sim_options, attributes=attributes, + vhdl_configuration_name=vhdl_configuration_name, ) def test(self, name): From 2acadcff129dad53604d0f44162acacc78945241 Mon Sep 17 00:00:00 2001 From: Lars Asplund Date: Wed, 29 Dec 2021 23:36:39 +0100 Subject: [PATCH 04/13] Added support for automatic discovery of testbench VHDL configurations. --- tests/acceptance/artificial/vhdl/arch1.vhd | 10 ++++++++++ tests/acceptance/artificial/vhdl/cfg1.vhd | 5 ----- tests/acceptance/artificial/vhdl/run.py | 17 ----------------- vunit/design_unit.py | 20 ++++++++++++++++++++ vunit/source_file.py | 4 ++-- vunit/test/bench_list.py | 22 ++++++++++++++++++++-- vunit/ui/testbench.py | 2 -- vunit/vhdl_parser.py | 2 +- 8 files changed, 53 insertions(+), 29 deletions(-) create mode 100644 tests/acceptance/artificial/vhdl/arch1.vhd diff --git a/tests/acceptance/artificial/vhdl/arch1.vhd b/tests/acceptance/artificial/vhdl/arch1.vhd new file mode 100644 index 000000000..da724ba0f --- /dev/null +++ b/tests/acceptance/artificial/vhdl/arch1.vhd @@ -0,0 +1,10 @@ +-- This Source Code Form is subject to the terms of the Mozilla Public +-- License, v. 2.0. If a copy of the MPL was not distributed with this file, +-- You can obtain one at http://mozilla.org/MPL/2.0/. +-- +-- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com + +architecture arch1 of ent is +begin + arch <= "arch1"; +end; diff --git a/tests/acceptance/artificial/vhdl/cfg1.vhd b/tests/acceptance/artificial/vhdl/cfg1.vhd index da4fbdcb5..1796c57ec 100644 --- a/tests/acceptance/artificial/vhdl/cfg1.vhd +++ b/tests/acceptance/artificial/vhdl/cfg1.vhd @@ -4,11 +4,6 @@ -- -- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com -architecture arch1 of ent is -begin - arch <= "arch1"; -end; - configuration cfg1 of tb_with_vhdl_configuration is for tb for ent_inst : ent diff --git a/tests/acceptance/artificial/vhdl/run.py b/tests/acceptance/artificial/vhdl/run.py index 544fd5086..b0699e7fa 100644 --- a/tests/acceptance/artificial/vhdl/run.py +++ b/tests/acceptance/artificial/vhdl/run.py @@ -94,27 +94,10 @@ def configure_tb_assert_stop_level(ui): test.set_sim_option("vhdl_assert_stop_level", vhdl_assert_stop_level) -def configure_tb_with_vhdl_configuration(ui): - def make_post_check(expected_arch): - def post_check(output_path): - arch = (Path(output_path) / "result.txt").read_text() - if arch[:-1] != expected_arch: - raise RuntimeError(f"Error! Got {arch[: -1]}, expected {expected_arch}") - - return True - - return post_check - - tb = ui.library("lib").entity("tb_with_vhdl_configuration") - tb.add_config(name="cfg1", post_check=make_post_check("arch1"), vhdl_configuration_name="cfg1") - tb.add_config(name="cfg2", post_check=make_post_check("arch2"), vhdl_configuration_name="cfg2") - - configure_tb_with_generic_config() configure_tb_same_sim_all_pass(vu) configure_tb_set_generic(vu) configure_tb_assert_stop_level(vu) -configure_tb_with_vhdl_configuration(vu) lib.entity("tb_no_generic_override").set_generic("g_val", False) lib.entity("tb_ieee_warning").test("pass").set_sim_option("disable_ieee_warnings", True) lib.entity("tb_other_file_tests").scan_tests_from_file(str(root / "other_file_tests.vhd")) diff --git a/vunit/design_unit.py b/vunit/design_unit.py index e3080e86c..882c5d411 100644 --- a/vunit/design_unit.py +++ b/vunit/design_unit.py @@ -41,6 +41,10 @@ def is_entity(self): def is_module(self): return False + @property + def is_vhdl_configuration(self): + return False + class VHDLDesignUnit(DesignUnit): """ @@ -96,6 +100,22 @@ def is_entity(self): return True +class VHDLConfiguration(VHDLDesignUnit): + "Represents a VHDL configuration." + + def __init__(self, name, source_file, entity_name): + VHDLDesignUnit.__init__(self, name, source_file, "configuration", True) + self._entity_name = entity_name + + @property + def entity_name(self): + return self._entity_name + + @property + def is_vhdl_configuration(self): + return True + + class Module(DesignUnit): """ Represents a Verilog Module diff --git a/vunit/source_file.py b/vunit/source_file.py index 370bffcd2..c9cc971fa 100644 --- a/vunit/source_file.py +++ b/vunit/source_file.py @@ -17,7 +17,7 @@ from vunit.vhdl_parser import VHDLReference from vunit.cached import file_content_hash from vunit.parsing.encodings import HDL_FILE_ENCODING -from vunit.design_unit import DesignUnit, VHDLDesignUnit, Entity, Module +from vunit.design_unit import DesignUnit, VHDLDesignUnit, Entity, Module, VHDLConfiguration from vunit.vhdl_standard import VHDLStandard from vunit.library import Library @@ -317,7 +317,7 @@ def _find_design_units(self, design_file): ) for configuration in design_file.configurations: - result.append(VHDLDesignUnit(configuration.identifier, self, "configuration")) + result.append(VHDLConfiguration(configuration.identifier, self, configuration.entity)) for body in design_file.package_bodies: result.append(VHDLDesignUnit(body.identifier, self, "package body", False, body.identifier)) diff --git a/vunit/test/bench_list.py b/vunit/test/bench_list.py index 8cc13d035..5f40dd371 100644 --- a/vunit/test/bench_list.py +++ b/vunit/test/bench_list.py @@ -25,6 +25,7 @@ class TestBenchList(object): def __init__(self, database=None): self._libraries = OrderedDict() self._database = database + self._vhdl_configurations = {} def add_from_source_file(self, source_file): """ @@ -33,8 +34,25 @@ def add_from_source_file(self, source_file): for design_unit in source_file.design_units: if design_unit.is_entity or design_unit.is_module: if tb_filter is None or tb_filter(design_unit): - if design_unit.is_module or design_unit.is_entity: - self._add_test_bench(TestBench(design_unit, self._database)) + test_bench = TestBench(design_unit, self._database) + self._add_test_bench(test_bench) + if design_unit.is_entity: + if design_unit.name not in self._vhdl_configurations: + self._vhdl_configurations[design_unit.name] = dict(test_bench=None, configurations=[]) + + self._vhdl_configurations[design_unit.name]["test_bench"] = test_bench + for configuration in self._vhdl_configurations[design_unit.name]["configurations"]: + test_bench.add_config(name=configuration, vhdl_configuration_name=configuration) + + if design_unit.is_vhdl_configuration: + if design_unit.entity_name not in self._vhdl_configurations: + self._vhdl_configurations[design_unit.entity_name] = dict(test_bench=None, configurations=[]) + + self._vhdl_configurations[design_unit.entity_name]["configurations"].append(design_unit.name) + if self._vhdl_configurations[design_unit.entity_name]["test_bench"]: + self._vhdl_configurations[design_unit.entity_name]["test_bench"].add_config( + name=design_unit.name, vhdl_configuration_name=design_unit.name + ) def _add_test_bench(self, test_bench): """ diff --git a/vunit/ui/testbench.py b/vunit/ui/testbench.py index efb2fc7de..3ca643063 100644 --- a/vunit/ui/testbench.py +++ b/vunit/ui/testbench.py @@ -130,7 +130,6 @@ def add_config( # pylint: disable=too-many-arguments post_check=None, sim_options=None, attributes=None, - vhdl_configuration_name=None, ): """ Add a configuration of this test bench or to all test cases within it by copying the default configuration. @@ -182,7 +181,6 @@ def add_config( # pylint: disable=too-many-arguments post_check=post_check, sim_options=sim_options, attributes=attributes, - vhdl_configuration_name=vhdl_configuration_name, ) def test(self, name): diff --git a/vunit/vhdl_parser.py b/vunit/vhdl_parser.py index a987eae18..d27c94df5 100644 --- a/vunit/vhdl_parser.py +++ b/vunit/vhdl_parser.py @@ -125,7 +125,7 @@ def find(cls, code): class VHDLConfiguration(object): """ - A configuratio declaration + A configuration declaration """ def __init__(self, identifier, entity): From b158d5713848b4d1b1c95250444f6729d8e33748 Mon Sep 17 00:00:00 2001 From: Lars Asplund Date: Fri, 31 Dec 2021 19:59:29 +0100 Subject: [PATCH 05/13] Added support for modifying existing VUnit configurations. --- tests/acceptance/artificial/vhdl/run.py | 18 ++++++ .../vhdl/tb_with_vhdl_configuration.vhd | 8 ++- tests/acceptance/test_artificial.py | 12 +++- tests/unit/test_ui.py | 64 +++++++++++++++++++ vunit/configuration.py | 4 +- vunit/ui/configuration.py | 40 ++++++++++++ vunit/ui/test.py | 4 ++ vunit/ui/testbench.py | 4 ++ 8 files changed, 149 insertions(+), 5 deletions(-) create mode 100644 vunit/ui/configuration.py diff --git a/tests/acceptance/artificial/vhdl/run.py b/tests/acceptance/artificial/vhdl/run.py index b0699e7fa..ba48f439a 100644 --- a/tests/acceptance/artificial/vhdl/run.py +++ b/tests/acceptance/artificial/vhdl/run.py @@ -94,10 +94,28 @@ def configure_tb_assert_stop_level(ui): test.set_sim_option("vhdl_assert_stop_level", vhdl_assert_stop_level) +def configure_tb_with_vhdl_configuration(ui): + def make_post_check(expected_arch): + def post_check(output_path): + arch = (Path(output_path) / "result.txt").read_text() + if arch[:-1] != expected_arch: + raise RuntimeError(f"Error! Got {arch[: -1]}, expected {expected_arch}") + + return True + + return post_check + + tb = ui.library("lib").test_bench("tb_with_vhdl_configuration") + tb.get_configs("cfg*").set_post_check(make_post_check("arch1")) + tb.test("test1").get_configs("cfg2").set_post_check(make_post_check("arch2")) + tb.test("test2").get_configs("cfg2").set_post_check(make_post_check("arch2")) + + configure_tb_with_generic_config() configure_tb_same_sim_all_pass(vu) configure_tb_set_generic(vu) configure_tb_assert_stop_level(vu) +configure_tb_with_vhdl_configuration(vu) lib.entity("tb_no_generic_override").set_generic("g_val", False) lib.entity("tb_ieee_warning").test("pass").set_sim_option("disable_ieee_warnings", True) lib.entity("tb_other_file_tests").scan_tests_from_file(str(root / "other_file_tests.vhd")) diff --git a/tests/acceptance/artificial/vhdl/tb_with_vhdl_configuration.vhd b/tests/acceptance/artificial/vhdl/tb_with_vhdl_configuration.vhd index 5dc7dfd0f..5d8326c03 100644 --- a/tests/acceptance/artificial/vhdl/tb_with_vhdl_configuration.vhd +++ b/tests/acceptance/artificial/vhdl/tb_with_vhdl_configuration.vhd @@ -38,7 +38,13 @@ begin writeline(result_fptr, result_line); file_close(result_fptr); - info(arch); + while test_suite loop + if run("test1") then + null; + elsif run("test2") then + null; + end if; + end loop; test_runner_cleanup(runner); wait; diff --git a/tests/acceptance/test_artificial.py b/tests/acceptance/test_artificial.py index 8ac0b0217..5ff833dae 100644 --- a/tests/acceptance/test_artificial.py +++ b/tests/acceptance/test_artificial.py @@ -247,10 +247,18 @@ def test_exit_0_flag(self): ), ( "passed", - "lib.tb_with_vhdl_configuration.cfg1", + "lib.tb_with_vhdl_configuration.cfg1.test1", ), ( "passed", - "lib.tb_with_vhdl_configuration.cfg2", + "lib.tb_with_vhdl_configuration.cfg1.test2", + ), + ( + "passed", + "lib.tb_with_vhdl_configuration.cfg2.test1", + ), + ( + "passed", + "lib.tb_with_vhdl_configuration.cfg2.test2", ), ) diff --git a/tests/unit/test_ui.py b/tests/unit/test_ui.py index 78af13ad6..f9f784f8a 100644 --- a/tests/unit/test_ui.py +++ b/tests/unit/test_ui.py @@ -1216,6 +1216,70 @@ def test_set_sim_option_before_adding_file(self): method("ghdl.elab_flags", [], allow_empty=True) self.assertRaises(ValueError, method, "ghdl.elab_flags", []) + def test_get_configurations(self): + ui = self._create_ui() + lib = ui.add_library("lib") + file_name = self.create_entity_file() + lib.add_source_file(file_name) + self.create_file( + "tb_ent.vhd", + """ +entity tb_ent is + generic ( + runner_cfg : string; + a : integer := 0; + b : integer := 0 + ); +end entity; + +architecture a of tb_ent is +begin + main : process + begin + if run("test1") then + elsif run("test2") then + end if; + end process; +end architecture; + """, + ) + lib.add_source_file("tb_ent.vhd") + + tb = lib.test_bench("tb_ent") + tb.add_config("cfg1", generics=dict(a=1)) + test2 = tb.test("test2") + test2.add_config("cfg2", generics=dict(b=2)) + + cfgs = tb.get_configs("cfg*") + n_cfg1 = n_cfg2 = 0 + for cfg_dict in cfgs.get_configuration_dicts(): + for cfg_name, cfg in cfg_dict.items(): + if cfg_name == "cfg1": + self.assertEqual(cfg.generics["a"], 1) + n_cfg1 += 1 + else: + self.assertEqual(cfg_name, "cfg2") + self.assertEqual(cfg.generics["b"], 2) + n_cfg2 += 1 + + self.assertEqual(n_cfg1, 2) + self.assertEqual(n_cfg2, 1) + + cfgs = test2.get_configs("cfg2") + n_cfg1 = n_cfg2 = 0 + for cfg_dict in cfgs.get_configuration_dicts(): + for cfg_name, cfg in cfg_dict.items(): + if cfg_name == "cfg1": + self.assertEqual(cfg.generics["a"], 1) + n_cfg1 += 1 + else: + self.assertEqual(cfg_name, "cfg2") + self.assertEqual(cfg.generics["b"], 2) + n_cfg2 += 1 + + self.assertEqual(n_cfg1, 0) + self.assertEqual(n_cfg2, 1) + def test_get_testbench_files(self): ui = self._create_ui() lib = ui.add_library("lib") diff --git a/vunit/configuration.py b/vunit/configuration.py index 663aeec90..173623c34 100644 --- a/vunit/configuration.py +++ b/vunit/configuration.py @@ -114,7 +114,7 @@ def set_vhdl_configuration_name(self, name): Set VHDL configuration name """ if self.generics: - raise GenericAndVHDLConfigurationException + raise GenericAndVHDLConfigurationException("Generics can't be used with VHDL configurations.") self.vhdl_configuration_name = name @@ -123,7 +123,7 @@ def set_generic(self, name, value): Set generic """ if self.vhdl_configuration_name: - raise GenericAndVHDLConfigurationException + raise GenericAndVHDLConfigurationException("Generics can't be used with VHDL configurations.") if name not in self._design_unit.generic_names: LOGGER.warning( "Generic '%s' set to value '%s' not found in %s '%s.%s'. Possible values are [%s]", diff --git a/vunit/ui/configuration.py b/vunit/ui/configuration.py new file mode 100644 index 000000000..2e03b4dce --- /dev/null +++ b/vunit/ui/configuration.py @@ -0,0 +1,40 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. +# +# Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com + +""" +UI class Configuration +""" + +from fnmatch import fnmatch +from collections import OrderedDict +from vunit.configuration import ConfigurationVisitor, DEFAULT_NAME + + +class Configuration(ConfigurationVisitor): + """ + User interface for configuration sets. + Provides methods for modifying existing configurations + """ + + def __init__(self, test_object, pattern): + self._selected_cfg_dicts = [] + self._test_object = test_object + for cfg_dict in test_object.get_configuration_dicts(): + selected_cfg_dict = OrderedDict() + for name, cfg in cfg_dict.items(): + if fnmatch(name if name is not DEFAULT_NAME else "", pattern): + selected_cfg_dict[name] = cfg + + if selected_cfg_dict: + self._selected_cfg_dicts.append(selected_cfg_dict) + + def get_configuration_dicts(self): + return self._selected_cfg_dicts + + def add_config(self, *args, **kwargs): + raise RuntimeError( + f"{type(self)} do not allow addition of new configurations, only modification of existing ones." + ) diff --git a/vunit/ui/test.py b/vunit/ui/test.py index 16294a99c..a1c08ef31 100644 --- a/vunit/ui/test.py +++ b/vunit/ui/test.py @@ -9,6 +9,7 @@ """ from .common import lower_generics +from .configuration import Configuration class Test(object): @@ -88,6 +89,9 @@ def add_config( # pylint: disable=too-many-arguments attributes=attributes, ) + def get_configs(self, pattern="*"): + return Configuration(self._test_case, pattern) + def set_attribute(self, name, value): """ Set a value of attribute within all |configurations| of this test diff --git a/vunit/ui/testbench.py b/vunit/ui/testbench.py index 3ca643063..83fbc44e1 100644 --- a/vunit/ui/testbench.py +++ b/vunit/ui/testbench.py @@ -11,6 +11,7 @@ from fnmatch import fnmatch from .common import lower_generics from .test import Test +from .configuration import Configuration class TestBench(object): @@ -183,6 +184,9 @@ def add_config( # pylint: disable=too-many-arguments attributes=attributes, ) + def get_configs(self, pattern="*"): + return Configuration(self._test_bench, pattern) + def test(self, name): """ Get a test within this test bench From 14c12506ef979cf67a09c028599e489ec049e8b5 Mon Sep 17 00:00:00 2001 From: Lars Asplund Date: Sat, 1 Jan 2022 14:08:13 +0100 Subject: [PATCH 06/13] Added documentation for the handling of VHDL configurations. --- docs/py/ui.rst | 29 +++++++++++++++++++--------- docs/py/vunit.rst | 6 ++++++ docs/run/user_guide.rst | 40 +++++++++++++++++++++++++++++++++++++-- vunit/ui/configuration.py | 4 ++++ vunit/ui/test.py | 6 ++++++ vunit/ui/testbench.py | 6 ++++++ 6 files changed, 80 insertions(+), 11 deletions(-) diff --git a/docs/py/ui.rst b/docs/py/ui.rst index d8be79da0..6c8286d48 100644 --- a/docs/py/ui.rst +++ b/docs/py/ui.rst @@ -24,20 +24,31 @@ pre_config and post_check :ref:`callback functions `. User :ref:`attributes ` can also be added as a part of a configuration. -Configurations can either be unique for each test case or must be -common for the entire test bench depending on the situation. For test -benches without test such as `tb_example` in the User Guide the -configuration is common for the entire test bench. For test benches -containing tests such as `tb_example_many` the configuration is done -for each test case. If the ``run_all_in_same_sim`` attribute has been used, +VHDL configurations for test bench entities are automatically discovered +and added to the set of configurations created in Python. + +Python-defined configurations can either be unique for each test case or +must be common for the entire test bench depending on the situation. +For test benches without tests such as `tb_example` in the User Guide +the configuration is common for the entire test bench. For test benches +containing tests such as `tb_example_many` the configuration is done for +each test case. If the ``run_all_in_same_sim`` attribute has been used, configuration is performed at the test bench level even if there are individual tests within since they must run in the same simulation. -In a VUnit all test benches and test cases are created with an unnamed default +In VUnit all test benches and test cases are created with an unnamed default configuration which is modified by different methods such as ``set_generic`` etc. In addition to the unnamed default configuration multiple named configurations -can be derived from it by using the ``add_config`` method. The default -configuration is only run if there are no named configurations. +can be derived from it by using the ``add_config`` method or by defining a +VHDL configuration. The default configuration is only run if there are no +named configurations. + +Named configurations can be retrieved from test benches and tests using the +``get_configs`` methods and then modified using methods such as ``set_pre_config``. +This is especially useful for VHDL configurations which are automatically +detected and added without any of the configuration capabilities provided +by the Python API. Note that generics can't be added to a VHDL-defined +configuration as that is not allowed in VHDL. .. _attributes: diff --git a/docs/py/vunit.rst b/docs/py/vunit.rst index c26e240ac..27d2821cd 100644 --- a/docs/py/vunit.rst +++ b/docs/py/vunit.rst @@ -35,6 +35,12 @@ Test .. autoclass:: vunit.ui.test.Test() +Configuration +------------- + +.. autoclass:: vunit.ui.configuration.Configuration() + :inherited-members: + Results ------- diff --git a/docs/run/user_guide.rst b/docs/run/user_guide.rst index 2499b1cff..2f435c029 100644 --- a/docs/run/user_guide.rst +++ b/docs/run/user_guide.rst @@ -22,8 +22,7 @@ The code has the following important properties: * The VUnit functionality is located in the ``vunit_lib`` library and is included with the library and context statements in the first two lines. -* The ``runner_cfg`` generic is used to control the testbench from PR. If the VR is used standalone, you will - need a default value, ``runner_cfg_default``, for the generic. Note that the generic **must** be named +* The ``runner_cfg`` generic is used to control the testbench from PR. Note that the generic **must** be named ``runner_cfg`` for the testbench to be recognized by PR (there is an exception which we'll get to later). * Every VUnit testbench has a main controlling process. It's labelled ``test_runner`` in this example but the name is not important. The process starts by setting up VUnit using the ``test_runner_setup`` procedure with ``runner_cfg`` as @@ -262,6 +261,43 @@ it is possible to read out assert error counters and use ``check_equal`` to veri ``test_runner_cleanup``. Failures like division by zero or out of range operations are other examples that won't be handle gracefully in this mode, and not something that VHDL provides any solutions for. +Testbenches with VHDL Configurations +------------------------------------ + +If there are VHDL configurations defined for the testbench entity, each configuration, and not the testbench +entity, defines a top-level for the simulation. VUnit automatically detects these VHDL configurations and treat +them as a special case of the larger testbench configuration concept provided by VUnit, see +:ref:`configurations `. + +VHDL doesn't allow setting generics when the top-level is a VHDL configuration which prevents the `runner_cfg` +generic to be set by PR. To work around this limitation PR provides a `runner.cfg` file containing the same +information. This file is read by `test_runner_setup` whenever its `runner_cfg` parameter is set to +`null_runner_cfg`. An example is shown below. + +.. code-block:: vhdl + + library vunit_lib; + context vunit_lib.vunit_context; + + entity tb_minimal is + generic (runner_cfg : string := null_runner_cfg); + end entity; + + architecture tb of tb_minimal is + begin + test_runner : process + begin + test_runner_setup(runner, runner_cfg); + + ... + + test_runner_cleanup(runner); + end process; + end architecture; + +Note that the `runner_cfg` generic must remain present since this is the token used by VUnit test scanning +to distinguish testbench entities from other entities. + Special Paths ------------- diff --git a/vunit/ui/configuration.py b/vunit/ui/configuration.py index 2e03b4dce..c1f32252a 100644 --- a/vunit/ui/configuration.py +++ b/vunit/ui/configuration.py @@ -35,6 +35,10 @@ def get_configuration_dicts(self): return self._selected_cfg_dicts def add_config(self, *args, **kwargs): + """ + This method is inherited from the superclass but not defined for this class. New + configurations are added to :class:`.TestBench` or :class:`.Test` objects. + """ raise RuntimeError( f"{type(self)} do not allow addition of new configurations, only modification of existing ones." ) diff --git a/vunit/ui/test.py b/vunit/ui/test.py index a1c08ef31..d45da8218 100644 --- a/vunit/ui/test.py +++ b/vunit/ui/test.py @@ -90,6 +90,12 @@ def add_config( # pylint: disable=too-many-arguments ) def get_configs(self, pattern="*"): + """ + Get test configurations matching pattern. + + :param pattern: A wildcard pattern matching the configuration name(s). + :returns: A :class:`.Configuration` object + """ return Configuration(self._test_case, pattern) def set_attribute(self, name, value): diff --git a/vunit/ui/testbench.py b/vunit/ui/testbench.py index 83fbc44e1..3a54c177e 100644 --- a/vunit/ui/testbench.py +++ b/vunit/ui/testbench.py @@ -185,6 +185,12 @@ def add_config( # pylint: disable=too-many-arguments ) def get_configs(self, pattern="*"): + """ + Get test bench configurations matching pattern. + + :param pattern: A wildcard pattern matching the configuration name(s). + :returns: A :class:`.Configuration` object + """ return Configuration(self._test_bench, pattern) def test(self, name): From ad49d3b5a794721bd0d86c4cae23c6cb630934b4 Mon Sep 17 00:00:00 2001 From: Lars Asplund Date: Sat, 1 Jan 2022 23:06:29 +0100 Subject: [PATCH 07/13] Added VHDL configuration examples. --- examples/vhdl/vhdl_configuration/dff.vhd | 62 ++++++++++++ .../handling_generics_limitation/tb_reset.vhd | 59 ++++++++++++ ..._selecting_dut_with_generate_statement.vhd | 95 +++++++++++++++++++ .../tb_state_change.vhd | 65 +++++++++++++ .../test_fixture.vhd | 38 ++++++++ examples/vhdl/vhdl_configuration/run.py | 61 ++++++++++++ ..._selecting_dut_with_vhdl_configuration.vhd | 92 ++++++++++++++++++ ...ng_test_runner_with_vhdl_configuration.vhd | 78 +++++++++++++++ .../vhdl/vhdl_configuration/test_reset.vhd | 37 ++++++++ .../vhdl/vhdl_configuration/test_runner.vhd | 22 +++++ .../vhdl_configuration/test_state_change.vhd | 43 +++++++++ tests/acceptance/test_external_run_scripts.py | 26 +++++ 12 files changed, 678 insertions(+) create mode 100644 examples/vhdl/vhdl_configuration/dff.vhd create mode 100644 examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_reset.vhd create mode 100644 examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_selecting_dut_with_generate_statement.vhd create mode 100644 examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_state_change.vhd create mode 100644 examples/vhdl/vhdl_configuration/handling_generics_limitation/test_fixture.vhd create mode 100644 examples/vhdl/vhdl_configuration/run.py create mode 100644 examples/vhdl/vhdl_configuration/tb_selecting_dut_with_vhdl_configuration.vhd create mode 100644 examples/vhdl/vhdl_configuration/tb_selecting_test_runner_with_vhdl_configuration.vhd create mode 100644 examples/vhdl/vhdl_configuration/test_reset.vhd create mode 100644 examples/vhdl/vhdl_configuration/test_runner.vhd create mode 100644 examples/vhdl/vhdl_configuration/test_state_change.vhd diff --git a/examples/vhdl/vhdl_configuration/dff.vhd b/examples/vhdl/vhdl_configuration/dff.vhd new file mode 100644 index 000000000..958541c37 --- /dev/null +++ b/examples/vhdl/vhdl_configuration/dff.vhd @@ -0,0 +1,62 @@ +-- This Source Code Form is subject to the terms of the Mozilla Public +-- License, v. 2.0. If a copy of the MPL was not distributed with this file, +-- You can obtain one at http://mozilla.org/MPL/2.0/. +-- +-- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com +library ieee; +use ieee.std_logic_1164.all; + +entity dff is + generic( + width : positive := 8 + ); + port( + clk : in std_logic; + reset : in std_logic; + d : in std_logic_vector(width - 1 downto 0); + q : out std_logic_vector(width - 1 downto 0) + ); +end; + +architecture rtl of dff is +begin + process(clk) is + begin + if rising_edge(clk) then + if reset = '1' then + q <= (others => '0'); + else + q <= d; + end if; + end if; + end process; +end; + +configuration dff_rtl of tb_selecting_dut_with_vhdl_configuration is + for tb + for test_fixture + for dut : dff + use entity work.dff(rtl); + end for; + end for; + end for; +end; + +architecture behavioral of dff is +begin + process + begin + wait until rising_edge(clk); + q <= (others => '0') when reset else d; + end process; +end; + +configuration dff_behavioral of tb_selecting_dut_with_vhdl_configuration is + for tb + for test_fixture + for dut : dff + use entity work.dff(behavioral); + end for; + end for; + end for; +end; diff --git a/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_reset.vhd b/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_reset.vhd new file mode 100644 index 000000000..465452bb1 --- /dev/null +++ b/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_reset.vhd @@ -0,0 +1,59 @@ +-- This Source Code Form is subject to the terms of the Mozilla Public +-- License, v. 2.0. If a copy of the MPL was not distributed with this file, +-- You can obtain one at http://mozilla.org/MPL/2.0/. +-- +-- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com +-- +-- Description: Instead of having a testbench containing a shared test fixture +-- and then use VHDL configurations to select different test runners implementing +-- different tests one can flip things upside down. Each test become a separate +-- top-level testbench and the shared test fixture is placed in a separate entity +-- imported by each tetbench. + +library vunit_lib; +context vunit_lib.vunit_context; + +library ieee; +use ieee.std_logic_1164.all; + +entity tb_reset is + generic( + runner_cfg : string; + width : positive + ); +end entity; + +architecture tb of tb_reset is + constant clk_period : time := 10 ns; + + signal reset : std_logic; + signal clk : std_logic; + signal d : std_logic_vector(width - 1 downto 0); + signal q : std_logic_vector(width - 1 downto 0); +begin + text_fixture_inst : entity work.test_fixture + generic map( + width => width, + clk_period => clk_period + ) + port map( + clk => clk, + reset => reset, + d => d, + q => q + ); + + test_runner : process + begin + test_runner_setup(runner, runner_cfg); + + d <= (others => '1'); + reset <= '1'; + wait until rising_edge(clk); + wait for 0 ns; + check_equal(q, 0); + + test_runner_cleanup(runner); + end process; + +end architecture; diff --git a/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_selecting_dut_with_generate_statement.vhd b/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_selecting_dut_with_generate_statement.vhd new file mode 100644 index 000000000..423f8ac0a --- /dev/null +++ b/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_selecting_dut_with_generate_statement.vhd @@ -0,0 +1,95 @@ +-- This Source Code Form is subject to the terms of the Mozilla Public +-- License, v. 2.0. If a copy of the MPL was not distributed with this file, +-- You can obtain one at http://mozilla.org/MPL/2.0/. +-- +-- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com +-- +-- Description: This is an example of a testbench using a generic instead +-- of VHDL configurations to select the DUT to run. Without VHDL configurations +-- the width generic to the dff entity can be exposed and modified at the top-level + +library vunit_lib; +context vunit_lib.vunit_context; + +library ieee; +use ieee.std_logic_1164.all; + +entity tb_selecting_dut_with_generate_statement is + generic( + runner_cfg : string; + width : positive; + dff_arch : string + ); +end entity; + +architecture tb of tb_selecting_dut_with_generate_statement is + constant clk_period : time := 10 ns; + + signal reset : std_logic; + signal clk : std_logic := '0'; + signal d : std_logic_vector(width - 1 downto 0); + signal q : std_logic_vector(width - 1 downto 0); +begin + test_runner : process + begin + test_runner_setup(runner, runner_cfg); + + while test_suite loop + if run("Test reset") then + d <= (others => '1'); + reset <= '1'; + wait until rising_edge(clk); + wait for 0 ns; + check_equal(q, 0); + + elsif run("Test state change") then + reset <= '0'; + + d <= (others => '1'); + wait until rising_edge(clk); + wait for 0 ns; + check_equal(q, std_logic_vector'(q'range => '1')); + + d <= (others => '0'); + wait until rising_edge(clk); + wait for 0 ns; + check_equal(q, 0); + end if; + end loop; + + test_runner_cleanup(runner); + end process; + + test_fixture : block is + begin + clk <= not clk after clk_period / 2; + + dut_selection : if dff_arch = "rtl" generate + dut : entity work.dff(rtl) + generic map( + width => width + ) + port map( + clk => clk, + reset => reset, + d => d, + q => q + ); + + elsif dff_arch = "behavioral" generate + dut : entity work.dff(behavioral) + generic map( + width => width + ) + port map( + clk => clk, + reset => reset, + d => d, + q => q + ); + + else generate + error("Unknown DFF architecture"); + end generate; + end block; +end architecture; diff --git a/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_state_change.vhd b/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_state_change.vhd new file mode 100644 index 000000000..38dc37720 --- /dev/null +++ b/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_state_change.vhd @@ -0,0 +1,65 @@ +-- This Source Code Form is subject to the terms of the Mozilla Public +-- License, v. 2.0. If a copy of the MPL was not distributed with this file, +-- You can obtain one at http://mozilla.org/MPL/2.0/. +-- +-- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com +-- +-- Description: Instead of having a testbench containing a shared test fixture +-- and then use VHDL configurations to select different test runners implementing +-- different tests one can flip things upside down. Each test become a separate +-- top-level testbench and the shared test fixture is placed in a separate entity +-- imported by each tetbench. + +library vunit_lib; +context vunit_lib.vunit_context; + +library ieee; +use ieee.std_logic_1164.all; + +entity tb_state_change is + generic( + runner_cfg : string; + width : positive + ); +end entity; + +architecture tb of tb_state_change is + constant clk_period : time := 10 ns; + + signal reset : std_logic; + signal clk : std_logic; + signal d : std_logic_vector(width - 1 downto 0); + signal q : std_logic_vector(width - 1 downto 0); +begin + text_fixture_inst : entity work.test_fixture + generic map( + width => width, + clk_period => clk_period + ) + port map( + clk => clk, + reset => reset, + d => d, + q => q + ); + + test_runner : process + begin + test_runner_setup(runner, runner_cfg); + + reset <= '0'; + + d <= (others => '1'); + wait until rising_edge(clk); + wait for 0 ns; + check_equal(q, std_logic_vector'(q'range => '1')); + + d <= (others => '0'); + wait until rising_edge(clk); + wait for 0 ns; + check_equal(q, 0); + + test_runner_cleanup(runner); + end process; + +end architecture; diff --git a/examples/vhdl/vhdl_configuration/handling_generics_limitation/test_fixture.vhd b/examples/vhdl/vhdl_configuration/handling_generics_limitation/test_fixture.vhd new file mode 100644 index 000000000..0eada4994 --- /dev/null +++ b/examples/vhdl/vhdl_configuration/handling_generics_limitation/test_fixture.vhd @@ -0,0 +1,38 @@ +-- This Source Code Form is subject to the terms of the Mozilla Public +-- License, v. 2.0. If a copy of the MPL was not distributed with this file, +-- You can obtain one at http://mozilla.org/MPL/2.0/. +-- +-- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com + +library ieee; +use ieee.std_logic_1164.all; + +entity test_fixture is + generic( + width : positive := 8; + clk_period : time + ); + port( + clk : out std_logic := '0'; + reset : in std_logic; + d : in std_logic_vector(width - 1 downto 0); + q : out std_logic_vector(width - 1 downto 0) + ); +end entity; + +architecture tb of test_fixture is +begin + clk <= not clk after clk_period / 2; + + dut : entity work.dff(rtl) + generic map( + width => width + ) + port map( + clk => clk, + reset => reset, + d => d, + q => q + ); + +end architecture; diff --git a/examples/vhdl/vhdl_configuration/run.py b/examples/vhdl/vhdl_configuration/run.py new file mode 100644 index 000000000..12cd15ca4 --- /dev/null +++ b/examples/vhdl/vhdl_configuration/run.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. +# +# Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com + +from pathlib import Path +from vunit import VUnit +from vunit.json4vhdl import encode_json, b16encode + +vu = VUnit.from_argv() +vu.add_vhdl_builtins() +lib = vu.add_library("lib") +lib.add_source_files(Path(__file__).parent / "*.vhd") +lib.add_source_files(Path(__file__).parent / "handling_generics_limitation" / "*.vhd") + +# VHDL configurations are detected automatically and are treated as a special +# case of the broader VUnit configuration concept. As such the configuration +# can be extended beyond the capabilities of a pure VHDL configuration. For example, +# by adding a post_check function. The exception is generics since VHDL doesn't allow +# generics to be combined with configurations. Workarounds for this limitation can be +# found in the handling_generics_limitation directory + +# Get the VHDL-defined configurations from test or testbench objects using a pattern matching +# configurations of interest. +tb = lib.test_bench("tb_selecting_dut_with_vhdl_configuration") +configurations = tb.get_configs("dff_*") + + +# Remember to run the run script with the -v flag to see the message from the dummy post_check +def post_check(output_path): + print("Running post-check") + + return True + + +configurations.set_post_check(post_check) + +# The testbenches in the handling_generics_limitation directory are examples of how the generics +# limitation of VHDL configurations can be worked around. This allow us to create configurations +# with different settings for the DUT width generic + +# This testbench replaces VHDL configurations with generate statements +tb = lib.test_bench("tb_selecting_dut_with_generate_statement") +for width in [8, 32]: + for arch in ["rtl", "behavioral"]: + tb.add_config(name=f"dff_{arch}_width={width}", generics=dict(dff_arch=arch, width=width)) + +# Instead of having a testbench containing a shared test fixture +# and then use VHDL configurations to select different test runners implementing +# different tests one can flip things upside down. Each test become a separate +# top-level testbench and the shared test fixture is placed in a separate entity +# imported by each tetbench. +for tb_name in ["tb_reset", "tb_state_change"]: + tb = lib.test_bench(tb_name) + for width in [8, 32]: + tb.add_config(name=f"width={width}", generics=dict(width=width)) + +vu.main() diff --git a/examples/vhdl/vhdl_configuration/tb_selecting_dut_with_vhdl_configuration.vhd b/examples/vhdl/vhdl_configuration/tb_selecting_dut_with_vhdl_configuration.vhd new file mode 100644 index 000000000..6cdb8ccfc --- /dev/null +++ b/examples/vhdl/vhdl_configuration/tb_selecting_dut_with_vhdl_configuration.vhd @@ -0,0 +1,92 @@ +-- This Source Code Form is subject to the terms of the Mozilla Public +-- License, v. 2.0. If a copy of the MPL was not distributed with this file, +-- You can obtain one at http://mozilla.org/MPL/2.0/. +-- +-- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com +-- +-- Description: This is an example of a testbench using VHDL configurations +-- to select DUT architecture + +library vunit_lib; +context vunit_lib.vunit_context; + +library ieee; +use ieee.std_logic_1164.all; + +entity tb_selecting_dut_with_vhdl_configuration is + -- VHDL doesn't support generics with VHDL configurations. + -- Assigning null_runner_cfg to runner_cfg will instruct + -- test_runner_setup to look for a runner.cfg file containing the + -- same information. Note that runner_cfg must remain present to + -- indicate that this is a testbench entity. + generic(runner_cfg : string := null_runner_cfg); +end entity; + +architecture tb of tb_selecting_dut_with_vhdl_configuration is + constant clk_period : time := 10 ns; + constant width : positive := 8; + + signal reset : std_logic; + signal clk : std_logic := '0'; + signal d : std_logic_vector(width - 1 downto 0); + signal q : std_logic_vector(width - 1 downto 0); + + component dff is + generic( + width : positive := width + ); + port( + clk : in std_logic; + reset : in std_logic; + d : in std_logic_vector(width - 1 downto 0); + q : out std_logic_vector(width - 1 downto 0) + ); + end component; + +begin + test_runner : process + begin + test_runner_setup(runner, runner_cfg); + + while test_suite loop + if run("Test reset") then + d <= (others => '1'); + reset <= '1'; + wait until rising_edge(clk); + wait for 0 ns; + check_equal(q, 0); + + elsif run("Test state change") then + reset <= '0'; + + d <= (others => '1'); + wait until rising_edge(clk); + wait for 0 ns; + check_equal(q, std_logic_vector'(q'range => '1')); + + d <= (others => '0'); + wait until rising_edge(clk); + wait for 0 ns; + check_equal(q, 0); + end if; + end loop; + + test_runner_cleanup(runner); + end process; + + test_fixture : block is + begin + clk <= not clk after clk_period / 2; + + dut : dff + generic map( + width => width + ) + port map( + clk => clk, + reset => reset, + d => d, + q => q + ); + end block; +end architecture; diff --git a/examples/vhdl/vhdl_configuration/tb_selecting_test_runner_with_vhdl_configuration.vhd b/examples/vhdl/vhdl_configuration/tb_selecting_test_runner_with_vhdl_configuration.vhd new file mode 100644 index 000000000..6338e4fa6 --- /dev/null +++ b/examples/vhdl/vhdl_configuration/tb_selecting_test_runner_with_vhdl_configuration.vhd @@ -0,0 +1,78 @@ +-- This Source Code Form is subject to the terms of the Mozilla Public +-- License, v. 2.0. If a copy of the MPL was not distributed with this file, +-- You can obtain one at http://mozilla.org/MPL/2.0/. +-- +-- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com +-- +-- Description: This is an example of a testbench using separate architectures +-- of a test runner entity to define different tests. This is a structure +-- found in OSVVM-native testbenches + +library vunit_lib; +context vunit_lib.vunit_context; + +library ieee; +use ieee.std_logic_1164.all; + +entity tb_selecting_test_runner_with_vhdl_configuration is + -- VHDL doesn't support generics with VHDL configurations. + -- Assigning null_runner_cfg to runner_cfg will instruct + -- test_runner_setup to look for a runner.cfg file containing the + -- same information. Note that runner_cfg must remain present to + -- indicate that this is a testbench entity. + generic(runner_cfg : string := null_runner_cfg); +end entity; + +architecture tb of tb_selecting_test_runner_with_vhdl_configuration is + constant clk_period : time := 10 ns; + constant width : positive := 8; + + signal reset : std_logic; + signal clk : std_logic := '0'; + signal d : std_logic_vector(width - 1 downto 0); + signal q : std_logic_vector(width - 1 downto 0); + + component test_runner is + generic( + clk_period : time; + width : positive; + nested_runner_cfg : string + ); + port( + reset : out std_logic; + clk : in std_logic; + d : out std_logic_vector(width - 1 downto 0); + q : in std_logic_vector(width - 1 downto 0) + ); + end component; + +begin + test_runner_inst : test_runner + generic map( + clk_period => clk_period, + width => width, + nested_runner_cfg => runner_cfg + ) + port map( + reset => reset, + clk => clk, + d => d, + q => q + ); + + test_fixture : block is + begin + clk <= not clk after clk_period / 2; + + dut : entity work.dff(rtl) + generic map( + width => width + ) + port map( + clk => clk, + reset => reset, + d => d, + q => q + ); + end block; +end architecture; diff --git a/examples/vhdl/vhdl_configuration/test_reset.vhd b/examples/vhdl/vhdl_configuration/test_reset.vhd new file mode 100644 index 000000000..e6d355191 --- /dev/null +++ b/examples/vhdl/vhdl_configuration/test_reset.vhd @@ -0,0 +1,37 @@ +-- This Source Code Form is subject to the terms of the Mozilla Public +-- License, v. 2.0. If a copy of the MPL was not distributed with this file, +-- You can obtain one at http://mozilla.org/MPL/2.0/. +-- +-- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com + +library vunit_lib; +context vunit_lib.vunit_context; + +library ieee; +use ieee.std_logic_1164.all; + +architecture test_reset_a of test_runner is +begin + main : process + begin + test_runner_setup(runner, nested_runner_cfg); + + d <= (others => '1'); + reset <= '1'; + wait until rising_edge(clk); + wait for 0 ns; + check_equal(q, 0); + + test_runner_cleanup(runner); + end process; + + test_runner_watchdog(runner, 10 * clk_period); +end; + +configuration test_reset of tb_selecting_test_runner_with_vhdl_configuration is + for tb + for test_runner_inst : test_runner + use entity work.test_runner(test_reset_a); + end for; + end for; +end; diff --git a/examples/vhdl/vhdl_configuration/test_runner.vhd b/examples/vhdl/vhdl_configuration/test_runner.vhd new file mode 100644 index 000000000..95a0dfe41 --- /dev/null +++ b/examples/vhdl/vhdl_configuration/test_runner.vhd @@ -0,0 +1,22 @@ +-- This Source Code Form is subject to the terms of the Mozilla Public +-- License, v. 2.0. If a copy of the MPL was not distributed with this file, +-- You can obtain one at http://mozilla.org/MPL/2.0/. +-- +-- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com + +library ieee; +use ieee.std_logic_1164.all; + +entity test_runner is + generic( + clk_period : time; + width : positive; + nested_runner_cfg : string + ); + port( + reset : out std_logic; + clk : in std_logic; + d : out std_logic_vector(width - 1 downto 0); + q : in std_logic_vector(width - 1 downto 0) + ); +end entity; diff --git a/examples/vhdl/vhdl_configuration/test_state_change.vhd b/examples/vhdl/vhdl_configuration/test_state_change.vhd new file mode 100644 index 000000000..1c82b7455 --- /dev/null +++ b/examples/vhdl/vhdl_configuration/test_state_change.vhd @@ -0,0 +1,43 @@ +-- This Source Code Form is subject to the terms of the Mozilla Public +-- License, v. 2.0. If a copy of the MPL was not distributed with this file, +-- You can obtain one at http://mozilla.org/MPL/2.0/. +-- +-- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com + +library vunit_lib; +context vunit_lib.vunit_context; + +library ieee; +use ieee.std_logic_1164.all; + +architecture test_state_change_a of test_runner is +begin + main : process + begin + test_runner_setup(runner, nested_runner_cfg); + + reset <= '0'; + + d <= (others => '1'); + wait until rising_edge(clk); + wait for 0 ns; + check_equal(q, std_logic_vector'(q'range => '1')); + + d <= (others => '0'); + wait until rising_edge(clk); + wait for 0 ns; + check_equal(q, 0); + + test_runner_cleanup(runner); + end process; + + test_runner_watchdog(runner, 10 * clk_period); +end; + +configuration test_state_change of tb_selecting_test_runner_with_vhdl_configuration is + for tb + for test_runner_inst : test_runner + use entity work.test_runner(test_state_change_a); + end for; + end for; +end; diff --git a/tests/acceptance/test_external_run_scripts.py b/tests/acceptance/test_external_run_scripts.py index 3773120b6..405043404 100644 --- a/tests/acceptance/test_external_run_scripts.py +++ b/tests/acceptance/test_external_run_scripts.py @@ -176,6 +176,32 @@ def test_vhdl_composite_generics_example_project(self): ], ) + def test_vhdl_configuration_example_project(self): + self.check(ROOT / "examples/vhdl/vhdl_configuration/run.py") + check_report( + self.report_file, + [ + ("passed", "lib.tb_selecting_dut_with_vhdl_configuration.dff_rtl.Test reset"), + ("passed", "lib.tb_selecting_dut_with_vhdl_configuration.dff_behavioral.Test reset"), + ("passed", "lib.tb_selecting_dut_with_vhdl_configuration.dff_rtl.Test state change"), + ("passed", "lib.tb_selecting_dut_with_vhdl_configuration.dff_behavioral.Test state change"), + ("passed", "lib.tb_selecting_test_runner_with_vhdl_configuration.test_reset"), + ("passed", "lib.tb_selecting_test_runner_with_vhdl_configuration.test_state_change"), + ("passed", "lib.tb_reset.width=8"), + ("passed", "lib.tb_reset.width=32"), + ("passed", "lib.tb_selecting_dut_with_generate_statement.dff_rtl_width=8.Test reset"), + ("passed", "lib.tb_selecting_dut_with_generate_statement.dff_behavioral_width=8.Test reset"), + ("passed", "lib.tb_selecting_dut_with_generate_statement.dff_rtl_width=32.Test reset"), + ("passed", "lib.tb_selecting_dut_with_generate_statement.dff_behavioral_width=32.Test reset"), + ("passed", "lib.tb_selecting_dut_with_generate_statement.dff_rtl_width=8.Test state change"), + ("passed", "lib.tb_selecting_dut_with_generate_statement.dff_behavioral_width=8.Test state change"), + ("passed", "lib.tb_selecting_dut_with_generate_statement.dff_rtl_width=32.Test state change"), + ("passed", "lib.tb_selecting_dut_with_generate_statement.dff_behavioral_width=32.Test state change"), + ("passed", "lib.tb_state_change.width=8"), + ("passed", "lib.tb_state_change.width=32"), + ], + ) + @mark.xfail( not (simulator_is("ghdl") or simulator_is("nvc")), reason="Support complex JSON strings as generic", From 8f456deaa7517d376520f1d93342f32c45dd37cd Mon Sep 17 00:00:00 2001 From: Lars Asplund Date: Sat, 8 Jan 2022 10:57:57 +0100 Subject: [PATCH 08/13] Refactor to better support extensions of VHDL configurations. --- docs/py/vunit.rst | 8 +++ examples/vhdl/vhdl_configuration/run.py | 23 +++++--- tests/acceptance/artificial/vhdl/run.py | 7 ++- tests/acceptance/test_artificial.py | 4 -- tests/unit/test_configuration.py | 4 +- tests/unit/test_incisive_interface.py | 4 +- tests/unit/test_test_suites.py | 2 +- vunit/configuration.py | 53 ++++++++++++----- vunit/sim_if/activehdl.py | 4 +- vunit/sim_if/ghdl.py | 4 +- vunit/sim_if/incisive.py | 4 +- vunit/sim_if/modelsim.py | 4 +- vunit/sim_if/rivierapro.py | 4 +- vunit/test/bench_list.py | 4 +- vunit/test/suites.py | 2 +- vunit/ui/configuration.py | 78 ++++++++++++++++++++----- vunit/ui/test.py | 14 ++++- vunit/ui/testbench.py | 14 ++++- 18 files changed, 170 insertions(+), 67 deletions(-) diff --git a/docs/py/vunit.rst b/docs/py/vunit.rst index 27d2821cd..ebc80bbc3 100644 --- a/docs/py/vunit.rst +++ b/docs/py/vunit.rst @@ -35,6 +35,14 @@ Test .. autoclass:: vunit.ui.test.Test() +ConfigurationList +----------------- + +.. autoclass:: vunit.ui.configuration.ConfigurationList() + :special-members: __iter__ + :inherited-members: + + Configuration ------------- diff --git a/examples/vhdl/vhdl_configuration/run.py b/examples/vhdl/vhdl_configuration/run.py index 12cd15ca4..87df15af2 100644 --- a/examples/vhdl/vhdl_configuration/run.py +++ b/examples/vhdl/vhdl_configuration/run.py @@ -19,24 +19,31 @@ # VHDL configurations are detected automatically and are treated as a special # case of the broader VUnit configuration concept. As such the configuration # can be extended beyond the capabilities of a pure VHDL configuration. For example, -# by adding a post_check function. The exception is generics since VHDL doesn't allow -# generics to be combined with configurations. Workarounds for this limitation can be -# found in the handling_generics_limitation directory +# by adding a pre_config or post_check function hooks. The exception is generics since VHDL +# doesn't allow generics to be combined with configurations. +# Workarounds for this limitation can be found in the handling_generics_limitation directory # Get the VHDL-defined configurations from test or testbench objects using a pattern matching # configurations of interest. tb = lib.test_bench("tb_selecting_dut_with_vhdl_configuration") -configurations = tb.get_configs("dff_*") +configs = tb.get_configs("dff_*") # Remember to run the run script with the -v flag to see the message from the dummy post_check -def post_check(output_path): - print("Running post-check") +def make_hook(msg): + def hook(output_path): + print(msg) - return True + return True + return hook -configurations.set_post_check(post_check) + +configs.set_post_check(make_hook("Common post_check")) + +# You can also loop over the matching configurations +for config in configs: + config.set_pre_config(make_hook(f"pre_config for {config.name}")) # The testbenches in the handling_generics_limitation directory are examples of how the generics # limitation of VHDL configurations can be worked around. This allow us to create configurations diff --git a/tests/acceptance/artificial/vhdl/run.py b/tests/acceptance/artificial/vhdl/run.py index ba48f439a..b0eb114f1 100644 --- a/tests/acceptance/artificial/vhdl/run.py +++ b/tests/acceptance/artificial/vhdl/run.py @@ -108,7 +108,12 @@ def post_check(output_path): tb = ui.library("lib").test_bench("tb_with_vhdl_configuration") tb.get_configs("cfg*").set_post_check(make_post_check("arch1")) tb.test("test1").get_configs("cfg2").set_post_check(make_post_check("arch2")) - tb.test("test2").get_configs("cfg2").set_post_check(make_post_check("arch2")) + for config in tb.test("test2").get_configs(): + if config.name == "cfg2": + config.set_post_check(make_post_check("arch2")) + tb.test("test1").delete_config("cfg1") + tb.add_config("foo") + tb.delete_config("foo") configure_tb_with_generic_config() diff --git a/tests/acceptance/test_artificial.py b/tests/acceptance/test_artificial.py index 5ff833dae..0d8908aed 100644 --- a/tests/acceptance/test_artificial.py +++ b/tests/acceptance/test_artificial.py @@ -245,10 +245,6 @@ def test_exit_0_flag(self): "failed", "lib.tb_assert_stop_level.Report failure when VHDL assert stop level = failure", ), - ( - "passed", - "lib.tb_with_vhdl_configuration.cfg1.test1", - ), ( "passed", "lib.tb_with_vhdl_configuration.cfg1.test2", diff --git a/tests/unit/test_configuration.py b/tests/unit/test_configuration.py index 6f4831573..4112e40a8 100644 --- a/tests/unit/test_configuration.py +++ b/tests/unit/test_configuration.py @@ -46,11 +46,11 @@ def test_error_on_setting_illegal_value_sim_option(self): self.assertRaises(ValueError, config.set_sim_option, "vhdl_assert_stop_level", "illegal") def test_error_on_both_generics_and_vhdl_configuration(self): - with _create_config(vhdl_configuration_name="cfg") as config: + with _create_config(vhdl_config_name="cfg") as config: self.assertRaises(GenericAndVHDLConfigurationException, config.set_generic, "foo", "bar") with _create_config(generics=dict(foo=17)) as config: - self.assertRaises(GenericAndVHDLConfigurationException, config.set_vhdl_configuration_name, "bar") + self.assertRaises(GenericAndVHDLConfigurationException, config.set_vhdl_config_name, "bar") def test_sim_option_is_not_mutated(self): with _create_config() as config: diff --git a/tests/unit/test_incisive_interface.py b/tests/unit/test_incisive_interface.py index ffbf5b72c..552b1f644 100644 --- a/tests/unit/test_incisive_interface.py +++ b/tests/unit/test_incisive_interface.py @@ -967,7 +967,7 @@ def test_configuration_and_entity_selection(self, find_cds_root_irun, find_cds_r with create_tempdir() as tempdir: design_unit = Entity("tb_entity", file_name=str(Path(tempdir) / "file.vhd")) design_unit.generic_names = ["runner_cfg"] - config = Configuration("name", design_unit, vhdl_configuration_name="cfg") + config = Configuration("name", design_unit, vhdl_config_name="cfg") simif = IncisiveInterface(prefix="prefix", output_path=self.output_path) self.assertEqual(simif._select_vhdl_top(config), "cfg") # pylint: disable=protected-access config = Configuration("name", design_unit) @@ -1002,5 +1002,5 @@ def make_config(sim_options=None, generics=None, verilog=False): cfg.sim_options = {} if sim_options is None else sim_options cfg.generics = {} if generics is None else generics - cfg.vhdl_configuration_name = None + cfg.vhdl_config_name = None return cfg diff --git a/tests/unit/test_test_suites.py b/tests/unit/test_test_suites.py index 33ea11787..9d07d9a68 100644 --- a/tests/unit/test_test_suites.py +++ b/tests/unit/test_test_suites.py @@ -216,7 +216,7 @@ def get_simulator_output_path(self, output_path): for expect_runner_cfg_generic in [False, True]: config = Configuration( - "name", design_unit, vhdl_configuration_name=None if expect_runner_cfg_generic else "cfg" + "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) diff --git a/vunit/configuration.py b/vunit/configuration.py index 173623c34..603acbb7e 100644 --- a/vunit/configuration.py +++ b/vunit/configuration.py @@ -43,14 +43,14 @@ def __init__( # pylint: disable=too-many-arguments pre_config=None, post_check=None, attributes=None, - vhdl_configuration_name=None, + vhdl_config_name=None, ): self.name = name self._design_unit = design_unit self.generics = {} if generics is None else generics self.sim_options = {} if sim_options is None else sim_options self.attributes = {} if attributes is None else attributes - self.vhdl_configuration_name = vhdl_configuration_name + self.vhdl_config_name = vhdl_config_name self.tb_path = str(Path(design_unit.original_file_name).parent) @@ -70,7 +70,7 @@ def copy(self): pre_config=self.pre_config, post_check=self.post_check, attributes=self.attributes.copy(), - vhdl_configuration_name=self.vhdl_configuration_name, + vhdl_config_name=self.vhdl_config_name, ) @property @@ -109,20 +109,20 @@ def set_attribute(self, name, value): else: raise AttributeException - def set_vhdl_configuration_name(self, name): + def set_vhdl_config_name(self, name): """ Set VHDL configuration name """ if self.generics: raise GenericAndVHDLConfigurationException("Generics can't be used with VHDL configurations.") - self.vhdl_configuration_name = name + self.vhdl_config_name = name def set_generic(self, name, value): """ Set generic """ - if self.vhdl_configuration_name: + if self.vhdl_config_name: raise GenericAndVHDLConfigurationException("Generics can't be used with VHDL configurations.") if name not in self._design_unit.generic_names: LOGGER.warning( @@ -210,7 +210,10 @@ def get_configuration_dicts(): def set_attribute(self, name, value): """ - Set attribute + Set attribute. + + :param name: Attribute name. + :param value: Attribute value. """ self._check_enabled() for configs in self.get_configuration_dicts(): @@ -219,7 +222,10 @@ def set_attribute(self, name, value): def set_generic(self, name, value): """ - Set generic + Set generic. + + :param name: Generic name. + :param value: Generic value. """ self._check_enabled() for configs in self.get_configuration_dicts(): @@ -228,8 +234,10 @@ def set_generic(self, name, value): def set_sim_option(self, name, value, overwrite=True): """ - Set sim option + Set simulation option + :param name: Simulation option name. + :param value: Simulation option value. :param overwrite: To overwrite the option or append to the existing value """ self._check_enabled() @@ -243,6 +251,8 @@ def set_sim_option(self, name, value, overwrite=True): def set_pre_config(self, value): """ Set pre_config function + + :param value: pre_config function. """ self._check_enabled() for configs in self.get_configuration_dicts(): @@ -252,6 +262,8 @@ def set_pre_config(self, value): def set_post_check(self, value): """ Set post_check function + + :param value: post_check function. """ self._check_enabled() for configs in self.get_configuration_dicts(): @@ -266,10 +278,10 @@ def add_config( # pylint: disable=too-many-arguments, too-many-branches post_check=None, sim_options=None, attributes=None, - vhdl_configuration_name=None, + vhdl_config_name=None, ): """ - Add a configuration copying unset fields from the default configuration: + Add a configuration copying unset fields from the default configuration. """ self._check_enabled() @@ -291,7 +303,7 @@ def add_config( # pylint: disable=too-many-arguments, too-many-branches config.post_check = post_check if generics is not None: - if config.vhdl_configuration_name: + if config.vhdl_config_name: raise GenericAndVHDLConfigurationException config.generics.update(generics) @@ -304,9 +316,22 @@ def add_config( # pylint: disable=too-many-arguments, too-many-branches raise AttributeException config.attributes.update(attributes) - if vhdl_configuration_name is not None: + if vhdl_config_name is not None: if config.generics: raise GenericAndVHDLConfigurationException - config.vhdl_configuration_name = vhdl_configuration_name + config.vhdl_config_name = vhdl_config_name configs[config.name] = config + + def delete_config(self, name): + """ + Delete a configuration. + """ + found_config = False + for configs in self.get_configuration_dicts(): + if name in configs: + found_config = True + del configs[name] + + if not found_config: + raise RuntimeError(f"Configuration name {name!s} not defined") diff --git a/vunit/sim_if/activehdl.py b/vunit/sim_if/activehdl.py index 4ad8cc4ae..d5959289a 100644 --- a/vunit/sim_if/activehdl.py +++ b/vunit/sim_if/activehdl.py @@ -241,12 +241,12 @@ def _create_load_function(self, config, output_path): config.library_name, ] - if config.vhdl_configuration_name is None: + if config.vhdl_config_name is None: vsim_flags.append(config.entity_name) if config.architecture_name is not None: vsim_flags.append(config.architecture_name) else: - vsim_flags.append(config.vhdl_configuration_name) + vsim_flags.append(config.vhdl_config_name) if config.sim_options.get("enable_coverage", False): coverage_file_path = str(Path(output_path) / "coverage.acdb") diff --git a/vunit/sim_if/ghdl.py b/vunit/sim_if/ghdl.py index 39435ed84..ad1ebea75 100644 --- a/vunit/sim_if/ghdl.py +++ b/vunit/sim_if/ghdl.py @@ -280,8 +280,8 @@ def _get_command(self, config, output_path, elaborate_only, ghdl_e, wave_file): # Enable coverage in linker cmd += ["-Wl,-lgcov"] - if config.vhdl_configuration_name is not None: - cmd += [config.vhdl_configuration_name] + if config.vhdl_config_name is not None: + cmd += [config.vhdl_config_name] else: cmd += [config.entity_name, config.architecture_name] diff --git a/vunit/sim_if/incisive.py b/vunit/sim_if/incisive.py index 4d06b2da0..bd6dc1ba6 100644 --- a/vunit/sim_if/incisive.py +++ b/vunit/sim_if/incisive.py @@ -280,10 +280,10 @@ def _get_mapped_libraries(self): @staticmethod def _select_vhdl_top(config): "Select VHDL configuration or entity as top." - if config.vhdl_configuration_name is None: + if config.vhdl_config_name is None: return f"{config.library_name!s}.{config.entity_name!s}:{config.architecture_name!s}" - return f"{config.vhdl_configuration_name!s}" + return f"{config.vhdl_config_name!s}" def simulate( self, output_path, test_suite_name, config, elaborate_only=False diff --git a/vunit/sim_if/modelsim.py b/vunit/sim_if/modelsim.py index c2fd3d309..d173be88d 100644 --- a/vunit/sim_if/modelsim.py +++ b/vunit/sim_if/modelsim.py @@ -260,8 +260,8 @@ def _create_load_function(self, test_suite_name, config, output_path): simulation_target = ( config.library_name + "." + config.entity_name + architecture_suffix - if config.vhdl_configuration_name is None - else config.library_name + "." + config.vhdl_configuration_name + if config.vhdl_config_name is None + else config.library_name + "." + config.vhdl_config_name ) vsim_flags = [ diff --git a/vunit/sim_if/rivierapro.py b/vunit/sim_if/rivierapro.py index 07015762c..b49e54b0d 100644 --- a/vunit/sim_if/rivierapro.py +++ b/vunit/sim_if/rivierapro.py @@ -308,7 +308,7 @@ def _create_load_function(self, test_suite_name, config, output_path): # pylint vsim_flags += ["-lib", config.library_name] - if config.vhdl_configuration_name is None: + if config.vhdl_config_name is None: # Add the the testbench top-level unit last as coverage is # only collected for the top-level unit specified last vsim_flags += [config.entity_name] @@ -316,7 +316,7 @@ def _create_load_function(self, test_suite_name, config, output_path): # pylint if config.architecture_name is not None: vsim_flags.append(config.architecture_name) else: - vsim_flags += [config.vhdl_configuration_name] + vsim_flags += [config.vhdl_config_name] tcl = """ proc vunit_load {{}} {{ diff --git a/vunit/test/bench_list.py b/vunit/test/bench_list.py index 5f40dd371..db8389884 100644 --- a/vunit/test/bench_list.py +++ b/vunit/test/bench_list.py @@ -42,7 +42,7 @@ def add_from_source_file(self, source_file): self._vhdl_configurations[design_unit.name]["test_bench"] = test_bench for configuration in self._vhdl_configurations[design_unit.name]["configurations"]: - test_bench.add_config(name=configuration, vhdl_configuration_name=configuration) + test_bench.add_config(name=configuration, vhdl_config_name=configuration) if design_unit.is_vhdl_configuration: if design_unit.entity_name not in self._vhdl_configurations: @@ -51,7 +51,7 @@ def add_from_source_file(self, source_file): self._vhdl_configurations[design_unit.entity_name]["configurations"].append(design_unit.name) if self._vhdl_configurations[design_unit.entity_name]["test_bench"]: self._vhdl_configurations[design_unit.entity_name]["test_bench"].add_config( - name=design_unit.name, vhdl_configuration_name=design_unit.name + name=design_unit.name, vhdl_config_name=design_unit.name ) def _add_test_bench(self, test_bench): diff --git a/vunit/test/suites.py b/vunit/test/suites.py index c058f65db..a203abbaa 100644 --- a/vunit/test/suites.py +++ b/vunit/test/suites.py @@ -232,7 +232,7 @@ def _simulate(self, output_path): } simulator_output_path = self._simulator_if.get_simulator_output_path(output_path) / "runner.cfg" - if config.vhdl_configuration_name is not None: + 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)) else: diff --git a/vunit/ui/configuration.py b/vunit/ui/configuration.py index c1f32252a..e8b63158b 100644 --- a/vunit/ui/configuration.py +++ b/vunit/ui/configuration.py @@ -5,7 +5,7 @@ # Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com """ -UI class Configuration +UI classes ConfigurationList and Configuration """ from fnmatch import fnmatch @@ -15,30 +15,76 @@ class Configuration(ConfigurationVisitor): """ - User interface for configuration sets. + User interface for a configuration. + + Provides methods for modifying an existing configuration + """ + + def __init__(self, name, configuration): + self._name = name + self.configuration = configuration + + @property + def name(self): + """ + :returns: the name of the configuration + """ + return self._name + + def get_configuration_dicts(self): + return self.configuration + + def add_config(self, *args, **kwargs): + """ """ # pylint: disable=empty-docstring + raise NotImplementedError( + f"{type(self)} do not allow addition of new configurations, only modification of existing ones." + ) + + def delete_config(self, *args, **kwargs): + """ """ # pylint: disable=empty-docstring + raise NotImplementedError(f"{type(self)} do not allow deletion of configurations, only modification.") + + +class ConfigurationList(ConfigurationVisitor): + """ + User interface for a list of configurations. + Provides methods for modifying existing configurations """ def __init__(self, test_object, pattern): - self._selected_cfg_dicts = [] + self._selected_config_dicts = [] + self._selected_configs = {} self._test_object = test_object - for cfg_dict in test_object.get_configuration_dicts(): - selected_cfg_dict = OrderedDict() - for name, cfg in cfg_dict.items(): - if fnmatch(name if name is not DEFAULT_NAME else "", pattern): - selected_cfg_dict[name] = cfg - if selected_cfg_dict: - self._selected_cfg_dicts.append(selected_cfg_dict) + for config_dict in test_object.get_configuration_dicts(): + selected_config_dict = OrderedDict() + for name, config in config_dict.items(): + name_as_string = name if name is not DEFAULT_NAME else "" + if fnmatch(name_as_string, pattern): + selected_config_dict[name_as_string] = config + if name_as_string not in self._selected_configs: + self._selected_configs[name_as_string] = [] + + self._selected_configs[name_as_string].append(OrderedDict({name_as_string: config})) + + if selected_config_dict: + self._selected_config_dicts.append(selected_config_dict) + + def __iter__(self): + """Iterate over :class:`.Configuration` objects.""" + for name, config in self._selected_configs.items(): + yield Configuration(name, config) def get_configuration_dicts(self): - return self._selected_cfg_dicts + return self._selected_config_dicts def add_config(self, *args, **kwargs): - """ - This method is inherited from the superclass but not defined for this class. New - configurations are added to :class:`.TestBench` or :class:`.Test` objects. - """ - raise RuntimeError( + """ """ # pylint: disable=empty-docstring + raise NotImplementedError( f"{type(self)} do not allow addition of new configurations, only modification of existing ones." ) + + def delete_config(self, *args, **kwargs): + """ """ # pylint: disable=empty-docstring + raise NotImplementedError(f"{type(self)} do not allow deletion of configurations, only modification.") diff --git a/vunit/ui/test.py b/vunit/ui/test.py index d45da8218..189405fbc 100644 --- a/vunit/ui/test.py +++ b/vunit/ui/test.py @@ -9,7 +9,7 @@ """ from .common import lower_generics -from .configuration import Configuration +from .configuration import ConfigurationList class Test(object): @@ -89,14 +89,22 @@ def add_config( # pylint: disable=too-many-arguments attributes=attributes, ) + def delete_config(self, name): + """ + Delete a configuration. + + :param name: The name of the configuration. + """ + self._test_case.delete_config(name) + def get_configs(self, pattern="*"): """ Get test configurations matching pattern. :param pattern: A wildcard pattern matching the configuration name(s). - :returns: A :class:`.Configuration` object + :returns: A :class:`.ConfigurationList` object """ - return Configuration(self._test_case, pattern) + return ConfigurationList(self._test_case, pattern) def set_attribute(self, name, value): """ diff --git a/vunit/ui/testbench.py b/vunit/ui/testbench.py index 3a54c177e..9119b0fc8 100644 --- a/vunit/ui/testbench.py +++ b/vunit/ui/testbench.py @@ -11,7 +11,7 @@ from fnmatch import fnmatch from .common import lower_generics from .test import Test -from .configuration import Configuration +from .configuration import ConfigurationList class TestBench(object): @@ -184,14 +184,22 @@ def add_config( # pylint: disable=too-many-arguments attributes=attributes, ) + def delete_config(self, name): + """ + Delete a configuration. + + :param name: The name of the configuration. + """ + self._test_bench.delete_config(name) + def get_configs(self, pattern="*"): """ Get test bench configurations matching pattern. :param pattern: A wildcard pattern matching the configuration name(s). - :returns: A :class:`.Configuration` object + :returns: A :class:`.ConfigurationList` object """ - return Configuration(self._test_bench, pattern) + return ConfigurationList(self._test_bench, pattern) def test(self, name): """ From 80ce39faf96b3e828a6c45ded211bdfcb7e06c81 Mon Sep 17 00:00:00 2001 From: Lars Asplund Date: Sun, 9 Jan 2022 23:49:27 +0100 Subject: [PATCH 09/13] 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 552b1f644..298110bbe 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 1eaff104a..00ac24d76 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 9d07d9a68..dca8d4b6b 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 8fdfe667c..672a7b34e 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 7a571caa2..0dc7a712b 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 @@ -210,13 +219,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 d5959289a..bc1f58dda 100644 --- a/vunit/sim_if/activehdl.py +++ b/vunit/sim_if/activehdl.py @@ -365,7 +365,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 """ @@ -379,7 +379,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" @@ -416,7 +416,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 """ @@ -425,31 +425,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 ad1ebea75..cd09d9cab 100644 --- a/vunit/sim_if/ghdl.py +++ b/vunit/sim_if/ghdl.py @@ -92,6 +92,7 @@ def __init__( # pylint: disable=too-many-arguments gtkwave_args="", backend="llvm", ): + SimulatorInterface.__init__(self, output_path, gui) self._prefix = prefix self._project = None @@ -259,7 +260,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 """ @@ -272,7 +273,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", []) @@ -304,13 +305,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, }, @@ -319,7 +320,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 """ @@ -350,7 +353,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 @@ -363,13 +366,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 bd6dc1ba6..efbc2e60c 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 b49e54b0d..b4a9d2bb5 100644 --- a/vunit/sim_if/rivierapro.py +++ b/vunit/sim_if/rivierapro.py @@ -247,6 +247,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 e2c1fbc94..a2f6b048c 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 20dc5ccc9..f3f6fa050 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 a203abbaa..c62b6e63c 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 """ From a38585f8815be4c11e23842080c625ec5f83dabb Mon Sep 17 00:00:00 2001 From: Lars Asplund Date: Sat, 5 Feb 2022 20:50:47 +0100 Subject: [PATCH 10/13] Fix license headers. --- examples/vhdl/vhdl_configuration/dff.vhd | 2 +- .../handling_generics_limitation/tb_reset.vhd | 2 +- .../tb_selecting_dut_with_generate_statement.vhd | 2 +- .../handling_generics_limitation/tb_state_change.vhd | 2 +- .../handling_generics_limitation/test_fixture.vhd | 2 +- examples/vhdl/vhdl_configuration/run.py | 2 +- .../tb_selecting_dut_with_vhdl_configuration.vhd | 2 +- .../tb_selecting_test_runner_with_vhdl_configuration.vhd | 2 +- examples/vhdl/vhdl_configuration/test_reset.vhd | 2 +- examples/vhdl/vhdl_configuration/test_runner.vhd | 2 +- examples/vhdl/vhdl_configuration/test_state_change.vhd | 2 +- tests/acceptance/artificial/vhdl/arch1.vhd | 2 +- tests/acceptance/artificial/vhdl/cfg1.vhd | 2 +- tests/acceptance/artificial/vhdl/cfg2.vhd | 2 +- tests/acceptance/artificial/vhdl/ent.vhd | 2 +- tests/acceptance/artificial/vhdl/tb_with_vhdl_configuration.vhd | 2 +- vunit/ui/configuration.py | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/examples/vhdl/vhdl_configuration/dff.vhd b/examples/vhdl/vhdl_configuration/dff.vhd index 958541c37..6c678687a 100644 --- a/examples/vhdl/vhdl_configuration/dff.vhd +++ b/examples/vhdl/vhdl_configuration/dff.vhd @@ -2,7 +2,7 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at http://mozilla.org/MPL/2.0/. -- --- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com +-- Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com library ieee; use ieee.std_logic_1164.all; diff --git a/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_reset.vhd b/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_reset.vhd index 465452bb1..66e4213cf 100644 --- a/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_reset.vhd +++ b/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_reset.vhd @@ -2,7 +2,7 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at http://mozilla.org/MPL/2.0/. -- --- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com +-- Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com -- -- Description: Instead of having a testbench containing a shared test fixture -- and then use VHDL configurations to select different test runners implementing diff --git a/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_selecting_dut_with_generate_statement.vhd b/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_selecting_dut_with_generate_statement.vhd index 423f8ac0a..02fb2b967 100644 --- a/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_selecting_dut_with_generate_statement.vhd +++ b/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_selecting_dut_with_generate_statement.vhd @@ -2,7 +2,7 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at http://mozilla.org/MPL/2.0/. -- --- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com +-- Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com -- -- Description: This is an example of a testbench using a generic instead -- of VHDL configurations to select the DUT to run. Without VHDL configurations diff --git a/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_state_change.vhd b/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_state_change.vhd index 38dc37720..b16060332 100644 --- a/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_state_change.vhd +++ b/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_state_change.vhd @@ -2,7 +2,7 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at http://mozilla.org/MPL/2.0/. -- --- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com +-- Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com -- -- Description: Instead of having a testbench containing a shared test fixture -- and then use VHDL configurations to select different test runners implementing diff --git a/examples/vhdl/vhdl_configuration/handling_generics_limitation/test_fixture.vhd b/examples/vhdl/vhdl_configuration/handling_generics_limitation/test_fixture.vhd index 0eada4994..7bc4ea247 100644 --- a/examples/vhdl/vhdl_configuration/handling_generics_limitation/test_fixture.vhd +++ b/examples/vhdl/vhdl_configuration/handling_generics_limitation/test_fixture.vhd @@ -2,7 +2,7 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at http://mozilla.org/MPL/2.0/. -- --- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com +-- Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com library ieee; use ieee.std_logic_1164.all; diff --git a/examples/vhdl/vhdl_configuration/run.py b/examples/vhdl/vhdl_configuration/run.py index 87df15af2..2edf0752c 100644 --- a/examples/vhdl/vhdl_configuration/run.py +++ b/examples/vhdl/vhdl_configuration/run.py @@ -4,7 +4,7 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at http://mozilla.org/MPL/2.0/. # -# Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com +# Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com from pathlib import Path from vunit import VUnit diff --git a/examples/vhdl/vhdl_configuration/tb_selecting_dut_with_vhdl_configuration.vhd b/examples/vhdl/vhdl_configuration/tb_selecting_dut_with_vhdl_configuration.vhd index 6cdb8ccfc..85f238e63 100644 --- a/examples/vhdl/vhdl_configuration/tb_selecting_dut_with_vhdl_configuration.vhd +++ b/examples/vhdl/vhdl_configuration/tb_selecting_dut_with_vhdl_configuration.vhd @@ -2,7 +2,7 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at http://mozilla.org/MPL/2.0/. -- --- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com +-- Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com -- -- Description: This is an example of a testbench using VHDL configurations -- to select DUT architecture diff --git a/examples/vhdl/vhdl_configuration/tb_selecting_test_runner_with_vhdl_configuration.vhd b/examples/vhdl/vhdl_configuration/tb_selecting_test_runner_with_vhdl_configuration.vhd index 6338e4fa6..401d04998 100644 --- a/examples/vhdl/vhdl_configuration/tb_selecting_test_runner_with_vhdl_configuration.vhd +++ b/examples/vhdl/vhdl_configuration/tb_selecting_test_runner_with_vhdl_configuration.vhd @@ -2,7 +2,7 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at http://mozilla.org/MPL/2.0/. -- --- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com +-- Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com -- -- Description: This is an example of a testbench using separate architectures -- of a test runner entity to define different tests. This is a structure diff --git a/examples/vhdl/vhdl_configuration/test_reset.vhd b/examples/vhdl/vhdl_configuration/test_reset.vhd index e6d355191..dcd1c348d 100644 --- a/examples/vhdl/vhdl_configuration/test_reset.vhd +++ b/examples/vhdl/vhdl_configuration/test_reset.vhd @@ -2,7 +2,7 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at http://mozilla.org/MPL/2.0/. -- --- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com +-- Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com library vunit_lib; context vunit_lib.vunit_context; diff --git a/examples/vhdl/vhdl_configuration/test_runner.vhd b/examples/vhdl/vhdl_configuration/test_runner.vhd index 95a0dfe41..cfa16ae77 100644 --- a/examples/vhdl/vhdl_configuration/test_runner.vhd +++ b/examples/vhdl/vhdl_configuration/test_runner.vhd @@ -2,7 +2,7 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at http://mozilla.org/MPL/2.0/. -- --- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com +-- Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com library ieee; use ieee.std_logic_1164.all; diff --git a/examples/vhdl/vhdl_configuration/test_state_change.vhd b/examples/vhdl/vhdl_configuration/test_state_change.vhd index 1c82b7455..d6e91575f 100644 --- a/examples/vhdl/vhdl_configuration/test_state_change.vhd +++ b/examples/vhdl/vhdl_configuration/test_state_change.vhd @@ -2,7 +2,7 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at http://mozilla.org/MPL/2.0/. -- --- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com +-- Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com library vunit_lib; context vunit_lib.vunit_context; diff --git a/tests/acceptance/artificial/vhdl/arch1.vhd b/tests/acceptance/artificial/vhdl/arch1.vhd index da724ba0f..81852bb12 100644 --- a/tests/acceptance/artificial/vhdl/arch1.vhd +++ b/tests/acceptance/artificial/vhdl/arch1.vhd @@ -2,7 +2,7 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at http://mozilla.org/MPL/2.0/. -- --- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com +-- Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com architecture arch1 of ent is begin diff --git a/tests/acceptance/artificial/vhdl/cfg1.vhd b/tests/acceptance/artificial/vhdl/cfg1.vhd index 1796c57ec..04b9b4804 100644 --- a/tests/acceptance/artificial/vhdl/cfg1.vhd +++ b/tests/acceptance/artificial/vhdl/cfg1.vhd @@ -2,7 +2,7 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at http://mozilla.org/MPL/2.0/. -- --- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com +-- Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com configuration cfg1 of tb_with_vhdl_configuration is for tb diff --git a/tests/acceptance/artificial/vhdl/cfg2.vhd b/tests/acceptance/artificial/vhdl/cfg2.vhd index 45f4f537a..1f877a47f 100644 --- a/tests/acceptance/artificial/vhdl/cfg2.vhd +++ b/tests/acceptance/artificial/vhdl/cfg2.vhd @@ -2,7 +2,7 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at http://mozilla.org/MPL/2.0/. -- --- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com +-- Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com architecture arch2 of ent is begin diff --git a/tests/acceptance/artificial/vhdl/ent.vhd b/tests/acceptance/artificial/vhdl/ent.vhd index 3dcfeb634..6b34cfe27 100644 --- a/tests/acceptance/artificial/vhdl/ent.vhd +++ b/tests/acceptance/artificial/vhdl/ent.vhd @@ -2,7 +2,7 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at http://mozilla.org/MPL/2.0/. -- --- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com +-- Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com entity ent is port(arch : out string(1 to 5)); diff --git a/tests/acceptance/artificial/vhdl/tb_with_vhdl_configuration.vhd b/tests/acceptance/artificial/vhdl/tb_with_vhdl_configuration.vhd index 5d8326c03..f8a23f6e4 100644 --- a/tests/acceptance/artificial/vhdl/tb_with_vhdl_configuration.vhd +++ b/tests/acceptance/artificial/vhdl/tb_with_vhdl_configuration.vhd @@ -2,7 +2,7 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at http://mozilla.org/MPL/2.0/. -- --- Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com +-- Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com library vunit_lib; context vunit_lib.vunit_context; diff --git a/vunit/ui/configuration.py b/vunit/ui/configuration.py index e8b63158b..e00861004 100644 --- a/vunit/ui/configuration.py +++ b/vunit/ui/configuration.py @@ -2,7 +2,7 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at http://mozilla.org/MPL/2.0/. # -# Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com +# Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com """ UI classes ConfigurationList and Configuration From c2e0a3db33d5002d8b352e86c07b979130a466ae Mon Sep 17 00:00:00 2001 From: Lars Asplund Date: Thu, 10 Feb 2022 21:17:29 +0100 Subject: [PATCH 11/13] WIP: Blog --- docs/blog/vhdl_configurations.rst | 429 ++++++++++++++++++ examples/vhdl/vhdl_configuration/dff.vhd | 20 - ..._selecting_dut_with_generate_statement.vhd | 8 +- examples/vhdl/vhdl_configuration/run.py | 2 +- ..._selecting_dut_with_vhdl_configuration.vhd | 22 +- ...ng_test_runner_with_vhdl_configuration.vhd | 14 +- .../vhdl/vhdl_configuration/test_reset.vhd | 22 +- .../vhdl_configuration/test_state_change.vhd | 22 +- tests/acceptance/test_external_run_scripts.py | 30 +- 9 files changed, 526 insertions(+), 43 deletions(-) create mode 100644 docs/blog/vhdl_configurations.rst diff --git a/docs/blog/vhdl_configurations.rst b/docs/blog/vhdl_configurations.rst new file mode 100644 index 000000000..e6f3f7388 --- /dev/null +++ b/docs/blog/vhdl_configurations.rst @@ -0,0 +1,429 @@ +:tags: VUnit, OSVVM, configurations +:author: lasplund +:excerpt: 1 + +Improved Support for VHDL Configurations and OSVVM +================================================== + +The last few months there have been a number of initiatives to improve +VUnit and OSVVM integration. For example, issue +`#776 `__ discusses how we can +exchange VUnit and OSVVM log entires to provide consistents logs. +Another example is the added support of top-level VHDL configurations +which + +1. makes it possible to select what device under test (DUT) to use in a + VUnit testbench +2. simplifies running traditional OSVVM testbenches under VUnit + +Top-level configurations will be the focus of this article but before we dig into the +details of these use cases we will describe how VUnit solved this in the +past. + +Selecting DUT Using Generics +---------------------------- + +Sometimes the VHDL DUT comes in different variants (architectures) and +there is a need to verify all of these with the same testbench. It could +be an FPGA and an ASIC implimentation or an RTL and a behavioral +architecture. The way this has been done in VUnit in the past is to use +a combination of generics and an if-generate statement. + +.. code:: vhdl + + entity tb_selecting_dut_with_generate_statement is + generic( + runner_cfg : string; + dut_arch : string + ); + end entity; + + architecture tb of tb_selecting_dut_with_generate_statement is + ... + begin + test_runner : process + begin + test_runner_setup(runner, runner_cfg); + + while test_suite loop + if run("Test reset") then + ... + elsif run("Test state change") then + ... + end if; + end loop; + + test_runner_cleanup(runner); + end process; + + dut_selection : if dut_arch = "rtl" generate + dut : entity work.dff(rtl) + port map( + clk => clk, + reset => reset, + d => d, + q => q + ); + + elsif dut_arch = "behavioral" generate + dut : entity work.dff(behavioral) + port map( + clk => clk, + reset => reset, + d => d, + q => q + ); + + else generate + error("Unknown DUT architecture"); + end generate; + + end architecture; + +The different settings of the ``dut_arch`` generics is handled with a +VUnit configuration in the Python run script. + +The use of both VUnit and VHDL configuration concepts may be confusing +at first but as we will see the VHDL configuration is a special case of +the larger VUnit configuration concept. + +.. code:: python + + tb = lib.test_bench("tb_selecting_dut_with_generate_statement") + for arch in ["rtl", "behavioral"]: + tb.add_config(name=f"dut_{arch}", generics=dict(dut_arch=arch)) + +If we list all tests we will now see that there are two for each test +case in the testbench. + +.. code:: console + + $ python run.py --list + lib.tb_selecting_dut_with_generate_statement.dut_rtl.Test reset + lib.tb_selecting_dut_with_generate_statement.dut_behavioral.Test reset + lib.tb_selecting_dut_with_generate_statement.dut_rtl.Test state change + lib.tb_selecting_dut_with_generate_statement.dut_behavioral.Test state change + Listed 4 tests + +Using this approach to DUT selection has no real weaknesses. In fact, as +we will explain later, it may actually be the approach to prefer. +However, VHDL configurations is a well-established approach and VUnit +philosophy dictates that existing VHDL testbenches should be supported +with minimum adaptation. + + +Selecting DUT Using VHDL Configurations +--------------------------------------- + +When using VHDL configurations you need to have + +1. A component declaration for the DUT. In the example below it has been + placed in the declarative part of the testbench architecture but it + can also be placed in a separate package. +2. A component instantiation of the declared component. Note that the + ``component`` keyword is optional and can be excluded. +3. A configuration declaration for each DUT architecture + +.. code:: vhdl + + architecture tb of tb_selecting_dut_with_vhdl_configuration is + ... + + -- Component declaration + component dff is + port( + clk : in std_logic; + reset : in std_logic; + d : in std_logic_vector(width - 1 downto 0); + q : out std_logic_vector(width - 1 downto 0) + ); + end component; + begin + test_runner : process + begin + test_runner_setup(runner, runner_cfg); + + while test_suite loop + if run("Test reset") then + ... + elsif run("Test state change") then + ... + end if; + end loop; + + test_runner_cleanup(runner); + end process; + + ... + + -- Component instantiation + dut : component dff + port map( + clk => clk, + reset => reset, + d => d, + q => q + ); + end architecture; + + -- Configuration declarations + configuration rtl of tb_selecting_dut_with_vhdl_configuration is + for tb + for dut : dff + use entity work.dff(rtl); + end for; + end for; + end; + + configuration behavioral of tb_selecting_dut_with_vhdl_configuration is + for tb + for dut : dff + use entity work.dff(behavioral); + end for; + end for; + end; + +The result when listing the tests is the same as when we use generics to +switch DUT architectures. + +.. code:: console + + $ python run.py --list + lib.tb_selecting_dut_with_vhdl_configuration.rtl.Test reset + lib.tb_selecting_dut_with_vhdl_configuration.behavioral.Test reset + lib.tb_selecting_dut_with_vhdl_configuration.rtl.Test state change + lib.tb_selecting_dut_with_vhdl_configuration.behavioral.Test state change + +The main drawback with VHDL configurations is that the VHDL standard +doesn’t allow them to be combined with top-level generics. This is a +fundamental problem for VUnit which use a ``runner_cfg`` generic to pass +information from the VUnit run script to the testbench, for example what +test cases to run. + +.. code:: vhdl + + entity tb_selecting_test_runner_with_vhdl_configuration is + generic(runner_cfg : string := null_runner_cfg); + end entity; + +The solution to this is not to remove the ``runner_cfg`` generic but to +give it the initial value ``null_runner_cfg`` to indicate that it holds +no information. Despite not providing any information the generic must +still be present since it is the signature used by VUnit to identify +VUnit testbenches when scanning the code base. + +When ``test_runner_setup`` is called with a ``null_runner_cfg`` the +procedure will get its configuration from a file named ``runner_cfg`` +that is placed by VUnit in the simulator working directory. + +Traditional OSVVM Testbenches +----------------------------- + +In the previous example the VUnit test cases were located in a process +called ``test_runner``. Having ``test_runner`` co-located with the DUT +is the simplest possible arrangement since the test cases have direct +access to the DUT interface. An alternative to this arrangement is to +place ``test_runner`` in an separate entity which is instantiated into +the the testbench. Such a ``test_runner`` entity needs access to +``runner_cfg`` and the interface ports of the DUT. + +.. code:: vhdl + + entity test_runner is + generic( + nested_runner_cfg : string + ); + port( + reset : out std_logic; + clk : in std_logic; + d : out std_logic_vector(width - 1 downto 0); + q : in std_logic_vector(width - 1 downto 0) + ); + end entity; + +Note that the runner configuration generic is called +``nested_runner_cfg`` and not ``runner_cfg`` which is the case in the +testbench. As mentioned before ``runner_cfg`` is the signature used to +identify a testbench, the top-level of a simulation. The ``test_runner`` +entity is not a simulation top-level and must not be mistaken for being +one. + +We can now replace the ``test_runner`` process with an instantiation of +this entity. + +.. code:: vhdl + + test_runner_inst : test_runner + generic map( + nested_runner_cfg => runner_cfg + ) + port map( + reset => reset, + clk => clk, + d => d, + q => q + ); + +Now that we’ve moved ``test_runner`` to a separate entity we can take +this arrangement one step further. Rather than having all test cases in +a single architecture to the ``test_runner`` entity we can put each test +case in a separate architecture along with a configuration selecting +that architecture for the testbench. Just like we used configurations to +select the DUT architecture. + +.. code:: vhdl + + architecture test_reset_a of test_runner is + begin + process + begin + test_runner_setup(runner, nested_runner_cfg); + + -- Test code here + + test_runner_cleanup(runner); + end process; + end; + + configuration test_reset of tb_selecting_test_runner_with_vhdl_configuration is + for tb + for test_runner_inst : test_runner + use entity work.test_runner(test_reset_a); + end for; + end for; + end; + +This arrangement is the traditional approach to OSVVM testbenches and +with the extended VUnit support for VHDL configurations you can now keep +that structure when adding VUnit capabilities. Below is the updated +testbench with the component declaration and instantiation. + +.. code:: vhdl + + architecture tb of tb_selecting_test_runner_with_vhdl_configuration is + ... + + -- Component declaration + component test_runner is + generic( + nested_runner_cfg : string + ); + port( + reset : out std_logic; + clk : in std_logic; + d : out std_logic_vector(width - 1 downto 0); + q : in std_logic_vector(width - 1 downto 0) + ); + end component; + begin + test_runner_inst : test_runner + generic map( + nested_runner_cfg => runner_cfg + ) + port map( + reset => reset, + clk => clk, + d => d, + q => q + ); + + ... + + dut : entity work.dff(rtl) + port map( + clk => clk, + reset => reset, + d => d, + q => q + ); + end architecture; + +If we list all tests to the testbench we will see this. + +.. code:: console + + $ python run.py --list + lib.tb_selecting_test_runner_with_vhdl_configuration.test_reset + lib.tb_selecting_test_runner_with_vhdl_configuration.test_state_change + +It looks like a regular VUnit testbench with two test cases but in +reality it is a testbench with two configurations. In practice that +makes no bigger difference. + +In the example above the DUT was an entity instantiation but nothing +prevents us from also making it a configurable component instantiation. +That will allow us to create configurations for all four combinations of +test case and DUT architecture. Here is one of those configuration +declarations: + +.. code:: vhdl + + configuration test_reset_rtl of tb_selecting_test_runner_with_vhdl_configuration is + for tb + for test_runner_inst : test_runner + use entity work.test_runner(test_reset_a); + end for; + + for test_fixture + for dut : dff + use entity work.dff(rtl); + end for; + end for; + end for; + end; + +This highlights a problem with configurations. All configuration +combinations have to be coded manually which doesn’t scale well as more +configuration options are added. What is needed is a way to do this +programatically and we’ll get back to that topic a bit later. + +Extending VHDL Configurations +----------------------------- + +Rather than treating VHDL configrations as a separate concept, VUnit +treats it as a part of the VUnit configuration concept. You can think of +the VHDL configuration as a VUnit configuration defined in VHDL instead +of in the Python run script. + +Being a part of the VUnit configuration concept means that you can +extend the VHDL configration with the other features provided by VUnit +configurations. This is done in the run script and the first step is to +retrieve one or several VHDL configrations from the testbench using +pattern matching. In the example below the pattern (``"*"``) matches all +VHDL configurations. This is also the default pattern if no argument is +provided to the ``get_configs`` method. + +.. code:: python + + tb = lib.test_bench("tb_selecting_dut_with_vhdl_configuration") + configs = tb.get_configs("*") + +Then we can extend the configurations. For example + +.. code:: python + + configs.set_post_check(my_post_check_function) + +It’s also possible to loop over the matching configurations and deal +with them independently. For example + +.. code:: python + + for config in configs: + config.set_pre_config(my_create_pre_config_function(config_name=config.name)) + +The problem with generics remains though and VHDL prevents us from doing +something like this: + +.. code:: python + + config.set_generic("my_integer_generic", 17) + +Using testbench generics to generate variants of the same testbench is +one of the more commonly used features for VUnit configurations which +makes this a serious limitation. + +Dealing with the Generics Dilemma +--------------------------------- + +To be written. diff --git a/examples/vhdl/vhdl_configuration/dff.vhd b/examples/vhdl/vhdl_configuration/dff.vhd index 6c678687a..8e0762b32 100644 --- a/examples/vhdl/vhdl_configuration/dff.vhd +++ b/examples/vhdl/vhdl_configuration/dff.vhd @@ -32,16 +32,6 @@ begin end process; end; -configuration dff_rtl of tb_selecting_dut_with_vhdl_configuration is - for tb - for test_fixture - for dut : dff - use entity work.dff(rtl); - end for; - end for; - end for; -end; - architecture behavioral of dff is begin process @@ -50,13 +40,3 @@ begin q <= (others => '0') when reset else d; end process; end; - -configuration dff_behavioral of tb_selecting_dut_with_vhdl_configuration is - for tb - for test_fixture - for dut : dff - use entity work.dff(behavioral); - end for; - end for; - end for; -end; diff --git a/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_selecting_dut_with_generate_statement.vhd b/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_selecting_dut_with_generate_statement.vhd index 02fb2b967..f33ed64e8 100644 --- a/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_selecting_dut_with_generate_statement.vhd +++ b/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_selecting_dut_with_generate_statement.vhd @@ -18,7 +18,7 @@ entity tb_selecting_dut_with_generate_statement is generic( runner_cfg : string; width : positive; - dff_arch : string + dut_arch : string ); end entity; @@ -64,7 +64,7 @@ begin begin clk <= not clk after clk_period / 2; - dut_selection : if dff_arch = "rtl" generate + dut_selection : if dut_arch = "rtl" generate dut : entity work.dff(rtl) generic map( width => width @@ -76,7 +76,7 @@ begin q => q ); - elsif dff_arch = "behavioral" generate + elsif dut_arch = "behavioral" generate dut : entity work.dff(behavioral) generic map( width => width @@ -89,7 +89,7 @@ begin ); else generate - error("Unknown DFF architecture"); + error("Unknown DUT architecture"); end generate; end block; end architecture; diff --git a/examples/vhdl/vhdl_configuration/run.py b/examples/vhdl/vhdl_configuration/run.py index 2edf0752c..f6afc4a5e 100644 --- a/examples/vhdl/vhdl_configuration/run.py +++ b/examples/vhdl/vhdl_configuration/run.py @@ -53,7 +53,7 @@ def hook(output_path): tb = lib.test_bench("tb_selecting_dut_with_generate_statement") for width in [8, 32]: for arch in ["rtl", "behavioral"]: - tb.add_config(name=f"dff_{arch}_width={width}", generics=dict(dff_arch=arch, width=width)) + tb.add_config(name=f"dut_{arch}_width={width}", generics=dict(dut_arch=arch, width=width)) # Instead of having a testbench containing a shared test fixture # and then use VHDL configurations to select different test runners implementing diff --git a/examples/vhdl/vhdl_configuration/tb_selecting_dut_with_vhdl_configuration.vhd b/examples/vhdl/vhdl_configuration/tb_selecting_dut_with_vhdl_configuration.vhd index 85f238e63..ec1b85432 100644 --- a/examples/vhdl/vhdl_configuration/tb_selecting_dut_with_vhdl_configuration.vhd +++ b/examples/vhdl/vhdl_configuration/tb_selecting_dut_with_vhdl_configuration.vhd @@ -78,7 +78,7 @@ begin begin clk <= not clk after clk_period / 2; - dut : dff + dut : component dff generic map( width => width ) @@ -90,3 +90,23 @@ begin ); end block; end architecture; + +configuration rtl of tb_selecting_dut_with_vhdl_configuration is + for tb + for test_fixture + for dut : dff + use entity work.dff(rtl); + end for; + end for; + end for; +end; + +configuration behavioral of tb_selecting_dut_with_vhdl_configuration is + for tb + for test_fixture + for dut : dff + use entity work.dff(behavioral); + end for; + end for; + end for; +end; diff --git a/examples/vhdl/vhdl_configuration/tb_selecting_test_runner_with_vhdl_configuration.vhd b/examples/vhdl/vhdl_configuration/tb_selecting_test_runner_with_vhdl_configuration.vhd index 401d04998..606af3c63 100644 --- a/examples/vhdl/vhdl_configuration/tb_selecting_test_runner_with_vhdl_configuration.vhd +++ b/examples/vhdl/vhdl_configuration/tb_selecting_test_runner_with_vhdl_configuration.vhd @@ -46,6 +46,18 @@ architecture tb of tb_selecting_test_runner_with_vhdl_configuration is ); end component; + component dff is + generic( + width : positive := width + ); + port( + clk : in std_logic; + reset : in std_logic; + d : in std_logic_vector(width - 1 downto 0); + q : out std_logic_vector(width - 1 downto 0) + ); + end component; + begin test_runner_inst : test_runner generic map( @@ -64,7 +76,7 @@ begin begin clk <= not clk after clk_period / 2; - dut : entity work.dff(rtl) + dut : component dff generic map( width => width ) diff --git a/examples/vhdl/vhdl_configuration/test_reset.vhd b/examples/vhdl/vhdl_configuration/test_reset.vhd index dcd1c348d..09205c152 100644 --- a/examples/vhdl/vhdl_configuration/test_reset.vhd +++ b/examples/vhdl/vhdl_configuration/test_reset.vhd @@ -28,10 +28,30 @@ begin test_runner_watchdog(runner, 10 * clk_period); end; -configuration test_reset of tb_selecting_test_runner_with_vhdl_configuration is +configuration test_reset_behavioral of tb_selecting_test_runner_with_vhdl_configuration is for tb for test_runner_inst : test_runner use entity work.test_runner(test_reset_a); end for; + + for test_fixture + for dut : dff + use entity work.dff(behavioral); + end for; + end for; + end for; +end; + +configuration test_reset_rtl of tb_selecting_test_runner_with_vhdl_configuration is + for tb + for test_runner_inst : test_runner + use entity work.test_runner(test_reset_a); + end for; + + for test_fixture + for dut : dff + use entity work.dff(rtl); + end for; + end for; end for; end; diff --git a/examples/vhdl/vhdl_configuration/test_state_change.vhd b/examples/vhdl/vhdl_configuration/test_state_change.vhd index d6e91575f..e6bb71e7d 100644 --- a/examples/vhdl/vhdl_configuration/test_state_change.vhd +++ b/examples/vhdl/vhdl_configuration/test_state_change.vhd @@ -34,10 +34,30 @@ begin test_runner_watchdog(runner, 10 * clk_period); end; -configuration test_state_change of tb_selecting_test_runner_with_vhdl_configuration is +configuration test_state_change_behavioral of tb_selecting_test_runner_with_vhdl_configuration is for tb for test_runner_inst : test_runner use entity work.test_runner(test_state_change_a); end for; + + for test_fixture + for dut : dff + use entity work.dff(behavioral); + end for; + end for; + end for; +end; + +configuration test_state_change_rtl of tb_selecting_test_runner_with_vhdl_configuration is + for tb + for test_runner_inst : test_runner + use entity work.test_runner(test_state_change_a); + end for; + + for test_fixture + for dut : dff + use entity work.dff(rtl); + end for; + end for; end for; end; diff --git a/tests/acceptance/test_external_run_scripts.py b/tests/acceptance/test_external_run_scripts.py index 405043404..1669e5c20 100644 --- a/tests/acceptance/test_external_run_scripts.py +++ b/tests/acceptance/test_external_run_scripts.py @@ -181,22 +181,24 @@ def test_vhdl_configuration_example_project(self): check_report( self.report_file, [ - ("passed", "lib.tb_selecting_dut_with_vhdl_configuration.dff_rtl.Test reset"), - ("passed", "lib.tb_selecting_dut_with_vhdl_configuration.dff_behavioral.Test reset"), - ("passed", "lib.tb_selecting_dut_with_vhdl_configuration.dff_rtl.Test state change"), - ("passed", "lib.tb_selecting_dut_with_vhdl_configuration.dff_behavioral.Test state change"), - ("passed", "lib.tb_selecting_test_runner_with_vhdl_configuration.test_reset"), - ("passed", "lib.tb_selecting_test_runner_with_vhdl_configuration.test_state_change"), + ("passed", "lib.tb_selecting_dut_with_vhdl_configuration.rtl.Test reset"), + ("passed", "lib.tb_selecting_dut_with_vhdl_configuration.behavioral.Test reset"), + ("passed", "lib.tb_selecting_dut_with_vhdl_configuration.rtl.Test state change"), + ("passed", "lib.tb_selecting_dut_with_vhdl_configuration.behavioral.Test state change"), + ("passed", "lib.tb_selecting_test_runner_with_vhdl_configuration.test_reset_behavioral"), + ("passed", "lib.tb_selecting_test_runner_with_vhdl_configuration.test_reset_rtl"), + ("passed", "lib.tb_selecting_test_runner_with_vhdl_configuration.test_state_change_behavioral"), + ("passed", "lib.tb_selecting_test_runner_with_vhdl_configuration.test_state_change_rtl"), ("passed", "lib.tb_reset.width=8"), ("passed", "lib.tb_reset.width=32"), - ("passed", "lib.tb_selecting_dut_with_generate_statement.dff_rtl_width=8.Test reset"), - ("passed", "lib.tb_selecting_dut_with_generate_statement.dff_behavioral_width=8.Test reset"), - ("passed", "lib.tb_selecting_dut_with_generate_statement.dff_rtl_width=32.Test reset"), - ("passed", "lib.tb_selecting_dut_with_generate_statement.dff_behavioral_width=32.Test reset"), - ("passed", "lib.tb_selecting_dut_with_generate_statement.dff_rtl_width=8.Test state change"), - ("passed", "lib.tb_selecting_dut_with_generate_statement.dff_behavioral_width=8.Test state change"), - ("passed", "lib.tb_selecting_dut_with_generate_statement.dff_rtl_width=32.Test state change"), - ("passed", "lib.tb_selecting_dut_with_generate_statement.dff_behavioral_width=32.Test state change"), + ("passed", "lib.tb_selecting_dut_with_generate_statement.dut_rtl_width=8.Test reset"), + ("passed", "lib.tb_selecting_dut_with_generate_statement.dut_behavioral_width=8.Test reset"), + ("passed", "lib.tb_selecting_dut_with_generate_statement.dut_rtl_width=32.Test reset"), + ("passed", "lib.tb_selecting_dut_with_generate_statement.dut_behavioral_width=32.Test reset"), + ("passed", "lib.tb_selecting_dut_with_generate_statement.dut_rtl_width=8.Test state change"), + ("passed", "lib.tb_selecting_dut_with_generate_statement.dut_behavioral_width=8.Test state change"), + ("passed", "lib.tb_selecting_dut_with_generate_statement.dut_rtl_width=32.Test state change"), + ("passed", "lib.tb_selecting_dut_with_generate_statement.dut_behavioral_width=32.Test state change"), ("passed", "lib.tb_state_change.width=8"), ("passed", "lib.tb_state_change.width=32"), ], From 92bd9277bd5507f77053ffc527dfdc08f8d675a4 Mon Sep 17 00:00:00 2001 From: Lars Asplund Date: Mon, 17 Jul 2023 21:59:38 +0200 Subject: [PATCH 12/13] Fixed linting issues. --- examples/vhdl/vhdl_configuration/dff.vhd | 2 +- .../handling_generics_limitation/tb_reset.vhd | 2 +- .../tb_selecting_dut_with_generate_statement.vhd | 2 +- .../handling_generics_limitation/tb_state_change.vhd | 2 +- .../handling_generics_limitation/test_fixture.vhd | 2 +- examples/vhdl/vhdl_configuration/run.py | 2 +- .../tb_selecting_dut_with_vhdl_configuration.vhd | 2 +- .../tb_selecting_test_runner_with_vhdl_configuration.vhd | 2 +- examples/vhdl/vhdl_configuration/test_reset.vhd | 2 +- examples/vhdl/vhdl_configuration/test_runner.vhd | 2 +- examples/vhdl/vhdl_configuration/test_state_change.vhd | 2 +- tests/acceptance/artificial/vhdl/arch1.vhd | 2 +- tests/acceptance/artificial/vhdl/cfg1.vhd | 2 +- tests/acceptance/artificial/vhdl/cfg2.vhd | 2 +- tests/acceptance/artificial/vhdl/ent.vhd | 2 +- .../acceptance/artificial/vhdl/tb_with_vhdl_configuration.vhd | 2 +- vunit/configuration.py | 3 +-- vunit/sim_if/ghdl.py | 1 - vunit/test/bench_list.py | 4 ++-- vunit/ui/configuration.py | 2 +- 20 files changed, 20 insertions(+), 22 deletions(-) diff --git a/examples/vhdl/vhdl_configuration/dff.vhd b/examples/vhdl/vhdl_configuration/dff.vhd index 8e0762b32..75c115f91 100644 --- a/examples/vhdl/vhdl_configuration/dff.vhd +++ b/examples/vhdl/vhdl_configuration/dff.vhd @@ -2,7 +2,7 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at http://mozilla.org/MPL/2.0/. -- --- Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com +-- Copyright (c) 2014-2023, Lars Asplund lars.anders.asplund@gmail.com library ieee; use ieee.std_logic_1164.all; diff --git a/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_reset.vhd b/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_reset.vhd index 66e4213cf..f9ea08ce5 100644 --- a/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_reset.vhd +++ b/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_reset.vhd @@ -2,7 +2,7 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at http://mozilla.org/MPL/2.0/. -- --- Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com +-- Copyright (c) 2014-2023, Lars Asplund lars.anders.asplund@gmail.com -- -- Description: Instead of having a testbench containing a shared test fixture -- and then use VHDL configurations to select different test runners implementing diff --git a/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_selecting_dut_with_generate_statement.vhd b/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_selecting_dut_with_generate_statement.vhd index f33ed64e8..98db308a4 100644 --- a/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_selecting_dut_with_generate_statement.vhd +++ b/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_selecting_dut_with_generate_statement.vhd @@ -2,7 +2,7 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at http://mozilla.org/MPL/2.0/. -- --- Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com +-- Copyright (c) 2014-2023, Lars Asplund lars.anders.asplund@gmail.com -- -- Description: This is an example of a testbench using a generic instead -- of VHDL configurations to select the DUT to run. Without VHDL configurations diff --git a/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_state_change.vhd b/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_state_change.vhd index b16060332..92c1c9fb4 100644 --- a/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_state_change.vhd +++ b/examples/vhdl/vhdl_configuration/handling_generics_limitation/tb_state_change.vhd @@ -2,7 +2,7 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at http://mozilla.org/MPL/2.0/. -- --- Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com +-- Copyright (c) 2014-2023, Lars Asplund lars.anders.asplund@gmail.com -- -- Description: Instead of having a testbench containing a shared test fixture -- and then use VHDL configurations to select different test runners implementing diff --git a/examples/vhdl/vhdl_configuration/handling_generics_limitation/test_fixture.vhd b/examples/vhdl/vhdl_configuration/handling_generics_limitation/test_fixture.vhd index 7bc4ea247..af8257a7f 100644 --- a/examples/vhdl/vhdl_configuration/handling_generics_limitation/test_fixture.vhd +++ b/examples/vhdl/vhdl_configuration/handling_generics_limitation/test_fixture.vhd @@ -2,7 +2,7 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at http://mozilla.org/MPL/2.0/. -- --- Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com +-- Copyright (c) 2014-2023, Lars Asplund lars.anders.asplund@gmail.com library ieee; use ieee.std_logic_1164.all; diff --git a/examples/vhdl/vhdl_configuration/run.py b/examples/vhdl/vhdl_configuration/run.py index f6afc4a5e..c5de66edd 100644 --- a/examples/vhdl/vhdl_configuration/run.py +++ b/examples/vhdl/vhdl_configuration/run.py @@ -4,7 +4,7 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at http://mozilla.org/MPL/2.0/. # -# Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com +# Copyright (c) 2014-2023, Lars Asplund lars.anders.asplund@gmail.com from pathlib import Path from vunit import VUnit diff --git a/examples/vhdl/vhdl_configuration/tb_selecting_dut_with_vhdl_configuration.vhd b/examples/vhdl/vhdl_configuration/tb_selecting_dut_with_vhdl_configuration.vhd index ec1b85432..0156af134 100644 --- a/examples/vhdl/vhdl_configuration/tb_selecting_dut_with_vhdl_configuration.vhd +++ b/examples/vhdl/vhdl_configuration/tb_selecting_dut_with_vhdl_configuration.vhd @@ -2,7 +2,7 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at http://mozilla.org/MPL/2.0/. -- --- Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com +-- Copyright (c) 2014-2023, Lars Asplund lars.anders.asplund@gmail.com -- -- Description: This is an example of a testbench using VHDL configurations -- to select DUT architecture diff --git a/examples/vhdl/vhdl_configuration/tb_selecting_test_runner_with_vhdl_configuration.vhd b/examples/vhdl/vhdl_configuration/tb_selecting_test_runner_with_vhdl_configuration.vhd index 606af3c63..78ee85954 100644 --- a/examples/vhdl/vhdl_configuration/tb_selecting_test_runner_with_vhdl_configuration.vhd +++ b/examples/vhdl/vhdl_configuration/tb_selecting_test_runner_with_vhdl_configuration.vhd @@ -2,7 +2,7 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at http://mozilla.org/MPL/2.0/. -- --- Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com +-- Copyright (c) 2014-2023, Lars Asplund lars.anders.asplund@gmail.com -- -- Description: This is an example of a testbench using separate architectures -- of a test runner entity to define different tests. This is a structure diff --git a/examples/vhdl/vhdl_configuration/test_reset.vhd b/examples/vhdl/vhdl_configuration/test_reset.vhd index 09205c152..e1c48fb37 100644 --- a/examples/vhdl/vhdl_configuration/test_reset.vhd +++ b/examples/vhdl/vhdl_configuration/test_reset.vhd @@ -2,7 +2,7 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at http://mozilla.org/MPL/2.0/. -- --- Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com +-- Copyright (c) 2014-2023, Lars Asplund lars.anders.asplund@gmail.com library vunit_lib; context vunit_lib.vunit_context; diff --git a/examples/vhdl/vhdl_configuration/test_runner.vhd b/examples/vhdl/vhdl_configuration/test_runner.vhd index cfa16ae77..8cf2e3ab3 100644 --- a/examples/vhdl/vhdl_configuration/test_runner.vhd +++ b/examples/vhdl/vhdl_configuration/test_runner.vhd @@ -2,7 +2,7 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at http://mozilla.org/MPL/2.0/. -- --- Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com +-- Copyright (c) 2014-2023, Lars Asplund lars.anders.asplund@gmail.com library ieee; use ieee.std_logic_1164.all; diff --git a/examples/vhdl/vhdl_configuration/test_state_change.vhd b/examples/vhdl/vhdl_configuration/test_state_change.vhd index e6bb71e7d..5037ca080 100644 --- a/examples/vhdl/vhdl_configuration/test_state_change.vhd +++ b/examples/vhdl/vhdl_configuration/test_state_change.vhd @@ -2,7 +2,7 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at http://mozilla.org/MPL/2.0/. -- --- Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com +-- Copyright (c) 2014-2023, Lars Asplund lars.anders.asplund@gmail.com library vunit_lib; context vunit_lib.vunit_context; diff --git a/tests/acceptance/artificial/vhdl/arch1.vhd b/tests/acceptance/artificial/vhdl/arch1.vhd index 81852bb12..085676720 100644 --- a/tests/acceptance/artificial/vhdl/arch1.vhd +++ b/tests/acceptance/artificial/vhdl/arch1.vhd @@ -2,7 +2,7 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at http://mozilla.org/MPL/2.0/. -- --- Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com +-- Copyright (c) 2014-2023, Lars Asplund lars.anders.asplund@gmail.com architecture arch1 of ent is begin diff --git a/tests/acceptance/artificial/vhdl/cfg1.vhd b/tests/acceptance/artificial/vhdl/cfg1.vhd index 04b9b4804..f8dec0fe5 100644 --- a/tests/acceptance/artificial/vhdl/cfg1.vhd +++ b/tests/acceptance/artificial/vhdl/cfg1.vhd @@ -2,7 +2,7 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at http://mozilla.org/MPL/2.0/. -- --- Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com +-- Copyright (c) 2014-2023, Lars Asplund lars.anders.asplund@gmail.com configuration cfg1 of tb_with_vhdl_configuration is for tb diff --git a/tests/acceptance/artificial/vhdl/cfg2.vhd b/tests/acceptance/artificial/vhdl/cfg2.vhd index 1f877a47f..12e5ea30c 100644 --- a/tests/acceptance/artificial/vhdl/cfg2.vhd +++ b/tests/acceptance/artificial/vhdl/cfg2.vhd @@ -2,7 +2,7 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at http://mozilla.org/MPL/2.0/. -- --- Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com +-- Copyright (c) 2014-2023, Lars Asplund lars.anders.asplund@gmail.com architecture arch2 of ent is begin diff --git a/tests/acceptance/artificial/vhdl/ent.vhd b/tests/acceptance/artificial/vhdl/ent.vhd index 6b34cfe27..cda1ce325 100644 --- a/tests/acceptance/artificial/vhdl/ent.vhd +++ b/tests/acceptance/artificial/vhdl/ent.vhd @@ -2,7 +2,7 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at http://mozilla.org/MPL/2.0/. -- --- Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com +-- Copyright (c) 2014-2023, Lars Asplund lars.anders.asplund@gmail.com entity ent is port(arch : out string(1 to 5)); diff --git a/tests/acceptance/artificial/vhdl/tb_with_vhdl_configuration.vhd b/tests/acceptance/artificial/vhdl/tb_with_vhdl_configuration.vhd index f8a23f6e4..206068de0 100644 --- a/tests/acceptance/artificial/vhdl/tb_with_vhdl_configuration.vhd +++ b/tests/acceptance/artificial/vhdl/tb_with_vhdl_configuration.vhd @@ -2,7 +2,7 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at http://mozilla.org/MPL/2.0/. -- --- Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com +-- Copyright (c) 2014-2023, Lars Asplund lars.anders.asplund@gmail.com library vunit_lib; context vunit_lib.vunit_context; diff --git a/vunit/configuration.py b/vunit/configuration.py index 603acbb7e..498d3e28e 100644 --- a/vunit/configuration.py +++ b/vunit/configuration.py @@ -204,8 +204,7 @@ class ConfigurationVisitor(object): def _check_enabled(self): pass - @staticmethod - def get_configuration_dicts(): + def get_configuration_dicts(self): raise NotImplementedError def set_attribute(self, name, value): diff --git a/vunit/sim_if/ghdl.py b/vunit/sim_if/ghdl.py index cd09d9cab..9e34470e1 100644 --- a/vunit/sim_if/ghdl.py +++ b/vunit/sim_if/ghdl.py @@ -92,7 +92,6 @@ def __init__( # pylint: disable=too-many-arguments gtkwave_args="", backend="llvm", ): - SimulatorInterface.__init__(self, output_path, gui) self._prefix = prefix self._project = None diff --git a/vunit/test/bench_list.py b/vunit/test/bench_list.py index db8389884..c4f9085a0 100644 --- a/vunit/test/bench_list.py +++ b/vunit/test/bench_list.py @@ -38,7 +38,7 @@ def add_from_source_file(self, source_file): self._add_test_bench(test_bench) if design_unit.is_entity: if design_unit.name not in self._vhdl_configurations: - self._vhdl_configurations[design_unit.name] = dict(test_bench=None, configurations=[]) + self._vhdl_configurations[design_unit.name] = {"test_bench": None, "configurations": []} self._vhdl_configurations[design_unit.name]["test_bench"] = test_bench for configuration in self._vhdl_configurations[design_unit.name]["configurations"]: @@ -46,7 +46,7 @@ def add_from_source_file(self, source_file): if design_unit.is_vhdl_configuration: if design_unit.entity_name not in self._vhdl_configurations: - self._vhdl_configurations[design_unit.entity_name] = dict(test_bench=None, configurations=[]) + self._vhdl_configurations[design_unit.entity_name] = {"test_bench": None, "configurations": []} self._vhdl_configurations[design_unit.entity_name]["configurations"].append(design_unit.name) if self._vhdl_configurations[design_unit.entity_name]["test_bench"]: diff --git a/vunit/ui/configuration.py b/vunit/ui/configuration.py index e00861004..298bfcc0b 100644 --- a/vunit/ui/configuration.py +++ b/vunit/ui/configuration.py @@ -2,7 +2,7 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at http://mozilla.org/MPL/2.0/. # -# Copyright (c) 2014-2022, Lars Asplund lars.anders.asplund@gmail.com +# Copyright (c) 2014-2023, Lars Asplund lars.anders.asplund@gmail.com """ UI classes ConfigurationList and Configuration From fe4aeece8f88401ba58a39d45899830d7a359835 Mon Sep 17 00:00:00 2001 From: Lars Asplund Date: Mon, 17 Jul 2023 22:44:09 +0200 Subject: [PATCH 13/13] Added configuration support to NVC. --- vunit/sim_if/nvc.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/vunit/sim_if/nvc.py b/vunit/sim_if/nvc.py index bb7165af7..39a3fab03 100644 --- a/vunit/sim_if/nvc.py +++ b/vunit/sim_if/nvc.py @@ -235,7 +235,9 @@ def compile_vhdl_file_command(self, source_file): cmd += [source_file.name] return cmd - 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 + ): # pylint: disable=too-many-branches """ Simulate with entity as top level using generics """ @@ -261,7 +263,10 @@ def simulate(self, output_path, test_suite_name, config, elaborate_only): cmd += ["-e"] cmd += config.sim_options.get("nvc.elab_flags", []) - cmd += [f"{config.entity_name}-{config.architecture_name}"] + if config.vhdl_config_name is not None: + cmd += [config.vhdl_config_name] + else: + cmd += [f"{config.entity_name}-{config.architecture_name}"] for name, value in config.generics.items(): cmd += [f"-g{name}={value}"] @@ -285,7 +290,7 @@ def simulate(self, output_path, test_suite_name, config, elaborate_only): status = True try: - proc = Process(cmd) + proc = Process(cmd, cwd=simulator_output_path) proc.consume_output() except Process.NonZeroExitCode: status = False @@ -298,6 +303,6 @@ def simulate(self, output_path, test_suite_name, config, elaborate_only): cmd += ["--script", str(Path(init_file).resolve())] stdout.write(f'{" ".join(cmd)}\n') - subprocess.call(cmd) + subprocess.call(cmd, cwd=simulator_output_path) return status