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

library osvvm; -- Utility Library
context osvvm.OsvvmContext;

use work.rtps_package.all;
use work.user_config.all;
use work.rtps_config_package.all;
use work.rtps_test_package.all;
use work.Type2_package.all;

-- This testbench tests the KEY_HOLDER commands of TYPE1.
-- It uses the writer_interface to send a valid payload, then reads the serialized key and the key hash. The returned serialized key is compared to a "handcrafted" reference, and the key hash is latched for later comparison.
-- Then the reference serialized key is pushed (resetting the internaly generated key hash), and the serialized key and key hash are re-read and compared (The key hash is compared to the previosuly compared).
-- The payload is sent in Little Endian.

entity L1_Type1_key_holder_test2 is
end entity;

architecture testbench of L1_Type1_key_holder_test2 is
    
    signal clk, reset : std_logic := '0';
    signal ready_sk, ready_w, ready_kh_in, ready_kh_out, valid_sk, valid_w, valid_kh_in, valid_kh_out, last_word_sk, last_word_w, last_word_kh_in, last_word_kh_out : std_logic := '0';
    signal data_sk, data_w, data_kh_in, data_kh_out : std_logic_vector(WORD_WIDTH-1 downto 0) := (others => '0');
    signal start_w, ack_w, start_kh, ack_kh, decode_error, encode_done : std_logic := '0';
    signal opcode_kh : KEY_HOLDER_OPCODE_TYPE := NOP;
    shared variable SB_out : osvvm.ScoreBoardPkg_slv.ScoreBoardPType;
    shared variable serialized_key : TEST_PACKET_TYPE := EMPTY_TEST_PACKET;
    signal key_hash, key_hash_latch : KEY_HASH_TYPE := KEY_HASH_NIL;
    
    signal id_in : std_logic_vector(CDR_LONG_WIDTH-1 downto 0) := (others => '0');
    signal a_in : std_logic_vector(CDR_LONG_WIDTH-1 downto 0) := (others => '0');
begin
    
    Type1_writer_interface_inst : entity work.Type1_writer_interface(arch)
        generic map (
            LITTLE_ENDIAN   => '1'
        )
        port map (
            clk                                 => clk,
            reset                               => reset,
            start_dds                           => open,
            ack_dds                             => '1',
            opcode_dds                          => open,
            instance_handle_out_dds             => HANDLE_NIL,
            source_ts_dds                       => open,
            max_wait_dds                        => open,
            done_dds                            => '1',
            return_code_dds                     => RETCODE_OK,
            instance_handle_in_dds              => open,
            ready_out_dds                       => ready_w,
            valid_out_dds                       => valid_w,
            data_out_dds                        => data_w,
            last_word_out_dds                   => last_word_w,
            ready_in_dds                        => open,
            valid_in_dds                        => '0',
            data_in_dds                         => (others => '0'),
            last_word_in_dds                    => '0',
            status_dds                          => (others => '0'),
            start_user                          => start_w,
            ack_user                            => ack_w,
            opcode_user                         => WRITE,
            instance_handle_in_user             => HANDLE_NIL,
            source_ts_user                      => TIME_ZERO,
            max_wait_user                       => TIME_INFINITE,
            done_user                           => open,
            return_code_user                    => open,
            instance_handle_out_user            => open,
            status_user                         => open,
            encode_done                         => encode_done,
            id                                  => id_in,
            a                                   => a_in
        );
    
    uut : entity work.key_holder(TYPE1)
        generic map (
            LITTLE_ENDIAN   => '1'
        )
        port map (
            clk             => clk,
            reset           => reset,
            start           => start_kh,
            opcode          => opcode_kh,
            ack             => ack_kh,
            decode_error    => decode_error,
            abort           => '0',
            data_in         => data_kh_in,
            valid_in        => valid_kh_in,
            ready_in        => ready_kh_in,
            last_word_in    => last_word_kh_in,
            data_out        => data_kh_out,
            valid_out       => valid_kh_out,
            ready_out       => ready_kh_out,
            last_word_out   => last_word_kh_out
        );
    
    
    stimulus_prc : process
        variable RV             : RandomPType;
        variable align_offset   : unsigned(MAX_ALIGN_OFFSET_WIDTH-1 downto 0) := (others => '0');
        variable SK, KH : AlertLogIDType;
        constant SERIALIZED_KEY_HEADER : std_logic_vector(WORD_WIDTH-1 downto 0) := CDR_LE & x"0000";
        
        procedure wait_on_sig(signal sig : std_logic) is
        begin
            if (sig /= '1') then
                wait on sig until sig = '1';
            end if;
        end procedure;
        
        procedure push_sk is
        begin
            for i in 0 to serialized_key.length-1 loop
                SB_out.Push(serialized_key.data(i));
            end loop;
        end procedure;
        
    begin
        
        SetAlertLogName("L1_Type1_key_holder_test2 - (Little Endian) - Generic");
        SetAlertEnable(FAILURE, TRUE);
        SetAlertEnable(ERROR, TRUE);
        SetAlertEnable(WARNING, TRUE);
        SetLogEnable(DEBUG, FALSE);
        SetLogEnable(PASSED, FALSE);
        SetLogEnable(INFO, TRUE);
        RV.InitSeed(RV'instance_name);
        SK  := GetAlertLogID("SerializedKey", ALERTLOG_BASE_ID);
        KH  := GetAlertLogID("KeyHash", ALERTLOG_BASE_ID);
        
        Log("Initial Reset", INFO);
        start_w <= '0';
        reset   <= '1';
        wait until rising_edge(clk);
        wait until rising_edge(clk);
        reset   <= '0';
        
        -- Serialized Key Payload Header
        gen_CDR(SERIALIZED_KEY_HEADER, ALIGN_4, align_offset, serialized_key);
        -- Reset Alignement
        align_offset    := (others => '0');
        
        Log("Setting Data in Writer Side", INFO);
        -- Static
        id_in   <= RV.RandSlv(id_in'length);
        a_in    <= RV.RandSlv(a_in'length);
        wait for 0 ns;
        gen_CDR(endian_swap('1',id_in), ALIGN_4, align_offset, serialized_key);
        
        -- Finalize Serialized Key
        if (align_offset(1 downto 0) /= "00") then
            serialized_key.length := serialized_key.length + 1;
        end if;
        
        push_sk;
        
        Log("Push DATA", INFO);
        opcode_kh   <= PUSH_DATA;
        start_w     <= '1';
        wait_on_sig(ack_w);
        wait until rising_edge(clk);
        start_w     <= '0';
        start_kh    <= '1';
        wait_on_sig(ack_kh);
        wait until rising_edge(clk);
        start_kh    <= '0';
        wait_on_sig(last_word_kh_in);
        wait_on_sig(ready_kh_in);
        wait until rising_edge(clk);
        
        Log("Read and Compare Serialized Key", INFO);
        opcode_kh   <= READ_SERIALIZED_KEY;
        start_kh    <= '1';
        wait_on_sig(ack_kh);
        wait until rising_edge(clk);
        start_kh    <= '0';
        wait_on_sig(last_word_kh_out);
        wait_on_sig(ready_kh_out);
        wait until rising_edge(clk);
        
        Log("Read Key Hash", INFO);
        opcode_kh   <= READ_KEY_HASH;
        start_kh    <= '1';
        wait_on_sig(ack_kh);
        wait until rising_edge(clk);
        start_kh    <= '0';
        wait_on_sig(last_word_kh_out);
        wait_on_sig(ready_kh_out);
        wait until rising_edge(clk);
        
        Log("Push Serialized Key", INFO);
        opcode_kh   <= PUSH_SERIALIZED_KEY;
        start_kh    <= '1';
        wait_on_sig(ack_kh);
        wait until rising_edge(clk);
        start_kh    <= '0';
        wait_on_sig(last_word_sk);
        wait_on_sig(ready_sk);
        wait until rising_edge(clk);
        
        push_sk;
        Log("Read and Compare Serialized Key", INFO);
        opcode_kh   <= READ_SERIALIZED_KEY;
        start_kh    <= '1';
        wait_on_sig(ack_kh);
        wait until rising_edge(clk);
        start_kh    <= '0';
        wait_on_sig(last_word_kh_out);
        wait_on_sig(ready_kh_out);
        wait until rising_edge(clk);
        
        -- Latch previous read Key Hash
        key_hash_latch  <= key_hash;
        Log("Read and Compare Key Hash", INFO);
        opcode_kh   <= READ_KEY_HASH;
        start_kh    <= '1';
        wait_on_sig(ack_kh);
        wait until rising_edge(clk);
        start_kh    <= '0';
        wait_on_sig(last_word_kh_out);
        wait_on_sig(ready_kh_out);
        wait until rising_edge(clk);
        AffirmIfEqual(KH, to_unsigned(key_hash), to_unsigned(key_hash_latch));
        
        TranscriptOpen(RESULTS_FILE, APPEND_MODE);
        SetTranscriptMirror;
        ReportAlerts;
        TranscriptClose;
        std.env.stop;
        wait;
    end process;
    
    clock_prc : process
    begin
        clk <= '0';
        wait for TEST_CLOCK_PERIOD/2;
        clk <= '1';
        wait for TEST_CLOCK_PERIOD/2;
    end process;
    
    alert_prc : process(all)
    begin
        if rising_edge(clk) then
            Alertif(decode_error = '1', "The Reader signals a DECODE Error", ERROR);
        end if;
    end process;
    
    switch_prc : process(all)
    begin
        case (opcode_kh) is
            when PUSH_SERIALIZED_KEY =>
                ready_sk        <= ready_kh_in;
                valid_kh_in     <= valid_sk;
                data_kh_in      <= data_sk;
                last_word_kh_in <= last_word_sk;
                ready_w         <= '0';
            when others =>
                ready_w         <= ready_kh_in;
                valid_kh_in     <= valid_w;
                data_kh_in      <= data_w;
                last_word_kh_in <= last_word_w;
                ready_sk        <= '0';
        end case;
    end process;
    
    sk_prc : process(all)
        variable cnt : natural := 0;
    begin
        valid_sk    <= '1';
        data_sk     <= serialized_key.data(cnt);
        if (cnt = serialized_key.length-1) then
            last_word_sk    <= '1';
        end if;
        
        if rising_edge(clk) then
            if (ready_sk = '1') then
                if (cnt = serialized_key.length-1) then
                    cnt := 0;
                else
                    cnt := cnt + 1;
                end if;
            end if;
        end if;
    end process;
    
    output_check_prc : process(all)
        variable cnt : natural range 0 to 3 := 0;
    begin
        if rising_edge(clk) then
            case (opcode_kh) is
                when READ_KEY_HASH =>
                    ready_kh_out    <= '1';
                    if (valid_kh_out = '1') then
                        key_hash(cnt)   <= data_kh_out;
                        if (cnt = 3) then
                            cnt := 0;
                            Alertif(last_word_kh_out = '0', "last_word_in is 0");
                        else
                            cnt := cnt + 1;
                        end if;
                    end if;
                when READ_SERIALIZED_KEY =>
                    ready_kh_out    <= '1';
                    if (valid_kh_out = '1') then
                        SB_out.Check(data_kh_out);
                        if (SB_out.empty) then
                            Alertif(last_word_kh_out = '0', "last_word_in is 0");
                        end if;
                    end if;
                when others =>
                    ready_kh_out    <= '0';
            end case;
        end if;
    end process;
    
    watchdog : process
    begin
        wait for 1 ms;
        Alert("Test timeout", FAILURE);
        std.env.stop;
    end process;
    
end architecture;