cocotb integrationΒΆ

Reference models and code for verification can be written swiftly using APyTypes, and incorporated using cocotb. Below is an exhaustive test of an eight-bit floating-point multiplier with round to nearest, ties to even.

Both source files, test_fp8_mul.py and fp8_multiplier.vhdl, are available under examples.

import os
from pathlib import Path
import cocotb
from cocotb.clock import Clock
from cocotb.runner import get_runner
from cocotb.triggers import FallingEdge
from apytypes import APyFloat, APyFloatArray


@cocotb.test()
async def fp8_mul_test_all(dut):
    """
    Exhaustive test of all inputs.
    """
    EXP_BITS, MAN_BITS = 4, 3
    fp8_values = APyFloatArray.from_bits(
        range(2 ** (1 + EXP_BITS + MAN_BITS)), exp_bits=EXP_BITS, man_bits=MAN_BITS
    )

    clock = Clock(dut.clk, 10, units="ns")
    cocotb.start_soon(clock.start(start_high=False))

    for x in fp8_values:
        dut.x_in.value = x.to_bits()
        for y in fp8_values:
            dut.y_in.value = y.to_bits()

            await FallingEdge(dut.clk)  # Input is set on falling clock edge
            await FallingEdge(dut.clk)  # Wait another clock cycle for the result

            z_dut = APyFloat.from_bits(
                int(dut.z_out.value), exp_bits=EXP_BITS, man_bits=MAN_BITS
            )

            z_ref = x * y

            # If the expected result is NaN, do not test for bit-exact representations
            if z_ref.is_nan:
                assert z_dut.is_nan
            else:
                assert int(dut.z_out.value) == z_ref.to_bits(), (
                    f"{z_dut!r}({z_dut}) == {z_ref!r}({z_ref})\n{x=}({x}), {y=}({y})"
                )
/home/runner/work/apytypes/apytypes/examples/test_fp8_mul.py:17: UserWarning: Python runners and associated APIs are an experimental feature and subject to change.
  from cocotb.runner import get_runner

The rest of the testbench is set up as one would normally do using cocotb.

def test_fp8_mul():
    """
    Simulate the floating-point multiplier using the Python runner.

    This file can be run directly or via pytest discovery.
    """
    sim = os.getenv("SIM", "nvc")

    dir_path = (
        os.path.dirname(os.path.realpath(__file__))
        if "__file__" in globals()
        else Path.cwd()
    )
    sources = [f"{dir_path}/fp8_multiplier.vhdl"]

    runner = get_runner(sim)
    runner.build(
        sources=sources,
        hdl_toplevel="fp8_multiplier",
    )

    runner.test(hdl_toplevel="fp8_multiplier", test_module="test_fp8_mul")


if __name__ == "__main__":
    test_fp8_mul()
INFO: Running command nvc --work=top -a /home/runner/work/apytypes/apytypes/examples/fp8_multiplier.vhdl in directory /home/runner/work/apytypes/apytypes/examples/sim_build
INFO: Running command nvc --work=top -e fp8_multiplier --no-save --jit -r --load=/opt/hostedtoolcache/Python/3.10.16/x64/lib/python3.10/site-packages/cocotb/libs/libcocotbvhpi_nvc.so in directory /home/runner/work/apytypes/apytypes/examples/sim_build
INFO: Results file: /home/runner/work/apytypes/apytypes/examples/sim_build/results.xml

The contents of fp8_multiplier.vhdl is shown below.

library ieee;
use ieee.std_logic_1164.all;
use ieee.float_pkg.all;

entity fp8_multiplier is
    port ( clk  : in std_logic := '0';
           x_in : in std_logic_vector(7 downto 0) := (others => '0');
           y_in : in std_logic_vector(7 downto 0) := (others => '0');
           z_out : out std_logic_vector(7 downto 0) := (others => '0'));
end fp8_multiplier;

architecture arch of fp8_multiplier is
    signal x, y : float (4 downto -3); -- 4 exponent bits, 3 mantissa bits
begin
    process(clk)
    begin
        if rising_edge(clk) then
            x <= to_float(x_in, x);
            y <= to_float(y_in, y);
            z_out <= to_std_logic_vector(x * y);
        end if;
    end process;
end arch;

Total running time of the script: (0 minutes 15.976 seconds)

Gallery generated by Sphinx-Gallery