From f4d7dc18ea94af34b58cdbf28ffdee1abea0c8af Mon Sep 17 00:00:00 2001 From: Peter Findlay Date: Thu, 13 Aug 2026 17:26:55 +0200 Subject: [PATCH 1/3] adding in draft support for qsim --- vunit/sim_if/modelsim.py | 104 +++++++++++++++++++-------- vunit/sim_if/vsim_simulator_mixin.py | 10 ++- 2 files changed, 84 insertions(+), 30 deletions(-) diff --git a/vunit/sim_if/modelsim.py b/vunit/sim_if/modelsim.py index d0ee6a13a..ec728bed2 100644 --- a/vunit/sim_if/modelsim.py +++ b/vunit/sim_if/modelsim.py @@ -59,7 +59,7 @@ def add_arguments(parser): group = parser.add_argument_group("modelsim/questa", description="ModelSim/Questa specific flags") group.add_argument( "--debugger", - choices=["original", "visualizer"], + choices=["original", "visualizer", "qone"], default="original", help="Debugger to use.", ) @@ -188,6 +188,7 @@ def __init__(self, prefix, output_path, *, persistent=False, gui=False, debugger support_ini_flag = self._find_in_help(prefix, "vcom", "-ini") self._ini_flag = "-ini" if support_ini_flag else "-modelsimini" self._ini_file_path, simulation_ini_file_name = self._find_ini_file(prefix, support_ini_flag) + self._debugger = debugger SimulatorInterface.__init__(self, output_path, gui) VsimSimulatorMixin.__init__( @@ -201,7 +202,6 @@ def __init__(self, prefix, output_path, *, persistent=False, gui=False, debugger self._coverage_files = set() assert not (persistent and gui) self._create_ini() - self._debugger = debugger self._vopt_retries = 3 # Contains design already optimized, i.e. the optimized design can be reused self._optimized_designs = {} @@ -345,17 +345,34 @@ def _optimize_design(self, config): """ Return True if design shall be optimized. """ - return config.sim_options.get("modelsim.three_step_flow", False) - def _early_load_in_gui_mode(self): # pylint: disable=unused-argument + def _early_load_in_gui_mode(self): """ 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" + return self._debugger in ["visualizer", "qone"] + + def _vsim_command(self): + """ + Returns 'qsim' if using questa one gui else returns 'vsim' + """ + vsim_command = "vsim" + if self._debugger == "qone" : + vsim_command = "qsim" + return vsim_command + + def _vopt_command(self): + """ + Returns 'qopt' if using questa one gui else returns 'vopt' + """ + vopt_command = "vopt" + if self._debugger == "qone" : + vopt_command = "qopt" + return vopt_command @staticmethod def _design_to_optimize(config): @@ -407,12 +424,12 @@ def _create_optimize_function(self, config): self._vopt_extra_args(config), f"{design_to_optimize}", "-work", - f"{{{config.library_name}}}", + f"{design_file_directory}", "-quiet", f"-floatgenerics+{config.entity_name}.", - f"-o {{{optimized_design}}}", + f"-o {optimized_design}", "-designfile", - f"{{{fix_path(design_file)}}}", + f"{fix_path(design_file)}", ] # There is a known bug in Modelsim/Questa that prevents the -(modelsim)ini flag from accepting @@ -423,16 +440,19 @@ def _create_optimize_function(self, config): vopt_flags += vopt_library_flags + if self._debugger == "qone": + vopt_flags.append("-debug,livesim") + tcl = """ proc vunit_optimize {{vopt_extra_args ""}} {""" tcl += """ - echo Optimizing using command 'vopt ${{vopt_extra_args}} {vopt_flags}' + echo Optimizing using command '{vopt} ${{vopt_extra_args}} {vopt_flags}' set vopt_failed [catch {{ - eval vopt ${{vopt_extra_args}} {{{vopt_flags}}} + eval {vopt} ${{vopt_extra_args}} {{{vopt_flags}}} }}] if {{${{vopt_failed}}}} {{ - echo Command 'vopt ${{vopt_extra_args}} {vopt_flags}' failed + echo Command '{vopt} ${{vopt_extra_args}} {vopt_flags}' failed echo Bad flag from vopt_extra_args? return true }} @@ -440,7 +460,8 @@ def _create_optimize_function(self, config): return false }} """.format( - vopt_flags=" ".join(vopt_flags) + vopt_flags=" ".join(vopt_flags), + vopt=self._vopt_command() ) return tcl @@ -465,17 +486,22 @@ def _run_persistent_optimize(self, optimize_file_name): def _run_optimize_batch_file(self, batch_file_name, script_path): """ Run a test bench in batch by invoking a new vsim process from the command line + + Warning - I have no idea why the optimise step for questa one is broken. + There is a behavioural difference between qsim -c -do "source batch_optimise.do" and qsim -do batch_optimise.do """ - try: - args = [ - str(Path(self._prefix) / "vsim"), - "-c", - "-l", - str(script_path / "transcript"), - "-do", - f'source "{fix_path(str(batch_file_name))!s}"', - ] + args = [str(Path(self._prefix) / self._vsim_command()),] + if self._debugger != "qone": + args += ["-c",] + + args += [ + "-l", + str(script_path / "transcript"), + "-do", + f'source "{fix_path(str(batch_file_name))!s}"', + ] + try: proc = Process(args, cwd=str(Path(self._sim_cfg_file_name).parent)) proc.consume_output() status = True @@ -637,14 +663,15 @@ def _load_setup(self, config, output_path, optimize_design): """ vsim_flags = " ".join(self._get_vsim_flags(config, output_path, optimize_design)) + vsim_command = self._vsim_command() tcl = f""" set vsim_failed [catch {{ - eval vsim ${{vsim_extra_args}} {{{vsim_flags}}} + eval {vsim_command} ${{vsim_extra_args}} {{{vsim_flags}}} }}] if {{${{vsim_failed}}}} {{ - echo Command 'vsim ${{vsim_extra_args}} {vsim_flags}' failed + echo Command '{vsim_command} ${{vsim_extra_args}} {vsim_flags}' failed echo Bad flag from vsim_extra_args? return true }} @@ -760,9 +787,17 @@ def _get_vsim_flags(self, config, output_path, optimize_design): ) ) + if self._debugger == "original": + vsim_flags += [ + "-wlf", + f"{{{fix_path(str(Path(output_path) / 'vsim.wlf'))}}}", + ] + else: + vsim_flags += [ + str("-" + self._vopt_command() + "args=-debug,livesim"), + ] + vsim_flags += [ - "-wlf", - f"{{{fix_path(str(Path(output_path) / 'vsim.wlf'))}}}", pli_str, set_generic_str, ] @@ -775,7 +810,12 @@ def _get_gui_option(self): This is required to support Questa Visualizer. """ - return "-visualizer" if self._debugger == "visualizer" else "-gui" + arg = "-gui" + if self._debugger == "visualizer": + arg = "-visualizer" + elif self._debugger == "qone": + arg = "" + return arg def _get_load_flags(self, config, output_path, optimize_design): """ @@ -800,9 +840,17 @@ def _get_load_flags(self, config, output_path, optimize_design): generics_file_name = Path(output_path) / "generics.flags" write_file(str(generics_file_name), set_generic_str) + if self._debugger == "original": + vsim_flags += [ + "-wlf", + f"{{{fix_path(str(Path(output_path) / 'vsim.wlf'))}}}", + ] + else: + vsim_flags += [ + str("-" + self._vopt_command() + "args=-debug,livesim"), + ] + vsim_flags += [ - "-wlf", - f"{fix_path(str(Path(output_path) / 'vsim.wlf'))}", pli_str, "-f", f"{fix_path(str(generics_file_name))}", diff --git a/vunit/sim_if/vsim_simulator_mixin.py b/vunit/sim_if/vsim_simulator_mixin.py index 616a1c18b..97944dcae 100644 --- a/vunit/sim_if/vsim_simulator_mixin.py +++ b/vunit/sim_if/vsim_simulator_mixin.py @@ -34,7 +34,7 @@ def __init__(self, prefix, persistent, sim_cfg_file_name): def create_process(ident): return Process( [ - str(Path(prefix) / "vsim"), + str(Path(prefix) / self._vsim_command()), "-c", "-l", str(Path(sim_cfg_file_name).parent / f"transcript{ident}"), @@ -314,6 +314,12 @@ def _create_gui_script(self, common_file_name, config): return tcl + def _vsim_command(self): + """ + Returns 'qsim' if using questa one gui else returns 'vsim' + """ + return "vsim" + def _run_batch_file(self, batch_file_name, gui=False, gui_option="-gui", extra_args=None): """ Run a test bench in batch by invoking a new vsim process from the command line @@ -321,7 +327,7 @@ def _run_batch_file(self, batch_file_name, gui=False, gui_option="-gui", extra_a try: args = [ - str(Path(self._prefix) / "vsim"), + str(Path(self._prefix) / self._vsim_command()), gui_option if gui else "-c", "-l", str(Path(batch_file_name).parent / "transcript"), From bfef89e87c5b1f0bac52258d3b1a66779f59d35c Mon Sep 17 00:00:00 2001 From: Peter Findlay Date: Fri, 14 Aug 2026 16:28:58 +0200 Subject: [PATCH 2/3] found and fixed bug in thread cleanup process preventing clean exit after sys.exit(0) call --- vunit/sim_if/__init__.py | 5 +++++ vunit/sim_if/modelsim.py | 33 +++++++++++++++++---------------- vunit/ui/__init__.py | 1 + 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/vunit/sim_if/__init__.py b/vunit/sim_if/__init__.py index 9e22f803b..07ea4dd43 100644 --- a/vunit/sim_if/__init__.py +++ b/vunit/sim_if/__init__.py @@ -329,6 +329,11 @@ def get_env(): Allows inheriting classes to overload this to modify environment variables. Return None for default environment """ + def clean_up(self): + """ + Can overload to add simulator specific clean up options + """ + def isfile(file_name): """ diff --git a/vunit/sim_if/modelsim.py b/vunit/sim_if/modelsim.py index ec728bed2..3db6ed751 100644 --- a/vunit/sim_if/modelsim.py +++ b/vunit/sim_if/modelsim.py @@ -111,14 +111,14 @@ def _find_any_ini_file(root: Path) -> Path | None: def _find_ini_file(self, prefix: str, support_ini_flag: bool) -> tuple[Path, str] | None: """ Find the INI file to use for the simulation and the name of the copy to be used for simulation. - """ - # The standard simulation INI file name is based on the name of the INI flag option. If such a file - # doesn't exist in the installation but there is another similar INI file, the simulation name - # is based on that file instead. We only revert to finding any INI file if the standard names - # can't be found. The reason is that the non-standard approaches are a somewhat unknown territory - # and this way we avoid discarding a standard name if there are several INI files in the installation - # directory. + The standard simulation INI file name is based on the name of the INI flag option. If such a file + doesn't exist in the installation but there is another similar INI file, the simulation name + is based on that file instead. We only revert to finding any INI file if the standard names + can't be found. The reason is that the non-standard approaches are a somewhat unknown territory + and this way we avoid discarding a standard name if there are several INI files in the installation + directory. + """ parent_dir = Path(prefix).parent installation_ini_name = "questa.ini" if support_ini_flag else "modelsim.ini" standard_installation_ini_file = parent_dir / installation_ini_name @@ -146,7 +146,6 @@ def find_prefix_from_path(cls): """ Find first valid Modelsim/Questa toolchain prefix """ - def has_ini(path): return cls._find_any_ini_file(Path(path).parent) is not None @@ -360,19 +359,13 @@ def _vsim_command(self): """ Returns 'qsim' if using questa one gui else returns 'vsim' """ - vsim_command = "vsim" - if self._debugger == "qone" : - vsim_command = "qsim" - return vsim_command + return "qsim" if self._debugger == "qone" else "vsim" def _vopt_command(self): """ Returns 'qopt' if using questa one gui else returns 'vopt' """ - vopt_command = "vopt" - if self._debugger == "qone" : - vopt_command = "qopt" - return vopt_command + return "qopt" if self._debugger == "qone" else "vopt" @staticmethod def _design_to_optimize(config): @@ -955,6 +948,14 @@ def get_env(): del env[key] return env + def clean_up(self): + """ + Explicitly clean up the persistent tcl shell instance + """ + SimulatorInterface.clean_up(self) + if self._persistent_shell is not None: + self._persistent_shell.teardown() + def encode_generic_value_for_tcl(value): """ diff --git a/vunit/ui/__init__.py b/vunit/ui/__init__.py index da1bbfcf4..1bd171b71 100644 --- a/vunit/ui/__init__.py +++ b/vunit/ui/__init__.py @@ -1062,6 +1062,7 @@ def _main_run(self, post_run): if post_run is not None: post_run(results=Results(self._output_path, simulator_if, report)) + simulator_if.clean_up() del simulator_if if self._args.xunit_xml is not None: From 67db217dfefcf6738fdb843f3e0ab6f14af919c2 Mon Sep 17 00:00:00 2001 From: Peter Findlay Date: Mon, 17 Aug 2026 13:01:41 +0200 Subject: [PATCH 3/3] better understanding of issue with qopt step - have disabled three step flow for qsim --- vunit/sim_if/modelsim.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/vunit/sim_if/modelsim.py b/vunit/sim_if/modelsim.py index 3db6ed751..cf895095d 100644 --- a/vunit/sim_if/modelsim.py +++ b/vunit/sim_if/modelsim.py @@ -344,6 +344,9 @@ def _optimize_design(self, config): """ Return True if design shall be optimized. """ + if config.sim_options.get("modelsim.three_step_flow", False) and self._debugger == "qone": + print ("VUNIT: three step flow is broken for questa one - two step flow will be attempted instead") + return False return config.sim_options.get("modelsim.three_step_flow", False) def _early_load_in_gui_mode(self): @@ -479,15 +482,9 @@ def _run_persistent_optimize(self, optimize_file_name): def _run_optimize_batch_file(self, batch_file_name, script_path): """ Run a test bench in batch by invoking a new vsim process from the command line - - Warning - I have no idea why the optimise step for questa one is broken. - There is a behavioural difference between qsim -c -do "source batch_optimise.do" and qsim -do batch_optimise.do """ - args = [str(Path(self._prefix) / self._vsim_command()),] - if self._debugger != "qone": - args += ["-c",] - - args += [ + args = [str(Path(self._prefix) / self._vsim_command()), + "-c", "-l", str(script_path / "transcript"), "-do",