----------------------------------------------------------------------------------
--						Implementace vysilaci casti protokolu IPv4  			  	  		  --
----------------------------------------------------------------------------------

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 ip_tx is
	PORT(
		TX_CLK: 						in std_logic;
		RX_CLK: 						in std_logic;
		-- z transportni vrsvy
		TX_DATA : 					in std_logic_vector(7 downto 0);
		TX_DATA_LEN: 				in std_logic_vector(UDP_ADDR_WIDTH - 1 downto 0);	
		TX_DATA_VLD: 				in std_logic;								
		TX_DATA_ACK : 				out std_logic;

		-- do / z MAC 
		DATA_TO_MAC : 				out std_logic_vector(7 downto 0);
		DATA_TO_MAC_VLD : 		out std_logic;
		DATA_TO_MAC_ACK : 		in std_logic;

		-- z RX IP 
		ARP_IP_CACHE: 				in std_logic_vector(31 downto 0);
		ARP_MAC_CACHE: 			in std_logic_vector(47 downto 0);
		ARP_REPLY:					in std_logic;
		ARP_REQUEST:				in std_logic;
		
		-- smerovani
		DESTINATION_IP: 			in std_logic_vector(31 downto 0) := (others => '0');
		DESTINATION_PROTOCOL:   in std_logic_vector(7 downto 0)	:= (others => '0');

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

architecture Behavioral of ip_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;

type state_type is (st_idle, st_reply_ack, st_request_ack, st_ip_ack, st_request, st_reply, st_mac_header,
st_ip_header, st_ip_data, st_wait_reply, st_wait_end, st_check_addr, st_check_local, st_check_gw);			  
-- stavovy automat
signal tx_state, next_state : state_type 								:= st_idle; 
-- Indexace paketu
signal tx_count: std_logic_vector(UDP_ADDR_WIDTH downto 0) 		:= (others => '0');
signal tx_count_en: std_logic												:= '0';
-- Signaly pro odeslani
signal data_to_mac_i: std_logic_vector(7 downto 0)					:= (others => '0');
signal data_to_mac_vld_i: std_logic										:= '0';
signal tx_data_ack_i: std_logic											:= '0';
-- Checksuma
signal data_for_sum: std_logic_vector(7 downto 0) 					:= (others => '0');
signal tx_checksum: std_logic_vector(15 downto 0) 					:= (others => '0');
signal calc_sum, rst_sum, local_i: std_logic							:= '0';
-- arp dotazi z rx vrstvy
signal arp_request_i, arp_reply_i: std_logic 						:= '0';
-- delka ip paketu
signal total_lenght: std_logic_vector(15 downto 0)					:= (others => '0');
signal data_len_pad: std_logic_vector(15 - UDP_ADDR_WIDTH downto 0); 
-- arp cache
signal mac_cache_i: std_logic_vector(47 downto 0) 					:= x"abcdefaabbcc";--(others => '0');
signal ip_cache_i: std_logic_vector(31 downto 0)  					:= x"0A1402DB";	 --(others => '0'); 

begin
	-- doplneni patky pro delku 
	data_len_pad <= (others => '0');
	
	-- vypocet checksum
	Inst_checksum: checksum PORT MAP(
		sum_out => tx_checksum,
		data_in => data_for_sum,
		reset => rst_sum,
		calc => calc_sum,
		clk => TX_CLK
	);

	-- NActeni adres z prijateho dotazu nebo odpovedi
	LOAD_ADDR1: process (RX_CLK)
	begin
		if (RX_CLK'event and RX_CLK = '1') then
			if ARP_REPLY = '1' or ARP_REQUEST = '1' then 
				mac_cache_i <= ARP_MAC_CACHE;
				ip_cache_i <= ARP_IP_CACHE;
			end if;
		end if;
	end process;

	-- NActeni priznaku pro dotaz
	LOAD_ADDR2: process (RX_CLK, RESET, tx_state)
	begin
		if RESET = '1' or tx_state = st_reply then 
			arp_request_i <= '0';
		elsif RX_CLK'event and RX_CLK = '1' then
			if ARP_REQUEST = '1' then arp_request_i <= '1'; end if;	
		end if;
	end process;

	-- NActeni priznaku pro odpoved
	LOAD_ADDR3: process (RX_CLK, RESET, tx_state)
	begin
		if RESET = '1' or tx_state = st_reply then 
			arp_reply_i <= '0';
		elsif RX_CLK'event and RX_CLK = '1' then
			if ARP_REPLY = '1' then arp_reply_i <= '1'; end if;	
		end if;
	end process;
		
	SYNC_PROC: process (TX_CLK)
	begin
		if (TX_CLK'event and TX_CLK = '1') then 
			
			if RESET = '1' then 
				-- global synchronni reset
				tx_state <= st_idle;

			else
				tx_state <= next_state;
				
				-- citac 
				if tx_count_en = '1' then tx_count <= tx_count + 1;
				else tx_count <= (others => '0'); end if;
						
				-- Výstupní registry
				DATA_TO_MAC <= data_to_mac_i;
				DATA_TO_MAC_VLD <= data_to_mac_vld_i;
				TX_DATA_ACK <= tx_data_ack_i;
				
			end if;
		end if;
	end process;

	total_lenght <= data_len_pad&TX_DATA_LEN + 20;
	local_i <= '1' when (DESTINATION_IP and ip_mask) = (FPGA_IP and ip_mask) else '0';

	---------------------------------------------------------------------
	-- FSM pro odesilani ------------------------------------------------		
	---------------------------------------------------------------------	
	ip_tx: process (tx_state, local_i, tx_count, total_lenght, tx_checksum, arp_request_i, arp_reply_i, mac_cache_i, ip_cache_i, 
						 DATA_TO_MAC_ACK, DESTINATION_IP, TX_DATA_VLD, TX_DATA, TX_DATA_LEN, DESTINATION_PROTOCOL)
	begin

		next_state <= st_idle;
		data_to_mac_i <= (others => '0');
		data_to_mac_vld_i <= '0';
		tx_count_en <= '0';
		tx_data_ack_i <= '0';
		calc_sum <= '0';
		rst_sum <= '0';
		data_for_sum <= (others => '0');
		
		case (tx_state) is
			
			---------------------------------------------------------------------
			-- Idle -------------------------------------------------------------
			when st_idle => 
				rst_sum <= '1';
				next_state <= st_idle;
				if arp_request_i = '1' then next_state <= st_reply_ack;
				elsif TX_DATA_VLD = '1' then next_state <= st_check_local; end if;
			
			---------------------------------------------------------------------
			-- Je cil. ip v teto siti? ------------------------------------------			
			when st_check_local =>
				if local_i = '1' then next_state <= st_check_addr; else next_state <= st_check_gw; end if;
		
			---------------------------------------------------------------------
			-- Je cil. ip v arp tabulce? ----------------------------------------
			when st_check_addr =>
				if DESTINATION_IP = ip_cache_i then next_state <= st_ip_ack; 
				else next_state <= st_request_ack; end if;

			---------------------------------------------------------------------
			-- Je Gateway v arp tabulce? ----------------------------------------
			when st_check_gw =>
				if gateway = ip_cache_i then next_state <= st_ip_ack; 
				else next_state <= st_request_ack; end if;

			---------------------------------------------------------------------
			-- Odeslat arp reply - cekani na mac_core ---------------------------
			when st_reply_ack =>	
				next_state <= st_reply_ack;
				data_to_mac_i <= mac_cache_i(47 downto 40);
				data_to_mac_vld_i <= '1';
				if DATA_TO_MAC_ACK = '1' then tx_count_en <= '1'; next_state <= st_reply; data_to_mac_i <= mac_cache_i(39 downto 32);end if;
					
			---------------------------------------------------------------------
			-- Odeslat arp reply ------------------------------------------------
			when st_reply =>
				next_state <= st_reply;
				data_to_mac_vld_i <= '1';
				tx_count_en <= '1';
				case tx_count(5 downto 0) is
				-- cilovo adresa je broadcast ff:ff:ff:ff:ff:ff
					when "000000" => data_to_mac_i <= mac_cache_i(39 downto 32);
					when "000001" => data_to_mac_i <= mac_cache_i(31 downto 24);
					when "000010" => data_to_mac_i <= mac_cache_i(23 downto 16);
					when "000011" => data_to_mac_i <= mac_cache_i(15 downto 8);
					when "000100" => data_to_mac_i <= mac_cache_i(7 downto 0);
					-- moje MAC
					when "000101" => data_to_mac_i <= FPGA_MAC(47 downto 40);
					when "000110" => data_to_mac_i <= FPGA_MAC(39 downto 32);
					when "000111" => data_to_mac_i <= FPGA_MAC(31 downto 24);
					when "001000" => data_to_mac_i <= FPGA_MAC(23 downto 16);
					when "001001" => data_to_mac_i <= FPGA_MAC(15 downto 8 );
					when "001010" => data_to_mac_i <= FPGA_MAC(7  downto 0 );
					-- delka ethernet paketu
					when "001011" => data_to_mac_i <= x"08";	-- x0806 ARP
					when "001100" => data_to_mac_i <= x"06";	-- 
				-- zacatek ARP paketu -----------------------------------------
					-- typ HW - x0001 ethernet
					when "001101" => data_to_mac_i <= x"00";
					when "001110" => data_to_mac_i <= x"01";
					-- typ protokolu x0800 - IP
					when "001111" => data_to_mac_i <= x"08";
					when "010000" => data_to_mac_i <= x"00";
					-- delka MAC adresy = 06
					when "010001" => data_to_mac_i <= x"06";
					-- delka IP adresy = 04
					when "010010" => data_to_mac_i <= x"04";
					-- typ operace = x0002 pro ARP reply - odpoved
					when "010011" => data_to_mac_i <= x"00";
					when "010100" => data_to_mac_i <= x"02";
					-- moje MAC adresa
					when "010101" => data_to_mac_i <= FPGA_MAC(47 downto 40);
					when "010110" => data_to_mac_i <= FPGA_MAC(39 downto 32);
					when "010111" => data_to_mac_i <= FPGA_MAC(31 downto 24);
					when "011000" => data_to_mac_i <= FPGA_MAC(23 downto 16);
					when "011001" => data_to_mac_i <= FPGA_MAC(15 downto 8 );
					when "011010" => data_to_mac_i <= FPGA_MAC(7  downto 0 );
					-- moje IP adresa
					when "011011" => data_to_mac_i <= FPGA_IP(31 downto 24);
					when "011100" => data_to_mac_i <= FPGA_IP(23 downto 16);
					when "011101" => data_to_mac_i <= FPGA_IP(15 downto 8);
					when "011110" => data_to_mac_i <= FPGA_IP(7 downto 0);
					--  MAC adresa PC 
					when "011111" => data_to_mac_i <= mac_cache_i(47 downto 40);
					when "100000" => data_to_mac_i <= mac_cache_i(39 downto 32);
					when "100001" => data_to_mac_i <= mac_cache_i(31 downto 24);
					when "100010" => data_to_mac_i <= mac_cache_i(23 downto 16);
					when "100011" => data_to_mac_i <= mac_cache_i(15 downto 8 );
					when "100100" => data_to_mac_i <= mac_cache_i(7  downto 0 );
					-- IP adresa PC 
					when "100101" => data_to_mac_i <= ip_cache_i(31 downto 24);
					when "100110" => data_to_mac_i <= ip_cache_i(23 downto 16);
					when "100111" => data_to_mac_i <= ip_cache_i(15 downto  8);
					when "101000" => data_to_mac_i <= ip_cache_i(7  downto  0);
					-- ARP odpoved odeslana 
					when others => 	next_state <= st_wait_end; data_to_mac_vld_i <= '0';

				end case;
			---------------------------------------------------------------------
			-- Odeslat arp request - cekani na mac_core -------------------------
			when st_request_ack =>	
				next_state <= st_request_ack;
				data_to_mac_i <= x"ff";
				data_to_mac_vld_i <= '1';
				if DATA_TO_MAC_ACK = '1' then tx_count_en <= '1'; next_state <= st_request; end if;
			
			---------------------------------------------------------------------
			-- Odeslat arp request ----------------------------------------------
			when st_request => 
				next_state <= st_request;
				data_to_mac_vld_i <= '1';
				tx_count_en <= '1';
				case tx_count(5 downto 0) is
				-- cilovo adresa je broadcast ff:ff:ff:ff:ff:ff
					when "000000" => data_to_mac_i <= x"ff";
					when "000001" => data_to_mac_i <= x"ff";
					when "000010" => data_to_mac_i <= x"ff";
					when "000011" => data_to_mac_i <= x"ff";
					when "000100" => data_to_mac_i <= x"ff";
					-- moje MAC
					when "000101" => data_to_mac_i <= FPGA_MAC(47 downto 40);
					when "000110" => data_to_mac_i <= FPGA_MAC(39 downto 32);
					when "000111" => data_to_mac_i <= FPGA_MAC(31 downto 24);
					when "001000" => data_to_mac_i <= FPGA_MAC(23 downto 16);
					when "001001" => data_to_mac_i <= FPGA_MAC(15 downto 8 );
					when "001010" => data_to_mac_i <= FPGA_MAC(7  downto 0 );
					-- delka ethernet paketu
					when "001011" => data_to_mac_i <= x"08";	-- typ x0806 ARP
					when "001100" => data_to_mac_i <= x"06";	-- 
					-- zacatek ARP paketu
					-- typ HW - x0001 ethernet
					when "001101" => data_to_mac_i <= x"00";
					when "001110" => data_to_mac_i <= x"01";
					-- typ protokolu x0800 - IP
					when "001111" => data_to_mac_i <= x"08";
					when "010000" => data_to_mac_i <= x"00";
					-- delka MAC adresy = 0x06
					when "010001" => data_to_mac_i <= x"06";
					-- delka IP adresy = 0x04
					when "010010" => data_to_mac_i <= x"04";
					-- typ operace = 0x0001 pro ARP request - dotaz
					when "010011" => data_to_mac_i <= x"00";
					when "010100" => data_to_mac_i <= x"01";
					-- moje MAC adresa
					when "010101" => data_to_mac_i <= FPGA_MAC(47 downto 40);
					when "010110" => data_to_mac_i <= FPGA_MAC(39 downto 32);
					when "010111" => data_to_mac_i <= FPGA_MAC(31 downto 24);
					when "011000" => data_to_mac_i <= FPGA_MAC(23 downto 16);
					when "011001" => data_to_mac_i <= FPGA_MAC(15 downto 8 );
					when "011010" => data_to_mac_i <= FPGA_MAC(7  downto 0 );
					-- moje IP adresa
					when "011011" => data_to_mac_i <= FPGA_IP(31 downto 24);
					when "011100" => data_to_mac_i <= FPGA_IP(23 downto 16);
					when "011101" => data_to_mac_i <= FPGA_IP(15 downto 8);
					when "011110" => data_to_mac_i <= FPGA_IP(7 downto 0);
					--  MAC adresa PC - vyplnena 0x00
					when "011111" => data_to_mac_i <= x"00";
					when "100000" => data_to_mac_i <= x"00";
					when "100001" => data_to_mac_i <= x"00";
					when "100010" => data_to_mac_i <= x"00";
					when "100011" => data_to_mac_i <= x"00";
					when "100100" => data_to_mac_i <= x"00";
					-- IP adresa PC - (cile)
					when "100101" => if local_i = '1' then data_to_mac_i <= DESTINATION_IP(31 downto 24); else data_to_mac_i <= gateway(31 downto 24); end if;
					when "100110" => if local_i = '1' then data_to_mac_i <= DESTINATION_IP(23 downto 16); else data_to_mac_i <= gateway(23 downto 16); end if;
					when "100111" => if local_i = '1' then data_to_mac_i <= DESTINATION_IP(15 downto 8); else data_to_mac_i <= gateway(15 downto 8); end if;
					when "101000" => if local_i = '1' then data_to_mac_i <= DESTINATION_IP(7 downto 0); else data_to_mac_i <= gateway(7 downto 0); end if;
					when others	=>   next_state <= st_wait_reply; data_to_mac_vld_i <= '0'; tx_count_en <= '0';
					
				end case;
			
			---------------------------------------------------------------------
			-- Cekat na odpoved, nebo novy dotaz --------------------------------			
			when st_wait_reply =>
				next_state <= st_wait_reply;
				if arp_reply_i = '1' or arp_request_i = '1' then next_state <= st_idle; end if;
			
			---------------------------------------------------------------------
			-- Odeslat ip paket - cekani na mac_core ----------------------------			
			when st_ip_ack =>	
				next_state <= st_ip_ack;
				data_to_mac_i <= mac_cache_i(47 downto 40);
				data_to_mac_vld_i <= '1';
				if DATA_TO_MAC_ACK = '1' then data_to_mac_i <= mac_cache_i(39 downto 32); tx_count_en <= '1'; next_state <= st_mac_header; end if;

			---------------------------------------------------------------------
			-- Odeslat ip paket - MAC hlavicka ----------------------------------		
			when st_mac_header =>
					next_state <= st_mac_header;
					data_to_mac_vld_i <= '1';
					calc_sum <= '1';
					tx_count_en <= '1';
					case tx_count(3 downto 0) is
						--when "0000" => data_to_mac_i <= mac_cache_i(47 downto 40); 
						when "0000" => data_to_mac_i <= mac_cache_i(39 downto 32); 
						when "0001" => data_to_mac_i <= mac_cache_i(31 downto 24); data_for_sum <= FPGA_IP(31 downto 24);
						when "0010" => data_to_mac_i <= mac_cache_i(23 downto 16); data_for_sum <= FPGA_IP(23 downto 16);
						when "0011" => data_to_mac_i <= mac_cache_i(15 downto 8 ); data_for_sum <= FPGA_IP(15 downto 8);
						when "0100" => data_to_mac_i <= mac_cache_i(7  downto 0 ); data_for_sum <= FPGA_IP(7 downto 0);
						
						when "0101" => data_to_mac_i <= FPGA_MAC(47 downto 40); data_for_sum <= DESTINATION_IP(31 downto 24);
						when "0110" => data_to_mac_i <= FPGA_MAC(39 downto 32); data_for_sum <= DESTINATION_IP(23 downto 16);
						when "0111" => data_to_mac_i <= FPGA_MAC(31 downto 24); data_for_sum <= DESTINATION_IP(15 downto 8); 
						when "1000" => data_to_mac_i <= FPGA_MAC(23 downto 16); data_for_sum <= DESTINATION_IP(7 downto 0); 
						when "1001" => data_to_mac_i <= FPGA_MAC(15 downto 8 ); data_for_sum <= IP_TTL;
						when "1010" => data_to_mac_i <= FPGA_MAC(7  downto 0 ); data_for_sum <= DESTINATION_PROTOCOL;
						-- typ x0800
						when "1011" => data_to_mac_i <= x"08"; 
						when "1100" => data_to_mac_i <= x"00"; next_state <= st_ip_header; tx_count_en <= '0';
				-- 	Konec MAC hlavicky - zacina vlastni IP packet
										
						when others => next_state <= st_idle; data_to_mac_vld_i <= '0';
					end case;	
			
			---------------------------------------------------------------------
			-- Odeslat ip paket - IP hlavicka -----------------------------------				
			when st_ip_header =>
					next_state <= st_ip_header;
					tx_count_en <= '1';
					data_to_mac_vld_i <= '1';
					calc_sum <= '1';
				
					case (tx_count(4 downto 0)) is 
						when "00000" => data_to_mac_i <= IP_VERSION & IP_IHL; data_for_sum <= IP_VERSION & IP_IHL;
						when "00001" => data_to_mac_i <= IP_TOS; data_for_sum <= IP_TOS;
						when "00010" => data_to_mac_i <= total_lenght(15 downto 8); data_for_sum <= total_lenght(15 downto 8);
						when "00011" => data_to_mac_i <= total_lenght(7 downto 0); data_for_sum <= total_lenght(7 downto 0);

						when "00100" => data_to_mac_i <= IP_ID(15 downto 8); data_for_sum <= IP_ID(15 downto 8);
						when "00101" => data_to_mac_i <= IP_ID(7 downto 0); data_for_sum <= IP_ID(7 downto 0);
						when "00110" => data_to_mac_i <= IP_FLAG(2 downto 0) & IP_OFFSET(12 downto 8); data_for_sum <= IP_FLAG(2 downto 0) & IP_OFFSET(12 downto 8);
						when "00111" => data_to_mac_i <= IP_OFFSET(7 downto 0); data_for_sum <= IP_OFFSET(7 downto 0);

						when "01000" => data_to_mac_i <= IP_TTL; 
						when "01001" => data_to_mac_i <= DESTINATION_PROTOCOL; 	-- protokol
						when "01010" => data_to_mac_i <= tx_checksum(15 downto 8); 										-- checksum
						when "01011" => data_to_mac_i <= tx_checksum(7 downto 0);										-- checksum
						-- moje IP 
						when "01100" => data_to_mac_i <= FPGA_IP(31 downto 24);  
						when "01101" => data_to_mac_i <= FPGA_IP(23 downto 16); 
						when "01110" => data_to_mac_i <= FPGA_IP(15 downto 8); 
						when "01111" => data_to_mac_i <= FPGA_IP(7 downto 0); 
						-- cilova IP
						when "10000" => data_to_mac_i <= DESTINATION_IP(31 downto 24); 
						when "10001" => data_to_mac_i <= DESTINATION_IP(23 downto 16);
						when "10010" => data_to_mac_i <= DESTINATION_IP(15 downto 8);
						when "10011" => data_to_mac_i <= DESTINATION_IP(7 downto 0); next_state <= st_ip_data;
																										 tx_data_ack_i <= '1';
						when others =>
					end case;
			
			---------------------------------------------------------------------
			-- Odeslat ip paket - data vyssi vrstvy -----------------------------		
			when st_ip_data => 
				next_state <= st_ip_data;
				data_to_mac_vld_i <= '1';
				tx_count_en <= '1';
				data_to_mac_i <= TX_DATA;	
				if (total_lenght(UDP_ADDR_WIDTH downto 0) < tx_count) then 
					next_state <= st_wait_end;
				end if;		
			
			---------------------------------------------------------------------
			-- cekat 1 cyklus - vystupy do pocatecnich hodnot -------------------		
			when st_wait_end =>
				next_state <= st_idle;
			
			when others =>
		end case;
	
	end process;

end Behavioral;

