From a32cf67956507c6426ee14075e2cbbe5724a5df4 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Fri, 8 Mar 2024 13:18:32 +0100 Subject: [PATCH] Support arbitrary waveform viewer --- docs/py/opts.rst | 5 ++-- tests/unit/test_ghdl_interface.py | 6 ++--- vunit/sim_if/ghdl.py | 42 ++++++++++++++++++------------- vunit/sim_if/nvc.py | 21 ++++++++-------- 4 files changed, 40 insertions(+), 34 deletions(-) diff --git a/docs/py/opts.rst b/docs/py/opts.rst index 8c15576f7..edf6ec737 100644 --- a/docs/py/opts.rst +++ b/docs/py/opts.rst @@ -194,9 +194,10 @@ The following simulation options are known. With ``--elaborate``, execute ``ghdl -e`` instead of ``ghdl --elab-run --no-run``. Must be a boolean. -``ghdl.gtkwave_script.gui`` - A user defined TCL-file that is sourced after the design has been loaded in the GUI. +``ghdl.viewer_script.gui`` + A user defined file that is sourced after the design has been loaded in the GUI. For example this can be used to configure the waveform viewer. Must be a string. + There are currently limitations in the HEAD revision of GTKWave that prevent the user from sourcing a list of scripts directly. The following is the current work around to sourcing multiple user TCL-files: diff --git a/tests/unit/test_ghdl_interface.py b/tests/unit/test_ghdl_interface.py index 5d528a7ad..36a1f9344 100644 --- a/tests/unit/test_ghdl_interface.py +++ b/tests/unit/test_ghdl_interface.py @@ -28,7 +28,7 @@ class TestGHDLInterface(unittest.TestCase): """ @mock.patch("vunit.sim_if.ghdl.GHDLInterface.find_executable") - def test_runtime_error_on_missing_gtkwave(self, find_executable): + def test_runtime_error_on_missing_viewer(self, find_executable): executables = {} def find_executable_side_effect(name): @@ -36,10 +36,10 @@ def find_executable_side_effect(name): find_executable.side_effect = find_executable_side_effect - executables["gtkwave"] = ["path"] + executables["viewer"] = ["path"] GHDLInterface(prefix="prefix", output_path="") - executables["gtkwave"] = [] + executables["viewer"] = [] GHDLInterface(prefix="prefix", output_path="") self.assertRaises(RuntimeError, GHDLInterface, prefix="prefix", output_path="", gui=True) diff --git a/vunit/sim_if/ghdl.py b/vunit/sim_if/ghdl.py index b24d7f644..11337626b 100644 --- a/vunit/sim_if/ghdl.py +++ b/vunit/sim_if/ghdl.py @@ -43,7 +43,7 @@ class GHDLInterface(SimulatorInterface): # pylint: disable=too-many-instance-at sim_options = [ ListOfStringOption("ghdl.sim_flags"), ListOfStringOption("ghdl.elab_flags"), - StringOption("ghdl.gtkwave_script.gui"), + StringOption("ghdl.viewer_script.gui"), BooleanOption("ghdl.elab_e"), ] @@ -54,12 +54,14 @@ def add_arguments(parser): """ group = parser.add_argument_group("ghdl", description="GHDL specific flags") group.add_argument( + "--viewer-fmt", "--gtkwave-fmt", choices=["vcd", "fst", "ghw"], default=None, - help="Save .vcd, .fst, or .ghw to open in gtkwave", + help="Save .vcd, .fst, or .ghw to open in waveform viewer", ) - group.add_argument("--gtkwave-args", default="", help="Arguments to pass to gtkwave") + group.add_argument("--viewer-args", "--gtkwave-args", default="", help="Arguments to pass to waveform viewer") + group.add_argument("--viewer", default="gtkwave", help="Waveform viewer to use") @classmethod def from_args(cls, args, output_path, **kwargs): @@ -71,8 +73,8 @@ def from_args(cls, args, output_path, **kwargs): output_path=output_path, prefix=prefix, gui=args.gui, - gtkwave_fmt=args.gtkwave_fmt, - gtkwave_args=args.gtkwave_args, + viewer_fmt=args.viewer_fmt, + viewer_args=args.viewer_args, backend=cls.determine_backend(prefix), ) @@ -88,20 +90,24 @@ def __init__( # pylint: disable=too-many-arguments output_path, prefix, gui=False, - gtkwave_fmt=None, - gtkwave_args="", + viewer_fmt=None, + viewer_args="", + viewer="gtkwave", backend="llvm", ): SimulatorInterface.__init__(self, output_path, gui) self._prefix = prefix self._project = None - if gui and (not self.find_executable("gtkwave")): - raise RuntimeError("Cannot find the gtkwave executable in the PATH environment variable. GUI not possible") + if gui and (not self.find_executable(viewer)): + raise RuntimeError( + f"Cannot find the {viewer} executable in the PATH environment variable. GUI not possible" + ) self._gui = gui - self._gtkwave_fmt = "ghw" if gui and gtkwave_fmt is None else gtkwave_fmt - self._gtkwave_args = gtkwave_args + self._viewer_fmt = "ghw" if gui and viewer_fmt is None else viewer_fmt + self._viewer_args = viewer_args + self._viewer = viewer self._backend = backend self._vhdl_standard = None self._coverage_test_dirs = set() @@ -293,11 +299,11 @@ def _get_command(self, config, output_path, elaborate_only, ghdl_e, wave_file): sim += ["--ieee-asserts=disable"] if wave_file: - if self._gtkwave_fmt == "ghw": + if self._viewer_fmt == "ghw": sim += [f"--wave={wave_file!s}"] - elif self._gtkwave_fmt == "vcd": + elif self._viewer_fmt == "vcd": sim += [f"--vcd={wave_file!s}"] - elif self._gtkwave_fmt == "fst": + elif self._viewer_fmt == "fst": sim += [f"--fst={wave_file!s}"] if not ghdl_e: @@ -333,8 +339,8 @@ def simulate(self, output_path, test_suite_name, config, elaborate_only): # pyl ghdl_e = elaborate_only and config.sim_options.get("ghdl.elab_e", False) - if self._gtkwave_fmt is not None: - data_file_name = str(Path(script_path) / f"wave.{self._gtkwave_fmt!s}") + if self._viewer_fmt is not None: + data_file_name = str(Path(script_path) / f"wave.{self._viewer_fmt!s}") if Path(data_file_name).exists(): remove(data_file_name) else: @@ -358,9 +364,9 @@ def simulate(self, output_path, test_suite_name, config, elaborate_only): # pyl status = False if self._gui and not elaborate_only: - cmd = ["gtkwave"] + shlex.split(self._gtkwave_args) + [data_file_name] + cmd = [self._viewer] + shlex.split(self._viewer_args) + [data_file_name] - init_file = config.sim_options.get(self.name + ".gtkwave_script.gui", None) + init_file = config.sim_options.get(self.name + ".viewer_script.gui", None) if init_file is not None: cmd += ["--script", str(Path(init_file).resolve())] diff --git a/vunit/sim_if/nvc.py b/vunit/sim_if/nvc.py index 4081944dc..3176a0ecf 100644 --- a/vunit/sim_if/nvc.py +++ b/vunit/sim_if/nvc.py @@ -44,7 +44,7 @@ class NVCInterface(SimulatorInterface): # pylint: disable=too-many-instance-att ListOfStringOption("nvc.sim_flags"), ListOfStringOption("nvc.elab_flags"), StringOption("nvc.heap_size"), - StringOption("nvc.gtkwave_script.gui"), + StringOption("nvc.viewer_script.gui"), ] @classmethod @@ -67,21 +67,20 @@ def find_prefix_from_path(cls): return cls.find_toolchain([cls.executable]) def __init__( # pylint: disable=too-many-arguments - self, - output_path, - prefix, - gui=False, - gtkwave_args="", + self, output_path, prefix, gui=False, viewer_args="", viewer="gtkwave" ): SimulatorInterface.__init__(self, output_path, gui) self._prefix = prefix self._project = None - if gui and (not self.find_executable("gtkwave")): - raise RuntimeError("Cannot find the gtkwave executable in the PATH environment variable. GUI not possible") + if gui and (not self.find_executable(viewer)): + raise RuntimeError( + f"Cannot find the {viewer} executable in the PATH environment variable. GUI not possible." + ) self._gui = gui - self._gtkwave_args = gtkwave_args + self._viewer_args = viewer_args + self._viewer = viewer self._vhdl_standard = None self._coverage_test_dirs = set() @@ -294,9 +293,9 @@ def simulate(self, output_path, test_suite_name, config, elaborate_only): # pyl status = False if self._gui and not elaborate_only: - cmd = ["gtkwave"] + shlex.split(self._gtkwave_args) + [str(wave_file)] + cmd = [self._viewer] + shlex.split(self._viewer_args) + [str(wave_file)] - init_file = config.sim_options.get(self.name + ".gtkwave_script.gui", None) + init_file = config.sim_options.get(self.name + ".viewer_script.gui", None) if init_file is not None: cmd += ["--script", str(Path(init_file).resolve())]