Skip to content

Commit

Permalink
Support arbitrary waveform viewer
Browse files Browse the repository at this point in the history
  • Loading branch information
oscargus committed Jul 19, 2024
1 parent 8d71e2d commit cff7bad
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 64 deletions.
6 changes: 3 additions & 3 deletions docs/cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,9 @@ VUnit automatically detects which simulators are available on the
``PATH`` environment variable and by default selects the first one
found. For people who have multiple simulators installed the
``VUNIT_SIMULATOR`` environment variable can be set to one of
``activehdl``, ``rivierapro``, ``ghdl`` or ``modelsim`` to specify
which simulator to use. ``modelsim`` is used for both ModelSim and
Questa as VUnit handles these simulators identically.
``activehdl``, ``rivierapro``, ``ghdl``, ``nvc```, or ``modelsim`` to
specify which simulator to use. ``modelsim`` is used for both ModelSim
and Questa as VUnit handles these simulators identically.

In addition to VUnit scanning the ``PATH`` the simulator executable
path can be explicitly configured by setting a
Expand Down
11 changes: 11 additions & 0 deletions docs/news.d/1002.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[GHDL/NVC] Arbitrary waveform viewers are now supported by passing the `--viewer`
command line arugment. As a consequence, ``ghdl.gtkwave_script.gui`` and
``nvc.gtkwave_script.gui`` are deprecated in favour of ``ghdl.viewer_script.gui``
and ``nvc.viewer_script.gui``, respectively. The ``--gtkwave-args`` and
``--gtkwave-fmt`` command line argument is deprecated in favour of ``--viewer-args``
and ``--viewer-fmt``, respectively. ``ghdl.viewer.gui`` and ``nvc.viewer.gui`` can
be used to set the preferred viewer from the run-file. Finally, if the requested
viewer does not exists, ``gtkwave`` or ``surfer`` is used, in that order. This
also means that VUnit uses ``surfer`` if ``gtkwave`` is not installed.

[NVC] It is possible to get VCD waveform files by passing ``--viewer-fmt=vcd``.
24 changes: 22 additions & 2 deletions docs/py/opts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,15 @@ 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.gui``
Name of waveform viewer to use. The command line argument ``--viewer`` will have
precedence if provided. If neither is provided, ``gtkwave`` or ``surfer`` will be
used.

``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:
Expand All @@ -225,3 +231,17 @@ The following simulation options are known.
``nvc.sim_flags``
Extra simulation flags passed to ``nvc -r``.
Must be a list of strings.

``nvc.viewer.gui``
Name of waveform viewer to use. The command line argument ``--viewer`` will have
precedence if provided. If neither is provided, ``gtkwave`` or ``surfer`` will be
used.

``nvc.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:
``source <path/to/script.tcl>``
16 changes: 0 additions & 16 deletions tests/unit/test_ghdl_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,6 @@ class TestGHDLInterface(unittest.TestCase):
Test the GHDL interface
"""

@mock.patch("vunit.sim_if.ghdl.GHDLInterface.find_executable")
def test_runtime_error_on_missing_gtkwave(self, find_executable):
executables = {}

def find_executable_side_effect(name):
return executables[name]

find_executable.side_effect = find_executable_side_effect

executables["gtkwave"] = ["path"]
GHDLInterface(prefix="prefix", output_path="")

executables["gtkwave"] = []
GHDLInterface(prefix="prefix", output_path="")
self.assertRaises(RuntimeError, GHDLInterface, prefix="prefix", output_path="", gui=True)

@mock.patch("subprocess.check_output", autospec=True)
def test_parses_llvm_backend(self, check_output):
version = b"""\
Expand Down
61 changes: 61 additions & 0 deletions vunit/sim_if/_ossmixin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
#
# Copyright (c) 2014-2024, Lars Asplund [email protected]
"""
Viewer handling for GHDL and NVC.
"""


class OSSMixin:
"""
Mixin class for handling common viewer functionality for the GHDL and NVC simulators.
"""

__slots__ = (
"_gui",
"_viewer",
"_viewer_fmt",
"_viewer_args",
"_gtkwave_available",
"_surfer_available",
)

def __init__(self, gui, viewer, viewer_fmt, viewer_args):
self._gui = gui
self._viewer_fmt = viewer_fmt
self._viewer_args = viewer_args
self._viewer = viewer
if gui:
self._gtkwave_available = self.find_executable("gtkwave")
self._surfer_available = self.find_executable("surfer")

def _get_viewer(self, config):
"""
Determine the waveform viewer to use.
Falls back to gtkwave or surfer, in that order, if none is provided or the provided
cannot be found.
"""
viewer = self._viewer or config.sim_options.get(self.name + ".viewer.gui", None)

if viewer is None:
if self._gtkwave_available:
viewer = "gtkwave"
elif self._surfer_available:
viewer = "surfer"
else:
raise RuntimeError("No viewer found. GUI not possible. Install GTKWave or Surfer.")

elif not self.find_executable(viewer):
viewers = []
if self._gtkwave_available:
viewers += ["gtkwave"]
if self._surfer_available:
viewers += ["surfer"]
addendum = f" The following viewer(s) are found in the path: {', '.join(viewers)}" if viewers else ""
raise RuntimeError(
f"Cannot find the {viewer} executable in the PATH environment variable. GUI not possible.{addendum}"
)
return viewer
62 changes: 40 additions & 22 deletions vunit/sim_if/ghdl.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@
from ..ostools import Process
from . import SimulatorInterface, ListOfStringOption, StringOption, BooleanOption
from ..vhdl_standard import VHDL
from ._ossmixin import OSSMixin

LOGGER = logging.getLogger(__name__)


class GHDLInterface(SimulatorInterface): # pylint: disable=too-many-instance-attributes
class GHDLInterface(SimulatorInterface, OSSMixin): # pylint: disable=too-many-instance-attributes
"""
Interface for GHDL simulator
"""
Expand All @@ -43,7 +44,9 @@ 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.gtkwave_script.gui"), # Deprecated in v5.1.0
StringOption("ghdl.viewer_script.gui"),
StringOption("ghdl.viewer.gui"),
BooleanOption("ghdl.elab_e"),
]

Expand All @@ -52,14 +55,16 @@ def add_arguments(parser):
"""
Add command line arguments
"""
group = parser.add_argument_group("ghdl", description="GHDL specific flags")
group = parser.add_argument_group("ghdl/nvc", description="GHDL/NVC 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. NVC does not support ghw.",
)
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=None, help="Waveform viewer to use")

@classmethod
def from_args(cls, args, output_path, **kwargs):
Expand All @@ -71,8 +76,9 @@ 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,
viewer=args.viewer,
backend=cls.determine_backend(prefix),
)

Expand All @@ -88,20 +94,23 @@ def __init__( # pylint: disable=too-many-arguments
output_path,
prefix,
gui=False,
gtkwave_fmt=None,
gtkwave_args="",
viewer_fmt=None,
viewer_args="",
viewer=None,
backend="llvm",
):
SimulatorInterface.__init__(self, output_path, gui)
OSSMixin.__init__(
self,
gui=gui,
viewer=viewer,
viewer_fmt="ghw" if gui and viewer_fmt is None else viewer_fmt,
viewer_args=viewer_args,
)

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")

self._gui = gui
self._gtkwave_fmt = "ghw" if gui and gtkwave_fmt is None else gtkwave_fmt
self._gtkwave_args = gtkwave_args
self._backend = backend
self._vhdl_standard = None
self._coverage_test_dirs = set() # For gcov
Expand Down Expand Up @@ -315,11 +324,11 @@ def _get_command(
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:
Expand Down Expand Up @@ -355,8 +364,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:
Expand All @@ -382,10 +391,19 @@ def simulate(self, output_path, test_suite_name, config, elaborate_only): # pyl
except Process.NonZeroExitCode:
status = False

if config.sim_options.get(self.name + ".gtkwave_script.gui", None):
w = (
"%s.gtkwave_script.gui is deprecated and will be removed " % self.name # pylint: disable=C0209
+ "in a future version, use %s.viewer_script.gui instead" % self.name # pylint: disable=C0209
)
LOGGER.warning(w)

if self._gui and not elaborate_only:
cmd = ["gtkwave"] + shlex.split(self._gtkwave_args) + [data_file_name]
cmd = [self._get_viewer(config)] + 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", config.sim_options.get(self.name + ".gtkwave_script.gui", None)
)
if init_file is not None:
cmd += ["--script", str(Path(init_file).resolve())]

Expand Down
Loading

0 comments on commit cff7bad

Please sign in to comment.