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

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

library Testbench_Lib4;
library Testbench_Lib5;

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

-- This testbench tests the general system operation by interconnecting two complete systems with a reader and a writer respectively, and perforing a loopback operation.
-- Libraries are used to allow to use systems with different configurations.
-- The testbench is interfacing with the readr and writer of Testbench_Lib5, and the loopback entity (test_loopback) is interfacing with the reader and writer of Testbench_Lib4.
-- The testbench->test_loopback communication uses a Type1 Instance with id=1, and the test_loopback->testbench communication uses a Type1 Instance with id=2.
-- The testbench performs a REGISTER_INSTANCE operation to get the Instance Handle of the response channel. The it sends 5 samples with content 1,2,3,4, and 5, respectively.
-- The test_loopback reads these Samples and responds with a x+1000 calculation on the Sample Contents (i.e. 1001,1002,1003,1004,1005).
-- The testbench reads only Samples with the correct instance and checks for the expected content.


entity L2_Type1_test2 is
end entity;

architecture testbench of L2_Type1_test2 is
    
    constant ZERO_SAMPLE_INFO : Testbench_Lib5.rtps_package.SAMPLE_INFO_TYPE := (
        sample_state                => (others => '0'),
        view_state                  => (others => '0'),
        instance_state              => (others => '0'),
        source_timestamp            => Testbench_Lib5.rtps_package.TIME_ZERO,
        instance_handle             => Testbench_Lib5.rtps_package.HANDLE_NIL,
        publication_handle          => Testbench_Lib5.rtps_package.HANDLE_NIL,
        disposed_generation_count   => (others => '0'),
        no_writers_generation_count => (others => '0'),
        sample_rank                 => (others => '0'),
        generation_rank             => (others => '0'),
        absolute_generation_rank    => (others => '0'),
        valid_data                  => '0'
    );
    
    signal clk, reset : std_logic := '0';
    signal time : TIME_TYPE := TIME_ZERO;
    signal empty_l4, full_l4, read_l4, write_l4 : std_logic := '0';
    signal data_in_l4, data_out_l4 : std_logic_vector(WORD_WIDTH-1 downto 0) := (others => '0');
    signal empty_l5, full_l5, read_l5, write_l5 : std_logic := '0';
    signal data_in_l5, data_out_l5 : std_logic_vector(WORD_WIDTH-1 downto 0) := (others => '0');
    signal start_w, ack_w, done_w, ready_out_w, valid_out_w, last_word_out_w, encode_done_w : std_logic := '0';
    --signal opcode_w : Testbench_Lib5.rtps_config_package.DDS_WRITER_OPCODE_TYPE := Testbench_Lib5.rtps_config_package.DDS_WRITER_OPCODE_TYPE'(NOP);
    signal opcode_w : DDS_WRITER_OPCODE_TYPE;
    signal instance_handle_in_w : INSTANCE_HANDLE_TYPE := HANDLE_NIL;
    signal instance_handle_out_w : Testbench_Lib5.rtps_package.INSTANCE_HANDLE_TYPE := Testbench_Lib5.rtps_package.HANDLE_NIL;
    signal source_ts_w : TIME_TYPE := TIME_INVALID;
    signal max_wait_w : DURATION_TYPE := DURATION_ZERO;
    signal return_code_w : std_logic_vector(RETURN_CODE_WIDTH-1 downto 0) := RETCODE_OK;
    signal data_out_w : std_logic_vector(WORD_WIDTH-1 downto 0) := (others => '0');
    signal status_w : std_logic_vector(STATUS_KIND_WIDTH-1 downto 0) := (others => '0');
    signal id_w, a_w : std_logic_vector(CDR_LONG_WIDTH-1 downto 0) := (others => '0');
    
    signal start_r, ack_r, get_data_r, done_r, sample_info_valid_r, sample_info_ack_r, eoc_r, valid_r, decode_error_r : std_logic := '0';
    signal opcode_r : DDS_READER_OPCODE_TYPE;
    signal instance_state_r : std_logic_vector(INSTANCE_STATE_KIND_WIDTH-1 downto 0) := ANY_INSTANCE_STATE;
    signal view_state_r : std_logic_vector(VIEW_STATE_KIND_WIDTH-1 downto 0) := ANY_VIEW_STATE;
    signal sample_state_r : std_logic_vector(SAMPLE_STATE_KIND_WIDTH-1 downto 0) := ANY_SAMPLE_STATE;
    signal instance_handle_r : INSTANCE_HANDLE_TYPE := HANDLE_NIL;
    signal max_samples_r : std_logic_vector(MAX_SAMPLES_WIDTH-1 downto 0) := (others => '0');
    signal return_code_r : std_logic_vector(RETURN_CODE_WIDTH-1 downto 0) := RETCODE_OK;
    signal sample_info_r : Testbench_Lib5.rtps_package.SAMPLE_INFO_TYPE := ZERO_SAMPLE_INFO;
    signal status_r : std_logic_vector(STATUS_KIND_WIDTH-1 downto 0) := (others => '0');
    signal id_r, a_r : std_logic_vector(CDR_LONG_WIDTH-1 downto 0) := (others => '0');
    
    signal inst : INSTANCE_HANDLE_TYPE := HANDLE_NIL;
    
    -- HACK:    For some reason (possibly Modelsim/Questasim Bug) I cannot declare the opcode signals from their respective Library sources, 
    --          because I cannot use a qualified expression to define an enumeration literal (and direct type casting cannot be done because 
    --          the types are not closely related). So I have to do define explicit type conversions between the types...
    
    function to_Lib5(input : DDS_READER_OPCODE_TYPE) return Testbench_Lib5.rtps_package.DDS_READER_OPCODE_TYPE is
    begin
        return Testbench_Lib5.rtps_package.DDS_READER_OPCODE_TYPE'VAL(DDS_READER_OPCODE_TYPE'POS(input));
    end function;
    
    function to_Lib5(input : DDS_WRITER_OPCODE_TYPE) return Testbench_Lib5.rtps_package.DDS_WRITER_OPCODE_TYPE is
    begin
        return Testbench_Lib5.rtps_package.DDS_WRITER_OPCODE_TYPE'VAL(DDS_WRITER_OPCODE_TYPE'POS(input));
    end function;
    
begin
    
    Lib4_inst : entity Testbench_Lib4.L2_Testbench_Lib4(arch)
        port map (
            -- SYSTEM
            clk         => clk,
            reset       => reset,
            time        => Testbench_Lib4.rtps_package.TIME_TYPE(time),
            -- INPUT
            empty       => empty_l4,
            read        => read_l4,
            data_in     => data_in_l4,
            -- OUTPUT
            full        => full_l4,
            write       => write_l4,
            data_out    => data_out_l4
        );
    
    fifo_l4_l5_inst : configuration work.FWFT_FIFO_cfg
        generic map (
            FIFO_DEPTH  => 65536/(WORD_WIDTH/BYTE_WIDTH),
            DATA_WIDTH  => WORD_WIDTH
        )
        port map
        (
            clk         => clk,
            reset       => reset,
            empty       => empty_l5,
            read        => read_l5,
            data_out    => data_in_l5,
            full        => full_l4,
            write       => write_l4,
            data_in     => data_out_l4,
            free        => open
        );
    
    fifo_l5_l4_inst : configuration work.FWFT_FIFO_cfg
        generic map (
            FIFO_DEPTH  => 65536/(WORD_WIDTH/BYTE_WIDTH),
            DATA_WIDTH  => WORD_WIDTH
        )
        port map
        (
            clk         => clk,
            reset       => reset,
            empty       => empty_l4,
            read        => read_l4,
            data_out    => data_in_l4,
            full        => full_l5,
            write       => write_l5,
            data_in     => data_out_l5,
            free        => open
        );
    
    Lib5_inst : entity Testbench_Lib5.L2_Testbench_Lib5(arch)
        port map (
            -- SYSTEM
            clk                                 => clk,
            reset                               => reset,
            time                                => Testbench_Lib5.rtps_package.TIME_TYPE(time),
            -- INPUT
            empty                               => empty_l5,
            read                                => read_l5,
            data_in                             => data_in_l5,
            -- OUTPUT
            full                                => full_l5,
            write                               => write_l5,
            data_out                            => data_out_l5,
            -- READER
            start_r                             => start_r,
            ack_r                               => ack_r,
            opcode_r                            => to_Lib5(opcode_r),
            instance_state_r                    => instance_state_r,
            view_state_r                        => view_state_r,
            sample_state_r                      => sample_state_r,
            instance_handle_r                   => Testbench_Lib5.rtps_package.INSTANCE_HANDLE_TYPE(instance_handle_r),
            max_samples_r                       => max_samples_r,
            get_data_r                          => get_data_r,
            done_r                              => done_r,
            return_code_r                       => return_code_r,
            sample_info_r                       => sample_info_r,
            sample_info_valid_r                 => sample_info_valid_r,
            sample_info_ack_r                   => sample_info_ack_r,
            eoc_r                               => eoc_r,
            status_r                            => status_r,
            decode_error_r                      => decode_error_r,
            id_r                                => id_r,
            a_r                                 => a_r,
            valid_r                             => valid_r,
            -- WRITER
            start_w                             => start_w,
            ack_w                               => ack_w,
            opcode_w                            => to_Lib5(opcode_w),
            instance_handle_in_w                => Testbench_Lib5.rtps_package.INSTANCE_HANDLE_TYPE(instance_handle_in_w),
            source_ts_w                         => Testbench_Lib5.rtps_package.TIME_TYPE(source_ts_w),
            max_wait_w                          => Testbench_Lib5.rtps_package.DURATION_TYPE(max_wait_w),
            done_w                              => done_w,
            return_code_w                       => return_code_w,
            instance_handle_out_w               => instance_handle_out_w,
            status_w                            => status_w,
            id_w                                => id_w,
            a_w                                 => a_w,
            encode_done_w                       => encode_done_w
        );
    
    stimulus_prc : process
        variable RV : RandomPType;
        variable j : natural := 1;
        
        -- Waits until signal toggles high for at least 1 ps
        procedure wait_on_sig(signal sig : std_logic) is
        begin
            loop
                if (sig /= '1') then
                    wait on sig until sig = '1';
                end if;
                -- Prevent triggering on glitches
                wait for 1 ps;
                if (sig = '1') then
                    exit;
                end if;
            end loop;
        end procedure;
        
        procedure wait_on_data(signal sig : std_logic_vector) is
        begin
            if (not check_mask(sig, DATA_AVAILABLE_STATUS)) then
                wait on sig until check_mask(sig, DATA_AVAILABLE_STATUS);
            end if;
        end procedure;
    begin
        
        SetAlertLogName("L2_Type1_test2 - Loopback");
        SetAlertEnable(FAILURE, TRUE);
        SetAlertEnable(ERROR, TRUE);
        SetAlertEnable(WARNING, TRUE);
        SetLogEnable(DEBUG, FALSE);
        SetLogEnable(PASSED, FALSE);
        SetLogEnable(INFO, TRUE);
        RV.InitSeed(RV'instance_name);
        
        Log("Initial Reset", INFO);
        reset   <= '1';
        wait until rising_edge(clk);
        wait until rising_edge(clk);
        reset   <= '0';
        
        Log("Register Target Instance (Get Instance Handle of Responce)", INFO);
        start_w             <= '1';
        opcode_w            <= REGISTER_INSTANCE;
        id_w                <= int(2,CDR_LONG_WIDTH);
        a_w                 <= (others => '0');
        wait_on_sig(ack_w);
        wait until rising_edge(clk);
        start_w             <= '0';
        wait_on_sig(done_w);
        wait for 1 ps; -- Make sure all signals stable
        inst    <= INSTANCE_HANDLE_TYPE(instance_handle_out_w);
        wait until rising_edge(clk);
        
        Log("Writing Samples", INFO);
        for i in 1 to 5 loop
            Log("Write Sample " & integer'image(i), INFO);
            start_w                 <= '1';
            opcode_w                <= WRITE;
            source_ts_w             <= TIME_INVALID;
            instance_handle_in_w    <= HANDLE_NIL;
            id_w                    <= int(1,CDR_LONG_WIDTH);
            a_w                     <= int(i,CDR_LONG_WIDTH);
            wait_on_sig(ack_w);
            wait until rising_edge(clk);
            start_w             <= '0';
            wait_on_sig(done_w);
            wait for 1 ps; -- Make sure all signals stable
            AlertIf(return_code_w /= RETCODE_OK, "DDS Write Operation Failed", FAILURE);
            wait until rising_edge(clk);
        end loop;
        
        -- NOTE: Because the Participant Announcement has not yet happened, the data written is considered historical
        --       and will be sent immediately on Endpoint matching.
        
        Log("Wait on Data Arrival", INFO);
        wait_on_data(status_r);
        wait until rising_edge(clk);
        
        Log("Read Samples", INFO);
        loop
            if (j = 5) then
                exit;
            end if;
            start_r     <= '1';
            opcode_r    <= TAKE_NEXT_SAMPLE;
            wait_on_sig(ack_r);
            wait until rising_edge(clk);
            start_r     <= '0';
            wait_on_sig(done_r);
            wait for 1 ps; -- Make sure all signals stable
            wait until rising_edge(clk);
            case (return_code_r) is
                when RETCODE_OK =>
                    wait_on_sig(sample_info_valid_r);
                    wait for 1 ps; -- Make sure all signals stable
                    -- Check if Sample is from target Instance
                    if (sample_info_r.valid_data = '1' and INSTANCE_HANDLE_TYPE(sample_info_r.instance_handle) = inst) then
                        Log("Read Sample " & integer'image(j), INFO);
                        sample_info_ack_r   <= '1';
                        get_data_r          <= '1';
                        wait until rising_edge(clk);
                        sample_info_ack_r   <= '0';
                        get_data_r          <= '0';
                        wait_on_sig(valid_r);
                        wait for 1 ps; -- Make sure all signals stable
                        AffirmIfEqual(id_r, int(2,CDR_LONG_WIDTH));
                        AffirmIfEqual(a_r, int(j+1000,CDR_LONG_WIDTH));
                        wait_on_sig(eoc_r);
                        wait until rising_edge(clk);
                        j := j + 1;
                    else
                        Log("Read Unknown Sample", INFO);
                        -- Ignore Sample
                        sample_info_ack_r   <= '1';
                        wait until rising_edge(clk);
                        sample_info_ack_r   <= '0';
                    end if;
                when RETCODE_NO_DATA =>
                    Log("No more Samples", INFO);
                    Log("Wait on Data Arrival", INFO);
                    wait_on_data(status_r);
                    wait until rising_edge(clk);
                when others =>
                    Alert("Unexpected DDS Reader Response", FAILURE);
            end case;
        end loop;
        
        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;
    
    time_prc : process(clk)
    begin
        if rising_edge(clk) then
            if (reset = '1') then
                time    <= TIME_ZERO;
            else
                time    <= time + CLOCK_DURATION;
            end if;
        end if;
    end process;
    
    watchdog : process
    begin
        wait for 1 ms;
        Alert("Test timeout", FAILURE);
        std.env.stop;
    end process;
    
end architecture;