Skip to content

Commit cff7bad

Browse files
committed
Support arbitrary waveform viewer
1 parent 8d71e2d commit cff7bad

File tree

7 files changed

+171
-64
lines changed

7 files changed

+171
-64
lines changed

docs/cli.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,9 @@ VUnit automatically detects which simulators are available on the
166166
``PATH`` environment variable and by default selects the first one
167167
found. For people who have multiple simulators installed the
168168
``VUNIT_SIMULATOR`` environment variable can be set to one of
169-
``activehdl``, ``rivierapro``, ``ghdl`` or ``modelsim`` to specify
170-
which simulator to use. ``modelsim`` is used for both ModelSim and
171-
Questa as VUnit handles these simulators identically.
169+
``activehdl``, ``rivierapro``, ``ghdl``, ``nvc```, or ``modelsim`` to
170+
specify which simulator to use. ``modelsim`` is used for both ModelSim
171+
and Questa as VUnit handles these simulators identically.
172172

173173
In addition to VUnit scanning the ``PATH`` the simulator executable
174174
path can be explicitly configured by setting a

docs/news.d/1002.feature.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[GHDL/NVC] Arbitrary waveform viewers are now supported by passing the `--viewer`
2+
command line arugment. As a consequence, ``ghdl.gtkwave_script.gui`` and
3+
``nvc.gtkwave_script.gui`` are deprecated in favour of ``ghdl.viewer_script.gui``
4+
and ``nvc.viewer_script.gui``, respectively. The ``--gtkwave-args`` and
5+
``--gtkwave-fmt`` command line argument is deprecated in favour of ``--viewer-args``
6+
and ``--viewer-fmt``, respectively. ``ghdl.viewer.gui`` and ``nvc.viewer.gui`` can
7+
be used to set the preferred viewer from the run-file. Finally, if the requested
8+
viewer does not exists, ``gtkwave`` or ``surfer`` is used, in that order. This
9+
also means that VUnit uses ``surfer`` if ``gtkwave`` is not installed.
10+
11+
[NVC] It is possible to get VCD waveform files by passing ``--viewer-fmt=vcd``.

docs/py/opts.rst

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,9 +201,15 @@ The following simulation options are known.
201201
With ``--elaborate``, execute ``ghdl -e`` instead of ``ghdl --elab-run --no-run``.
202202
Must be a boolean.
203203

204-
``ghdl.gtkwave_script.gui``
205-
A user defined TCL-file that is sourced after the design has been loaded in the GUI.
204+
``ghdl.viewer.gui``
205+
Name of waveform viewer to use. The command line argument ``--viewer`` will have
206+
precedence if provided. If neither is provided, ``gtkwave`` or ``surfer`` will be
207+
used.
208+
209+
``ghdl.viewer_script.gui``
210+
A user defined file that is sourced after the design has been loaded in the GUI.
206211
For example this can be used to configure the waveform viewer. Must be a string.
212+
207213
There are currently limitations in the HEAD revision of GTKWave that prevent the
208214
user from sourcing a list of scripts directly. The following is the current work
209215
around to sourcing multiple user TCL-files:
@@ -225,3 +231,17 @@ The following simulation options are known.
225231
``nvc.sim_flags``
226232
Extra simulation flags passed to ``nvc -r``.
227233
Must be a list of strings.
234+
235+
``nvc.viewer.gui``
236+
Name of waveform viewer to use. The command line argument ``--viewer`` will have
237+
precedence if provided. If neither is provided, ``gtkwave`` or ``surfer`` will be
238+
used.
239+
240+
``nvc.viewer_script.gui``
241+
A user defined file that is sourced after the design has been loaded in the GUI.
242+
For example this can be used to configure the waveform viewer. Must be a string.
243+
244+
There are currently limitations in the HEAD revision of GTKWave that prevent the
245+
user from sourcing a list of scripts directly. The following is the current work
246+
around to sourcing multiple user TCL-files:
247+
``source <path/to/script.tcl>``

tests/unit/test_ghdl_interface.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,6 @@ class TestGHDLInterface(unittest.TestCase):
2727
Test the GHDL interface
2828
"""
2929

30-
@mock.patch("vunit.sim_if.ghdl.GHDLInterface.find_executable")
31-
def test_runtime_error_on_missing_gtkwave(self, find_executable):
32-
executables = {}
33-
34-
def find_executable_side_effect(name):
35-
return executables[name]
36-
37-
find_executable.side_effect = find_executable_side_effect
38-
39-
executables["gtkwave"] = ["path"]
40-
GHDLInterface(prefix="prefix", output_path="")
41-
42-
executables["gtkwave"] = []
43-
GHDLInterface(prefix="prefix", output_path="")
44-
self.assertRaises(RuntimeError, GHDLInterface, prefix="prefix", output_path="", gui=True)
45-
4630
@mock.patch("subprocess.check_output", autospec=True)
4731
def test_parses_llvm_backend(self, check_output):
4832
version = b"""\

vunit/sim_if/_ossmixin.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# This Source Code Form is subject to the terms of the Mozilla Public
2+
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
3+
# You can obtain one at http://mozilla.org/MPL/2.0/.
4+
#
5+
# Copyright (c) 2014-2024, Lars Asplund [email protected]
6+
"""
7+
Viewer handling for GHDL and NVC.
8+
"""
9+
10+
11+
class OSSMixin:
12+
"""
13+
Mixin class for handling common viewer functionality for the GHDL and NVC simulators.
14+
"""
15+
16+
__slots__ = (
17+
"_gui",
18+
"_viewer",
19+
"_viewer_fmt",
20+
"_viewer_args",
21+
"_gtkwave_available",
22+
"_surfer_available",
23+
)
24+
25+
def __init__(self, gui, viewer, viewer_fmt, viewer_args):
26+
self._gui = gui
27+
self._viewer_fmt = viewer_fmt
28+
self._viewer_args = viewer_args
29+
self._viewer = viewer
30+
if gui:
31+
self._gtkwave_available = self.find_executable("gtkwave")
32+
self._surfer_available = self.find_executable("surfer")
33+
34+
def _get_viewer(self, config):
35+
"""
36+
Determine the waveform viewer to use.
37+
38+
Falls back to gtkwave or surfer, in that order, if none is provided or the provided
39+
cannot be found.
40+
"""
41+
viewer = self._viewer or config.sim_options.get(self.name + ".viewer.gui", None)
42+
43+
if viewer is None:
44+
if self._gtkwave_available:
45+
viewer = "gtkwave"
46+
elif self._surfer_available:
47+
viewer = "surfer"
48+
else:
49+
raise RuntimeError("No viewer found. GUI not possible. Install GTKWave or Surfer.")
50+
51+
elif not self.find_executable(viewer):
52+
viewers = []
53+
if self._gtkwave_available:
54+
viewers += ["gtkwave"]
55+
if self._surfer_available:
56+
viewers += ["surfer"]
57+
addendum = f" The following viewer(s) are found in the path: {', '.join(viewers)}" if viewers else ""
58+
raise RuntimeError(
59+
f"Cannot find the {viewer} executable in the PATH environment variable. GUI not possible.{addendum}"
60+
)
61+
return viewer

vunit/sim_if/ghdl.py

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@
2121
from ..ostools import Process
2222
from . import SimulatorInterface, ListOfStringOption, StringOption, BooleanOption
2323
from ..vhdl_standard import VHDL
24+
from ._ossmixin import OSSMixin
2425

2526
LOGGER = logging.getLogger(__name__)
2627

2728

28-
class GHDLInterface(SimulatorInterface): # pylint: disable=too-many-instance-attributes
29+
class GHDLInterface(SimulatorInterface, OSSMixin): # pylint: disable=too-many-instance-attributes
2930
"""
3031
Interface for GHDL simulator
3132
"""
@@ -43,7 +44,9 @@ class GHDLInterface(SimulatorInterface): # pylint: disable=too-many-instance-at
4344
sim_options = [
4445
ListOfStringOption("ghdl.sim_flags"),
4546
ListOfStringOption("ghdl.elab_flags"),
46-
StringOption("ghdl.gtkwave_script.gui"),
47+
StringOption("ghdl.gtkwave_script.gui"), # Deprecated in v5.1.0
48+
StringOption("ghdl.viewer_script.gui"),
49+
StringOption("ghdl.viewer.gui"),
4750
BooleanOption("ghdl.elab_e"),
4851
]
4952

@@ -52,14 +55,16 @@ def add_arguments(parser):
5255
"""
5356
Add command line arguments
5457
"""
55-
group = parser.add_argument_group("ghdl", description="GHDL specific flags")
58+
group = parser.add_argument_group("ghdl/nvc", description="GHDL/NVC specific flags")
5659
group.add_argument(
60+
"--viewer-fmt",
5761
"--gtkwave-fmt",
5862
choices=["vcd", "fst", "ghw"],
5963
default=None,
60-
help="Save .vcd, .fst, or .ghw to open in gtkwave",
64+
help="Save .vcd, .fst, or .ghw to open in waveform viewer. NVC does not support ghw.",
6165
)
62-
group.add_argument("--gtkwave-args", default="", help="Arguments to pass to gtkwave")
66+
group.add_argument("--viewer-args", "--gtkwave-args", default="", help="Arguments to pass to waveform viewer")
67+
group.add_argument("--viewer", default=None, help="Waveform viewer to use")
6368

6469
@classmethod
6570
def from_args(cls, args, output_path, **kwargs):
@@ -71,8 +76,9 @@ def from_args(cls, args, output_path, **kwargs):
7176
output_path=output_path,
7277
prefix=prefix,
7378
gui=args.gui,
74-
gtkwave_fmt=args.gtkwave_fmt,
75-
gtkwave_args=args.gtkwave_args,
79+
viewer_fmt=args.viewer_fmt,
80+
viewer_args=args.viewer_args,
81+
viewer=args.viewer,
7682
backend=cls.determine_backend(prefix),
7783
)
7884

@@ -88,20 +94,23 @@ def __init__( # pylint: disable=too-many-arguments
8894
output_path,
8995
prefix,
9096
gui=False,
91-
gtkwave_fmt=None,
92-
gtkwave_args="",
97+
viewer_fmt=None,
98+
viewer_args="",
99+
viewer=None,
93100
backend="llvm",
94101
):
95102
SimulatorInterface.__init__(self, output_path, gui)
103+
OSSMixin.__init__(
104+
self,
105+
gui=gui,
106+
viewer=viewer,
107+
viewer_fmt="ghw" if gui and viewer_fmt is None else viewer_fmt,
108+
viewer_args=viewer_args,
109+
)
110+
96111
self._prefix = prefix
97112
self._project = None
98113

99-
if gui and (not self.find_executable("gtkwave")):
100-
raise RuntimeError("Cannot find the gtkwave executable in the PATH environment variable. GUI not possible")
101-
102-
self._gui = gui
103-
self._gtkwave_fmt = "ghw" if gui and gtkwave_fmt is None else gtkwave_fmt
104-
self._gtkwave_args = gtkwave_args
105114
self._backend = backend
106115
self._vhdl_standard = None
107116
self._coverage_test_dirs = set() # For gcov
@@ -315,11 +324,11 @@ def _get_command(
315324
sim += ["--ieee-asserts=disable"]
316325

317326
if wave_file:
318-
if self._gtkwave_fmt == "ghw":
327+
if self._viewer_fmt == "ghw":
319328
sim += [f"--wave={wave_file!s}"]
320-
elif self._gtkwave_fmt == "vcd":
329+
elif self._viewer_fmt == "vcd":
321330
sim += [f"--vcd={wave_file!s}"]
322-
elif self._gtkwave_fmt == "fst":
331+
elif self._viewer_fmt == "fst":
323332
sim += [f"--fst={wave_file!s}"]
324333

325334
if not ghdl_e:
@@ -355,8 +364,8 @@ def simulate(self, output_path, test_suite_name, config, elaborate_only): # pyl
355364

356365
ghdl_e = elaborate_only and config.sim_options.get("ghdl.elab_e", False)
357366

358-
if self._gtkwave_fmt is not None:
359-
data_file_name = str(Path(script_path) / f"wave.{self._gtkwave_fmt!s}")
367+
if self._viewer_fmt is not None:
368+
data_file_name = str(Path(script_path) / f"wave.{self._viewer_fmt!s}")
360369
if Path(data_file_name).exists():
361370
remove(data_file_name)
362371
else:
@@ -382,10 +391,19 @@ def simulate(self, output_path, test_suite_name, config, elaborate_only): # pyl
382391
except Process.NonZeroExitCode:
383392
status = False
384393

394+
if config.sim_options.get(self.name + ".gtkwave_script.gui", None):
395+
w = (
396+
"%s.gtkwave_script.gui is deprecated and will be removed " % self.name # pylint: disable=C0209
397+
+ "in a future version, use %s.viewer_script.gui instead" % self.name # pylint: disable=C0209
398+
)
399+
LOGGER.warning(w)
400+
385401
if self._gui and not elaborate_only:
386-
cmd = ["gtkwave"] + shlex.split(self._gtkwave_args) + [data_file_name]
402+
cmd = [self._get_viewer(config)] + shlex.split(self._viewer_args) + [data_file_name]
387403

388-
init_file = config.sim_options.get(self.name + ".gtkwave_script.gui", None)
404+
init_file = config.sim_options.get(
405+
self.name + ".viewer_script.gui", config.sim_options.get(self.name + ".gtkwave_script.gui", None)
406+
)
389407
if init_file is not None:
390408
cmd += ["--script", str(Path(init_file).resolve())]
391409

0 commit comments

Comments
 (0)