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 Mar 12, 2024
1 parent 594a2c0 commit a32cf67
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 34 deletions.
5 changes: 3 additions & 2 deletions docs/py/opts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,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
6 changes: 3 additions & 3 deletions tests/unit/test_ghdl_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,18 @@ class TestGHDLInterface(unittest.TestCase):
"""

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

def find_executable_side_effect(name):
return executables[name]

find_executable.side_effect = find_executable_side_effect

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

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

Expand Down
42 changes: 24 additions & 18 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 @@ -54,12 +54,14 @@ def add_arguments(parser):
"""
group = parser.add_argument_group("ghdl", description="GHDL 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",
)
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,8 @@ 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,
backend=cls.determine_backend(prefix),
)

Expand All @@ -88,20 +90,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()
Expand Down Expand Up @@ -293,11 +299,11 @@ def _get_command(self, config, output_path, elaborate_only, ghdl_e, wave_file):
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 @@ -333,8 +339,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 @@ -358,9 +364,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
21 changes: 10 additions & 11 deletions vunit/sim_if/nvc.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,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 @@ -67,21 +67,20 @@ def find_prefix_from_path(cls):
return cls.find_toolchain([cls.executable])

def __init__( # pylint: disable=too-many-arguments
self,
output_path,
prefix,
gui=False,
gtkwave_args="",
self, output_path, prefix, gui=False, 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
self._viewer_args = viewer_args
self._viewer = viewer
self._vhdl_standard = None
self._coverage_test_dirs = set()

Expand Down Expand Up @@ -294,9 +293,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 a32cf67

Please sign in to comment.