----------------------------------------------------------------------------------
--				Implementace vysilaci casti protokolu APP (vlastni protokol) 		  --
----------------------------------------------------------------------------------

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
use work.my_constants.all;

entity app_tx is
	port (
		GTX_CLK			: in std_logic;							-- clk pro odmereni casu
		TX_CLK			: in std_logic;							-- clk pro taktovani odesilani
		RX_CLK			: in std_logic;							-- clk pro prijem signalu z rx
		-- uzivatelske rozhrani vstup
		TX_DATA			: in std_logic_vector(7 downto 0);
		TX_DATA_VLD		: in std_logic;
		TX_DATA_ACK		: out std_logic;		
		TX_DATA_END		: in std_logic;
		-- do UDP vrstvy
		DATA_TO_UDP		: out std_logic_vector(7 downto 0);
		DATA_TO_UDP_VLD: out std_logic;
		DATA_TO_UDP_EN	: in std_logic;
		-- Control
		SEND_ACK			: in std_logic;
		SEND_SYNC		: in std_logic;
		SEND_ESYN		: in std_logic;
		SEND_ERR			: in std_logic;
		SEND_ADR			: in std_logic_vector(15 downto 0);
		-- Info o prijeti ACK, ERR paketu
		RECV_ACK			: in std_logic;
		RECV_ERR			: in std_logic;
		RECV_ADR			: in std_logic_vector(APP_ADDR_WIDTH-1 downto 0);	
		-- reset
		RESET				: in std_logic
	);
end app_tx;

architecture Behavioral of app_tx is

-- stavy pro tx
type tx_state_type is (st_idle, st_connected, st_header, st_data, st_resend,
							  st_sblock, st_ack, st_sync, st_esyn, st_err, st_check_id);

signal tx_state, tx_next_state : tx_state_type := st_idle; 

-- stavy pro výstupní buffer (window)
type obuffer_state_type is (st_idle, st_err_del, st_ack_del, st_timeout);
signal obuffer_state, obuffer_next_state : obuffer_state_type;
-- Vystupni registr do tx UDP
signal data_to_udp_i: std_logic_vector(7 downto 0) := (others=>'0');
signal data_to_udp_vld_i: std_logic := '0';
-- Registrova pamet pro ulohy k odeslani [poslat ACK], [poslat SYNC], [poslat ESYN], [poslat ERR]
signal send_ack_i, send_sync_i, send_esyn_i, send_err_i: std_logic	:= '0';
signal send_sync_rst, send_esyn_rst, send_err_rst, send_ack_rst: std_logic	:= '0'; -- resetovani splnene ulohy
signal send_adr_i: std_logic_vector(15 downto 0);	-- Pamet pro potvrzovane ID
-- Registrova pamet pro ulohy ke zpracovani uvnitr bloku [prijat ACK], [prijat ERR]
signal recv_ack_i, recv_err_i: std_logic		:= '0';
signal recv_ack_rst, recv_err_rst: std_logic	:= '0';
-- Citac pro globalni ID tx paketu
signal global_id_tx, global_id_tx_next: std_logic_vector(15 downto 0) := (others=>'0');
signal global_id_tx_en, global_id_tx_rst: std_logic := '0';
-- Počítání bajtů
signal tx_count, tx_count_next: std_logic_vector(UDP_ADDR_WIDTH-1 downto 0) := (others=>'0');
signal tx_count_en: std_logic := '0';
-- Vypocet doby timeoutu, do ktere pokud neprijde odpoved odesle se buffer znovu 
signal count_timeout, count_timeout_next: std_logic_vector(TIMEOUT_WIDTH downto 0) := (others=>'0');				-- Mereni timeoutu 
signal timeout_rst: std_logic	:= '0';
-- Vypocet, kdz odeslat potvrzovaci paket
signal count_window, count_window_next: std_logic_vector(3 downto 0) := (others => '0');
signal count_window_rst, count_window_inc: std_logic := '0';
-- Signaly pro znovu odeslani v pripade ERR paketu nebo timeoutu
signal resend, resend_next, resend_rst, error, timeout: std_logic := '0';

-- deklarace blokove RAM - výstupní buffer (okno) -------------------------
type ram_type is array (2**(APP_ADDR_WIDTH+UDP_ADDR_WIDTH)-1 downto 0) of std_logic_vector (8 downto 0);
signal obuffer: ram_type;
signal write_address, read_address: std_logic_vector (UDP_ADDR_WIDTH-1 downto 0) := (others=>'0'); -- Pristup k jednotlivim bajtum v bunce
signal write_block, read_block, last_block, read_block_next, last_block_next: std_logic_vector(APP_ADDR_WIDTH-1  downto 0)	:=(others=>'0');		-- Pristup k jednotlivym 1kB bunkam v RAM		
signal write_enable, read_enable, read_block_inc, read_block_load, last_block_rst, delete_block: std_logic 	:= '0';
signal ram_output: std_logic_vector (8 downto 0)	:= (others => '0');
signal tx_data_ack_i: std_logic := '0';
---------------------------------------------------------------------------
signal obuffer_full, obuffer_full_next, obuffer_empty, obuffer_empty_next, set_full, set_empty, set_empty2, rst_full, rst_empty: std_logic := '0';

begin

	------------------------------------------------------------------------
	-- Mereni casu timeoutu pro ACK paket ----------------------------------
	------------------------------------------------------------------------
	process (GTX_CLK)
	begin
		if (GTX_CLK'event and GTX_CLK = '1') then	
			count_timeout <= count_timeout_next;
		end if;
	end process;
	
	count_timeout_next <= (others => '0') when timeout_rst = '1' or obuffer_empty = '1' else
								  count_timeout when count_timeout(TIMEOUT_WIDTH) = '1' else 
								  count_timeout + 1;						  
	-- nejvyssi bit citace pro timeout
	timeout <= '1' when count_timeout(TIMEOUT_WIDTH) = '1' else '0';
	
	------------------------------------------------------------------------	
	-- VYSILACI SEKVENCNI CAST ---------------------------------------------
	------------------------------------------------------------------------
	process (TX_CLK)
	begin
		if (TX_CLK'event and TX_CLK = '1') then

			if (RESET = '1') then
				tx_state <= st_idle;
				obuffer_state <= st_idle;

			else 
				-- FSM
				tx_state <= tx_next_state;
				obuffer_state <= obuffer_next_state;
				------------------------------------------------------------------------
				------------------------------------------------------------------------				
				tx_count <= tx_count_next;				
				global_id_tx <= global_id_tx_next;
				count_window <= count_window_next;
				resend <= resend_next;
				
				------------------------------------------------------------------------
				-- Blokocá RAM	- výstupní buffer ---------------------------------------				
				-- zapis
				if (write_enable = '1') then	
					obuffer(conv_integer(write_block & write_address)) <= data_to_udp_vld_i & data_to_udp_i;	
				end if;
				-- cteni
				if read_enable = '1' then read_address <= read_address + 1;							
				else read_address <= (others => '0'); end if;				
				ram_output <= obuffer(conv_integer(read_block & read_address))(8 downto 0);								
				
				------------------------------------------------------------------------
				-- Adresovani bloku v ram ----------------------------------------------
				last_block <= last_block_next;
				read_block <= read_block_next;
				obuffer_full <= obuffer_full_next;
				obuffer_empty <= obuffer_empty_next;

				------------------------------------------------------------------------
				-- Out. reg. -----------------------------------------------------------
				DATA_TO_UDP <= data_to_udp_i;
				DATA_TO_UDP_VLD <= data_to_udp_vld_i;
				TX_DATA_ACK	<= tx_data_ack_i;			
			end if;
		end if;
	end process;
	
	------------------------------------------------------------------------
	-- KOMBINACNI CAST -----------------------------------------------------
	------------------------------------------------------------------------	
	-- plna pamet
	obuffer_full_next <= '1' when set_full = '1' else
								'0' when rst_full = '1' else
								obuffer_full;
	-- prazdna pamet
	obuffer_empty_next <= '1' when set_empty = '1' or set_empty2 = '1' else
								 '0' when rst_empty = '1' else
								 obuffer_empty;
	------------------------------------------------------------------------
	-- Pocitani delky okna = bloku odeslaneho bez nutnosti potvrzeni ------- 
	count_window_next <= (others => '0') when count_window_rst = '1' else
								 count_window + 1 when count_window_inc = '1' else
								 count_window;

	tx_count_next <=  tx_count + 1 when tx_count_en = '1' or write_enable = '1' else
							(others => '0');

	write_address <= tx_count;
	------------------------------------------------------------------------
	-- Mazani a cteni jednotlivych bloku v pameti -------------------------- 
	last_block_next <= (others => '0') when RESET = '1' or last_block_rst = '1' else
							 last_block + 1 when delete_block = '1' else
							 last_block;

	read_block_next <= (others => '0') when RESET = '1' or last_block_rst = '1' else 
							 read_block + 1 when read_block_inc = '1' else
							 last_block when read_block_load = '1' else
							 read_block;

	------------------------------------------------------------------------
	-- Globalni ID pro odesilani -------------------------------------------
	global_id_tx_next <= (others => '0') when global_id_tx_rst = '1' else
							global_id_tx + 1 when global_id_tx_en = '1' else
							global_id_tx;
	
	write_block <= global_id_tx(APP_ADDR_WIDTH-1  downto 0);
	
	------------------------------------------------------------------------
	-- Pamet pro prijate potvrzeni a chyby - D s asyn rst ------------------	
	process (RX_CLK, recv_ack_rst)
	begin
		if recv_ack_rst = '1' then
			recv_ack_i <= '0';
		elsif (RX_CLK'event and RX_CLK = '1') then
			if RECV_ACK = '1' then recv_ack_i <= '1'; end if;
		end if;
	end process;
	
	process (RX_CLK, recv_err_rst)
	begin
		if recv_err_rst = '1' then
			recv_err_i <= '0';
		elsif (RX_CLK'event and RX_CLK = '1') then
			if RECV_ERR = '1' then recv_err_i <= '1'; end if;
		end if;
	end process;
			
	------------------------------------------------------------------------
	-- Registr úloh pro automat app_tx -------------------------------------
	process (RX_CLK, send_ack_rst)
	begin
		if send_ack_rst = '1' then
			send_ack_i <= '0';
		elsif (RX_CLK'event and RX_CLK = '1') then
			if SEND_ACK = '1' then send_ack_i <= '1'; end if;
		end if;
	end process;
	
	process (RX_CLK, send_sync_rst)	-- navazani spojeni 
	begin
		if send_sync_rst = '1' then
			send_sync_i <= '0';
		elsif (RX_CLK'event and RX_CLK = '1') then
			if SEND_SYNC = '1' then send_sync_i <= '1'; end if;
		end if;
	end process;

	process (RX_CLK, send_err_rst)	-- chyba
	begin
		if send_err_rst = '1' then
			send_err_i <= '0';
		elsif (RX_CLK'event and RX_CLK = '1') then
			if SEND_ERR = '1' then send_err_i <= '1'; end if;
		end if;
	end process;	
	
	process (RX_CLK, send_esyn_rst)	-- konec spojeni 
	begin
		if send_esyn_rst = '1' then
			send_esyn_i <= '0';
		elsif (RX_CLK'event and RX_CLK = '1') then
			if SEND_ESYN = '1' then send_esyn_i <= '1'; end if;
		end if;
	end process;
	
	process (RX_CLK) -- Nacteni ID z prijateho paketu
	begin
		if RX_CLK'event and RX_CLK = '1' then
			if (SEND_ACK or SEND_ERR) = '1' then send_adr_i <= SEND_ADR; end if;
		end if;
	end process;
	
	------------------------------------------------------------------------
	-- Priznak pro znovu odeslani okna v pripade timeoutu nebo err	---------
	resend_next <= '1' when error = '1' or timeout = '1' else
						'0' when resend_rst = '1' else
						resend;
	
	------------------------------------------------------------------------
	-- FSM pro odeslilani -------------------------------------------------
	------------------------------------------------------------------------
	app_tx: process (tx_state, tx_count(1 downto 0), DATA_TO_UDP_EN, TX_DATA, TX_DATA_VLD, TX_DATA_END, 
						  send_ack_i, send_sync_i, send_esyn_i, send_adr_i, send_err_i, global_id_tx, obuffer_empty,
						  ram_output, resend, count_window, read_block, write_block, last_block, obuffer_full)
	begin
		tx_next_state <= st_idle;
		tx_count_en <= '0';
		data_to_udp_i <= x"00";
		data_to_udp_vld_i <= '0';
		send_sync_rst <= '0';
		send_esyn_rst <= '0';
		global_id_tx_en <= '0';
		global_id_tx_rst <= '0';
		write_enable <= '0';
		read_block_inc <= '0';
		send_ack_rst <= '0';
		read_enable <= '0';
		count_window_rst <= '0';
		count_window_inc <= '0';
		send_err_rst <= '0';
		tx_data_ack_i <= '0';
		resend_rst <= '0';
		last_block_rst <= '0';
		set_full <= '0';
		rst_empty <= '0';
		set_empty2 <= '0';
		
		case tx_state is 	
			---------------------------------------------------------------------------------
			-- Odpojeny stav ----------------------------------------------------------------
			when st_idle =>
				tx_next_state <= st_idle;
				set_empty2 <= '1';				-- pocatecni nastaveni pameti 
				if send_sync_i = '1' then
					tx_next_state <= st_sync;
				else
					send_err_rst <= '1';
					send_esyn_rst <= '1';
					send_ack_rst <= '1';
					global_id_tx_rst <= '1';
					count_window_rst <= '1';
					resend_rst <= '1';
					last_block_rst <= '1';
				end if;

			---------------------------------------------------------------------------------
			-- Pripojeno - vyckava se -------------------------------------------------------			
			when st_connected =>
				tx_next_state <= st_connected;
				if DATA_TO_UDP_EN = '0' then tx_next_state <= st_connected;
				elsif send_ack_i = '1' then tx_next_state <= st_ack;
				elsif send_err_i = '1' then tx_next_state <= st_err;
				elsif send_sync_i = '1' then tx_next_state <= st_sync;
				elsif send_esyn_i = '1' then tx_next_state <= st_esyn;
				elsif resend = '1' then tx_next_state <= st_resend;
				elsif (TX_DATA_VLD = '1') and (obuffer_full = '0') then tx_next_state <= st_header;
				end if;
			
			---------------------------------------------------------------------------------
			-- Odeslani zadosti o pripojeni, nebo odpoved na timeout paket ------------------
			when st_sync =>
				tx_next_state <= st_sync;
				if DATA_TO_UDP_EN = '1' then
					tx_count_en <= '1';
					data_to_udp_vld_i <= '1';
					global_id_tx_rst <= '1';
					last_block_rst <= '1';
					resend_rst <= '1';
					count_window_rst <= '1';
					case tx_count(1 downto 0) is 
						when "00" => data_to_udp_i <= x"AA";
						when "01" => data_to_udp_i <= x"12";
						when "10" => data_to_udp_i <= x"00";
						when "11" => 
							data_to_udp_i <= x"00"; 
							send_sync_rst <= '1'; 
							tx_next_state <= st_connected; 
						when others =>
					end case;
				end if;
			
			---------------------------------------------------------------------------------
			-- Odeslani potvrzovaciho paketu ------------------------------------------------				
			when st_ack =>
				tx_next_state <= st_ack;
				tx_count_en <= '1';
				data_to_udp_vld_i <= '1';
				case tx_count(1 downto 0) is 
					when "00" => data_to_udp_i <= x"BB";
					when "01" => data_to_udp_i <= x"12";
					when "10" => data_to_udp_i <= send_adr_i(15 downto 8);
					when "11" => data_to_udp_i <= send_adr_i(7 downto 0); send_ack_rst <= '1'; tx_next_state <= st_connected;
					when others =>
				end case;
				
			---------------------------------------------------------------------------------
			-- Odeslani paketu pro ukonceni spojeni -----------------------------------------
			when st_esyn =>
				tx_next_state <= st_esyn;
				tx_count_en <= '1';
				data_to_udp_vld_i <= '1';
				case tx_count(1 downto 0) is 
					when "00" => data_to_udp_i <= x"DD";
					when "01" => data_to_udp_i <= x"12";
					when "10" => data_to_udp_i <= x"00";
					when "11" => data_to_udp_i <= x"00"; send_esyn_rst <= '1'; tx_next_state <= st_idle;
					when others =>
				end case;

			---------------------------------------------------------------------------------
			-- Odeslani error paketu s ID posleniho prijateho bloku -------------------------
			when st_err =>
				tx_next_state <= st_err;
				tx_count_en <= '1';
				data_to_udp_vld_i <= '1';
				case tx_count(1 downto 0) is 
					when "00" => data_to_udp_i <= x"EE";
					when "01" => data_to_udp_i <= x"12";
					when "10" => data_to_udp_i <= send_adr_i(15 downto 8);
					when "11" => data_to_udp_i <= send_adr_i(7 downto 0); send_err_rst <= '1'; tx_next_state <= st_connected;
					when others =>
				end case;
			
			---------------------------------------------------------------------------------
			-- Odeslani hlavicky datoveho paketu --------------------------------------------
			when st_header =>
				tx_next_state <= st_header;
				tx_count_en <= '1';
				data_to_udp_vld_i <= '1';
				write_enable <= '1';
				case tx_count(1 downto 0) is 
					when "00" => data_to_udp_i <= x"CC";
					when "01" =>
						data_to_udp_i <= x"00";
						count_window_inc <= '1';
						if (WINDOW_SIZE = count_window) or (TX_DATA_END = '1') then
							data_to_udp_i <= x"01";
							count_window_rst <= '1';
						end if;
					when "10" => data_to_udp_i <= global_id_tx(15 downto 8);
					when "11" => data_to_udp_i <= global_id_tx(7 downto 0); tx_next_state <= st_data; tx_data_ack_i <= '1';
					when others =>
				end case;
			
			---------------------------------------------------------------------------------
			-- Data datoveho paketu ---------------------------------------------------------		
			when st_data =>
				
				write_enable <= '1';
				tx_next_state <= st_data;
				data_to_udp_vld_i <= '1';
				data_to_udp_i <= TX_DATA;
				if TX_DATA_VLD = '0' then
					rst_empty <= '1';
					if (last_block = write_block + 1) then set_full <= '1'; end if;
					global_id_tx_en <= '1';
					data_to_udp_i <= x"00";				
					data_to_udp_vld_i <= '0';
					tx_next_state <= st_connected; 
				end if;
							
			---------------------------------------------------------------------------------			
			-- Chyba v prenosu, nutno odeslat cely buffer znovu -----------------------------
			when st_resend =>
				tx_next_state <= st_resend;			
				if DATA_TO_UDP_EN = '1' then tx_next_state <= st_sblock; read_enable <= '1'; end if;
			---------------------------------------------------------------------------------			
			-- Odeslat blok -----------------------------------------------------------------
			when st_sblock =>
				tx_next_state <= st_sblock;
				read_enable <= '1';
				data_to_udp_i <= ram_output(7 downto 0);
				data_to_udp_vld_i <= ram_output(8);
				if ram_output(8) = '0' then tx_next_state <= st_check_id; read_block_inc <= '1'; end if;
				
			---------------------------------------------------------------------------------			
			-- Je uz odeslano vse? ----------------------------------------------------------
			when st_check_id =>
				tx_next_state <= st_connected;	-- Na zacatek kvuli priorite nekderych paketu				
				if read_block = write_block then resend_rst <= '1'; end if;

		end case;			
	end process;
	
	------------------------------------------------------------------------	
	-- Obsluha vystupniho Bufferu ------------------------------------------
	------------------------------------------------------------------------
	output_buffer: process (obuffer_state, recv_ack_i, recv_err_i, last_block, write_block, timeout, RECV_ADR)
	begin
		delete_block <= '0';
		timeout_rst <= '0';
		recv_ack_rst <= '0';
		recv_err_rst <= '0';
		error <= '0';	
		read_block_load <= '0';
		set_empty <= '0';
		rst_full <= '0';
	
		case obuffer_state is 
			
			---------------------------------------------------------------------------------
			-- Prisel potvrzovaci nebo error paket ------------------------------------------			
			when st_idle =>
				obuffer_next_state <= st_idle;
				if recv_err_i = '1' then 
					obuffer_next_state <= st_err_del;
				elsif recv_ack_i = '1' then 
					obuffer_next_state <= st_ack_del; 
				elsif timeout = '1' then 
					obuffer_next_state <= st_timeout; 
				end if;
				
			---------------------------------------------------------------------------------
			-- Smazat vsechny potvrzene bloky -----------------------------------------------
			when st_ack_del =>
				recv_ack_rst <= '1';
				obuffer_next_state <= st_ack_del;
				timeout_rst <= '1';
				delete_block <= '1';
				if last_block = RECV_ADR then
					obuffer_next_state <= st_idle;
					rst_full <= '1';						
					if last_block + 1 = write_block then set_empty <= '1'; end if;				 
				end if;

			when st_err_del =>
				obuffer_next_state <= st_err_del;
				timeout_rst <= '1';
				recv_err_rst <= '1';
				if last_block = RECV_ADR then 
					obuffer_next_state <= st_timeout;
					error <= '1';
					if last_block /= write_block then rst_full <= '1'; end if;				 
				else delete_block <= '1'; end if;
			
			---------------------------------------------------------------------------------
			-- Obsluha pro timeout - nacist prvni blok k odeslani ---------------------------			
			when st_timeout =>
				obuffer_next_state <= st_idle;
				timeout_rst <= '1';
				read_block_load <= '1';				
			
		end case;
	end process;

end Behavioral;
