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

entity mem_ctrl is
    generic (
        ADDR_WIDTH          : natural;
        DATA_WIDTH          : natural;
        MEMORY_DEPTH        : natural;
        MAX_BURST_LENGTH    : natural
    );
    port (
        clk         : in std_logic;
        reset       : in std_logic;
        addr        : in std_logic_vector(ADDR_WIDTH-1 downto 0);
        read        : in std_logic;
        ready_in    : out std_logic;
        valid_in    : in std_logic;
        data_in     : in std_logic_vector(DATA_WIDTH-1 downto 0);
        ready_out   : in std_logic;
        valid_out   : out std_logic;
        data_out    : out std_logic_vector(DATA_WIDTH-1 downto 0)
    );
end entity;

architecture arch of mem_ctrl is
    
    -- *CONSTANT DECLARATION*
    constant READ_LATENCY : natural := 1;
    
    -- *TYPE DECLARATION*
    
    -- *SIGNAL DECLARATION*
    signal mem_read_data : std_logic_vector(DATA_WIDTH-1 downto 0);
    signal delay_line : std_logic_vector(READ_LATENCY-1 downto 0) := (others => '0');
    signal fifo_empty : std_logic := '0';
    signal fifo_cnt : natural range 0 to MAX_BURST_LENGTH := 0;
    signal delay_cnt : natural range 0 to READ_LATENCY := 0;
    signal ready_in_sig, valid_out_sig : std_logic := '0';
    
begin

    --*****COMPONENT INSTANTIATION*****
    ram_inst : entity work.single_port_ram(arch)
        generic map (
            ADDR_WIDTH      => ADDR_WIDTH,
            DATA_WIDTH      => DATA_WIDTH,
            MEMORY_DEPTH    => MEMORY_DEPTH
        )
        port map (
            clk         => clk,
            addr        => addr,
            wen         => ready_in_sig and valid_in and (not read),
            ren         => ready_in_sig and valid_in and read,
            wr_data     => data_in,
            rd_data     => mem_read_data
        );
    
    
    ready_in_sig    <= '0' when (fifo_cnt - delay_cnt = 0) else '1';
    ready_in        <= ready_in_sig;
    valid_out_sig   <= not fifo_empty;
    valid_out       <= valid_out_sig;
    
    delay_line_prc :  process(clk)
    begin
        if rising_edge(clk) then
            if (reset = '1') then
                delay_line      <= (others => '0');
                delay_cnt       <= 0;
            else
                -- Shift Right
                if (READ_LATENCY > 1) then
                    delay_line(READ_LATENCY-2 downto 0) <= delay_line(READ_LATENCY-1 downto 1);
                    delay_line(READ_LATENCY-1)          <= ready_in_sig and valid_in and read;
                    
                    if ((ready_in_sig and valid_in and read) = '1' and delay_line(1) = '0') then
                        delay_cnt   <= delay_cnt + 1;
                    elsif ((ready_in_sig and valid_in and read) = '0' and delay_line(1) = '1') then
                        delay_cnt   <= delay_cnt - 1;
                    end if;
                else
                    delay_line(0)  <= ready_in_sig and valid_in and read;
                    
                    if ((ready_in_sig and valid_in and read) = '1' and delay_line(0) = '0') then
                        delay_cnt   <= delay_cnt + 1;
                    elsif ((ready_in_sig and valid_in and read) = '0' and delay_line(0) = '1') then
                        delay_cnt   <= delay_cnt - 1;
                    end if;
                end if;
            end if;
        end if;
    end process;
    
    burst_fifo_inst : entity work.FWFT_FIFO(arch)
        generic map (
            FIFO_DEPTH  => MAX_BURST_LENGTH,
            DATA_WIDTH  => DATA_WIDTH
        )
        port map (
            clk         => clk,
            reset       => reset,
            data_in     => mem_read_data,
            write       => delay_line(0),
            read        => ready_out and valid_out_sig,
            data_out    => data_out,
            empty       => fifo_empty,
            full        => open,
            free        => fifo_cnt
        );
end architecture;