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

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

library Testbench_Lib2;
library Testbench_Lib3;

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 a complete system with a single writer with a complete sysetm with a single reader.
-- Libraries are used to allow to use systems with different configurations. Testbench_Lib2 contains a single endpoint writer (Type1), and Testbench_Lib3 contains a single endpoint reader (Type1).
-- Both Libraries have compatible settings for matching.
-- The testbench first registers 2 instances, and writes 2 samples for each instance (once using the instance handle, and onc using HANDLE_NIL). This initial 4 writes are done before
-- the two systems have discovered each other (due to the initial Participant Announcement happening at 50k ns), and are thus considered historical data (Data that will be sent to the
-- Reader immediately on matching due to trasient local durability setting).
-- After data has arrived on the reader side, the data is read out and compared to the original.
-- Afterwards another sample on an unknown instance is written, and the instance handle is fetched via a register lookup operation. After the sample has arrived at the reader side the data is again
-- compared to the original.

entity L2_Type1_test1 is
end entity;

architecture testbench of L2_Type1_test1 is
    
    constant ZERO_SAMPLE_INFO : Testbench_Lib3.rtps_package.SAMPLE_INFO_TYPE := (
        sample_state                => (others => '0'),
        view_state                  => (others => '0'),
        instance_state              => (others => '0'),
        source_timestamp            => Testbench_Lib3.rtps_package.TIME_ZERO,
        instance_handle             => Testbench_Lib3.rtps_package.HANDLE_NIL,
        publication_handle          => Testbench_Lib3.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;
    -- INPUT
    signal start_w, ack_w, done_w, ready_out_w, valid_out_w, last_word_out_w, encode_done_w, empty_rtps_w, full_rtps_w, rd_rtps_w, wr_rtps_w : std_logic := '0';
    --signal opcode_w : Testbench_Lib2.rtps_config_package.DDS_WRITER_OPCODE_TYPE := Testbench_Lib2.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_Lib2.rtps_package.INSTANCE_HANDLE_TYPE := Testbench_Lib2.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, data_in_rtps_w, data_out_rtps_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, empty_rtps_r, full_rtps_r, rd_rtps_r, wr_rtps_r, sample_info_valid_r, sample_info_ack_r, eoc_r, valid_r : std_logic := '0';
    signal opcode_r : DDS_READER_OPCODE_TYPE;
    signal data_in_rtps_r, data_out_rtps_r : std_logic_vector(WORD_WIDTH-1 downto 0) := (others => '0');
    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_Lib3.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 id1, id2, id3, a1, a2, a3 : std_logic_vector(CDR_LONG_WIDTH-1 downto 0) := (others => '0');
    signal inst1, inst2, inst3 : 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_Lib2(input : DDS_WRITER_OPCODE_TYPE) return Testbench_Lib2.rtps_package.DDS_WRITER_OPCODE_TYPE is
    begin
        return Testbench_Lib2.rtps_package.DDS_WRITER_OPCODE_TYPE'VAL(DDS_WRITER_OPCODE_TYPE'POS(input));
    end function;
    
    function to_Lib3(input : DDS_READER_OPCODE_TYPE) return Testbench_Lib3.rtps_package.DDS_READER_OPCODE_TYPE is
    begin
        return Testbench_Lib3.rtps_package.DDS_READER_OPCODE_TYPE'VAL(DDS_READER_OPCODE_TYPE'POS(input));
    end function;
    
begin
    
    Lib2_inst : entity Testbench_Lib2.L2_Testbench_Lib2(arch)
        port map (
            -- SYSTEM
            clk                         => clk,
            reset                       => reset,
            time                        => Testbench_Lib2.rtps_package.TIME_TYPE(time),
            -- INPUT
            empty                       => empty_rtps_w,
            read                        => rd_rtps_w,
            data_in                     => data_in_rtps_w,
            -- OUTPUT
            data_out                    => data_out_rtps_w,
            write                       => wr_rtps_w,
            full                        => full_rtps_w,
            -- TO USER ENTITY
            start_user                  => start_w,
            ack_user                    => ack_w,
            opcode_user                 => to_Lib2(opcode_w),
            instance_handle_in_user     => Testbench_Lib2.rtps_package.INSTANCE_HANDLE_TYPE(instance_handle_in_w),
            source_ts_user              => Testbench_Lib2.rtps_package.TIME_TYPE(source_ts_w),
            max_wait_user               => Testbench_Lib2.rtps_package.DURATION_TYPE(max_wait_w),
            done_user                   => done_w,
            return_code_user            => return_code_w,
            instance_handle_out_user    => instance_handle_out_w,
            -- Communication Status
            status_user                 => status_w,
            -- ###GENERATED START###
            id                          => id_w,
            a                           => a_w,
            -- ###GENERATED END###
            encode_done                 => encode_done_w
        );
    
    fifo_r_w_inst : configuration work.FWFT_FIFO_cfg
        generic map (
            FIFO_DEPTH  => 65536/(WORD_WIDTH/BYTE_WIDTH),
            DATA_WIDTH  => WORD_WIDTH
        )
        port map
        (
            reset       => reset,
            clk         => clk,
            data_in     => data_out_rtps_r,
            write       => wr_rtps_r,
            read        => rd_rtps_w,
            data_out    => data_in_rtps_w,
            empty       => empty_rtps_w,
            full        => full_rtps_r,
            free        => open
        );
    
    fifo_w_r_inst : configuration work.FWFT_FIFO_cfg
        generic map (
            FIFO_DEPTH  => 65536/(WORD_WIDTH/BYTE_WIDTH),
            DATA_WIDTH  => WORD_WIDTH
        )
        port map
        (
            reset       => reset,
            clk         => clk,
            data_in     => data_out_rtps_w,
            write       => wr_rtps_w,
            read        => rd_rtps_r,
            data_out    => data_in_rtps_r,
            empty       => empty_rtps_r,
            full        => full_rtps_w,
            free        => open
        );
    
    Lib3_inst : entity Testbench_Lib3.L2_Testbench_Lib3(arch)
        port map (
            -- SYSTEM
            clk                                 => clk,
            reset                               => reset,
            time                                => Testbench_Lib3.rtps_package.TIME_TYPE(time),
            -- INPUT
            empty                               => empty_rtps_r,
            read                                => rd_rtps_r,
            data_in                             => data_in_rtps_r,
            -- OUTPUT
            data_out                            => data_out_rtps_r,
            write                               => wr_rtps_r,
            full                                => full_rtps_r,
            -- TO USER ENTITY
            start_user                          => start_r,
            ack_user                            => ack_r,
            opcode_user                         => to_Lib3(opcode_r),
            instance_state_user                 => instance_state_r,
            view_state_user                     => view_state_r,
            sample_state_user                   => sample_state_r,
            instance_handle_user                => Testbench_Lib3.rtps_package.INSTANCE_HANDLE_TYPE(instance_handle_r),
            max_samples_user                    => max_samples_r,
            get_data_user                       => get_data_r,
            done_user                           => done_r,
            return_code_user                    => return_code_r,
            sample_info_user                    => sample_info_r,
            sample_info_valid_user              => sample_info_valid_r,
            sample_info_ack_user                => sample_info_ack_r,
            eoc_user                            => eoc_r,
            -- Communication Status
            status_user                         => status_r,
            decode_error                        => open,
            
            -- ###GENERATED START###
            id                                  => id_r,
            a                                   => a_r,
            -- ###GENERATED END###
            valid                               => valid_r
        );
    
    stimulus_prc : process
        variable RV : RandomPType;
        
        -- 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;
        
        procedure try_read is
        begin
            loop
                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
                case (return_code_r) is
                    when RETCODE_OK =>
                        -- DONE
                        exit;
                    when RETCODE_NO_DATA =>
                        Log("No Samples", INFO);
                        Log("Wait on Data Arrival", INFO);
                        wait_on_data(status_r);
                        Log("Retry Read", INFO);
                    when others =>
                        Alert("Unexpected DDS Reader Response", FAILURE);
                end case;
                wait until rising_edge(clk);
            end loop;
        end procedure;
        
    begin
        
        SetAlertLogName("L2_Type1_test1 - General");
        SetAlertEnable(FAILURE, TRUE);
        SetAlertEnable(ERROR, TRUE);
        SetAlertEnable(WARNING, TRUE);
        SetLogEnable(DEBUG, FALSE);
        SetLogEnable(PASSED, FALSE);
        SetLogEnable(INFO, TRUE);
        RV.InitSeed(RV'instance_name);
        
        id1 <= RV.RandSlv(CDR_LONG_WIDTH);
        id2 <= RV.RandSlv(CDR_LONG_WIDTH);
        id3 <= RV.RandSlv(CDR_LONG_WIDTH);
        a1  <= RV.RandSlv(CDR_LONG_WIDTH);
        a2  <= RV.RandSlv(CDR_LONG_WIDTH);
        a3  <= RV.RandSlv(CDR_LONG_WIDTH);
        
        Log("Initial Reset", INFO);
        reset   <= '1';
        wait until rising_edge(clk);
        wait until rising_edge(clk);
        reset   <= '0';
        
        Log("Register Instance 1", INFO);
        start_w             <= '1';
        opcode_w            <= REGISTER_INSTANCE;
        id_w                <= id1;
        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
        inst1   <= INSTANCE_HANDLE_TYPE(instance_handle_out_w);
        
        Log("Register Instance 2", INFO);
        start_w             <= '1';
        opcode_w            <= REGISTER_INSTANCE;
        id_w                <= id2;
        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
        inst2   <= INSTANCE_HANDLE_TYPE(instance_handle_out_w);
        
        Log("Write Instance 1 Sample 1 (Historical)", INFO);
        start_w                 <= '1';
        opcode_w                <= WRITE;
        source_ts_w             <= TIME_INVALID;
        instance_handle_in_w    <= inst1;
        id_w                    <= id1;
        a_w                     <= a1;
        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", ERROR);
        wait until rising_edge(clk);
        
        Log("Write Instance 2 Sample 1 (Historical)", INFO);
        start_w                 <= '1';
        opcode_w                <= WRITE;
        source_ts_w             <= TIME_INVALID;
        instance_handle_in_w    <= inst2;
        id_w                    <= id2;
        a_w                     <= a1;
        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", ERROR);
        wait until rising_edge(clk);
        
        Log("Write Instance 1 Sample 2 (Historical)", INFO);
        start_w                 <= '1';
        opcode_w                <= WRITE;
        source_ts_w             <= TIME_INVALID;
        instance_handle_in_w    <= HANDLE_NIL;
        id_w                    <= id1;
        a_w                     <= a2;
        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", ERROR);
        wait until rising_edge(clk);
        
        Log("Write Instance 1 Sample 1 (Historical)", INFO);
        start_w                 <= '1';
        opcode_w                <= WRITE;
        source_ts_w             <= TIME_INVALID;
        instance_handle_in_w    <= HANDLE_NIL;
        id_w                    <= id2;
        a_w                     <= a2;
        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", ERROR);
        wait until rising_edge(clk);
        
        -- 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 Sample (Instance 1 Sample 1)", INFO);
        try_read;
        wait_on_sig(sample_info_valid_r);
        wait for 1 ps; -- Make sure all signals stable
        AlertIf(sample_info_r.valid_data /= '1', "Reader returned no Data", ERROR);
        AffirmIfEqual(to_unsigned(INSTANCE_HANDLE_TYPE(sample_info_r.instance_handle)), to_unsigned(inst1));
        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, id1);
        AffirmIfEqual(a_r, a1);
        wait_on_sig(eoc_r);
        wait until rising_edge(clk);
        
        Log("Read Sample (Instance 2 Sample 1)", INFO);
        try_read;
        wait_on_sig(sample_info_valid_r);
        wait for 1 ps; -- Make sure all signals stable
        AlertIf(sample_info_r.valid_data /= '1', "Reader returned no Data", ERROR);
        AffirmIfEqual(to_unsigned(INSTANCE_HANDLE_TYPE(sample_info_r.instance_handle)), to_unsigned(inst2));
        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, id2);
        AffirmIfEqual(a_r, a1);
        wait_on_sig(eoc_r);
        wait until rising_edge(clk);
        
        Log("Read Sample (Instance 1 Sample 2)", INFO);
        try_read;
        wait_on_sig(sample_info_valid_r);
        wait for 1 ps; -- Make sure all signals stable
        AlertIf(sample_info_r.valid_data /= '1', "Reader returned no Data", ERROR);
        AffirmIfEqual(to_unsigned(INSTANCE_HANDLE_TYPE(sample_info_r.instance_handle)), to_unsigned(inst1));
        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, id1);
        AffirmIfEqual(a_r, a2);
        wait_on_sig(eoc_r);
        wait until rising_edge(clk);
        
        Log("Read Sample (Instance 2 Sample 2)", INFO);
        try_read;
        wait_on_sig(sample_info_valid_r);
        wait for 1 ps; -- Make sure all signals stable
        AlertIf(sample_info_r.valid_data /= '1', "Reader returned no Data", ERROR);
        AffirmIfEqual(to_unsigned(INSTANCE_HANDLE_TYPE(sample_info_r.instance_handle)), to_unsigned(inst2));
        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, id2);
        AffirmIfEqual(a_r, a2);
        wait_on_sig(eoc_r);
        wait until rising_edge(clk);
        
        Log("Write Instance 3 Sample 1", INFO);
        start_w                 <= '1';
        opcode_w                <= WRITE;
        source_ts_w             <= TIME_INVALID;
        instance_handle_in_w    <= HANDLE_NIL;
        id_w                    <= id3;
        a_w                     <= a3;
        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", ERROR);
        wait until rising_edge(clk);
        
        Log("Lookup Instance 3", INFO);
        start_w             <= '1';
        opcode_w            <= LOOKUP_INSTANCE;
        id_w                <= id3;
        a_w                 <= a1;
        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
        inst3   <= INSTANCE_HANDLE_TYPE(instance_handle_out_w);
        
        Log("Wait on Data Arrival", INFO);
        wait_on_data(status_r);
        wait until rising_edge(clk);
        
        Log("Read Sample (Instance 3 Sample 1)", INFO);
        try_read;
        wait_on_sig(sample_info_valid_r);
        wait for 1 ps; -- Make sure all signals stable
        AlertIf(sample_info_r.valid_data /= '1', "Reader returned no Data", ERROR);
        AffirmIfEqual(to_unsigned(INSTANCE_HANDLE_TYPE(sample_info_r.instance_handle)), to_unsigned(inst3));
        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, id3);
        AffirmIfEqual(a_r, a3);
        wait_on_sig(eoc_r);
        wait until rising_edge(clk);
        
        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;