Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support arbitrary waveform viewer #1002

Merged
merged 2 commits into from
Aug 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 argument. 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 arguments are 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. If no viewer is explicitly
requested, ``gtkwave`` or ``surfer`` is used, in that order. This also means that
VUnit now 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
60 changes: 60 additions & 0 deletions vunit/sim_if/_viewermixin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# 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 ViewerMixin:
"""
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.
"""
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: 39 additions & 23 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 ._viewermixin import ViewerMixin

LOGGER = logging.getLogger(__name__)


class GHDLInterface(SimulatorInterface): # pylint: disable=too-many-instance-attributes
class GHDLInterface(SimulatorInterface, ViewerMixin): # 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,
oscargus marked this conversation as resolved.
Show resolved Hide resolved
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)
ViewerMixin.__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 All @@ -123,7 +132,7 @@ def _get_version_output(cls, prefix):
@classmethod
def _get_help_output(cls, prefix):
"""
Get the output of 'ghdl --version'
Get the output of 'ghdl --help'
"""
return subprocess.check_output([str(Path(prefix) / cls.executable), "--help"]).decode()

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,17 @@ 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):
LOGGER.warning("%s.gtkwave_script.gui is deprecated and will be removed "
"in a future version, use %s.viewer_script.gui instead",
self.name, self.name)

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