----------------------------------------------------------------------------------
--							Implementace vysilaci casti protokolu UDP  			  		  --
----------------------------------------------------------------------------------

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 udp_tx is
	port (
		CLK_TX: in std_logic;
			
		-- uzivatelske rozhrani vstup
		TX_DATA: in std_logic_vector(7 downto 0);
		TX_DATA_VLD: in std_logic;
		TX_DATA_EN: out std_logic;
		
		-- do IP vrstvy
		DATA_TO_IP: out std_logic_vector(7 downto 0);
		DATA_TO_IP_VLD: out std_logic;
		DATA_TO_IP_LEN: out std_logic_vector(UDP_ADDR_WIDTH - 1 downto 0);
		DATA_TO_IP_ACK: in std_logic;
		
		--Control
		DESTINATION_PORT: in std_logic_vector(15 downto 0);		-- Port aplikace v PC
		DESTINATION_IP: in std_logic_vector(31 downto 0);

		-- reset
		RESET: in std_logic
	);
end udp_tx;

architecture Behavioral of udp_tx is

COMPONENT checksum
PORT(
	DATA_IN : IN std_logic_vector(7 downto 0);
	RESET : IN std_logic;
	CALC : IN std_logic;
	CLK : IN std_logic;          
	SUM_OUT : OUT std_logic_vector(15 downto 0)
	);
END COMPONENT;

-- Indexace 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';
-- Stavovy automat pro prenos
type tx_state_type is (st_idle, st_wait_ack, st_header, st_data, st_end);
signal tx_state, tx_next_state : tx_state_type;
-- Stavovy automat pro zapis do RAM
type buffer_state_type is (st_idle, st_write, st_wait1, st_wait2, st_add_hdr);
signal buffer_state, buffer_next_state : buffer_state_type;

-- deklarace blokove RAM -----------------------------
type ram_type is array (2**UDP_ADDR_WIDTH - 1 downto 0) of std_logic_vector (7 downto 0);
signal ibuffer: ram_type;
signal write_address, read_address: std_logic_vector (UDP_ADDR_WIDTH - 1 downto 0) := (others=>'0');
signal write_en, read_en: std_logic										:= '0';
signal ram_output, ram_input: std_logic_vector (7 downto 0)		:= (others=>'0');
-------------------------------------------------------
-- Checksuma
signal data_to_sum: std_logic_vector(7 downto 0)					:= (others=>'0');
signal checksum_i: std_logic_vector(15 downto 0) 					:= (others=>'0');
signal compute_en, sum_reset: std_logic								:= '0';
-- Ulozeni delky paketu v bufferu
signal lenght: std_logic_vector(UDP_ADDR_WIDTH - 1 downto 0)	:= (others=>'0');
signal load_len: std_logic													:= '0';
-- Ovladani zaplneni bufferu
signal full_buffer, full_buffer_next, ram_busy: std_logic		:= '0';
signal set_full, set_empty: std_logic									:= '0';
-- Vstupy vystupnich registru
signal data_to_ip_i: std_logic_vector(7 downto 0) 																:= (others=>'0');
signal data_to_ip_len_i, data_to_ip_len_i_next: std_logic_vector(UDP_ADDR_WIDTH - 1 downto 0) 	:= (others=>'0');
signal data_to_ip_vld_i: std_logic 																					:= '0';
signal sending, tx_count_mem: std_logic																			:= '0';
-- doplneni vektoru delky do 16bit slova
signal data_len_pad: std_logic_vector(15 - UDP_ADDR_WIDTH downto 0) 										:= (others=>'0');  

begin
	
	data_len_pad <= (others => '0');
	
	--------------------------------------------------------------------------
	-- Checksuma --------------------------------------------------------
	--------------------------------------------------------------------------		
	Inst_checksum: checksum PORT MAP(
		sum_out => checksum_i,
		data_in => data_to_sum,
		reset => sum_reset,
		calc => compute_en,
		clk => CLK_TX
	);

	--------------------------------------------------------------------------
	-- Sekvencni cast --------------------------------------------------------
	--------------------------------------------------------------------------		
	process (CLK_TX)
	begin
		if (CLK_TX'event and CLK_TX = '1') then

			if (RESET = '1') then
				tx_state <= st_idle;
				buffer_state <= st_idle;
				write_address <= (others => '0');
				read_address <= (others => '0');
				full_buffer <= '0';
			else 		
				-- Stavove automaty
				tx_state <= tx_next_state;
				buffer_state <= buffer_next_state;
				
				---------------------------------------------------------------------------
				-- Blokocá RAM	------------------------------------------------------------
				-- Zapis
				if (write_en = '1') then				
					write_address <= write_address + 1;								
					ibuffer(conv_integer(write_address)) <= ram_input;
				end if;
				-- Cteni
				if read_en = '1' then read_address <= read_address + 1;	end if;		
				ram_output <= ibuffer(conv_integer(read_address));				
				--------------------------------------------------------------------------										
				
				full_buffer <= full_buffer_next;
				tx_count <= tx_count_next;
				data_to_ip_len_i <= data_to_ip_len_i_next;
						
				-- Registrove vystupy ----------------------------------------------------
				DATA_TO_IP <= data_to_ip_i;
				DATA_TO_ip_VLD <= data_to_ip_vld_i;
				DATA_TO_IP_LEN <= data_to_ip_len_i;
			end if;
		end if;
	end process;
	
	--------------------------------------------------------------------------
	-- Kombinacni cast -------------------------------------------------------
	--------------------------------------------------------------------------	
	-- Vystupy / vstup 
	TX_DATA_EN <= full_buffer nor ram_busy;
	ram_input <= TX_DATA;
	
	-- delka dat + hlavicka
	lenght <= write_address - read_address;	
	
	data_to_ip_len_i_next <= lenght + 8 when load_len = '1' else data_to_ip_len_i;

	-- Pocitani jednotlivych bajtu datagramu
	tx_count_next <= tx_count + 1 when tx_count_en = '1' or tx_count_mem = '1' else 
						  (others => '0');
	
	-- Indikator plneho bufferu
	full_buffer_next <= '1' when set_full = '1' else
							  '0' when set_empty = '1' else 
							  full_buffer;


	--------------------------------------------------------------------------
	-- Zápis do vstupní FIFO -------------------------------------------------
	--------------------------------------------------------------------------	
	ram_write: process (buffer_state, data_to_ip_len_i, full_buffer, sending, tx_count(4 downto 0), 
							  TX_DATA, TX_DATA_VLD, DESTINATION_PORT, DESTINATION_IP)
	begin
		buffer_next_state <= st_idle;
		write_en <= '0';
		set_full <= '0';
		compute_en <= '0';
		load_len <= '0';
		tx_count_mem <= '0';
		data_to_sum <= x"00";
		ram_busy <= '1';
		
		case buffer_state is
			--------------------------------------------------------------------------
			-- Idle ------------------------------------------------------------------
			when st_idle =>
				buffer_next_state <= st_idle;
				ram_busy <= '0';
				if TX_DATA_VLD = '1' and full_buffer = '0' then 
					buffer_next_state <= st_write;
					write_en <= '1';
					compute_en <= '1';
					data_to_sum <= TX_DATA;
				end if;

			--------------------------------------------------------------------------
			-- Zápis do bufferu ------------------------------------------------------			
			when st_write =>
				buffer_next_state <= st_write;
				compute_en <= '1';
				ram_busy <= '0';
				write_en <= '1';
				data_to_sum <= TX_DATA;
				if TX_DATA_VLD = '0' then 
					buffer_next_state <= st_wait1; 
					compute_en <= '0';
					write_en <= '0';
					data_to_sum <= x"00";
				end if;
		
			--------------------------------------------------------------------------
			-- Pokud se data prave posilaji musi se pockat az se odeslou -------------					
			when st_wait1 =>
				buffer_next_state <= st_wait1;
				if sending = '0' then 
					buffer_next_state <= st_wait2;
					load_len <= '1';
				end if;
				
			--------------------------------------------------------------------------
			-- Pockat 1 takt kvuli dobehnuti vypoctu checksumy -----------------------					
			when st_wait2 =>				
				buffer_next_state <= st_add_hdr;
			
			--------------------------------------------------------------------------
			-- Pripocita k checksume dat i pseudohlavicku ----------------------------		
			when st_add_hdr => 
				buffer_next_state <= st_add_hdr;				
				compute_en <= '1';
				tx_count_mem <= '1';
				case tx_count(4 downto 0) is 
					when "00000" => data_to_sum <= DESTINATION_IP(31 downto 24);
					when "00001" => data_to_sum <= DESTINATION_IP(23 downto 16);
					when "00010" => data_to_sum <= DESTINATION_IP(15 downto 8);			
					when "00011" => data_to_sum <= DESTINATION_IP(7 downto 0);		
					when "00100" => data_to_sum <= FPGA_IP(31 downto 24);
					when "00101" => data_to_sum <= FPGA_IP(23 downto 16);
					when "00110" => data_to_sum <= FPGA_IP(15 downto 8);			
					when "00111" => data_to_sum <= FPGA_IP(7 downto 0);		
					when "01000" => data_to_sum <= x"00";		
					when "01001" => data_to_sum <= x"11";														-- protokol (0x11 = UDP)			
					when "01010" => data_to_sum <= data_len_pad&data_to_ip_len_i(UDP_ADDR_WIDTH - 1 downto 8);	-- delka		
					when "01011" => data_to_sum <= data_to_ip_len_i(7 downto 0);		
					when "01100" => data_to_sum <= FPGA_PORT(15 downto 8);			
					when "01101" => data_to_sum <= FPGA_PORT(7 downto 0);		
					when "01110" => data_to_sum <= DESTINATION_PORT(15 downto 8);			
					when "01111" => data_to_sum <= DESTINATION_PORT(7 downto 0);		
					when "10000" => data_to_sum <= data_len_pad&data_to_ip_len_i(UDP_ADDR_WIDTH - 1 downto 8);	-- delka		
					when "10001" => data_to_sum <= data_to_ip_len_i(7 downto 0);		
					when "10010" => compute_en <= '0';				-- konec vypoctu
					when "10011" => compute_en <= '0';
										 set_full <= '1';					
										 buffer_next_state <= st_idle; 		
					when others =>
				end case;

		end case;

	end process;
	
	--------------------------------------------------------------------------
	-- Obsluha pro odesilani -------------------------------------------------
	--------------------------------------------------------------------------	
	UDP_tx: process (tx_state, data_to_ip_len_i, tx_count, ram_output, checksum_i, write_en, full_buffer, 
						  DATA_TO_IP_ACK, DESTINATION_IP, DESTINATION_PORT)
	begin
		tx_next_state <= st_idle;
		sum_reset <= '0';
		tx_count_en <= '0';
		data_to_ip_i <= x"00";
		data_to_ip_vld_i <= '0';
		read_en	<= '0';	
		set_empty <= '0';
		sending <= '1';
		
		case tx_state is 
			--------------------------------------------------------------------------
			-- Idle ------------------------------------------------------------------		
			when st_idle =>
				tx_next_state <= st_idle;
				sending <= '0';
				if full_buffer = '1' then tx_next_state <= st_wait_ack; end if;

			--------------------------------------------------------------------------
			-- Ceka se na potvrzeni od IP --------------------------------------------
			when st_wait_ack =>
				tx_next_state <= st_wait_ack;
				data_to_ip_vld_i <= '1';
				data_to_ip_i <= FPGA_PORT(15 downto 8);
				if DATA_TO_IP_ACK = '1' then 
					tx_next_state <= st_header;
					data_to_ip_i <= FPGA_PORT(7 downto 0);
				end if;

			--------------------------------------------------------------------------
			-- Odeslani UDP hlavicky -------------------------------------------------
			when st_header =>
				tx_next_state <= st_header;
				data_to_ip_vld_i <= '1';
				tx_count_en <= '1';
				-- vytvorime hlavicku 
				case tx_count(2 downto 0) is 
					when "000" => data_to_ip_i <= DESTINATION_PORT(15 downto 8);
					when "001" => data_to_ip_i <= DESTINATION_PORT(7 downto 0);			
					when "010" => data_to_ip_i <= data_len_pad&data_to_ip_len_i(UDP_ADDR_WIDTH - 1 downto 8);		-- delka dat H
					when "011" => data_to_ip_i <= data_to_ip_len_i(7 downto 0);		-- delka dat L
					when "100" => data_to_ip_i <= checksum_i(15 downto 8);				-- checksuma muzem dat 0
					when "101" => data_to_ip_i <= checksum_i(7 downto 0);				-- checksuma muzem dat 0
									 tx_next_state <= st_data; 
									 sum_reset <= '1';
									 read_en <= '1';
									 set_empty <= '1';
					when others =>
				end case;
		
			--------------------------------------------------------------------------
			-- Odeslani dat z bufferu (UDP data) -------------------------------------			
			when st_data =>
				tx_next_state <= st_data;
				read_en <= '1';
				tx_count_en <= '1';
				data_to_ip_vld_i <= '1';
				data_to_ip_i <= ram_output(7 downto 0);
				if tx_count + 3 = data_to_ip_len_i then 
					read_en <= '0'; 
					tx_next_state <= st_end;
				end if;
			
			when st_end =>
				tx_next_state <= st_idle; 
	
		
		end case;				
	end process;

end Behavioral;

