diff --git a/docs/py/vunit.rst b/docs/py/vunit.rst index b960f50c58..1a10ba767a 100644 --- a/docs/py/vunit.rst +++ b/docs/py/vunit.rst @@ -30,6 +30,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 194201db9d..93ca23af27 100644 --- a/examples/vhdl/vhdl_configuration/run.py +++ b/examples/vhdl/vhdl_configuration/run.py @@ -19,23 +19,30 @@ # 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 ac0778bbac..c10e593234 100644 --- a/tests/acceptance/artificial/vhdl/run.py +++ b/tests/acceptance/artificial/vhdl/run.py @@ -96,7 +96,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 5af5d541ed..bdf9621203 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 b3bb952d6d..acf98b0e68 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 987580e0d1..a18ddb08fa 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 6fdbcf9bab..8cb75c8b79 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 51f9abf683..8acf997477 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 6d36b68307..b036d9b6b1 100644 --- a/vunit/sim_if/activehdl.py +++ b/vunit/sim_if/activehdl.py @@ -238,12 +238,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 99b5103852..11e47bc9c7 100644 --- a/vunit/sim_if/ghdl.py +++ b/vunit/sim_if/ghdl.py @@ -286,8 +286,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 8b5d3702e1..386bd2d8c0 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 c024985a74..a0a37d7ed1 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 bb75ff5085..09cd3f8818 100644 --- a/vunit/sim_if/rivierapro.py +++ b/vunit/sim_if/rivierapro.py @@ -301,7 +301,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] @@ -309,7 +309,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 180845d60e..78dadb8a07 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 da81edff66..4bb3dcf3a0 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 c1f32252ab..bc4d3f1566 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): + """ """ + raise NotImplementedError( + f"{type(self)} do not allow addition of new configurations, only modification of existing ones." + ) + + def delete_config(self, *args, **kwargs): + """ """ + 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( + """ """ + raise NotImplementedError( f"{type(self)} do not allow addition of new configurations, only modification of existing ones." ) + + def delete_config(self, *args, **kwargs): + """ """ + 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 9e2e0c32df..2792910004 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 f41f7b1679..25c8b00ff9 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): """