library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
    
entity asynchronous_fifo is	
	generic (
		write_address_width	:	integer   := 3;
		read_address_width	:	integer   := 2;
    	write_data_width	:	integer   := 12;  
    	read_data_width		:	integer   := 24
	);
    port (  
		read_clock_in		: in	std_logic;	  
        read_enable_in		: in	std_logic;	
		write_clock_in		: in	std_logic;		  
        write_enable_in		: in	std_logic; 
        reset_in			: in  	std_logic;
        data_in     		: in	std_logic_vector (write_data_width - 1 downto 0);
        data_out			: out	std_logic_vector (read_data_width - 1 downto 0);
        empty_out			: out	std_logic;		  
        full_out			: out	std_logic
    );
end entity asynchronous_fifo;

architecture synthesis of asynchronous_fifo is
	signal next_word_to_write     		: std_logic_vector (write_address_width - 1 downto 0);	
	signal next_word_to_write_binary	: std_logic_vector (write_address_width - 1 downto 0);	 
	signal next_write_address			: std_logic_vector (write_address_width - 1 downto 0);
    signal next_word_to_read      		: std_logic_vector (read_address_width  - 1 downto 0);	  
    signal unused			     		: std_logic_vector (read_address_width  - 1 downto 0);
    signal equal_addresses       		: std_logic;
    signal next_write_address_enable	: std_logic;
    signal next_read_address_enable		: std_logic;
    signal set_status           		: std_logic;
    signal reset_status					: std_logic;
    signal status               		: std_logic;
    signal preset_full					: std_logic;
    signal preset_empty					: std_logic;
    signal empty		           		: std_logic;
	signal full							: std_logic;  
    
    component gray_counter is
	    generic (
	        counter_width 		: integer := 4
	    );
	    port (	
			clock_in        	: in  	std_logic;	
			reset_in      		: in  	std_logic; 
	        enable_in     		: in  	std_logic;  			  
			binary_count_out	: out	std_logic_vector (counter_width - 1 downto 0);
        	gray_count_out		: out	std_logic_vector (counter_width - 1 downto 0)  
	    );
    end component gray_counter;	 		  
	
	component dual_port_memory is
		generic (
			write_address_width	:	integer   := 3;
			read_address_width	:	integer   := 2;
	    	write_data_width	:	integer   := 12;  
	    	read_data_width		:	integer   := 24
		);  
		port (
	    	write_clock_in		: in	std_logic; 
	    	write_enable_in		: in	std_logic;  
	    	write_address_in    : in	std_logic_vector(write_address_width - 1 downto 0); 
	    	data_in				: in	std_logic_vector(write_data_width - 1 downto 0);
	    	read_clock_in		: in	std_logic;
	    	read_enable_in      : in	std_logic;
	    	read_address_in     : in	std_logic_vector(read_address_width - 1 downto 0);
	    	data_out			: out	std_logic_vector(read_data_width - 1 downto 0)
		);	
	end component dual_port_memory;
	
begin

    next_write_address_enable <= write_enable_in and (not full);
    next_read_address_enable  <= read_enable_in  and (not empty);
	next_write_address        <= next_word_to_write(write_address_width - 1 downto 1) &
	                             next_word_to_write_binary(0);
           
    write_gray_counter : gray_counter  
	generic map ( 
		counter_width		=> write_address_width
	)
    port map (	
		clock_in			=> write_clock_in,	
        reset_in      		=> reset_in,	   
        enable_in     		=> next_write_address_enable,	
		binary_count_out	=> next_word_to_write_binary,
        gray_count_out		=> next_word_to_write
    );
       
    read_gray_counter : gray_counter	
	generic map ( 
		counter_width		=> read_address_width
	)
    port map (			   
		clock_in			=> read_clock_in,	  
	    reset_in			=> reset_in, 
		enable_in     		=> next_read_address_enable,
		binary_count_out	=> unused,
        gray_count_out		=> next_word_to_read
    );	 
	
	storage : dual_port_memory
	generic map (
		write_address_width	=> write_address_width,
		read_address_width	=> read_address_width,
    	write_data_width	=> write_data_width,
    	read_data_width		=> read_data_width
	)  
	port map (
    	write_clock_in		=> write_clock_in,
    	write_enable_in		=> write_enable_in,  
    	write_address_in    => next_write_address,
    	data_in				=> data_in,
    	read_clock_in		=> read_clock_in,
    	read_enable_in      => read_enable_in,
    	read_address_in     => next_word_to_read,
    	data_out			=> data_out
	);	

    equal_addresses <= '1' when (next_word_to_write(write_address_width - 1 downto 1) = next_word_to_read) else '0';

    --'quadrant selectors' logic:
    process (next_word_to_write, next_word_to_read)
        variable set_status_bit_0 	: std_logic;
        variable set_status_bit_1	: std_logic;
        variable reset_status_bit_0 : std_logic;
        variable reset_status_bit_1 : std_logic;
    begin
        set_status_bit_0 := next_word_to_write(write_address_width - 2) xnor next_word_to_read(read_address_width - 1);
        set_status_bit_1 := next_word_to_write(write_address_width - 1) xor  next_word_to_read(read_address_width-2);
        set_status <= set_status_bit_0 and set_status_bit_1;
        
        reset_status_bit_0 := next_word_to_write(write_address_width - 2) xor  next_word_to_read(read_address_width-1);
        reset_status_bit_1 := next_word_to_write(write_address_width - 1) xnor next_word_to_read(read_address_width - 2);
        reset_status      <= reset_status_bit_0 and reset_status_bit_1;
    end process;
    
    --'status' latch logic:
    process (set_status, reset_status, reset_in) 
	begin--d latch w/ asynchronous clear & preset.
        if (reset_status = '1' or reset_in = '1') then
            status <= '0';  --going 'empty'.
        elsif (set_status = '1') then
            status <= '1';  --going 'full'.
        end if;
    end process;
    
    --'full_out' logic for the writing port:
    preset_full <= status and equal_addresses;  --'full' fifo.
    
    process (write_clock_in, preset_full) 
	begin --d flip-flop w/ asynchronous preset.
        if (preset_full = '1') then
            full <= '1';
        elsif (rising_edge(write_clock_in)) then
            full <= '0';
        end if;
    end process;

    
    --'empty_out' logic for the reading port:
    preset_empty <= not status and equal_addresses;  --'empty' fifo.
    
    process (read_clock_in, preset_empty) 
	begin --d flip-flop w/ asynchronous preset.
        if (preset_empty = '1') then
            empty <= '1';
        elsif (rising_edge(read_clock_in)) then
            empty <= '0';
        end if;
    end process;  
	
    full_out  <= full;   
    empty_out <= empty;
end architecture;