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 11, 2024
1 parent fbe8d06 commit 8ac9375
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 39 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>``
82 changes: 62 additions & 20 deletions vunit/sim_if/ghdl.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import shlex
import re
import shutil
import warnings
from json import dump
from sys import stdout # To avoid output catched in non-verbose mode
from ..exceptions import CompileError
Expand Down Expand Up @@ -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,24 @@ 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)
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._viewer_fmt = "ghw" if gui and viewer_fmt is None else 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")
self._viewer_available = False

self._backend = backend
self._vhdl_standard = None
self._coverage_test_dirs = set() # For gcov
Expand Down Expand Up @@ -315,11 +325,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 +365,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 +392,16 @@ 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):
warnings.warn(f"{self.name}.gtkwave_script.gui is deprecated and will be removed "
f"in a future version, use {self.name}.viewer_script.gui instead")

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 All @@ -394,6 +410,32 @@ def simulate(self, output_path, test_suite_name, config, elaborate_only): # pyl

return status

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 and not self._gtkwave_available and not self._surfer_available:
raise RuntimeError("No viewer found. GUI not possible. Install GTKWave or Surfer.")
if not self._viewer_available and not self.find_executable(viewer):
if not self._gtkwave_available or not self._surfer_available:
raise RuntimeError(
f"Cannot find the {viewer} executable in the PATH environment variable. GUI not possible"
)
if self._gtkwave_available:
warnings.warn(f"Cannot find {viewer}, using GTKWave instead.")
viewer = "gtkwave"
elif self._surfer_available:
warnings.warn(f"Cannot find {viewer}, using Surfer instead.")
viewer = "surfer"
else:
self._viewer_available = True
return viewer

def _compile_source_file(self, source_file, printer):
"""
Runs parent command for compilation, and moves any .gcno files to the compilation output
Expand Down
77 changes: 63 additions & 14 deletions vunit/sim_if/nvc.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import subprocess
import shlex
import re
import warnings
from sys import stdout # To avoid output catched in non-verbose mode
from ..exceptions import CompileError
from ..ostools import Process
Expand Down Expand Up @@ -44,7 +45,8 @@ 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"),
StringOption("nvc.viewer.gui"),
]

@classmethod
Expand All @@ -58,6 +60,9 @@ def from_args(cls, args, output_path, **kwargs):
prefix=prefix,
gui=args.gui,
num_threads=args.num_threads,
viewer_fmt=args.viewer_fmt,
viewer_args=args.viewer_args,
viewer=args.viewer
)

@classmethod
Expand All @@ -73,20 +78,28 @@ def __init__( # pylint: disable=too-many-arguments
prefix,
num_threads,
gui=False,
gtkwave_args="",
viewer_fmt=None,
viewer_args="",
viewer=None
):
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")

self._gui = gui
self._gtkwave_args = gtkwave_args
if viewer_fmt == "ghw":
warnings.warn("NVC does not support ghw, defaulting to fst")
viewer_fmt = None # Defaults to FST later
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")
self._viewer_available = False

self._vhdl_standard = None
self._coverage_test_dirs = set()

(major, minor) = self.determine_version(prefix)
self._supports_jit = major > 1 or (major == 1 and minor >= 9)

Expand Down Expand Up @@ -241,7 +254,7 @@ def compile_vhdl_file_command(self, source_file):
cmd += [source_file.name]
return cmd

def simulate(self, output_path, test_suite_name, config, elaborate_only): # pylint: disable=too-many-branches
def simulate(self, output_path, test_suite_name, config, elaborate_only): # pylint: disable=too-many-branches, disable=too-many-statements
"""
Simulate with entity as top level using generics
"""
Expand All @@ -251,16 +264,17 @@ def simulate(self, output_path, test_suite_name, config, elaborate_only): # pyl
if not script_path.exists():
makedirs(script_path)

libdir = self._project.get_library(config.library_name).directory
cmd = self._get_command(self._vhdl_standard, config.library_name, libdir)

if self._gui:
wave_file = script_path / (f"{config.entity_name}.fst")
fmt = self._viewer_fmt or "fst"
wave_file = script_path / (f"{config.entity_name}.{fmt}")
if wave_file.exists():
remove(wave_file)
else:
wave_file = None

libdir = self._project.get_library(config.library_name).directory
cmd = self._get_command(self._vhdl_standard, config.library_name, libdir)

cmd += ["-H", config.sim_options.get("nvc.heap_size", "64m")]
cmd += config.sim_options.get("nvc.global_flags", [])

Expand Down Expand Up @@ -289,6 +303,9 @@ def simulate(self, output_path, test_suite_name, config, elaborate_only): # pyl
if wave_file:
cmd += [f"--wave={wave_file}"]

if self._viewer_fmt:
cmd += [f"--format={self._viewer_fmt}"]

print(" ".join([f"'{word}'" if " " in word else word for word in cmd]))

status = True
Expand All @@ -299,14 +316,46 @@ 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):
warnings.warn(f"{self.name}.gtkwave_script.gui is deprecated and will be removed "
f"in a future version, use {self.name}.viewer_script.gui instead")

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

stdout.write(f'{" ".join(cmd)}\n')
subprocess.call(cmd)

return status

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 and not self._gtkwave_available and not self._surfer_available:
raise RuntimeError("No viewer found. GUI not possible. Install GTKWave or Surfer.")
if not self._viewer_available and not self.find_executable(viewer):
if not self._gtkwave_available or not self._surfer_available:
raise RuntimeError(
f"Cannot find the {viewer} executable in the PATH environment variable. GUI not possible"
)
if self._gtkwave_available:
warnings.warn(f"Cannot find {viewer}, using GTKWave instead.")
viewer = "gtkwave"
elif self._surfer_available:
warnings.warn(f"Cannot find {viewer}, using Surfer instead.")
viewer = "surfer"
else:
self._viewer_available = True
return viewer

0 comments on commit 8ac9375

Please sign in to comment.