LIBRARY ieee;
USE ieee.std_logic_1164.ALL;

-- Uncomment the following library declaration if using
-- arithmetic functions with Signed or Unsigned values
USE ieee.numeric_std.ALL;

ENTITY fifo_tb_conc IS
END fifo_tb_conc;

ARCHITECTURE behavior OF fifo_tb_conc IS

    -- Component Declaration for the Unit Under Test (UUT)

    COMPONENT fifo
    PORT(
         clk   : IN   std_logic;
         rst   : IN   std_logic;
         din   : IN   std_logic_vector(7 downto 0);
         wr_en : IN   std_logic;
         rd_en : IN   std_logic;
         dout  : OUT  std_logic_vector(7 downto 0);
         full  : OUT  std_logic;
         empty : OUT  std_logic
        );
	END COMPONENT;

	-- Inputs
	signal clk   : std_logic := '0';
	signal rst   : std_logic := '0';
	signal din   : std_logic_vector(7 downto 0) := (others => '0');
	signal wr_en : std_logic := '0';
	signal rd_en : std_logic := '0';

	-- Outputs
	signal dout  : std_logic_vector (7 downto 0);
	signal full  : std_logic;
	signal empty : std_logic;

	-- Clock period definitions
	constant clk_period : time := 10 ns;
	constant NUM_CYCLES : integer := 300;

	signal rst_done : std_logic := '0';

	type read_state_type is (not_ready, reset_done, first_read, first_data, normal);

	signal read_state : read_state_type := not_ready;
	signal read_state_next : read_state_type;

	signal last_value : std_logic_vector (7 downto 0);
BEGIN

	-- Instantiate the Unit Under Test (UUT)
	uut: fifo PORT MAP (
		clk   => clk,
		rst   => rst,
		din   => din,
		wr_en => wr_en,
		rd_en => rd_en,
		dout  => dout,
		full  => full,
		empty => empty
		);

	------------------
	-- Clock process
	--
	-- Along with NUM_CYCLES this determines how many
	-- cycles this test bench is run for.
	------------------
	clk_process :process
	begin
		--while done /= '1' loop
		for i in 0 to NUM_CYCLES loop
			clk <= '0';
			wait for clk_period/2;
			clk <= '1';
			wait for clk_period/2;
		end loop;

		wait;  -- wait for other processes to finish
	end process;

	----------
	-- reset
	----------
	reset_proc: process
	begin
		rst <= '1';
		wait for 6*clk_period;  -- XXX, arbitrary delay?
		-- When using the simulator if this delay is too short it print errors
		-- about reset and read/write enable being improperly enabled.
		-- And it will loose written data vales?
		-- It hasn't been determined what exactly this delay should be and if
		-- this problem also occurs with hardware.

		rst <= '0';
		wait for clk_period;

		assert (empty = '1') report "FIFO should be empty after reset" severity ERROR;

		rst_done <= '1';

		wait;
	end process;

	------------------
	-- write process
	------------------
	write_proc: process (clk)
	begin
		if (rst_done = '1') then
			if (rising_edge(clk)) then
				if (full /= '1') then
					wr_en <= '1';
					-- counter that resets to 0
					if (unsigned(din) < 255) then
						din <= std_logic_vector(unsigned(din) + 1);
					else
						din <= (others => '0');
					end if;
				else
					wr_en <= '0';
				end if;
			end if;
		end if;
	end process;

	-----------------
	-- read process
	-----------------
	read_proc: process (clk)
	begin
		read_state <= read_state_next;

		if (rising_edge(clk)) then

			-- defaults
			read_state_next <= not_ready;
			rd_en <= '0';

			case read_state is
				when not_ready =>
					if (rst_done = '1' and empty /= '1') then
						-- enable read
						read_state_next <= first_read;
						rd_en <= '1';
					end if;
				when first_read =>
					if (rst_done = '1' and empty /= '1') then
						-- first read, data isn't valid yet
						read_state_next <= first_data;
						rd_en <= '1';
					end if;
				when first_data =>
					if (rst_done = '1' and empty /= '1') then
						-- got one valid piece of data
						read_state_next <= normal;
						rd_en <= '1';
						last_value <= dout after 1 ns;
					end if;
				when normal =>
					if (rst_done = '1' and empty /= '1') then
						-- got at least two valid pieces of data
						read_state_next <= normal;
						rd_en <= '1';

						assert (unsigned(last_value) + 1 = unsigned(dout)) report "Value read from the FIFO is to 1 + the previous value" severity ERROR;
						last_value <= dout after 1 ns;
					end if;
				when others =>
					read_state_next <= not_ready;
					rd_en <= '0';
			end case;
		end if;
	end process;

END;