From 9c3a1eba200c2b6769aead40403e074188e11166 Mon Sep 17 00:00:00 2001 From: Lars Asplund Date: Fri, 6 Dec 2024 16:28:34 +0100 Subject: [PATCH] Added support Questa Visualizer. --- vunit/sim_if/modelsim.py | 175 ++++++++++++++++++++------- vunit/sim_if/vsim_simulator_mixin.py | 50 ++++++-- 2 files changed, 176 insertions(+), 49 deletions(-) diff --git a/vunit/sim_if/modelsim.py b/vunit/sim_if/modelsim.py index 4512dd4e3..4e3f67e55 100644 --- a/vunit/sim_if/modelsim.py +++ b/vunit/sim_if/modelsim.py @@ -51,6 +51,19 @@ class ModelSimInterface(VsimSimulatorMixin, SimulatorInterface): # pylint: disa BooleanOption("modelsim.three_step_flow"), ] + @staticmethod + def add_arguments(parser): + """ + Add command line arguments + """ + group = parser.add_argument_group("modelsim/questa", description="ModelSim/Questa specific flags") + group.add_argument( + "--debugger", + choices=["original", "visualizer"], + default="original", + help="Debugger to use.", + ) + @classmethod def from_args(cls, args, output_path, **kwargs): """ @@ -63,6 +76,7 @@ def from_args(cls, args, output_path, **kwargs): output_path=output_path, persistent=persistent, gui=args.gui, + debugger=args.debugger, ) @classmethod @@ -90,7 +104,7 @@ def supports_coverage(): """ return True - def __init__(self, prefix, output_path, persistent=False, gui=False): + def __init__(self, prefix, output_path, *, persistent=False, gui=False, debugger="original"): SimulatorInterface.__init__(self, output_path, gui) VsimSimulatorMixin.__init__( self, @@ -102,6 +116,7 @@ def __init__(self, prefix, output_path, persistent=False, gui=False): self._coverage_files = set() assert not (persistent and gui) self._create_modelsim_ini() + self._debugger = debugger # Contains design already optimized, i.e. the optimized design can be reused self._optimized_designs = {} # Contains locks for each library. If locked, a design belonging to the library @@ -248,6 +263,15 @@ def _optimize_design(self, config): return config.sim_options.get("modelsim.three_step_flow", False) + def _early_load_in_gui_mode(self): # pylint: disable=unused-argument + """ + Return True if design is to be loaded on the first vsim call rather than + in the second vsim call embedded in the script file. + + This is required for Questa Visualizer. + """ + return self._debugger == "visualizer" + @staticmethod def _design_to_optimize(config): """ @@ -282,13 +306,28 @@ def _create_optimize_function(self, config): design_to_optimize = self._design_to_optimize(config) optimized_design = self._to_optimized_design(design_to_optimize) + vopt_library_flags = [] + design_file_directory = None + for library in self._libraries: + vopt_library_flags += ["-L", library.name] + if library.name == config.library_name: + design_file_directory = library.directory + + if not design_file_directory: + raise RuntimeError(f"Failed to find library directory for {config.library_name}") + + design_file = str(Path(design_file_directory) / f"{optimized_design}.bin") + vopt_flags = [ self._vopt_extra_args(config), f"{design_to_optimize}", - f"-work {{{config.library_name}}}", + "-work", + f"{{{config.library_name}}}", "-quiet", f"-floatgenerics+{config.entity_name}.", f"-o {{{optimized_design}}}", + "-designfile", + f"{{{fix_path(design_file)}}}", ] # There is a known bug in modelsim that prevents the -modelsimini flag from accepting @@ -297,8 +336,7 @@ def _create_optimize_function(self, config): modelsimini_option = f"-modelsimini {fix_path(self._sim_cfg_file_name)!s}" vopt_flags.insert(0, modelsimini_option) - for library in self._libraries: - vopt_flags += ["-L", library.name] + vopt_flags += vopt_library_flags tcl = """ proc vunit_optimize {{vopt_extra_args ""}} {""" @@ -478,18 +516,7 @@ def _create_load_function(self, test_suite_name, config, output_path, optimize_d Create the vunit_load TCL function that runs the vsim command and loads the design """ - if optimize_design: - simulation_target = self._to_optimized_design(self._design_to_optimize(config)) - else: - simulation_target = self._design_to_optimize(config) - - set_generic_str = " ".join( - ( - f"-g/{config.entity_name!s}/{name!s}={encode_generic_value(value)!s}" - for name, value in config.generics.items() - ) - ) - pli_str = " ".join(f"-pli {{{fix_path(name)!s}}}" for name in config.sim_options.get("pli", [])) + vsim_flags = self._get_vsim_flags(config, output_path, optimize_design) if config.sim_options.get("enable_coverage", False): coverage_file = str(Path(output_path) / "coverage.ucdb") @@ -498,32 +525,8 @@ def _create_load_function(self, test_suite_name, config, output_path, optimize_d f"coverage save -onexit -testname {{{test_suite_name!s}}} -assert -directive " f"-cvg -codeAll {{{fix_path(coverage_file)!s}}}" ) - coverage_args = "-coverage" else: coverage_save_cmd = "" - coverage_args = "" - - vsim_flags = [ - f"-wlf {{{fix_path(str(Path(output_path) / 'vsim.wlf'))!s}}}", - f"-work {{{config.library_name}}}", - "-quiet", - "-t ps", - # for correct handling of verilog fatal/finish - "-onfinish stop", - pli_str, - set_generic_str, - simulation_target, - coverage_args, - self._vsim_extra_args(config), - ] - - # There is a known bug in modelsim that prevents the -modelsimini flag from accepting - # a space in the path even with escaping, see issue #36 - if " " not in self._sim_cfg_file_name: - vsim_flags.insert(0, f"-modelsimini {fix_path(self._sim_cfg_file_name)!s}") - - for library in self._libraries: - vsim_flags += ["-L", library.name] vhdl_assert_stop_level_mapping = {"warning": 1, "error": 2, "failure": 3} @@ -566,6 +569,84 @@ def _create_load_function(self, test_suite_name, config, output_path, optimize_d return tcl + def _common_vsim_flags(self, config, output_path, optimize_design): + """Return vsim flags to normal and early load mode.""" + if optimize_design: + simulation_target = self._to_optimized_design(self._design_to_optimize(config)) + else: + simulation_target = self._design_to_optimize(config) + + pli_str = " ".join(f"-pli {{{fix_path(name)!s}}}" for name in config.sim_options.get("pli", [])) + + if config.sim_options.get("enable_coverage", False): + coverage_args = "-coverage" + else: + coverage_args = "" + + vsim_flags = [ + simulation_target, + "-wlf", + f"{fix_path(str(Path(output_path) / 'vsim.wlf'))}", + "-work", + f"{config.library_name}", + "-quiet", + pli_str, + coverage_args, + self._vsim_extra_args(config), + ] + + # There is a known bug in modelsim that prevents the -modelsimini flag from accepting + # a space in the path even with escaping, see issue #36 + if " " not in self._sim_cfg_file_name: + vsim_flags.insert(1, "-modelsimini") + vsim_flags.insert(2, f"{fix_path(self._sim_cfg_file_name)}") + + for library in self._libraries: + vsim_flags += ["-L", library.name] + + return vsim_flags + + def _get_vsim_flags(self, config, output_path, optimize_design): + """Return vsim flags for load function.""" + vsim_flags = self._common_vsim_flags(config, output_path, optimize_design) + + set_generic_str = " ".join( + ( + f"-g/{config.entity_name!s}/{name!s}={encode_generic_value(value)!s}" + for name, value in config.generics.items() + ) + ) + + vsim_flags += [ + set_generic_str, + "-t ps", + # for correct handling of Verilog fatal/finish + "-onfinish stop", + ] + + return vsim_flags + + def _get_load_flags(self, config, output_path, optimize_design): + """ + Return extra flags needed for the first vsim call in GUI mode when early load is enabled. + + This is required for Questa Visualizer. + """ + vsim_flags = self._common_vsim_flags(config, output_path, optimize_design) + + set_generic_str = " ".join( + ( + f"-g/{config.entity_name!s}/{name!s}={encode_generic_value_for_file(value)!s}" + for name, value in config.generics.items() + ) + ) + generics_file_name = Path(output_path) / "generics.flags" + write_file(str(generics_file_name), set_generic_str) + + vsim_flags += ["-visualizer", "-f", f"{fix_path(str(generics_file_name))}"] + + return vsim_flags + @staticmethod def _create_run_function(): """ @@ -666,7 +747,7 @@ def get_env(): def encode_generic_value(value): """ - Ensure values with space in them are quoted + Ensure values with space in them are quoted properly for the command line """ s_value = str(value) if " " in s_value: @@ -676,6 +757,18 @@ def encode_generic_value(value): return s_value +def encode_generic_value_for_file(value): + """ + Ensure values with space in them are quoted properly for argument files + """ + s_value = str(value) + if " " in s_value: + return f"'\"{s_value}\"'" + if "," in s_value: + return f"'\"{s_value}\"'" + return s_value + + def parse_modelsimini(file_name): """ Parse a modelsim.ini file diff --git a/vunit/sim_if/vsim_simulator_mixin.py b/vunit/sim_if/vsim_simulator_mixin.py index 583b26c13..1d0f8b2ec 100644 --- a/vunit/sim_if/vsim_simulator_mixin.py +++ b/vunit/sim_if/vsim_simulator_mixin.py @@ -290,19 +290,24 @@ def _source_tcl_file(file_name, config, message): ) return tcl - def _create_gui_script(self, common_file_name, config): + def _create_gui_script(self, common_file_name, config, include_vunit_load=True): """ Create the user facing script which loads common functions and prints a help message """ tcl = f'source "{fix_path(common_file_name)!s}"\n' tcl += self._create_user_init_function(config) - tcl += "if {![vunit_load]} {\n" - tcl += " vunit_user_init\n" - tcl += " vunit_help\n" - tcl += "}\n" + if include_vunit_load: + tcl += "if {![vunit_load]} {\n" + tcl += " vunit_user_init\n" + tcl += " vunit_help\n" + tcl += "}\n" + else: + tcl += "vunit_user_init\n" + tcl += "vunit_help\n" + return tcl - def _run_batch_file(self, batch_file_name, gui=False): + def _run_batch_file(self, batch_file_name, gui=False, extra_args=None): """ Run a test bench in batch by invoking a new vsim process from the command line """ @@ -317,6 +322,9 @@ def _run_batch_file(self, batch_file_name, gui=False): f'source "{fix_path(batch_file_name)!s}"', ] + if extra_args: + args += extra_args + proc = Process(args, cwd=str(Path(self._sim_cfg_file_name).parent)) proc.consume_output() except Process.NonZeroExitCode: @@ -354,6 +362,23 @@ def _optimize(self, config, script_path): # pylint: disable=unused-argument """ return False + def _early_load_in_gui_mode(self): # pylint: disable=unused-argument + """ + Return True if design is to be loaded on the first vsim call rather than + in the second vsim call embedded in the script file. + + This is required for Questa Visualizer. + """ + return False + + def _get_load_flags(self, config, output_path, optimize_design): # pylint: disable=unused-argument + """ + Return extra flags needed for the first vsim call in GUI mode when early load is enabled. + + This is required for Questa Visualizer. + """ + return [] + def simulate(self, output_path, test_suite_name, config, elaborate_only): """ Run a test bench @@ -375,14 +400,23 @@ def simulate(self, output_path, test_suite_name, config, elaborate_only): test_suite_name, config, script_path, output_path, optimize_design=optimize_design ), ) - write_file(str(gui_file_name), self._create_gui_script(str(common_file_name), config)) + + early_load = self._gui and self._early_load_in_gui_mode() + write_file( + str(gui_file_name), + self._create_gui_script(str(common_file_name), config, include_vunit_load=not early_load), + ) write_file( str(batch_file_name), self._create_batch_script(str(common_file_name), 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), + gui=True, + extra_args=self._get_load_flags(config, output_path, optimize_design) if early_load else None, + ) if self._persistent_shell is not None: return self._run_persistent(str(common_file_name), load_only=elaborate_only)