Source code for test_permutation

"""
Testbench for the permutation module.

This module tests the permutation module by comparing the
output of the Python implementation with the verilog 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_tools.runner import get_runner
from permutation_model import PermutationModel

# 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,
    initialize_dut,
)

if TYPE_CHECKING:
    from cocotb.handle import HierarchyObject
    from cocotb_tools.runner import Runner

INIT_INPUTS = {
    "i_sys_enable": 0,
    "i_mux_select": 0,
    "i_enable_xor_key_begin": 0,
    "i_enable_xor_data_begin": 0,
    "i_enable_xor_key_end": 0,
    "i_enable_xor_lsb_end": 0,
    "i_enable_cipher_reg": 0,
    "i_enable_tag_reg": 0,
    "i_enable_state_reg": 0,
    "i_state": init_hierarchy(dims=(5,), bitwidth=64, use_random=False),
    "i_round": 0,
    "i_data": 0x0000000000000000,
    "i_key": 0x00000000000000000000000000000000,
}


[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 _ = PermutationModel() # Initialize the DUT await initialize_dut(dut=dut, inputs=INIT_INPUTS, outputs={}) 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 permutation_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 permutation_model = PermutationModel() # Reset the DUT await reset_dut_test(dut=dut) # Test with specific inputs dut_inputs = { "i_mux_select": 0, "i_enable_xor_key_begin": 0, "i_enable_xor_data_begin": 0, "i_enable_xor_key_end": 0, "i_enable_xor_lsb_end": 0, "i_enable_cipher_reg": 0, "i_enable_tag_reg": 0, "i_enable_state_reg": 1, "i_state": [ 0x4484A574CC1220E9, 0xB9D923E9D31C04E8, 0x7C40162196D79E1E, 0xC36DF040C62A25A2, 0xC77518AF6E08589F, ], "i_round": 0, "i_data": 0x6167652056484480, "i_key": 0x000102030405060708090A0B0C0D0E0F, } dut._log.info("Starting Permutation With the Last Permutation State") # Log the Key and Data dut._log.info("Key : 0x{:032X}".format(dut_inputs["i_key"])) dut._log.info("Data : 0x{:016X}".format(dut_inputs["i_data"])) # Set dut inputs for key, value in dut_inputs.items(): dut.__getattr__(name=key).value = value await dut.clock.rising_edge dut_inputs["i_mux_select"] = 1 for i_round in range(1, 13): # Update the values dut_inputs["i_round"] = i_round # Set dut inputs for key, value in dut_inputs.items(): dut.__getattr__(name=key).value = value await dut.clock.rising_edge # Update and Assert the output permutation_model.assert_output( dut=dut, inputs=dut_inputs, ) await dut.clock.rising_edge 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 permutation_test with error: {e}\n" f"DUT state at error:\n" f"{formatted_dut_state}" ) raise RuntimeError(error_message) from e
[docs] def test_permutation() -> 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 = "permutation" # 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", f"{rtl_path}/substitution_layer/sbox.sv", f"{rtl_path}/substitution_layer/substitution_layer.sv", f"{rtl_path}/diffusion_layer/diffusion_layer.sv", f"{rtl_path}/xor/xor_begin.sv", f"{rtl_path}/xor/xor_end.sv", f"{rtl_path}/permutation/permutation.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_permutation()