-- -------------------------------------------------------------------------- --
--              TRASHERNET SoC - A Trashy Ethernet SoC for FPGAs              --
-- -------------------------------------------------------------------------- --
-- TODO
-- -------------------------------------------------------------------------- --
-- Author       : Markus Koch <markus@notsyncing.net>
-- Contributors : None
-- License      : Mozilla Public License (MPL) Version 2
-- -------------------------------------------------------------------------- --

-- Tech hint: Width of FIFO will be width of `data_in` + 1.

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

entity fifo_block is
	generic(
		SIZE : integer := 15            -- Entries in the FIFO (of size data), SIZE=(n^2)-1 recommended
	);
	port(
		-- generic ports
		clk        : in  std_logic;     -- System clock
		rst_a      : in  std_logic;     -- Asynchronous reset
		clr        : in  std_logic;     -- Remove all entries

		-- FIFO in
		data_in    : in  std_logic_vector; -- Data input into FIFO
		write      : in  std_logic;     -- Data is latched when `data_in_valid and not full`
		commit     : in  std_logic;     -- Finalize block
		abort      : in  std_logic;     -- Abort current block
		full       : out std_logic;     -- FIFO is full

		-- FIFO out
		data_out   : out std_logic_vector; -- Data output from FIFO
		data_first : out std_logic;     -- Currently visible word is first in packet
		empty      : out std_logic;     -- FIFO is empty. '0' means "data is available".
		read       : in  std_logic;     -- Acknowledge that `data_out` was read. The word is removed and next one shifted from FIFO when `read and not empty`.

		-- FIFO meta
		usage      : out integer range 0 to SIZE -- Usage counter
	);
end fifo_block;

architecture rtl of fifo_block is
	constant MEMORY_WIDTH : integer := data_in'length + 1;
	subtype pointer_t is integer range 0 to SIZE - 1;
	type memory_t is array (pointer_t) of std_logic_vector(MEMORY_WIDTH - 1 downto 0);

	signal memory                  : memory_t;
	signal read_pointer            : pointer_t;
	signal write_pointer           : pointer_t;
	signal read_pointer_last       : pointer_t;
	signal write_pointer_committed : pointer_t;
	signal current_is_first        : std_logic;

begin
	fifo : process(rst_a, clk) is
		variable succesful_read     : std_logic;
		variable succesful_write    : std_logic;
		variable write_pointer_next : pointer_t;
		variable read_pointer_next  : pointer_t;
	begin
		if rst_a then
			read_pointer            <= 0;
			write_pointer           <= 0;
			read_pointer_last       <= SIZE - 1;
			write_pointer_committed <= 0;
			current_is_first        <= '1';
			usage                   <= 0;

		elsif rising_edge(clk) then
			succesful_read  := read and not empty;
			succesful_write := write and (not full or succesful_read);

			write_pointer_next := write_pointer;
			read_pointer_next  := read_pointer;

			if succesful_read then
				read_pointer_next := (read_pointer_next + 1) mod SIZE;
				read_pointer_last <= read_pointer;
			end if;

			if succesful_write then
				memory(write_pointer) <= current_is_first & data_in;
				write_pointer_next    := (write_pointer_next + 1) mod SIZE;
				current_is_first      <= '0';
			end if;

			if succesful_write xor succesful_read then
				if succesful_write then
					usage <= usage + 1;
				else
					usage <= usage - 1;
				end if;
			end if;

			if commit then
				write_pointer_committed <= write_pointer;
				current_is_first        <= '1';
			elsif abort then
				write_pointer_next := write_pointer_committed;
				current_is_first   <= '1';
			end if;

			read_pointer  <= read_pointer_next;
			write_pointer <= write_pointer_next;

			if clr then
				read_pointer            <= 0;
				write_pointer           <= 0;
				read_pointer_last       <= SIZE - 1;
				write_pointer_committed <= 0;
				current_is_first        <= '1';
				usage                   <= 0;
			end if;
		end if;
	end process fifo;

	full  <= '1' when read_pointer_last = write_pointer else '0';
	empty <= '1' when (read_pointer = write_pointer_committed) else '0';

	data_out   <= memory(read_pointer)(data_out'range);
	data_first <= memory(read_pointer)(MEMORY_WIDTH - 1);

end rtl;