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

Add NVC support #904

Merged
merged 3 commits into from
Feb 27, 2023
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
2 changes: 2 additions & 0 deletions examples/vhdl/array/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,6 @@
VU.set_compile_option("ghdl.flags", ["-frelaxed"])
VU.set_sim_option("ghdl.elab_flags", ["-frelaxed"])

VU.set_compile_option("nvc.a_flags", ["--relaxed"])

VU.main()
2 changes: 2 additions & 0 deletions vunit/sim_if/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from .ghdl import GHDLInterface
from .incisive import IncisiveInterface
from .modelsim import ModelSimInterface
from .nvc import NVCInterface
from .rivierapro import RivieraProInterface
from . import BooleanOption, ListOfStringOption, VHDLAssertLevelOption

Expand All @@ -33,6 +34,7 @@ def supported_simulators():
ActiveHDLInterface,
GHDLInterface,
IncisiveInterface,
NVCInterface,
]

def _extract_compile_options(self):
Expand Down
296 changes: 296 additions & 0 deletions vunit/sim_if/nvc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
# 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-2022, Lars Asplund lars.anders.asplund@gmail.com

"""
Interface for NVC simulator
"""

from pathlib import Path
from os import environ, makedirs, remove
import logging
import subprocess
import shlex
import re
from sys import stdout # To avoid output catched in non-verbose mode
from ..exceptions import CompileError
from ..ostools import Process
from . import SimulatorInterface, ListOfStringOption, StringOption
from . import run_command
from ..vhdl_standard import VHDL

LOGGER = logging.getLogger(__name__)


class NVCInterface(SimulatorInterface): # pylint: disable=too-many-instance-attributes
"""
Interface for NVC simulator
"""

name = "nvc"
executable = environ.get("NVC", "nvc")
supports_gui_flag = True
supports_colors_in_gui = True

compile_options = [
ListOfStringOption("nvc.a_flags"),
]

sim_options = [
ListOfStringOption("nvc.sim_flags"),
ListOfStringOption("nvc.elab_flags"),
StringOption("nvc.heap_size"),
StringOption("nvc.gtkwave_script.gui"),
]

@classmethod
def from_args(cls, args, output_path, **kwargs):
"""
Create instance from args namespace
"""
prefix = cls.find_prefix()
return cls(
output_path=output_path,
prefix=prefix,
gui=args.gui,
)

@classmethod
def find_prefix_from_path(cls):
"""
Find first valid NVC toolchain prefix
"""
return cls.find_toolchain([cls.executable])

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

if self.use_color:
environ["NVC_COLORS"] = "always"

def has_valid_exit_code(self): # pylint: disable=arguments-differ
"""
Return if the simulation should fail with nonzero exit codes
"""
return self._vhdl_standard >= VHDL.STD_2008

@classmethod
def _get_version_output(cls, prefix):
"""
Get the output of 'nvc --version'
"""
return subprocess.check_output([str(Path(prefix) / cls.executable), "--version"]).decode()

@classmethod
def determine_version(cls, prefix):
"""
Determine the NVC version
"""
return float(
re.match(
r"nvc ([0-9]*\.[0-9]*).*\(.*\)",
cls._get_version_output(prefix),
).group(1)
)

@classmethod
def supports_vhpi(cls):
"""
Returns True when the simulator supports VHPI
"""
return True

@classmethod
def supports_coverage(cls):
"""
Returns True when the simulator supports coverage
"""
return False

@classmethod
def supports_vhdl_call_paths(cls):
"""
Returns True when this simulator supports VHDL-2019 call paths
"""
return True

@classmethod
def supports_vhdl_package_generics(cls):
"""
Returns True when this simulator supports VHDL package generics
"""
return True

def setup_library_mapping(self, project):
"""
Setup library mapping
"""
self._project = project

for library in project.get_libraries():
path = Path(library.directory)
if not path.exists():
if not path.parent.exists():
makedirs(path.parent)

if not run_command(
[str(Path(self._prefix) / self.executable), "--work=" + library.directory, "--init"],
env=self.get_env(),
):
raise RuntimeError("Failed to initialise library " + library.directory)

vhdl_standards = set(
source_file.get_vhdl_standard()
for source_file in project.get_source_files_in_order()
if source_file.is_vhdl
)

if not vhdl_standards:
self._vhdl_standard = VHDL.STD_2008
elif len(vhdl_standards) != 1:
raise RuntimeError(f"NVC cannot handle mixed VHDL standards, found {vhdl_standards!r}")
else:
self._vhdl_standard = list(vhdl_standards)[0]

def compile_source_file_command(self, source_file):
"""
Returns the command to compile a single source_file
"""
if source_file.is_vhdl:
return self.compile_vhdl_file_command(source_file)

LOGGER.error("Unknown file type: %s", source_file.file_type)
raise CompileError

@staticmethod
def _std_str(vhdl_standard):
"""
Convert standard to format of NVC command line flag
"""
if vhdl_standard == VHDL.STD_1993:
return "1993"

if vhdl_standard == VHDL.STD_2002:
return "2002"

if vhdl_standard == VHDL.STD_2008:
return "2008"

if vhdl_standard == VHDL.STD_2019:
return "2019"

raise ValueError(f"Invalid VHDL standard {vhdl_standard}")

def _get_command(self, std, worklib, workpath):
"""
Get basic NVC command with global options
"""
cmd = [
str(Path(self._prefix) / self.executable),
f"--work={worklib}:{workpath!s}",
f"--std={self._std_str(std)}",
]

for library in self._project.get_libraries():
cmd += [f"--map={library.name}:{library.directory}"]

return cmd

def compile_vhdl_file_command(self, source_file):
"""
Returns the command to compile a VHDL file
"""
cmd = self._get_command(
source_file.get_vhdl_standard(), source_file.library.name, source_file.library.directory
)

cmd += ["-a"]
cmd += source_file.compile_options.get("nvc.a_flags", [])

cmd += [source_file.name]
return cmd

def simulate(self, output_path, test_suite_name, config, elaborate_only):
"""
Simulate with entity as top level using generics
"""

script_path = Path(output_path) / self.name

if not script_path.exists():
makedirs(script_path)

if self._gui:
wave_file = script_path / (f"{config.entity_name}.fst")
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 += ["-e"]

cmd += config.sim_options.get("nvc.elab_flags", [])
cmd += [f"{config.entity_name}-{config.architecture_name}"]

for name, value in config.generics.items():
cmd += [f"-g{name}={value}"]

if not elaborate_only:
cmd += ["--no-save"]
if self._supports_jit:
cmd += ["--jit"]
cmd += ["-r"]
cmd += config.sim_options.get("nvc.sim_flags", [])
cmd += [f"--exit-severity={config.vhdl_assert_stop_level}"]

if config.sim_options.get("disable_ieee_warnings", False):
cmd += ["--ieee-warnings=off"]

if wave_file:
cmd += [f"--wave={wave_file}"]

print(" ".join(cmd))

status = True

try:
proc = Process(cmd)
proc.consume_output()
except Process.NonZeroExitCode:
status = False

if self._gui and not elaborate_only:
cmd = ["gtkwave"] + shlex.split(self._gtkwave_args) + [wave_file]

init_file = 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
2 changes: 2 additions & 0 deletions vunit/vhdl/data_types/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@
continue
LIB.add_source_file(fname)

VU.set_sim_option("nvc.heap_size", "256m")

VU.main()