from collections.abc import Callable, Iterable
from os import environ
from os.path import exists
from pathlib import Path
from typing import Any
from cocotb_tools.runner import get_runner
from elasticai.creator.file_generation import find_project_root
[docs]
def run_cocotb_sim_for_src_dir(
src_files: Iterable[str] | Iterable[Path],
top_module_name: str,
cocotb_test_module: str,
path2src: str = "",
defines: dict | Callable[[], dict] = lambda: {},
params: dict | Callable[[], dict] = lambda: {},
timescale: tuple[str, str] = ("1ps", "1fs"),
en_debug_mode: bool = True,
waveform_save_dst: str = "",
) -> Path:
"""Function for running Verilog/VHDL Simulation using COCOTB environment
:param src_files: List with source files of each used Verilog/VHDL file
:param top_module_name: Name of the top module (from file)
:param cocotb_test_module: Fully qualified name of python module containing cotName of the cocotb testbench in Python
:param path2src: Path to the folder in which all src files are available for testing
:param defines: Dictionary of parameters to pass to the module [key: value, ...] - usable only in Verilog
:param params: Dictionary of parameters to pass to the module [key: value, ...] - value will be ignored
:param timescale: Tuple with Timescale value for simulation (step, accuracy)
:param en_debug_mode: Enable debug mode
:param waveform_save_dst: Path to the destination folder for saving waveform file
:return: Path to folder which includes waveform file [Default: simulation output folder]
"""
_path2src = Path(path2src)
return run_cocotb_sim(
src_files=[_path2src / f for f in src_files],
top_module_name=top_module_name,
cocotb_test_module=cocotb_test_module,
defines=defines,
params=params,
timescale=timescale,
en_debug_mode=en_debug_mode,
waveform_save_dst=waveform_save_dst,
)
def _build_language_map() -> dict[str, str]:
language_mapping = {}
for ks, language in [((".v", ".sv"), "verilog"), ((".vhd", ".vhdl"), "vhdl")]:
for k in ks:
language_mapping[k] = language
return language_mapping
[docs]
def run_cocotb_sim(
src_files: Iterable[str] | Iterable[Path],
top_module_name: str,
cocotb_test_module: str,
defines: dict[str, Any] | Callable[[], dict[str, Any]] = lambda: {},
params: dict[str, Any] | Callable[[], dict[str, Any]] = lambda: {},
timescale: tuple[str, str] = ("1ps", "1fs"),
en_debug_mode: bool = True,
waveform_save_dst: str = "",
build_sim_dir: str | Path | None = None,
) -> Path:
"""Function for running Verilog/VHDL Simulation using COCOTB environment
:param src_files: List with source files of each used Verilog/VHDL file
:param top_module_name: Name of the top module (from file)
:param cocotb_test_module: Fully qualified name of python module containing cotName of the cocotb testbench in Python
:param defines: Dictionary of parameters to pass to the module [key: value, ...] - usable only in Verilog
:param params: Dictionary of parameters to pass to the module [key: value, ...] - value will be ignored
:param timescale: Tuple with Timescale value for simulation (step, accuracy)
:param en_debug_mode: Enable debug mode
:param waveform_save_dst: Path to the destination folder for saving waveform file
:return: Path to folder which includes waveform file [Default: simulation output folder]
"""
design_sources = [Path(m) for m in src_files]
params = _normalize_dict_arg(params)
defines = _normalize_dict_arg(defines)
if len(design_sources) == 0:
raise ValueError("no design sources specified")
if any(map(lambda x: not x.exists(), design_sources)):
raise FileNotFoundError(f"Design file does not exist {design_sources}")
runner_mapping = {"verilog": "icarus", "vhdl": "ghdl"}
suffix = design_sources[0].suffix
language_mapping = _build_language_map()
if suffix not in language_mapping:
raise ValueError(f"File ending {suffix} not supported")
language = language_mapping[suffix]
runner = get_runner(runner_mapping[language])
environ["COCOTB_RESOLVE_X"] = "ZEROS"
environ["COCOTB_LOG_LEVEL"] = "INFO" if en_debug_mode else "WARNING"
environ["COCOTB_REDUCED_LOG_FMT"] = "0" if en_debug_mode else "1"
environ["MACOSX_DEPLOYMENT_TARGET"] = "15.0"
if build_sim_dir is None:
build_sim_dir = find_project_root() / "build_sim"
build_sim_dir.mkdir(exist_ok=True, parents=True)
else:
build_sim_dir = Path(build_sim_dir)
build_waveform_dir = (
build_sim_dir.absolute()
if waveform_save_dst == ""
else Path(waveform_save_dst).absolute()
)
build_waveform_dir.mkdir(exist_ok=True, parents=True)
if language == "verilog":
# --- Building new dump file for getting vcd file
plus_args = []
build_args = ["-s", "cocotb_iverilog_dump_v2"]
dump_file_src = _create_iverilog_dump_file(
top_module_name=top_module_name,
dump_dst=build_sim_dir,
path=build_waveform_dir,
)
design_sources.append(dump_file_src)
else:
top_module_name = top_module_name.lower()
build_args = []
plus_args = [f"--vcd={build_waveform_dir / 'waveforms'}.vcd"]
runner.build(
sources=design_sources,
hdl_toplevel=top_module_name,
always=True,
clean=False,
waves=True,
build_args=build_args,
defines=defines,
parameters=params,
timescale=timescale,
build_dir=build_sim_dir,
)
runner.test(
hdl_toplevel=top_module_name,
test_module=[cocotb_test_module],
hdl_toplevel_lang=language,
gui=False,
plusargs=plus_args,
waves=True,
parameters=params,
timescale=timescale,
build_dir=build_sim_dir,
test_dir=build_sim_dir,
)
return build_waveform_dir.absolute()
def _create_iverilog_dump_file(
top_module_name: str, path: Path, dump_dst: Path
) -> Path:
dumpfile_path = path / "waveforms.vcd"
with open(dump_dst / "dump.v", "w") as f:
f.write("module cocotb_iverilog_dump_v2();\n")
f.write("initial begin\n")
f.write(f' $dumpfile("{dumpfile_path}");\n')
f.write(f" $dumpvars(0, {top_module_name});\n")
f.write("end\n")
f.write("endmodule\n")
return dump_dst / "dump.v"
def _normalize_dict_arg(
arg: dict[str, Any] | Callable[[], dict[str, Any]],
) -> dict[str, Any]:
if callable(arg):
return arg()
return arg
[docs]
def check_cocotb_test_result(result_folder_cocotb: str = "build_sim") -> bool:
path2xml = find_project_root() / result_folder_cocotb / "results.xml"
if not exists(path2xml):
raise FileNotFoundError(
"Output file of cocotb simulation 'results.xml' not found"
)
else:
with open(path2xml, "r") as f:
lines = f.readlines()
for line in lines:
if "failure" in line:
return False
return True