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

library work;
    use work.avalon_slave.all;
    use work.test_utility.all;

library std;
    use std.textio.all;

package test_data_channel_pkg is
    procedure is_empty( signal clk : in std_logic;
        signal req : out work.avalon_slave.Request;
        signal rsp : in work.avalon_slave.Response;
        variable res : out boolean );

    procedure assert_empty( signal clk : in std_logic;
        signal req : out work.avalon_slave.Request;
        signal rsp : in work.avalon_slave.Response );

    procedure assert_not_empty( signal clk : in std_logic;
        signal req : out work.avalon_slave.Request;
        signal rsp : in work.avalon_slave.Response );

    procedure assert_full( signal clk : in std_logic;
        signal req : out work.avalon_slave.Request;
        signal rsp : in work.avalon_slave.Response );

    procedure assert_not_full( signal clk : in std_logic;
        signal req : out work.avalon_slave.Request;
        signal rsp : in work.avalon_slave.Response );

    procedure assert_level( signal clk : in std_logic;
        signal req : out work.avalon_slave.Request;
        signal rsp : in work.avalon_slave.Response;
        variable level : in std_logic_vector );

    procedure assert_config( signal clk : in std_logic;
        signal req : out work.avalon_slave.Request;
        signal rsp : in work.avalon_slave.Response;
        variable config : in std_logic_vector );

    procedure write_config( signal clk : in std_logic;
        signal req : out work.avalon_slave.Request;
        variable config : in std_logic_vector );

    procedure write_and_assert_config( signal clk : in std_logic;
        signal req : out work.avalon_slave.Request;
        signal rsp : in work.avalon_slave.Response;
        variable config : in std_logic_vector );

    procedure write_clear( signal clk : in std_logic;
        signal req : out work.avalon_slave.Request );

    procedure write_sw_sink( signal clk : in std_logic;
        signal req : out work.avalon_slave.Request;
        variable data : in std_logic_vector );

    procedure read_sw_source( signal clk : in std_logic;
        signal req : out work.avalon_slave.Request;
        signal rsp : in work.avalon_slave.Response;
        variable data : out std_logic_vector );

    procedure assert_read_sw_source_eq( signal clk : in std_logic;
        signal req : out work.avalon_slave.Request;
        signal rsp : in work.avalon_slave.Response;
        variable expected : in std_logic_vector );

    procedure write_hw_sink( signal clk : in std_logic;
        signal write : out std_logic;
        signal writedata : out std_logic_vector;
        variable data : in std_logic_vector );

    procedure assert_read_hw_source_eq( signal clk : in std_logic;
        signal read : out std_logic;
        signal readdata : in std_logic_vector;
        variable expected : in std_logic_vector );

    procedure write_content( signal clk : in std_logic;
        signal req : out work.avalon_slave.Request;
        signal rsp : in work.avalon_slave.Response );

    procedure check_and_write_content( signal clk : in std_logic;
        signal req : out work.avalon_slave.Request;
        signal rsp : in work.avalon_slave.Response;
        constant expected : real_array );

end package test_data_channel_pkg;

package body test_data_channel_pkg is
    procedure is_empty( signal clk : in std_logic;
        signal req : out work.avalon_slave.Request;
        signal rsp : in work.avalon_slave.Response;
        variable res : out boolean ) is

        variable address : std_logic_vector( 3 downto 0 );
        variable data : std_logic_vector( 31 downto 0 );
        constant EMPTY : std_logic_vector( 31 downto 0 ) := x"00000001";
    begin
        wait until falling_edge( clk );
        address := std_logic_vector( to_unsigned( 1, address'length ) );

        work.test_avalon_slave.read( clk => clk,
            address => address,
            req => req,
            rsp => rsp,
            data => data );

        res := data = EMPTY;
    end procedure is_empty;

    procedure assert_empty( signal clk : in std_logic;
        signal req : out work.avalon_slave.Request;
        signal rsp : in work.avalon_slave.Response ) is

        variable address : std_logic_vector( 3 downto 0 );
        variable expected_readdata : std_logic_vector( 31 downto 0 ) := x"00000001";
    begin
        wait until falling_edge( clk );
        address := std_logic_vector( to_unsigned( 1, address'length ) );
        expected_readdata := x"00000001";

        work.test_avalon_slave.assert_readdata_eq( clk => clk,
            address => address,
            req => req,
            rsp => rsp,
            expected => expected_readdata,
            message => TEST_FAIL & " assert_empty" );
    end procedure assert_empty;

    procedure assert_not_empty( signal clk : in std_logic;
        signal req : out work.avalon_slave.Request;
        signal rsp : in work.avalon_slave.Response ) is

        variable address : std_logic_vector( 3 downto 0 );
        variable expected_readdata : std_logic_vector( 31 downto 0 ) := x"00000001";
    begin
        wait until falling_edge( clk );
        address := std_logic_vector( to_unsigned( 1, address'length ) );
        expected_readdata := x"00000000";

        work.test_avalon_slave.assert_readdata_eq( clk => clk,
            address => address,
            req => req,
            rsp => rsp,
            expected => expected_readdata,
            message => TEST_FAIL & " assert_not_empty" );
    end procedure assert_not_empty;

    procedure assert_full( signal clk : in std_logic;
        signal req : out work.avalon_slave.Request;
        signal rsp : in work.avalon_slave.Response ) is

        variable address : std_logic_vector( 3 downto 0 );
        variable expected_readdata : std_logic_vector( 31 downto 0 ) := x"00000001";
    begin
        wait until falling_edge( clk );
        address := std_logic_vector( to_unsigned( 2, address'length ) );
        expected_readdata := x"00000001";

        work.test_avalon_slave.assert_readdata_eq( clk => clk,
            address => address,
            req => req,
            rsp => rsp,
            expected => expected_readdata,
            message => TEST_FAIL & " assert_full" );
    end procedure assert_full;

    procedure assert_not_full( signal clk : in std_logic;
        signal req : out work.avalon_slave.Request;
        signal rsp : in work.avalon_slave.Response ) is

        variable address : std_logic_vector( 3 downto 0 );
        variable expected_readdata : std_logic_vector( 31 downto 0 ) := x"00000001";
    begin
        wait until falling_edge( clk );
        address := std_logic_vector( to_unsigned( 2, address'length ) );
        expected_readdata := x"00000000";

        work.test_avalon_slave.assert_readdata_eq( clk => clk,
            address => address,
            req => req,
            rsp => rsp,
            expected => expected_readdata,
            message => TEST_FAIL & " assert_not_full" );
    end procedure assert_not_full;

    procedure assert_level( signal clk : in std_logic;
        signal req : out work.avalon_slave.Request;
        signal rsp : in work.avalon_slave.Response;
        variable level : in std_logic_vector ) is

        variable address : std_logic_vector( 3 downto 0 );
    begin
        wait until falling_edge( clk );
        address := std_logic_vector( to_unsigned( 3, address'length ) );

        work.test_avalon_slave.assert_readdata_eq( clk => clk,
            address => address,
            req => req,
            rsp => rsp,
            expected => level,
            message => TEST_FAIL & " assert_level" );
    end procedure assert_level;

    procedure assert_config( signal clk : in std_logic;
        signal req : out work.avalon_slave.Request;
        signal rsp : in work.avalon_slave.Response;
        variable config : in std_logic_vector ) is

        variable address : std_logic_vector( 3 downto 0 );
    begin
        wait until falling_edge( clk );
        address := std_logic_vector( to_unsigned( 0, address'length ) );

        work.test_avalon_slave.assert_readdata_eq( clk => clk,
            address => address,
            req => req,
            rsp => rsp,
            expected => config,
            message => TEST_FAIL & " assert_config" );
    end procedure assert_config;

    procedure write_config( signal clk : in std_logic;
        signal req : out work.avalon_slave.Request;
        variable config : in std_logic_vector ) is

        variable address : std_logic_vector( 3 downto 0 );
    begin
        wait until falling_edge( clk );
        address := std_logic_vector( to_unsigned( 0, address'length ) );

        work.test_avalon_slave.write( clk => clk,
            address => address,
            req => req,
            data => config );
    end procedure write_config;

    procedure write_and_assert_config( signal clk : in std_logic;
        signal req : out work.avalon_slave.Request;
        signal rsp : in work.avalon_slave.Response;
        variable config : in std_logic_vector ) is

        variable address : std_logic_vector( 3 downto 0 );
    begin
        write_config( clk => clk, req => req, config => config );
        assert_config( clk => clk, req => req, rsp => rsp, config => config );
    end procedure write_and_assert_config;

    procedure write_clear( signal clk : in std_logic;
        signal req : out work.avalon_slave.Request ) is

        variable address : std_logic_vector( 3 downto 0 );
        variable clear : std_logic_vector( 31 downto 0 ) := x"00000001";
    begin
        wait until falling_edge( clk );
        address := std_logic_vector( to_unsigned( 6, address'length ) );

        work.test_avalon_slave.write( clk => clk,
            address => address,
            req => req,
            data => clear );
    end procedure write_clear;

    procedure write_sw_sink( signal clk : in std_logic;
        signal req : out work.avalon_slave.Request;
        variable data : in std_logic_vector ) is

        variable address : std_logic_vector( 3 downto 0 );
    begin
        wait until falling_edge( clk );
        address := std_logic_vector( to_unsigned( 4, address'length ) );

        work.test_avalon_slave.write( clk => clk,
            address => address,
            req => req,
            data => data );
    end procedure write_sw_sink;

    procedure read_sw_source( signal clk : in std_logic;
        signal req : out work.avalon_slave.Request;
        signal rsp : in work.avalon_slave.Response;
        variable data : out std_logic_vector ) is

        variable address : std_logic_vector( 3 downto 0 );
    begin
        wait until falling_edge( clk );
        address := std_logic_vector( to_unsigned( 5, address'length ) );

        work.test_avalon_slave.read( clk => clk,
            address => address,
            req => req,
            rsp => rsp,
            data => data );
    end procedure read_sw_source;

    procedure assert_read_sw_source_eq( signal clk : in std_logic;
        signal req : out work.avalon_slave.Request;
        signal rsp : in work.avalon_slave.Response;
        variable expected : in std_logic_vector ) is

        variable address : std_logic_vector( 3 downto 0 );
    begin
        wait until falling_edge( clk );
        address := std_logic_vector( to_unsigned( 5, address'length ) );

        work.test_avalon_slave.assert_readdata_eq( clk => clk,
            address => address,
            req => req,
            rsp => rsp,
            expected => expected,
            message => TEST_FAIL & " assert_readdata_eq" );
    end procedure assert_read_sw_source_eq;

    procedure write_hw_sink( signal clk : in std_logic;
        signal write : out std_logic;
        signal writedata : out std_logic_vector;
        variable data : in std_logic_vector ) is
    begin

        wait until falling_edge( clk );
        write <= '1';
        writedata <= data;
        wait until falling_edge( clk );
        write <= '0';

    end procedure write_hw_sink;

    procedure assert_read_hw_source_eq( signal clk : in std_logic;
        signal read : out std_logic;
        signal readdata : in std_logic_vector;
        variable expected : in std_logic_vector ) is
    begin

        wait until falling_edge( clk );
        assert( readdata = expected )
            report TEST_FAIL & " assert_read_hw_source_eq" & LF &
                "    expected: " & to_string( expected ) & LF &
                "    actual:   " & to_string( readdata ) & LF
                severity error;
        wait until falling_edge( clk );
        read <= '1';
        wait until falling_edge( clk );
        read <= '0';

    end procedure assert_read_hw_source_eq;

    procedure write_content( signal clk : in std_logic;
        signal req : out work.avalon_slave.Request;
        signal rsp : in work.avalon_slave.Response ) is

        variable index : integer := 0;
        variable empty : boolean;
        variable value : std_logic_vector( 31 downto 0 );
        variable float_value : float32;
        variable real_value : real;
        file data_file : text;
    begin
        std.textio.write( std.textio.OUTPUT, "    write_content ... " );

        file_open( data_file, "data.py", write_mode );
        std.textio.write( data_file, "float_data = [" );

        while true loop
            is_empty( clk => clk, req => req, rsp => rsp, res => empty );
            if ( empty ) then
                exit;
            end if;
            read_sw_source( clk => clk, req => req, rsp => rsp,
                data => value );
            float_value := to_float( value );
            real_value := to_real( float_value );
            std.textio.write( data_file, to_string( real_value ) & "," );
            index := index + 1;
        end loop;

        while index < 1024 loop
            std.textio.write( data_file, "0.0 ," );
            index := index + 1;
        end loop;

        std.textio.write( data_file, "]" & LF );
        file_close( data_file );

        std.textio.write( std.textio.OUTPUT, TEST_OK );
    end procedure write_content;

    procedure check_and_write_content( signal clk : in std_logic;
        signal req : out work.avalon_slave.Request;
        signal rsp : in work.avalon_slave.Response;
        constant expected : real_array ) is

        variable expected_readdata : std_logic_vector( 31 downto 0 );
        variable index : integer := 0;
        variable empty : boolean;
        variable value : std_logic_vector( 31 downto 0 );
        variable float_value : float32;
        variable real_value : real;
        variable expected_value : real;
        variable abs_err : real := 0.5e-1;
        file data_file : text;
    begin
        std.textio.write( std.textio.OUTPUT, "    check_and_write_content ... " );

        assert_full( clk => clk, req => req, rsp => rsp );

        expected_readdata := std_logic_vector( to_unsigned( 0, expected_readdata'length ) );
        assert_level( clk => clk, req => req, rsp => rsp,
            level => expected_readdata );

        file_open( data_file, "data.py", write_mode );
        std.textio.write( data_file, "float_data = [" );

        while true loop
            is_empty( clk => clk, req => req, rsp => rsp, res => empty );
            if ( empty ) then
                exit;
            end if;
            read_sw_source( clk => clk, req => req, rsp => rsp,
                data => value );
            float_value := to_float( value );
            real_value := to_real( float_value );
            std.textio.write( data_file, to_string( real_value ) & "," );
            expected_value := expected( index );
            assert_element_near( real_value, expected_value, abs_err, index );
            index := index + 1;
        end loop;

        std.textio.write( data_file, "]" & LF );
        file_close( data_file );

        std.textio.write( std.textio.OUTPUT, TEST_OK );
    end procedure check_and_write_content;
end package body test_data_channel_pkg;
