diff --git a/docs/cli.rst b/docs/cli.rst index 6e94f1c3c..ecaa351d8 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -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 diff --git a/docs/py/opts.rst b/docs/py/opts.rst index c782ab69d..a9cc31fa0 100644 --- a/docs/py/opts.rst +++ b/docs/py/opts.rst @@ -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: diff --git a/vunit/sim_if/ghdl.py b/vunit/sim_if/ghdl.py index 6fd33dee0..6674f8de3 100644 --- a/vunit/sim_if/ghdl.py +++ b/vunit/sim_if/ghdl.py @@ -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"), ] @@ -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): @@ -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), ) @@ -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 @@ -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: @@ -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: @@ -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())] diff --git a/vunit/sim_if/nvc.py b/vunit/sim_if/nvc.py index 8f64ecae3..41efb0e07 100644 --- a/vunit/sim_if/nvc.py +++ b/vunit/sim_if/nvc.py @@ -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 @@ -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 @@ -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 @@ -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() @@ -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", []) @@ -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 @@ -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())]