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 10, 2024
1 parent fbe8d06 commit a2cc9f2
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 35 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
5 changes: 3 additions & 2 deletions docs/py/opts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,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:
Expand Down
45 changes: 26 additions & 19 deletions vunit/sim_if/ghdl.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
]

Expand All @@ -52,14 +52,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="gtkwave", help="Waveform viewer to use")

@classmethod
def from_args(cls, args, output_path, **kwargs):
Expand All @@ -71,8 +73,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 +91,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() # For gcov
Expand Down Expand Up @@ -315,11 +322,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 +362,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 @@ -383,9 +390,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())]

Expand Down
40 changes: 29 additions & 11 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,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
Expand All @@ -58,6 +59,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,17 +77,26 @@ def __init__( # pylint: disable=too-many-arguments
prefix,
num_threads,
gui=False,
gtkwave_args="",
viewer_fmt=None,
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
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
self._vhdl_standard = None
self._coverage_test_dirs = set()

Expand Down Expand Up @@ -251,15 +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")
viewer_fmt = self._viewer_fmt or "fst"
wave_file = script_path / (f"{config.entity_name}.{viewer_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)
viewer_fmt = None

cmd += ["-H", config.sim_options.get("nvc.heap_size", "64m")]
cmd += config.sim_options.get("nvc.global_flags", [])
Expand Down Expand Up @@ -289,6 +304,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={viewer_fmt}"]

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

status = True
Expand All @@ -300,9 +318,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())]

Expand Down

0 comments on commit a2cc9f2

Please sign in to comment.