From 1cbcf25bc42e0935328ea779953333f9be178089 Mon Sep 17 00:00:00 2001 From: Nathan Francque Date: Sun, 6 Mar 2022 16:31:23 -0500 Subject: [PATCH 1/4] Add feature to select simulator progamatically with a simple test --- tests/unit/test_ui.py | 10 ++++++ vunit/sim_if/__init__.py | 21 +++++++++++++ vunit/sim_if/factory.py | 18 +++++++++++ vunit/ui/__init__.py | 66 +++++++++++++++++++++++++++++++++------- 4 files changed, 104 insertions(+), 11 deletions(-) diff --git a/tests/unit/test_ui.py b/tests/unit/test_ui.py index 9b0447c18..2112a74df 100644 --- a/tests/unit/test_ui.py +++ b/tests/unit/test_ui.py @@ -1143,6 +1143,16 @@ def test_get_simulator_name(self): ui = self._create_ui() self.assertEqual(ui.get_simulator_name(), "mock") + def test_set_simulator(self): + ui = self._create_ui() + prefix = ui.get_simulator_prefix() + # Just change it + prefix = Path("mock") + ui.set_simulator("modelsim", "modelsim_2021_2", prefix) + self.assertEqual(ui.get_simulator_name(), "modelsim") + self.assertEqual(ui.get_simulator_id(), "modelsim_2021_2") + self.assertEqual(ui.get_simulator_prefix(), prefix) + def _create_ui(self, *args): """Create an instance of the VUnit public interface class""" with mock.patch( diff --git a/vunit/sim_if/__init__.py b/vunit/sim_if/__init__.py index 18d2bfdbf..ebcb39778 100644 --- a/vunit/sim_if/__init__.py +++ b/vunit/sim_if/__init__.py @@ -52,6 +52,8 @@ class SimulatorInterface(object): # pylint: disable=too-many-public-methods def __init__(self, output_path, gui): self._output_path = output_path self._gui = gui + self.id = None + self.prefix = None @property def output_path(self): @@ -310,6 +312,25 @@ def get_env(): Allows inheriting classes to overload this to modify environment variables. Return None for default environment """ + def set_id(self, id): + """ + Sets id of simulator, used for unique separate output directories for multiple versions of a class + param: id: The id to use + """ + if id is None: + self.id = self.name + else: + self.id = id + + def set_prefix(self, prefix): + """ + Sets prefix of simulator, if set overrides PATH searching + param: prefix: The prefix to use + """ + if prefix is None: + self.prefix = None + else: + self.prefix = prefix def isfile(file_name): """ diff --git a/vunit/sim_if/factory.py b/vunit/sim_if/factory.py index 038b30609..af3fd952f 100644 --- a/vunit/sim_if/factory.py +++ b/vunit/sim_if/factory.py @@ -126,6 +126,24 @@ def select_simulator(self): return simulator_class + def get_simulator(self, simulator): + """ + Get a simulator class by name + param: simulator: Name of a supported simulator + """ + supported_simulators = self.supported_simulators() + name_mapping = { + simulator_class.name: simulator_class + for simulator_class in self.supported_simulators() + } + if simulator not in name_mapping: + raise ValueError( + f"Unknown simulator {simulator}, expected one of {supported_simulators}" + ) + + simulator = name_mapping[simulator] + return simulator + def add_arguments(self, parser): """ Add command line arguments to parser diff --git a/vunit/ui/__init__.py b/vunit/ui/__init__.py index bb6a868b6..b12353e5c 100644 --- a/vunit/ui/__init__.py +++ b/vunit/ui/__init__.py @@ -190,6 +190,7 @@ def test_filter(name, attribute_names): """ ) print(hline) + self._simulator_if = self._create_simulator_if(for_init=True) def _create_database(self): """ @@ -748,18 +749,26 @@ def _main(self, post_run): all_ok = self._main_run(post_run) return all_ok - def _create_simulator_if(self): + def _create_simulator_if(self, for_init=False): """ Create new simulator instance + param: for_init: Allows missing simulator with a warning instead of an error """ if self._simulator_class is None: - LOGGER.error( + missing_simulator_msg = ( "No available simulator detected.\n" "Simulator binary folder must be available in PATH environment variable.\n" "Simulator binary folder can also be set the in VUNIT__PATH environment variable.\n" ) - sys.exit(1) + if for_init: + LOGGER.warning( + missing_simulator_msg + "Please set a simulator either through environment variables or through set_simulator" + ) + return None + else: + LOGGER.error(missing_simulator_msg) + sys.exit(1) if not Path(self._simulator_output_path).exists(): os.makedirs(self._simulator_output_path) @@ -770,9 +779,10 @@ def _main_run(self, post_run): """ Main with running tests """ - simulator_if = self._create_simulator_if() - test_list = self._create_tests(simulator_if) - self._compile(simulator_if) + if not self._simulator_if: + self._simulator_if = self._create_simulator_if() + test_list = self._create_tests(self._simulator_if) + self._compile(self._simulator_if) print() start_time = ostools.get_time() @@ -790,9 +800,7 @@ def _main_run(self, post_run): report.print_str() if post_run is not None: - post_run(results=Results(self._output_path, simulator_if, report)) - - del simulator_if + post_run(results=Results(self._output_path, self._simulator_if, report)) if self._args.xunit_xml is not None: xml = report.to_junit_xml_str(self._args.xunit_xml_format) @@ -879,8 +887,9 @@ def _main_compile_only(self): """ Main function when only compiling """ - simulator_if = self._create_simulator_if() - self._compile(simulator_if) + if not self._simulator_if: + self._simulator_if = self._create_simulator_if() + self._compile(self._simulator_if) return True def _create_output_path(self, clean: bool): @@ -1074,6 +1083,24 @@ def get_simulator_name(self): return None return self._simulator_class.name + def get_simulator_id(self): + """ + Get the id of the simulator used. + Will return None if no simulator was found. + """ + if self._simulator_if is None: + return None + return self._simulator_if.id + + def get_simulator_prefix(self): + """ + Get the perfix of the simulator used. + Will return None if no simulator was found. + """ + if self._simulator_if is None: + return None + return self._simulator_if.prefix + def simulator_supports_coverage(self): """ Returns True when the simulator supports coverage @@ -1083,3 +1110,20 @@ def simulator_supports_coverage(self): if self._simulator_class is None: return None return self._simulator_class.supports_coverage() + + + def set_simulator(self, simulator, id=None, prefix=None): + """ + Set the simulator to use, alternative to using environment variables + Useful for frequent simulator switching + param: simulator: Name of simulator to use + param: id: Optionally specify unique name for this instance of this simulator + param: prefix: Optionally specify prefix of the simulator + """ + self._simulator_class = SIMULATOR_FACTORY.get_simulator(simulator) + self._simulator_if = self._create_simulator_if() + self._simulator_if.set_id(id) + self._simulator_if.set_prefix(prefix) + self._simulator_output_path = str( + Path(self._output_path) / self.get_simulator_id() + ) From ddc8fe25244b6006ea44c1f77ee430aaf79a9d77 Mon Sep 17 00:00:00 2001 From: Nathan Francque Date: Sun, 6 Mar 2022 16:46:05 -0500 Subject: [PATCH 2/4] prefix is now a public attribute --- vunit/sim_if/activehdl.py | 16 ++++++++-------- vunit/sim_if/ghdl.py | 6 +++--- vunit/sim_if/incisive.py | 12 ++++++------ vunit/sim_if/modelsim.py | 10 +++++----- vunit/sim_if/rivierapro.py | 14 +++++++------- vunit/sim_if/vsim_simulator_mixin.py | 6 +++--- 6 files changed, 32 insertions(+), 32 deletions(-) diff --git a/vunit/sim_if/activehdl.py b/vunit/sim_if/activehdl.py index f087ee10d..84166826f 100644 --- a/vunit/sim_if/activehdl.py +++ b/vunit/sim_if/activehdl.py @@ -76,7 +76,7 @@ def supports_coverage(): def __init__(self, prefix, output_path, gui=False): SimulatorInterface.__init__(self, output_path, gui) self._library_cfg = str(Path(output_path) / "library.cfg") - self._prefix = prefix + self.prefix = prefix self._create_library_cfg() self._libraries = [] self._coverage_files = set() @@ -120,7 +120,7 @@ def compile_vhdl_file_command(self, source_file): """ return ( [ - str(Path(self._prefix) / "vcom"), + str(Path(self.prefix) / "vcom"), "-quiet", "-j", str(Path(self._library_cfg).parent), @@ -138,7 +138,7 @@ def compile_verilog_file_command(self, source_file): """ Returns the command to compile a Verilog file """ - args = [str(Path(self._prefix) / "vlog"), "-quiet", "-lc", self._library_cfg] + args = [str(Path(self.prefix) / "vlog"), "-quiet", "-lc", self._library_cfg] args += source_file.compile_options.get("activehdl.vlog_flags", []) args += ["-work", source_file.library.name, source_file.name] for library in self._libraries: @@ -162,7 +162,7 @@ def create_library(self, library_name, path, mapped_libraries=None): if not file_exists(path): proc = Process( - [str(Path(self._prefix) / "vlib"), library_name, path], + [str(Path(self.prefix) / "vlib"), library_name, path], cwd=str(Path(self._library_cfg).parent), env=self.get_env(), ) @@ -172,7 +172,7 @@ def create_library(self, library_name, path, mapped_libraries=None): return proc = Process( - [str(Path(self._prefix) / "vmap"), library_name, path], + [str(Path(self.prefix) / "vmap"), library_name, path], cwd=str(Path(self._library_cfg).parent), env=self.get_env(), ) @@ -186,7 +186,7 @@ def _create_library_cfg(self): return with Path(self._library_cfg).open("w", encoding="utf-8") as ofile: - ofile.write(f'$INCLUDE = "{str(Path(self._prefix).parent / "vlib" / "library.cfg")}"\n') + ofile.write(f'$INCLUDE = "{str(Path(self.prefix).parent / "vlib" / "library.cfg")}"\n') _library_re = re.compile(r'([a-zA-Z_]+)\s=\s"(.*)"') @@ -324,7 +324,7 @@ def merge_coverage(self, file_name, args=None): fptr.write(merge_command + "\n") vcover_cmd = [ - str(Path(self._prefix) / "vsimsa"), + str(Path(self.prefix) / "vsimsa"), "-tcl", str(fix_path(merge_script_name)), ] @@ -393,7 +393,7 @@ def _run_batch_file(self, batch_file_name, gui, cwd): try: args = [ - str(Path(self._prefix) / "vsim"), + str(Path(self.prefix) / "vsim"), "-gui" if gui else "-c", "-l", str(Path(batch_file_name).parent / "transcript"), diff --git a/vunit/sim_if/ghdl.py b/vunit/sim_if/ghdl.py index 9ba32440e..002a4cc76 100644 --- a/vunit/sim_if/ghdl.py +++ b/vunit/sim_if/ghdl.py @@ -94,7 +94,7 @@ def __init__( # pylint: disable=too-many-arguments backend="llvm", ): SimulatorInterface.__init__(self, output_path, gui) - self._prefix = prefix + self.prefix = prefix self._project = None if gui and (not self.find_executable("gtkwave")): @@ -237,7 +237,7 @@ def compile_vhdl_file_command(self, source_file): Returns the command to compile a vhdl file """ cmd = [ - str(Path(self._prefix) / self.executable), + str(Path(self.prefix) / self.executable), "-a", f"--workdir={source_file.library.directory!s}", f"--work={source_file.library.name!s}", @@ -269,7 +269,7 @@ def _get_command(self, config, output_path, elaborate_only, ghdl_e, wave_file): """ Return GHDL simulation command """ - cmd = [str(Path(self._prefix) / self.executable)] + cmd = [str(Path(self.prefix) / self.executable)] cmd += ["-e"] if ghdl_e else ["--elab-run"] diff --git a/vunit/sim_if/incisive.py b/vunit/sim_if/incisive.py index 52c934053..6294d2398 100644 --- a/vunit/sim_if/incisive.py +++ b/vunit/sim_if/incisive.py @@ -87,7 +87,7 @@ def __init__( # pylint: disable=too-many-arguments self, prefix, output_path, gui=False, log_level=None, cdslib=None, hdlvar=None ): SimulatorInterface.__init__(self, output_path, gui) - self._prefix = prefix + self.prefix = prefix self._libraries = [] self._log_level = log_level if cdslib is None: @@ -102,14 +102,14 @@ def find_cds_root_irun(self): """ Finds irun cds root """ - return subprocess.check_output([str(Path(self._prefix) / "cds_root"), "irun"]).splitlines()[0] + return subprocess.check_output([str(Path(self.prefix) / "cds_root"), "irun"]).splitlines()[0] def find_cds_root_virtuoso(self): """ Finds virtuoso cds root """ try: - return subprocess.check_output([str(Path(self._prefix) / "cds_root"), "virtuoso"]).splitlines()[0] + return subprocess.check_output([str(Path(self.prefix) / "cds_root"), "virtuoso"]).splitlines()[0] except subprocess.CalledProcessError: return None @@ -182,7 +182,7 @@ def compile_vhdl_file_command(self, source_file): """ Returns command to compile a VHDL file """ - cmd = str(Path(self._prefix) / "irun") + cmd = str(Path(self.prefix) / "irun") args = [] args += ["-compile"] args += ["-nocopyright"] @@ -212,7 +212,7 @@ def compile_verilog_file_command(self, source_file): """ Returns commands to compile a Verilog file """ - cmd = str(Path(self._prefix) / "irun") + cmd = str(Path(self.prefix) / "irun") args = [] args += ["-compile"] args += ["-nocopyright"] @@ -291,7 +291,7 @@ def simulate(self, output_path, test_suite_name, config, elaborate_only=False): steps = ["elaborate", "simulate"] for step in steps: - cmd = str(Path(self._prefix) / "irun") + cmd = str(Path(self.prefix) / "irun") args = [] if step == "elaborate": args += ["-elaborate"] diff --git a/vunit/sim_if/modelsim.py b/vunit/sim_if/modelsim.py index f44035a81..db3447617 100644 --- a/vunit/sim_if/modelsim.py +++ b/vunit/sim_if/modelsim.py @@ -106,7 +106,7 @@ def _create_modelsim_ini(self): if not file_exists(parent): os.makedirs(parent) - original_modelsim_ini = os.environ.get("VUNIT_MODELSIM_INI", str(Path(self._prefix).parent / "modelsim.ini")) + original_modelsim_ini = os.environ.get("VUNIT_MODELSIM_INI", str(Path(self.prefix).parent / "modelsim.ini")) with Path(original_modelsim_ini).open("rb") as fread: with Path(self._sim_cfg_file_name).open("wb") as fwrite: fwrite.write(fread.read()) @@ -159,7 +159,7 @@ def compile_vhdl_file_command(self, source_file): """ return ( [ - str(Path(self._prefix) / "vcom"), + str(Path(self.prefix) / "vcom"), "-quiet", "-modelsimini", self._sim_cfg_file_name, @@ -178,7 +178,7 @@ def compile_verilog_file_command(self, source_file): Returns the command to compile a verilog file """ args = [ - str(Path(self._prefix) / "vlog"), + str(Path(self.prefix) / "vlog"), "-quiet", "-modelsimini", self._sim_cfg_file_name, @@ -208,7 +208,7 @@ def create_library(self, library_name, path, mapped_libraries=None): os.makedirs(apath) if not file_exists(path): - proc = Process([str(Path(self._prefix) / "vlib"), "-unix", path], env=self.get_env()) + proc = Process([str(Path(self.prefix) / "vlib"), "-unix", path], env=self.get_env()) proc.consume_output(callback=None) if library_name in mapped_libraries and mapped_libraries[library_name] == path: @@ -378,7 +378,7 @@ def merge_coverage(self, file_name, args=None): args = [] coverage_files = str(Path(self._output_path) / "coverage_files.txt") - vcover_cmd = [str(Path(self._prefix) / "vcover"), "merge", "-inputs"] + [coverage_files] + args + [file_name] + vcover_cmd = [str(Path(self.prefix) / "vcover"), "merge", "-inputs"] + [coverage_files] + args + [file_name] with Path(coverage_files).open("w", encoding="utf-8") as fptr: for coverage_file in self._coverage_files: if file_exists(coverage_file): diff --git a/vunit/sim_if/rivierapro.py b/vunit/sim_if/rivierapro.py index 012b944d1..51f88acc3 100644 --- a/vunit/sim_if/rivierapro.py +++ b/vunit/sim_if/rivierapro.py @@ -173,7 +173,7 @@ def compile_vhdl_file_command(self, source_file): return ( [ - str(Path(self._prefix) / "vcom"), + str(Path(self.prefix) / "vcom"), "-quiet", "-j", str(Path(self._sim_cfg_file_name).parent), @@ -192,7 +192,7 @@ def compile_verilog_file_command(self, source_file): Returns the command to compile a Verilog file """ args = [ - str(Path(self._prefix) / "vlog"), + str(Path(self.prefix) / "vlog"), "-quiet", "-lc", self._sim_cfg_file_name, @@ -224,7 +224,7 @@ def create_library(self, library_name, path, mapped_libraries=None): if not file_exists(path): proc = Process( - [str(Path(self._prefix) / "vlib"), library_name, path], + [str(Path(self.prefix) / "vlib"), library_name, path], cwd=str(Path(self._sim_cfg_file_name).parent), env=self.get_env(), ) @@ -234,7 +234,7 @@ def create_library(self, library_name, path, mapped_libraries=None): return proc = Process( - [str(Path(self._prefix) / "vmap"), library_name, path], + [str(Path(self.prefix) / "vmap"), library_name, path], cwd=str(Path(self._sim_cfg_file_name).parent), env=self.get_env(), ) @@ -252,7 +252,7 @@ def _create_library_cfg(self): @property def _builtin_library_cfg(self): - return str(Path(self._prefix).parent / "vlib" / "library.cfg") + return str(Path(self.prefix).parent / "vlib" / "library.cfg") _library_re = re.compile(r"([a-zA-Z_0-9]+)\s=\s(.*)") @@ -261,7 +261,7 @@ def _get_mapped_libraries(self, library_cfg_file): Get mapped libraries by running vlist on the working directory """ lines = [] - proc = Process([str(Path(self._prefix) / "vlist")], cwd=str(Path(library_cfg_file).parent)) + proc = Process([str(Path(self.prefix) / "vlist")], cwd=str(Path(library_cfg_file).parent)) proc.consume_output(callback=lines.append) libraries = {} @@ -412,7 +412,7 @@ def merge_coverage(self, file_name, args=None): mscript = str(merge_script_name).replace("\\", "/") vcover_cmd = [ - str(Path(self._prefix) / "vsim"), + str(Path(self.prefix) / "vsim"), "-c", "-do", f"source {{{mscript}}}; quit;", diff --git a/vunit/sim_if/vsim_simulator_mixin.py b/vunit/sim_if/vsim_simulator_mixin.py index ff21c0e04..144521ea2 100644 --- a/vunit/sim_if/vsim_simulator_mixin.py +++ b/vunit/sim_if/vsim_simulator_mixin.py @@ -24,11 +24,11 @@ class VsimSimulatorMixin(object): """ def __init__(self, prefix, persistent, sim_cfg_file_name): - self._prefix = prefix + self.prefix = prefix sim_cfg_file_name = str(Path(sim_cfg_file_name).resolve()) self._sim_cfg_file_name = sim_cfg_file_name - prefix = self._prefix # Avoid circular dependency inhibiting process destruction + prefix = self.prefix # Avoid circular dependency inhibiting process destruction env = self.get_env() def create_process(ident): @@ -270,7 +270,7 @@ def _run_batch_file(self, batch_file_name, gui=False): try: args = [ - str(Path(self._prefix) / "vsim"), + str(Path(self.prefix) / "vsim"), "-gui" if gui else "-c", "-l", str(Path(batch_file_name).parent / "transcript"), From c7079d0893f82f1a3beb4871829ed746a82178be Mon Sep 17 00:00:00 2001 From: Nathan Francque Date: Sun, 6 Mar 2022 17:15:35 -0500 Subject: [PATCH 3/4] Fix default prefix --- vunit/sim_if/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vunit/sim_if/__init__.py b/vunit/sim_if/__init__.py index ebcb39778..0e238fc6f 100644 --- a/vunit/sim_if/__init__.py +++ b/vunit/sim_if/__init__.py @@ -328,7 +328,7 @@ def set_prefix(self, prefix): param: prefix: The prefix to use """ if prefix is None: - self.prefix = None + self.prefix = self.__class__.find_prefix() else: self.prefix = prefix From 52a4a9789ca04f28f4b8056fb4b1dc43a05da473 Mon Sep 17 00:00:00 2001 From: Nathan Francque Date: Mon, 7 Mar 2022 07:20:36 -0500 Subject: [PATCH 4/4] Run black --- tests/common.py | 2 +- vunit/sim_if/__init__.py | 1 + vunit/sim_if/factory.py | 11 +++-------- vunit/ui/__init__.py | 18 ++++++++---------- 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/tests/common.py b/tests/common.py index a6f9af5b4..c8f024dee 100644 --- a/tests/common.py +++ b/tests/common.py @@ -92,7 +92,7 @@ def create_tempdir(path: Path = None): """ if path is None: - path = Path(__file__).parent / ("tempdir_%i" % random.randint(0, 2**64 - 1)) + path = Path(__file__).parent / ("tempdir_%i" % random.randint(0, 2 ** 64 - 1)) if path.exists(): shutil.rmtree(path) diff --git a/vunit/sim_if/__init__.py b/vunit/sim_if/__init__.py index 0e238fc6f..0638c7c96 100644 --- a/vunit/sim_if/__init__.py +++ b/vunit/sim_if/__init__.py @@ -332,6 +332,7 @@ def set_prefix(self, prefix): else: self.prefix = prefix + def isfile(file_name): """ Case insensitive Path.is_file() diff --git a/vunit/sim_if/factory.py b/vunit/sim_if/factory.py index af3fd952f..824e666e0 100644 --- a/vunit/sim_if/factory.py +++ b/vunit/sim_if/factory.py @@ -128,18 +128,13 @@ def select_simulator(self): def get_simulator(self, simulator): """ - Get a simulator class by name + Get a simulator class by name param: simulator: Name of a supported simulator """ supported_simulators = self.supported_simulators() - name_mapping = { - simulator_class.name: simulator_class - for simulator_class in self.supported_simulators() - } + name_mapping = {simulator_class.name: simulator_class for simulator_class in self.supported_simulators()} if simulator not in name_mapping: - raise ValueError( - f"Unknown simulator {simulator}, expected one of {supported_simulators}" - ) + raise ValueError(f"Unknown simulator {simulator}, expected one of {supported_simulators}") simulator = name_mapping[simulator] return simulator diff --git a/vunit/ui/__init__.py b/vunit/ui/__init__.py index b12353e5c..a02da2b31 100644 --- a/vunit/ui/__init__.py +++ b/vunit/ui/__init__.py @@ -752,7 +752,7 @@ def _main(self, post_run): def _create_simulator_if(self, for_init=False): """ Create new simulator instance - param: for_init: Allows missing simulator with a warning instead of an error + param: for_init: Allows missing simulator with a warning instead of an error """ if self._simulator_class is None: @@ -763,7 +763,8 @@ def _create_simulator_if(self, for_init=False): ) if for_init: LOGGER.warning( - missing_simulator_msg + "Please set a simulator either through environment variables or through set_simulator" + missing_simulator_msg + + "Please set a simulator either through environment variables or through set_simulator" ) return None else: @@ -1111,19 +1112,16 @@ def simulator_supports_coverage(self): return None return self._simulator_class.supports_coverage() - def set_simulator(self, simulator, id=None, prefix=None): """ Set the simulator to use, alternative to using environment variables - Useful for frequent simulator switching - param: simulator: Name of simulator to use - param: id: Optionally specify unique name for this instance of this simulator - param: prefix: Optionally specify prefix of the simulator + Useful for frequent simulator switching + param: simulator: Name of simulator to use + param: id: Optionally specify unique name for this instance of this simulator + param: prefix: Optionally specify prefix of the simulator """ self._simulator_class = SIMULATOR_FACTORY.get_simulator(simulator) self._simulator_if = self._create_simulator_if() self._simulator_if.set_id(id) self._simulator_if.set_prefix(prefix) - self._simulator_output_path = str( - Path(self._output_path) / self.get_simulator_id() - ) + self._simulator_output_path = str(Path(self._output_path) / self.get_simulator_id())