From e41db3e07decde9890fbe7258699ef37e0ecd49f Mon Sep 17 00:00:00 2001 From: CharrierTim Date: Thu, 13 Nov 2025 16:36:53 +0100 Subject: [PATCH] feat(sim_if/nvc): add nvc coverage support --- examples/vhdl/coverage/run.py | 3 +++ examples/vhdl/uart/run.py | 24 +++++++++++++++++- vunit/sim_if/nvc.py | 46 ++++++++++++++++++++++++++++++++--- 3 files changed, 69 insertions(+), 4 deletions(-) diff --git a/examples/vhdl/coverage/run.py b/examples/vhdl/coverage/run.py index 1fe1c7d7a..39131a6d3 100644 --- a/examples/vhdl/coverage/run.py +++ b/examples/vhdl/coverage/run.py @@ -18,6 +18,8 @@ def post_run(results): call(["gcovr", "coverage_data"]) else: call(["gcovr", "-a", "coverage_data/gcovr.json"]) + elif VU.get_simulator_name() == "nvc": + call(["nvc", "--cover-report", "coverage_data.ncdb", "-o", "output_coverage"]) VU = VUnit.from_argv() @@ -28,6 +30,7 @@ def post_run(results): LIB.set_sim_option("enable_coverage", True) +LIB.set_sim_option("nvc.elab_flags", ["--cover=branch,statement"]) LIB.set_compile_option("rivierapro.vcom_flags", ["-coverage", "bs"]) LIB.set_compile_option("rivierapro.vlog_flags", ["-coverage", "bs"]) LIB.set_compile_option("modelsim.vcom_flags", ["+cover=bs"]) diff --git a/examples/vhdl/uart/run.py b/examples/vhdl/uart/run.py index bd58e18dc..f3145bfed 100644 --- a/examples/vhdl/uart/run.py +++ b/examples/vhdl/uart/run.py @@ -16,6 +16,19 @@ from pathlib import Path from vunit import VUnit +from subprocess import call + + +def post_run(results): + results.merge_coverage(file_name="coverage_data") + if VU.get_simulator_name() == "ghdl": + if results._simulator_if._backend == "gcc": + call(["gcovr", "coverage_data"]) + else: + call(["gcovr", "-a", "coverage_data/gcovr.json"]) + elif VU.get_simulator_name() == "nvc": + call(["nvc", "--cover-report", "coverage_data.ncdb", "-o", "output_coverage"]) + VU = VUnit.from_argv() VU.add_vhdl_builtins() @@ -27,4 +40,13 @@ VU.add_library("uart_lib").add_source_files(SRC_PATH / "*.vhd") VU.add_library("tb_uart_lib").add_source_files(SRC_PATH / "test" / "*.vhd") -VU.main() +VU.set_sim_option("enable_coverage", True) + +VU.set_sim_option("nvc.elab_flags", ["--cover=branch,statement"]) +VU.set_compile_option("rivierapro.vcom_flags", ["-coverage", "bs"]) +VU.set_compile_option("rivierapro.vlog_flags", ["-coverage", "bs"]) +VU.set_compile_option("modelsim.vcom_flags", ["+cover=bs"]) +VU.set_compile_option("modelsim.vlog_flags", ["+cover=bs"]) +VU.set_compile_option("enable_coverage", True) + +VU.main(post_run=post_run) diff --git a/vunit/sim_if/nvc.py b/vunit/sim_if/nvc.py index e0c84cf0a..6d9fb5f57 100644 --- a/vunit/sim_if/nvc.py +++ b/vunit/sim_if/nvc.py @@ -17,7 +17,7 @@ import re from sys import stdout # To avoid output catched in non-verbose mode from ..exceptions import CompileError -from ..ostools import Process +from ..ostools import Process, file_exists from . import SimulatorInterface, ListOfStringOption, StringOption from . import run_command from ._viewermixin import ViewerMixin @@ -86,10 +86,11 @@ def __init__( # pylint: disable=too-many-arguments self._project = None self._vhdl_standard = None - self._coverage_test_dirs = set() + self._coverage_files = set() (major, minor) = self.determine_version(prefix) self._supports_jit = major > 1 or (major == 1 and minor >= 9) self._ieee_warnings_global = major > 1 or (major == 1 and minor >= 16) + self._supports_coverage_merge = major > 1 or (major == 1 and minor >= 14) if self.use_color: environ["NVC_COLORS"] = "always" @@ -135,7 +136,7 @@ def supports_coverage(cls): """ Returns True when the simulator supports coverage """ - return False + return True @classmethod def supports_vhdl_call_paths(cls): @@ -273,6 +274,12 @@ def simulate( cmd += ["-e"] cmd += config.sim_options.get("nvc.elab_flags", []) + + if config.sim_options.get("enable_coverage", False): + coverage_file_path = str(Path(output_path) / "coverage.ncdb") + self._coverage_files.add(coverage_file_path) + cmd += [f"--cover-file={coverage_file_path}"] + if config.vhdl_configuration_name is not None: cmd += [config.vhdl_configuration_name] else: @@ -329,3 +336,36 @@ def simulate( subprocess.call(cmd) return status + + def merge_coverage(self, file_name, args=None): + """ + Merge coverage from all test cases. + """ + + if not self._supports_coverage_merge: + LOGGER.error( + "Current nvc version does not support coverage database merge." + ) + return + + coverage_files = [] + + for coverage_file in self._coverage_files: + if file_exists(coverage_file): + coverage_files.append(coverage_file) + else: + LOGGER.warning("Missing coverage file: %s", coverage_file) + + nvc_coverage_merge_cmd = [ + "nvc", + "--cover-merge", + "-o", + f"{file_name}.ncdb", + ] + + nvc_coverage_merge_cmd.extend(coverage_files) + + print(f"Merging coverage files into {file_name!s}.ncdb...") + nvc_coverage_merge_process = Process(nvc_coverage_merge_cmd, env=self.get_env()) + nvc_coverage_merge_process.consume_output() + print("Done merging coverage files")