"""
Testbench for the Diffusion Layer module.
This module tests the Diffusion Layer module by comparing the
output of the Python implementation with the VHDL implementation.
@author: Timothée Charrier
"""
from __future__ import annotations
import os
import sys
from pathlib import Path
from typing import TYPE_CHECKING
import cocotb
from cocotb.triggers import Timer
from cocotb_tools.runner import get_runner
from diffusion_layer_model import DiffusionLayerModel
# Add the directory containing the utils.py file to the Python path
sys.path.insert(0, str(object=(Path(__file__).parent.parent).resolve()))
from cocotb_utils import (
generate_coverage_report_questa,
generate_coverage_report_verilator,
get_dut_state,
init_hierarchy,
)
if TYPE_CHECKING:
from cocotb.handle import HierarchyObject
from cocotb_tools.runner import Runner
INIT_INPUTS = {
"i_state": init_hierarchy(dims=(5,), bitwidth=64, use_random=False),
}
[docs]
async def initialize_dut(dut: HierarchyObject, inputs: dict) -> None:
"""
Initialize the DUT with the given inputs.
Parameters
----------
dut : HierarchyObject
The device under test (DUT).
inputs : dict
The input dictionary.
"""
for key, value in inputs.items():
getattr(dut, key).value = value
await Timer(time=10, unit="ns")
[docs]
@cocotb.test()
async def reset_dut_test(dut: HierarchyObject) -> None:
"""
Test the DUT's behavior during reset.
Verifies that the output is correctly reset and remains stable.
Parameters
----------
dut : HierarchyObject
The device under test (DUT).
Raises
------
RuntimeError
If the DUT fails to reset.
"""
try:
# Define the model
diffusion_layer_model = DiffusionLayerModel()
# Initialize the DUT
await initialize_dut(dut=dut, inputs=INIT_INPUTS)
# Verify the output
diffusion_layer_model.assert_output(dut=dut, state=INIT_INPUTS["i_state"])
except Exception as e:
dut_state = get_dut_state(dut=dut)
formatted_dut_state: str = "\n".join(
[f"{key}: {value}" for key, value in dut_state.items()],
)
error_message: str = (
f"Failed in reset_dut_test with error: {e}\n"
f"DUT state at error:\n"
f"{formatted_dut_state}"
)
raise RuntimeError(error_message) from e
[docs]
@cocotb.test()
async def diffusion_layer_test(dut: HierarchyObject) -> None:
"""
Test the Diffusion Layer module.
Parameters
----------
dut : HierarchyObject
The device under test (DUT).
Raises
------
RuntimeError
If the DUT fails to compute the correct output.
"""
try:
# Define the model
diffusion_layer_model = DiffusionLayerModel()
await reset_dut_test(dut=dut)
# Test with specific inputs
dut_inputs: list[dict[str, list[int]]] = [
{
"i_state": [
0x8849060F0C0D0EFF,
0x80410E05040506F7,
0xFFFFFFFFFFFFFF0F,
0x80400406000000F0,
0x0808080A08080808,
],
},
{
"i_state": [
0x8CBD402180B4D43D,
0x778B87B53BCCBF49,
0x3E7883FEF208E8C0,
0x0B48487C6AFB2C4D,
0x0F20CDA96AE53627,
],
},
{
"i_state": [
0xBAF6B13AFEB21E28,
0xABA64F6758F07EB1,
0x59D013C5A157E2F3,
0xA4CDCEB4CF026350,
0xA982986B3A1FBA70,
],
},
]
for inputs in dut_inputs:
await initialize_dut(dut=dut, inputs=inputs)
diffusion_layer_model.assert_output(dut=dut, state=inputs["i_state"])
except Exception as e:
dut_state = get_dut_state(dut=dut)
formatted_dut_state: str = "\n".join(
[f"{key}: {value}" for key, value in dut_state.items()],
)
error_message: str = (
f"Failed in diffusion_layer_test with error: {e}\n"
f"DUT state at error:\n"
f"{formatted_dut_state}"
)
raise RuntimeError(error_message) from e
[docs]
def test_diffusion_layer() -> None:
"""
Function Invoked by the test runner to execute the tests.
Raises
------
RuntimeError
If the test fails to build or run.
"""
# Define the simulator to use
default_simulator: str = "verilator"
# Define the top-level library and entity
library: str = "lib_rtl"
entity: str = "diffusion_layer"
# Default Generics Configuration
generics: dict[str, str] = {}
# Define paths
rtl_path: Path = (Path(__file__).parent.parent.parent / "rtl/").resolve()
build_dir: Path = Path("sim_build")
# Define the coverage file and output folder
output_folder: Path = build_dir / "coverage_report"
if default_simulator == "questa":
ucdb_file: Path = build_dir / f"{entity}_coverage.ucdb"
elif default_simulator == "verilator":
dat_file: Path = build_dir / "coverage.dat"
# Define the sources
sources: list[str] = [
f"{rtl_path}/ascon_pkg.sv",
f"{rtl_path}/diffusion_layer/diffusion_layer.sv",
]
# Define the build and test arguments
if default_simulator == "questa":
build_args: list[str] = [
"-svinputport=net",
"-O5",
"+cover=sbfec",
]
test_args: list[str] = [
"-coverage",
"-no_autoacc",
]
pre_cmd: list[str] = [
f"coverage save {entity}_coverage.ucdb -onexit",
]
elif default_simulator == "verilator":
build_args: list[str] = [
"-j",
"0",
"-Wall",
"--coverage",
"--coverage-max-width",
"320",
]
test_args: list[str] = []
pre_cmd = None
try:
# Get simulator name from environment
simulator: str = os.environ.get("SIM", default=default_simulator)
# Initialize the test runner
runner: Runner = get_runner(simulator_name=simulator)
# Build HDL sources
runner.build(
build_args=build_args,
build_dir=str(build_dir),
clean=True,
hdl_library=library,
hdl_toplevel=entity,
parameters=generics,
sources=sources,
waves=True,
)
# Run tests
runner.test(
build_dir=str(build_dir),
hdl_toplevel=entity,
hdl_toplevel_library=library,
pre_cmd=pre_cmd,
test_args=test_args,
test_module=f"test_{entity}",
waves=True,
)
# Generate the coverage report
if simulator == "questa":
generate_coverage_report_questa(
ucdb_file=ucdb_file,
output_folder=output_folder,
)
elif simulator == "verilator":
generate_coverage_report_verilator(
dat_file=dat_file,
output_folder=output_folder,
)
# Log the wave file
wave_file: Path = (
build_dir / "dump.vcd"
if simulator == "verilator"
else build_dir / "vsim.wlf"
)
sys.stdout.write(f"Waveform file: {wave_file}\n")
except Exception as e:
error_message: str = f"Failed in {__file__} with error: {e}"
raise RuntimeError(error_message) from e
if __name__ == "__main__":
test_diffusion_layer()