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

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

use work.rtps_test_package.all;

entity L0_mem_ctrl_test1 is
end entity;

architecture testbench of L0_mem_ctrl_test1 is
    
    -- *CONSTANT DECLARATION*
    constant WORD_WIDTH     : natural := 32;
    constant ADDR_WIDTH     : natural := 3;
    constant MEMORY_DEPTH   : natural := 2**ADDR_WIDTH;
    constant BURST_LENGTH   : natural := MEMORY_DEPTH;
    
    -- *TYPE DECLARATION*
    type TEST_ARRAY_TYPE is array (0 to MEMORY_DEPTH-1) of std_logic_vector(WORD_WIDTH-1 downto 0);
    
    -- *SIGNAL DECLARATION*
    signal clk, read, ready_in, valid_in, ready_out, valid_out : std_logic := '0';
    signal data_in, data_out : std_logic_vector(WORD_WIDTH-1 downto 0) := (others => '0');
    signal addr : std_logic_vector(ADDR_WIDTH-1 downto 0) := (others => '0');
    signal reset : std_logic := '1';
    shared variable SB : osvvm.ScoreBoardPkg_slv.ScoreBoardPType;
    
begin
    
    
    -- Unit Under Test
    uut : entity work.mem_ctrl(arch)
        generic map (
            ADDR_WIDTH          => ADDR_WIDTH,
            DATA_WIDTH          => WORD_WIDTH,
            MEMORY_DEPTH        => MEMORY_DEPTH,
            MAX_BURST_LENGTH    => BURST_LENGTH
        )
        port map (
            clk         => clk,
            reset       => reset,
            addr        => addr,
            read        => read,
            ready_in    => ready_in,
            valid_in    => valid_in,
            data_in     => data_in,
            ready_out   => ready_out,
            valid_out   => valid_out,
            data_out    => data_out
        );
    
    stimulus_prc : process
        variable RV : RandomPType;
        variable test_array : TEST_ARRAY_TYPE := (others => (others => '0'));
        variable tmp : std_logic_vector(WORD_WIDTH-1 downto 0) := (others => '0');
    begin
        SetAlertLogName("mem_ctrl - Level 0 - Generic");
        SetAlertEnable(FAILURE, TRUE);
        SetAlertEnable(ERROR, TRUE);
        SetAlertEnable(WARNING, TRUE);
        SetLogEnable(DEBUG, FALSE);
        SetLogEnable(PASSED, FALSE);
        SetLogEnable(INFO, TRUE);
        RV.InitSeed(RV'instance_name);
        
        Log("Initiating Test", INFO);
        valid_in    <= '0';
        data_in     <= (others => '0');
        reset       <= '1';
        wait until rising_edge(clk);
        wait until rising_edge(clk);
        reset       <= '0';
        
        
        Log("Test Write Burst", INFO);
        AffirmIf(valid_out = '0', "Read output without Read request");
        -- Write entire Memory with random data
        for i in 0 to MEMORY_DEPTH-1 loop
            valid_in        <= '1';
            addr            <= std_logic_vector(to_unsigned(i, ADDR_WIDTH));
            tmp             := RV.RandSlv(WORD_WIDTH);
            test_array(i)   := tmp;
            data_in         <= tmp;
            if (ready_in = '0') then
                wait until ready_in = '1';
            end if;
            wait until rising_edge(clk);
        end loop;
        valid_in    <= '0';
        addr        <= (others => '0');
        data_in     <= (others => '0');
        wait until rising_edge(clk);
        
        
        Log("Test Single Read", INFO);
        -- Read one element from memory
        AffirmIf(valid_out = '0', "Read output without Read request");
        valid_in    <= '1';
        addr        <= std_logic_vector(to_unsigned(0, ADDR_WIDTH));
        read        <= '1';
        SB.Push(test_array(0));
        wait until rising_edge(clk);
        valid_in    <= '0';
        addr        <= (others => '0');
        read        <= '0';
        wait until rising_edge(clk);
        wait until rising_edge(clk);
        
        
        Log("Test Read Burst", INFO);
        -- Fill Read Burst
        for i in MEMORY_DEPTH-1 downto 0 loop
            valid_in    <= '1';
            addr        <= std_logic_vector(to_unsigned(i, ADDR_WIDTH));
            read        <= '1';
            SB.Push(test_array(i));
            if (ready_in = '0') then
                wait until ready_in = '1';
            end if;
            wait until rising_edge(clk);
        end loop;
        valid_in    <= '0';
        addr        <= (others => '0');
        read        <= '0';
        wait until rising_edge(clk);
        wait until rising_edge(clk);
        
        
        Log("Test Concurrent Request/Read Operation", INFO);
        -- Request
        valid_in    <= '1';
        addr        <= std_logic_vector(to_unsigned(0, ADDR_WIDTH));
        read        <= '1';
        SB.Push(test_array(0));
        if (ready_in = '0') then
                wait until ready_in = '1';
            end if;
        wait until rising_edge(clk);
        valid_in    <= '0';
        addr        <= (others => '0');
        read        <= '0';
        -- Read and Request
        if (valid_out /= '1') then
            wait until valid_out = '1';
        end if;
        valid_in    <= '1';
        addr        <= std_logic_vector(to_unsigned(1, ADDR_WIDTH));
        read        <= '1';
        SB.Push(test_array(1));
        AlertIf(ready_in /= '1', "Precondition for concurrent operation not met");
        wait until rising_edge(clk);
        valid_in    <= '0';
        addr        <= (others => '0');
        read        <= '0';
        -- Read
        wait until rising_edge(clk);
        wait until rising_edge(clk);
        
        
        Log("Test Operation Order", INFO);
        -- Request Address 0
        valid_in    <= '1';
        addr        <= std_logic_vector(to_unsigned(0, ADDR_WIDTH));
        read        <= '1';
        SB.Push(test_array(0));
        if (ready_in = '0') then
            wait until ready_in = '1';
        end if;
        wait until rising_edge(clk);
        -- Write Address 0
        read        <= '0';
        data_in     <= test_array(MEMORY_DEPTH-1);
        if (ready_in = '0') then
            wait until ready_in = '1';
        end if;
        wait until rising_edge(clk);
        -- Request Address 0
        read        <= '1';
        SB.Push(test_array(7));
        data_in     <= (others => '0');
        if (ready_in = '0') then
            wait until ready_in = '1';
        end if;
        wait until rising_edge(clk);
        valid_in    <= '0';
        addr        <= (others => '0');
        read        <= '0';
        wait until rising_edge(clk);
        wait until rising_edge(clk);
        
        -- Wait until test Completion
        if (not SB.empty) then
            wait until SB.empty;
        end if;
        TranscriptOpen(RESULTS_FILE, APPEND_MODE);
        SetTranscriptMirror;
        ReportAlerts;
        TranscriptClose;
        std.env.stop;
        wait;
    end process;
    
    output_check_prc : process(all)
    begin
        if falling_edge(clk) then
            if (valid_out = '1') then
                ready_out   <= '1';
                SB.Check(data_out);
            else
                ready_out   <= '0';
            end if;
        end if;
    end process;
    
    clock_prc : process
    begin
        clk <= '0';
        wait for 25 ns;
        clk <= '1';
        wait for 25 ns;
    end process;
    
    watchdog : process
    begin
        wait for 1 ms;
        Alert("Test timeout", FAILURE);
        std.env.stop;
    end process;
    
end architecture;