"""
Testbench for the Addition Layer module.
This module tests the Addition 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 random
import sys
from pathlib import Path
from typing import TYPE_CHECKING
import cocotb
from addition_layer_model import AddLayerModel
from cocotb.triggers import Timer
from cocotb_tools.runner import get_runner
# 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
# Define the IOs and their default values at reset
INIT_INPUTS = {
"i_state": init_hierarchy(dims=(5,), bitwidth=64, use_random=False),
"i_round": 0,
}
# Define the inputs
IV = 0x80400C0600000000
KEY = 0x000102030405060708090A0B0C0D0E0F
NONCE = 0x000102030405060708090A0B0C0D0E0F
STATE: list[int] = [
IV,
(KEY >> 64) & 0xFFFFFFFFFFFFFFFF, # Upper 64 bits of KEY
KEY & 0xFFFFFFFFFFFFFFFF, # Lower 64 bits of KEY
(NONCE >> 64) & 0xFFFFFFFFFFFFFFFF, # Upper 64 bits of NONCE
NONCE & 0xFFFFFFFFFFFFFFFF, # Lower 64 bits of NONCE
]
[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
adder_model = AddLayerModel(
inputs=INIT_INPUTS,
)
# Initialize the DUT
await initialize_dut(dut=dut, inputs=INIT_INPUTS)
# Assert the output
adder_model.assert_output(dut=dut, inputs=INIT_INPUTS)
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 addition_layer_test(dut: HierarchyObject) -> None:
"""
Test the DUT's behavior during normal computation.
Verifies that the output is correctly computed.
Parameters
----------
dut : HierarchyObject
The device under test (DUT).
Raises
------
RuntimeError
If the DUT fails to compute the correct output.
"""
try:
# Define the model
adder_model = AddLayerModel(
inputs=INIT_INPUTS,
)
await reset_dut_test(dut=dut)
# Set dut inputs defined by i_state = [IV, P1, P2, P3, P4]
dut_inputs = {
"i_state": STATE,
"i_round": 0,
}
# Initialize the DUT
await initialize_dut(dut=dut, inputs=dut_inputs)
# Assert the output
adder_model.assert_output(dut=dut, inputs=dut_inputs)
dut._log.info("Starting random tests...")
# Try with random inputs
for _ in range(10):
# Generate random inputs
dut_inputs = {
"i_state": init_hierarchy(dims=(5,), bitwidth=64, use_random=True),
"i_round": random.randint(0, 10),
}
# Set the inputs
await initialize_dut(dut=dut, inputs=dut_inputs)
# Update and Assert the output
adder_model.assert_output(dut=dut, inputs=dut_inputs)
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 addition_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_addition_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 = "addition_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}/addition_layer/addition_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_addition_layer()