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

entity fifo is
    generic (
        DEPTH : positive := 1024
    );
    port
    (
        aclr : in std_logic;
        clock : in std_logic;
        sclr : in std_logic;
        data : in std_logic_vector( 31 downto 0 );
        rdreq : in std_logic;
        wrreq : in std_logic;
        empty : out std_logic;
        full : out std_logic;
        q : out std_logic_vector( 31 downto 0 );
        usedw : out std_logic_vector( 9 downto 0 )
    );
end entity fifo;

architecture rtl of fifo is

    type Operation is (
        OPERATION_IDLE,
        OPERATION_CLEAR,
        OPERATION_READ,
        OPERATION_WRITE,
        OPERATION_READ_WRITE
    );

    signal is_empty : boolean;
    signal is_full : boolean;

    signal is_read : boolean;
    signal is_write : boolean;
    signal is_read_write : boolean;

    signal next_operation : Operation;

    signal fifo_data : work.reg32.RegArray( 0 to DEPTH - 1 );

    signal write_index : integer range 0 to DEPTH - 1;
    signal read_index : integer range 0 to DEPTH - 1;
    signal item_count : integer range 0 to DEPTH;

    function increment_with_overflow( value : integer; max : integer ) return integer
    is
    begin
        if ( value < max - 1 ) then
            return value + 1;
        end if;
        return 0;
    end function increment_with_overflow;

begin

    c_is_empty: is_empty <= item_count = 0;
    c_is_full: is_full <= item_count = DEPTH;

    c_is_read: is_read <= rdreq = '1' and not is_empty;
    c_is_write: is_write <= wrreq = '1' and not is_full;
    c_is_read_write: is_read_write <= is_read and is_write;

    c_next_operation: next_operation <= OPERATION_CLEAR when sclr
        else OPERATION_READ_WRITE when is_read_write
        else OPERATION_READ when is_read
        else OPERATION_WRITE when is_write
        else OPERATION_IDLE;

    sync: process( clock, aclr ) is
    begin
        if ( aclr = '1' ) then
            write_index <= 0;
            read_index <= 0;
            item_count <= 0;
        elsif ( rising_edge( clock ) ) then

            case next_operation is
            when OPERATION_IDLE =>
                null;

            when OPERATION_CLEAR =>
                write_index <= 0;
                read_index <= 0;
                item_count <= 0;

            when OPERATION_READ =>
                item_count <= item_count - 1;
                read_index <= increment_with_overflow( read_index, DEPTH );

            when OPERATION_WRITE =>
                fifo_data( write_index ) <= data;
                item_count <= item_count + 1;
                write_index <= increment_with_overflow( write_index, DEPTH );

            when OPERATION_READ_WRITE =>
                read_index <= increment_with_overflow( read_index, DEPTH );

                fifo_data( write_index ) <= data;
                write_index <= increment_with_overflow( write_index, DEPTH );
            end case;

        end if;
    end process;

    c_assign_q: q <= data when ( is_empty and is_write ) else
        fifo_data( read_index );

    c_assign_usedw:
        usedw <= std_logic_vector( to_unsigned( item_count, usedw'length ) );

    full <= '1' when is_full else '0';
    empty <= '1' when is_empty else '0';
end architecture rtl;