-------------------------------------------------------------------------------
--
-- Project name: UDP_100G
--
-- (C) Copyright 2019 DFC Design, s.r.o., Brno, Czech Republic
-- Author: Jakub Hadamek (j.hadamek@dfcdesign.cz)
--
-------------------------------------------------------------------------------
--
-- This file is part of UDP_100G IP core
-- 
-------------------------------------------------------------------------------



library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

Library xpm;
use xpm.vcomponents.all;

-- Uncomment the following library declaration if using
-- arithmetic functions with Signed or Unsigned values
use IEEE.NUMERIC_STD.ALL;

-- Uncomment the following library declaration if instantiating
-- any Xilinx leaf cells in this code.
--library UNISIM;
--use UNISIM.VComponents.all;

entity frame_receiver is
   port (
      -- Interface reset
      RESET                : in  std_logic;
      -- Interface clock
      CLK                  : in  std_logic;
      -- RX from CMAC
      S_AXIS_TVALID        : in  std_logic;
      S_AXIS_TREADY        : out std_logic;
      S_AXIS_TUSER         : in  std_logic;
      S_AXIS_TLAST         : in  std_logic;
      S_AXIS_TKEEP         : in  std_logic_vector(63 downto 0);
      S_AXIS_TDATA         : in  std_logic_vector(511 downto 0);
      -- RX to data FIFO
      RD_FIFO_WREN         : out std_logic;
      RD_FIFO_FULL         : in  std_logic;
      RD_FIFO_DATA         : out std_logic_vector(575 downto 0);
      RD_FIFO_RST_BUSY     : in  std_logic;
      -- RX to tag FIFO
      RT_FIFO_WREN         : out std_logic;
      RT_FIFO_FULL         : in  std_logic;
      RT_FIFO_DATA         : out std_logic_vector(128 downto 0); -- last bit valid/invalid frame
      RT_FIFO_RST_BUSY     : in  std_logic;
      -- ETH/IP/UDP
      HOST_MAC             : in  std_logic_vector(47 downto 0);
      HOST_IP              : in  std_logic_vector(31 downto 0);
      HOST_NET_MASK        : in  std_logic_vector(31 downto 0);
      -- Status
      RX_OVERFLOW          : out std_logic;
      -- ARP
      ARP_REQUEST          : out std_logic;
      ARP_SRC_MAC          : out std_logic_vector(47 downto 0);
      ARP_SRC_IP           : out std_logic_vector(31 downto 0)
   );
end;

architecture Behavioral of frame_receiver is

   -- convert number to tkeep
   function num2tkeep (byte_cnt   : natural range 0 to 64)
      return std_logic_vector is
      variable tkeep          : std_logic_vector(63 downto 0) := (others => '0');
      begin
         if byte_cnt > 0 and byte_cnt < 64 then
            tkeep(byte_cnt-1 downto 0) := (others => '1');
         else
            tkeep := (others => '0');
         end if;
         return tkeep;
      end num2tkeep;

   -- convert tkeep to number
   function tkeep2num (vector : std_logic_vector)
      return natural is
         variable vec      : std_logic_vector(vector'range) := (others => '0');
      begin
         vec := vector;
         for i in 0 to vec'length-1 loop
            if vec(i) = '0' then
               return i;
            end if;
         end loop;
         return vec'length;
      end tkeep2num;

   -- convert number to tkeep
   function nat2vec (nat         : natural range 0 to 64;
                     vec_len     : natural range 1 to 64)
      return std_logic_vector is
      variable vec          : std_logic_vector(vec_len-1 downto 0) := (others => '0');
      begin
         vec  := std_logic_vector(to_unsigned(nat, vec'length));
         return vec;
      end nat2vec;


   -- Swap bytes form LSB ... MSB to MSB ... LSB
   function swap_bytes (vector   : std_logic_vector)
      return std_logic_vector is
         variable swapped_vector    : std_logic_vector(vector'length - 1 downto 0);
         variable vec               : std_logic_vector(vector'length - 1 downto 0);
      begin 
         vec := vector;
         for i in 0 to vec'length/8 - 1 loop
            swapped_vector(swapped_vector'length - i*8 - 1 downto swapped_vector'length - i*8 - 8) := vec((i+1)*8 - 1 downto i*8);
         end loop;
         return swapped_vector;
      end swap_bytes;

   -- Swap bytes in 16 bit words
   function int16_array_byte_swap (vector : std_logic_vector)
      return std_logic_vector is
      variable ret_array   : std_logic_vector(vector'range);
      begin
         for i in 0 to vector'length/16 - 1 loop
            ret_array(i*16 + 15 downto i*16 + 8) := vector(i*16 + 7 downto i*16 + 0);
            ret_array(i*16 + 7 downto i*16 + 0) := vector(i*16 + 15 downto i*16 + 8);
         end loop;
         return ret_array;
      end int16_array_byte_swap;    

   -- Calculate part of checksum
   function half_checksum (ip_hdr : std_logic_vector)
      return std_logic_vector is
      variable ip_hdr_reordered  : std_logic_vector(ip_hdr'range) := (others => '0');
      variable ret_val_tmp       : std_logic_vector(19 downto 0) := (others => '0');
      variable ret_val           : std_logic_vector(15 downto 0) := (others => '0');
      begin
         ip_hdr_reordered := int16_array_byte_swap(ip_hdr);
         for i in 0 to ip_hdr_reordered'length/16 - 1 loop
            ret_val_tmp := ret_val_tmp + ip_hdr_reordered(i*16 + 15 downto i*16);
         end loop;
         ret_val := ret_val_tmp(15 downto 0) + ret_val_tmp(19 downto 16);
         return ret_val;
      end half_checksum;  

   -- Calculate part of checksum
   function checksum_part (data : std_logic_vector; checksum   : std_logic_vector(15 downto 0))
      return std_logic_vector is
      variable ip_hdr_reordered  : std_logic_vector(data'range) := (others => '0');
      variable ret_val_tmp       : std_logic_vector(19 downto 0) := (others => '0');
      variable ret_val           : std_logic_vector(15 downto 0) := (others => '0');
      begin
         ip_hdr_reordered := int16_array_byte_swap(data);
         for i in 0 to ip_hdr_reordered'length/16 - 1 loop
            ret_val_tmp := ret_val_tmp + ip_hdr_reordered(i*16 + 15 downto i*16);
         end loop;
         ret_val_tmp := ret_val_tmp + checksum;
         ret_val := ret_val_tmp(15 downto 0) + ret_val_tmp(19 downto 16);
         return ret_val;
      end checksum_part;  

   -- Calculate checksum from data and precalculated checksum
   function full_checksum (data : std_logic_vector; rest : std_logic_vector(15 downto 0))
      return std_logic_vector is
      variable data_reordered    : std_logic_vector(data'range) := (others => '0');
      variable ret_val_tmp       : std_logic_vector(19 downto 0) := (others => '0');
      variable ret_val           : std_logic_vector(15 downto 0) := (others => '0');
      begin
         data_reordered := int16_array_byte_swap(data);
         for i in 0 to data_reordered'length/16 - 1 loop
            ret_val_tmp := ret_val_tmp + data_reordered(i*16 + 15 downto i*16);
         end loop;
         ret_val_tmp := ret_val_tmp + rest;
         ret_val := ret_val_tmp(15 downto 0) + ret_val_tmp(19 downto 16);
         return not ret_val;
      end full_checksum;   

   -- or vector
   function or_vec (vec : std_logic_vector)
      return std_logic is
      variable retval : std_logic := '0';
      begin
         for i in 0 to vec'length-1 loop
               retval := retval or vec(i);
         end loop;
         return retval;
      end or_vec;

   -- and vector
   function and_vec (vec : std_logic_vector)
      return std_logic is
      variable retval : std_logic := '1';
      begin
         for i in 0 to vec'length-1 loop
               retval := retval and vec(i);
         end loop;
         return retval;
      end and_vec;

   -- Broadcasts  
   constant LC_MAC_BROADCAST     : std_logic_vector(47 downto 0) := (others => '1');
   constant LC_IP_BROADCAST      : std_logic_vector(31 downto 0) := (others => '1');
   -- Ethertypes
   constant LC_ETH_ETHERTYPE_IPV4   : std_logic_vector(15 downto 0) := x"0800";
   constant LC_ETH_ETHERTYPE_ARP    : std_logic_vector(15 downto 0) := x"0806";
   -- IP protocols
   constant LC_IP_PROTO_UDP         : std_logic_vector(7 downto 0) := x"11";
   -- Headers sizes
   constant LC_MAC_HDR_SIZE         : natural := 14*8;
   constant LC_IP_HDR_SIZE          : natural := 20*8;
   constant LC_UDP_HDR_SIZE         : natural := 8*8;
   constant LC_HDR_SIZE             : natural := LC_MAC_HDR_SIZE + LC_IP_HDR_SIZE + LC_UDP_HDR_SIZE;
   -- Minimum size of packet
   constant LC_MINIMAL_PACKET_SIZE     : natural := 64;
   -- Headers offsets
   constant LC_IP_HDR_OFFSET        : natural := LC_MAC_HDR_SIZE;
   constant LC_UDP_HDR_OFFSET       : natural := LC_IP_HDR_SIZE + LC_MAC_HDR_SIZE;
   -- IP infos offsets
   constant LC_IP_LEN_OFFSET        : natural := 16;
   constant LC_IP_PROTO_OFFSET      : natural := 72;
   constant LC_IP_CHKSUM_OFFSET     : natural := 80;
   constant LC_IP_SRC_OFFSET        : natural := 96;
   constant LC_IP_DST_OFFSET        : natural := 128;
   -- UDP infos offsets
   constant LC_UDP_SRC_OFFSET       : natural := 0;
   constant LC_UDP_DST_OFFSET       : natural := 16;
   constant LC_UDP_LEN_OFFSET       : natural := 32;
   -- MAC infos offsets
   constant LC_MAC_DST_OFFSET       : natural := 0;
   constant LC_MAC_SRC_OFFSET       : natural := 48;
   constant LC_MAC_TYPE_OFFSET      : natural := 96;

   -- One cycle delayed AXI Stream
   signal tvalid_d            : std_logic := '0';
   signal tlast_d             : std_logic := '0';
   signal tuser_d             : std_logic := '0';
   signal tkeep_d             : std_logic_vector(63 downto 0) := (others => '0');
   signal tdata_d             : std_logic_vector(511 downto 0) := (others => '0'); 

   -- Two cycles delayed AXI Stream
   signal tvalid_dd            : std_logic := '0';
   signal tlast_dd             : std_logic := '0';
   signal tuser_dd             : std_logic := '0';
   signal tkeep_dd             : std_logic_vector(63 downto 0) := (others => '0');
   signal tdata_dd             : std_logic_vector(511 downto 0) := (others => '0'); 
   
   -- Two cycles delayed AXI Stream
   signal tvalid_ddd           : std_logic := '0';
   signal tlast_ddd            : std_logic := '0';
   signal tuser_ddd            : std_logic := '0';
   signal tkeep_ddd            : std_logic_vector(63 downto 0) := (others => '0');
   signal tdata_ddd            : std_logic_vector(511 downto 0) := (others => '0'); 

   -- Packet headers and infos (parsed from delayed AXI Stream)
   signal pkt_header             : std_logic_vector(LC_HDR_SIZE-1 downto 0) := (others => '0');
   signal ip_header              : std_logic_vector(LC_IP_HDR_SIZE-1 downto 0) := (others => '0'); 
   signal pkt_header_d          : std_logic_vector(LC_HDR_SIZE-1 downto 0) := (others => '0');
   signal ip_header_d           : std_logic_vector(LC_IP_HDR_SIZE-1 downto 0) := (others => '0'); 
   signal half_ip_header      : std_logic_vector(LC_IP_HDR_SIZE/2-1 downto 0) := (others => '0'); 

   -- One cycle delayed packet headers and infos  (parsed from delayed AXI Stream)
   signal pkt_header_dd        : std_logic_vector(LC_HDR_SIZE-1 downto 0) := (others => '0');
   signal mac_header_dd        : std_logic_vector(LC_MAC_HDR_SIZE-1 downto 0) := (others => '0'); 
   signal ip_header_dd         : std_logic_vector(LC_IP_HDR_SIZE-1 downto 0) := (others => '0'); 
   signal udp_header_dd        : std_logic_vector(LC_UDP_HDR_SIZE-1 downto 0) := (others => '0'); 
   signal ip_hdr_chksum_dd     : std_logic_vector(15 downto 0) := (others => '0');
   signal dst_ip_dd            : std_logic_vector(31 downto 0) := (others => '0');
   signal dst_mac_dd           : std_logic_vector(47 downto 0) := (others => '0'); 
   signal udp_hdr_len_dd       : std_logic_vector(15 downto 0) := (others => '0');
   signal ethertype_dd         : std_logic_vector(15 downto 0) := (others => '0');
   signal ip_proto_dd   		  : std_logic_vector(7 downto 0) := (others => '0'); 

   -- Two cycles delayed packet headers and infos (parsed from delayed AXI Stream)
   signal pkt_header_ddd       : std_logic_vector(LC_HDR_SIZE-1 downto 0) := (others => '0');
   signal mac_header_ddd       : std_logic_vector(LC_MAC_HDR_SIZE-1 downto 0) := (others => '0'); 
   signal ip_header_ddd        : std_logic_vector(LC_IP_HDR_SIZE-1 downto 0) := (others => '0'); 
   signal udp_header_ddd       : std_logic_vector(LC_UDP_HDR_SIZE-1 downto 0) := (others => '0'); 
   signal src_udp_ddd          : std_logic_vector(15 downto 0) := (others => '0');
   signal dst_udp_ddd          : std_logic_vector(15 downto 0) := (others => '0');
   signal src_ip_ddd           : std_logic_vector(31 downto 0) := (others => '0');
   signal src_mac_ddd          : std_logic_vector(47 downto 0) := (others => '0'); 
   signal udp_hdr_len_ddd      : std_logic_vector(15 downto 0) := (others => '0');
   
   
--   -- Three cycles delayed packet headers and infos (parsed from delayed AXI Stream)
--   signal pkt_header_ddd      : std_logic_vector(LC_HDR_SIZE-1 downto 0) := (others => '0');
--   signal mac_header_ddd      : std_logic_vector(LC_MAC_HDR_SIZE-1 downto 0) := (others => '0'); 
--   signal ip_header_ddd       : std_logic_vector(LC_IP_HDR_SIZE-1 downto 0) := (others => '0'); 
--   signal udp_header_ddd      : std_logic_vector(LC_UDP_HDR_SIZE-1 downto 0) := (others => '0'); 
--   signal src_udp_ddd         : std_logic_vector(15 downto 0) := (others => '0');
--   signal dst_udp_ddd         : std_logic_vector(15 downto 0) := (others => '0');
--   signal src_ip_ddd          : std_logic_vector(31 downto 0) := (others => '0');
--   signal src_mac_ddd         : std_logic_vector(47 downto 0) := (others => '0'); 
--   signal udp_hdr_len_ddd     : std_logic_vector(15 downto 0) := (others => '0');
   
   
   -- IP header checksum calculation 
   signal ip_hdr_chksum_first_part     : std_logic_vector(15 downto 0) := (others => '0');
   signal ip_hdr_chksum_second_part    : std_logic_vector(15 downto 0) := (others => '0');
   signal chksum_calc  : std_logic_vector(15 downto 0) := (others => '0');

   -- Registered packet infos
   signal src_udp_reg         : std_logic_vector(15 downto 0) := (others => '0');
   signal dst_udp_reg         : std_logic_vector(15 downto 0) := (others => '0');
   signal src_ip_reg          : std_logic_vector(31 downto 0) := (others => '0');
   signal src_mac_reg         : std_logic_vector(47 downto 0) := (others => '0');    

   -- Packet size 
   signal data_len            : std_logic_vector(15 downto 0) := (others => '0');
   signal data_len_reg        : std_logic_vector(15 downto 0) := (others => '0');
  
   -- Check signals
   signal chksum_ok           : std_logic := '0';
   signal mac_ok              : std_logic := '0';
   signal ip_ok               : std_logic := '0';
   signal ethertype_ip	      : std_logic := '0';
   signal ethertype_arp	      : std_logic := '0';
   signal ip_proto_udp	      : std_logic := '0';
   signal data_len_ok	      : std_logic := '0';

   -- signals towards FIFOs
   signal rx_data             : std_logic_vector(511 + 64 downto 0) := (others => '0');
   signal rx_tag              : std_logic_vector(RT_FIFO_DATA'range) := (others => '0');
   signal rx_tag_wren         : std_logic := '0';
   signal rx_data_wren        : std_logic := '0';   
      
   -- Packet size counter for control purpose
   signal byte_cnt            : std_logic_vector(15 downto 0) := (others => '0');

   -- Registered part of incoming data stream
   signal tdata_rest          : std_logic_vector(S_AXIS_TDATA'length - LC_HDR_SIZE - 1 downto 0) := (others => '0');
   signal tkeep_rest          : std_logic_vector((S_AXIS_TDATA'length - LC_HDR_SIZE)/8 - 1 downto 0) := (others => '0');   
   signal tlast_rest          : std_logic := '0';
   signal tuser_rest          : std_logic := '0';
 
   -- other core signals
   signal new_packet          : std_logic := '1';   
   signal packet_valid        : std_logic := '0';   
   signal fifo_not_full       : std_logic := '0';
   signal discard_packet      : std_logic := '0';
   signal short_packet		   : std_logic := '0';
   signal data_fifo_full      : std_logic := '0';
   signal tag_fifo_full       : std_logic := '0';  
   signal store_tag           : std_logic := '0';  
   signal store_tag_reg       : std_logic := '0';  
   
   signal arp_ip_ok           : std_logic := '0';
   signal arp_dst_ip_dd       : std_logic_vector(31 downto 0) := (others => '0');
   signal arp_src_ip_ddd      : std_logic_vector(31 downto 0) := (others => '0');
   signal arp_src_mac_ddd     : std_logic_vector(47 downto 0) := (others => '0');
   
   constant LC_ARP_MAC_SRC_OFFSET       : natural := 22*8;
   constant LC_ARP_IP_SRC_OFFSET        : natural := 28*8;
   constant LC_ARP_IP_DST_OFFSET        : natural := 38*8;
   
   
   ----------------------------------------------------------
--   signal rd_fifo_wren_i      : std_logic := '0';
--   signal rt_fifo_wren_i      : std_logic := '0';
--   signal data_fifo_wren      : std_logic := '0';
--   signal tag_fifo_wren       : std_logic := '0';
   
--   signal tag_fifo_rdy        : std_logic := '0';
--   signal data_fifo_rdy       : std_logic := '0';
--   signal fifo_rdy            : std_logic := '0';
   
   
   signal rest_not_empty      : std_logic := '0';
   signal tkeep_rest_int      : std_logic_vector(15 downto 0) := (others => '0');
   signal short_pkt_tkeep     : std_logic_vector(tkeep_rest'range) := (others => '0');
   ----------------------------------------------------------
   
   
begin

--   tag_fifo_rdy <= not RT_FIFO_FULL and not RT_FIFO_RST_BUSY;
--   data_fifo_rdy <= not RD_FIFO_FULL and not RD_FIFO_RST_BUSY;
--   fifo_rdy <= tag_fifo_rdy and data_fifo_rdy;

   -- FIFOs full (FIFO busy take into consideration)
   data_fifo_full <= RD_FIFO_FULL or RD_FIFO_RST_BUSY;
   tag_fifo_full <= RT_FIFO_FULL or RT_FIFO_RST_BUSY;
   -- Neither one FIFO is full
   fifo_not_full <= not tag_fifo_full and not data_fifo_full;

   ----------------------------------------
   -----   Delay incoming AXI Stream ------
   ----------------------------------------
   -- Delay incoming AXI Stream
   delay_axis_proc: process(CLK)
      begin
         if rising_edge(CLK) then
            if RESET = '1' then
               tvalid_d <= '0';
               tlast_d  <= '0';
               tuser_d  <= '0';
               tkeep_d  <= (others => '0'); 
               tdata_d  <= (others => '0');
               tvalid_dd <= '0';
               tlast_dd  <= '0';
               tuser_dd  <= '0';
               tkeep_dd  <= (others => '0'); 
               tdata_dd  <= (others => '0');
            else
				  -- One cycle delay
               tvalid_d <= S_AXIS_TVALID;
               tlast_d  <= S_AXIS_TLAST; 
               tuser_d  <= S_AXIS_TUSER; 
               tkeep_d  <= S_AXIS_TKEEP; 
               tdata_d  <= S_AXIS_TDATA; 
			      -- Two cycles delay
               tvalid_dd <= tvalid_d;
               tlast_dd  <= tlast_d; 
               tuser_dd  <= tuser_d; 
               tkeep_dd  <= tkeep_d; 
               tdata_dd  <= tdata_d; 
			      -- Three cycles delay
               tvalid_ddd <= tvalid_dd;
               tlast_ddd  <= tlast_dd; 
               tuser_ddd  <= tuser_dd; 
               tkeep_ddd  <= tkeep_dd; 
               tdata_ddd  <= tdata_dd; 
            end if;
         end if;
      end process;

   ----------------------------------------
   --------   Parse packet header   -------
   ----------------------------------------
   -- Packet headers and infos (parsed from delayed AXI Stream)
   pkt_header      <= S_AXIS_TDATA(LC_HDR_SIZE - 1 downto 0);   
   ip_header       <= pkt_header(LC_IP_HDR_OFFSET + LC_IP_HDR_SIZE - 1 downto LC_IP_HDR_OFFSET); 
   pkt_header_d      <= tdata_d(LC_HDR_SIZE - 1 downto 0);   
   ip_header_d       <= pkt_header_d(LC_IP_HDR_OFFSET + LC_IP_HDR_SIZE - 1 downto LC_IP_HDR_OFFSET); 

   -- One cycle delayed packet headers and infos  (parsed from delayed AXI Stream)
   pkt_header_dd <= tdata_dd(LC_HDR_SIZE - 1 downto 0);  
   ip_header_dd  <= pkt_header_dd(LC_IP_HDR_OFFSET + LC_IP_HDR_SIZE - 1 downto LC_IP_HDR_OFFSET); 
   udp_header_dd <= pkt_header_dd(LC_UDP_HDR_OFFSET + LC_UDP_HDR_SIZE - 1 downto LC_UDP_HDR_OFFSET);  
   ethertype_dd       <= swap_bytes(pkt_header_dd(LC_MAC_TYPE_OFFSET+ethertype_dd'left downto LC_MAC_TYPE_OFFSET));
   dst_mac_dd         <= swap_bytes(pkt_header_dd(LC_MAC_DST_OFFSET+dst_mac_dd'left downto LC_MAC_DST_OFFSET)); 
   ip_proto_dd        <= swap_bytes(ip_header_dd(LC_IP_PROTO_OFFSET+ip_proto_dd'left downto LC_IP_PROTO_OFFSET)); 
   ip_hdr_chksum_dd   <= swap_bytes(ip_header_dd(LC_IP_CHKSUM_OFFSET+ip_hdr_chksum_dd'left downto LC_IP_CHKSUM_OFFSET));
   dst_ip_dd          <= swap_bytes(ip_header_dd(LC_IP_DST_OFFSET+dst_ip_dd'left downto LC_IP_DST_OFFSET));
   udp_hdr_len_dd     <= swap_bytes(udp_header_dd(LC_UDP_LEN_OFFSET+udp_hdr_len_dd'left downto LC_UDP_LEN_OFFSET));

   -- Two cycles delayed packet headers and infos (parsed from delayed AXI Stream)
   pkt_header_ddd <= tdata_ddd(LC_HDR_SIZE - 1 downto 0);  
   mac_header_ddd <= pkt_header_ddd(LC_MAC_HDR_SIZE - 1 downto 0); 
   ip_header_ddd  <= pkt_header_ddd(LC_IP_HDR_OFFSET + LC_IP_HDR_SIZE - 1 downto LC_IP_HDR_OFFSET); 
   udp_header_ddd <= pkt_header_ddd(LC_UDP_HDR_OFFSET + LC_UDP_HDR_SIZE - 1 downto LC_UDP_HDR_OFFSET);  
   src_ip_ddd          <= swap_bytes(ip_header_ddd(LC_IP_SRC_OFFSET+src_ip_ddd'left downto LC_IP_SRC_OFFSET));
   src_mac_ddd         <= swap_bytes(pkt_header_ddd(LC_MAC_SRC_OFFSET+src_mac_ddd'left downto LC_MAC_SRC_OFFSET)); 
   src_udp_ddd         <= swap_bytes(udp_header_ddd(LC_UDP_SRC_OFFSET+src_udp_ddd'left downto LC_UDP_SRC_OFFSET));
   dst_udp_ddd         <= swap_bytes(udp_header_ddd(LC_UDP_DST_OFFSET+dst_udp_ddd'left downto LC_UDP_DST_OFFSET)); 
   udp_hdr_len_ddd     <= swap_bytes(udp_header_ddd(LC_UDP_LEN_OFFSET+udp_hdr_len_ddd'left downto LC_UDP_LEN_OFFSET));

   -- ARP packet parse
--   arp_dst_ip_d   <= swap_bytes(pkt_header_d(LC_ARP_IP_DST_OFFSET+arp_dst_ip_d'left downto LC_ARP_IP_DST_OFFSET));    
   arp_dst_ip_dd  <= swap_bytes(pkt_header_ddd(LC_ARP_IP_DST_OFFSET+arp_dst_ip_dd'left downto LC_ARP_IP_DST_OFFSET));
   arp_src_ip_ddd  <= swap_bytes(pkt_header_ddd(LC_ARP_IP_SRC_OFFSET+arp_src_ip_ddd'left downto LC_ARP_IP_SRC_OFFSET));
   arp_src_mac_ddd <= swap_bytes(pkt_header_ddd(LC_ARP_MAC_SRC_OFFSET+arp_src_mac_ddd'left downto LC_ARP_MAC_SRC_OFFSET));

   ----------------------------------------
   --------   IP header chekcsum    -------
   ----------------------------------------
   -- Calculate part of IP header checksum checksum
   checksum_calc_proc: process(CLK)
      variable first_part  : std_logic_vector(31 downto 0);
      variable second_part  : std_logic_vector(63 downto 0);
      variable third_part  : std_logic_vector(63 downto 0);
--      variable hdr_rest : std_logic_vector(ip_header_d'length/2-1 downto 0) := (others => '0');
      variable chksum   : std_logic_vector(15 downto 0) := (others => '0');
      begin

         if rising_edge(CLK) then
            first_part := ip_header(31 downto 0);
            second_part := ip_header_d(95 downto 32);
            third_part := ip_header_dd(ip_header_dd'left downto 96);
            if RESET = '1' then
               ip_hdr_chksum_first_part <= (others => '0');
               ip_hdr_chksum_second_part <= (others => '0');
               chksum_ok <= '0';
            else
				-- First part of IP header checksum
				ip_hdr_chksum_first_part <= checksum_part(swap_bytes(first_part), x"0000");
				ip_hdr_chksum_second_part <= checksum_part(swap_bytes(second_part), ip_hdr_chksum_first_part);
--				hdr_rest := swap_bytes(third_part);
				chksum := full_checksum(swap_bytes(third_part), ip_hdr_chksum_second_part);
				chksum_ok <= not or_vec(chksum);
            end if;
         end if;
      end process;  

   ----------------------------------------
   --------   Packet checking       -------
   ----------------------------------------
   -- Protocol and data length checking 
   pkt_proto_check_proc: process(CLK)
      begin
         if rising_edge(CLK) then
            if RESET = '1' then
               ethertype_ip <= '0';
               ethertype_arp <= '0';
               ip_proto_udp <= '0';
               data_len_ok <= '0';
               data_len <= (others => '0');
            else
               ethertype_ip <= '0';
               ethertype_arp <= '0';
               ip_proto_udp <= '0';
               data_len_ok <= '0';
               -- Check if packet EtherType is supported
               if ethertype_dd = LC_ETH_ETHERTYPE_IPV4 then
               	ethertype_ip <= '1';
               elsif ethertype_dd = LC_ETH_ETHERTYPE_ARP then
               	ethertype_arp <= '1';
               end if;
               -- Check if IP protocol is supported
               if ip_proto_dd = LC_IP_PROTO_UDP then
               	ip_proto_udp <= '1';
               end if;      
               -- Checks if data length is valid
               if udp_hdr_len_dd >= LC_UDP_HDR_SIZE/8 then
               	data_len_ok <= '1';
               	data_len <= udp_hdr_len_dd - LC_UDP_HDR_SIZE/8;
               end if;
            end if; 
         end if;
      end process;  
      
   -- Destination addresses checking
   pkt_dst_check_proc: process(CLK)
      begin
        if rising_edge(CLK) then
            if RESET = '1' then
				mac_ok <= '0';
				ip_ok <= '0';
			else
				mac_ok <= '0';
				ip_ok <= '0';
		      arp_ip_ok <= '0';
            -- Check if destination MAC is valid for us
				if dst_mac_dd = LC_MAC_BROADCAST or dst_mac_dd = HOST_MAC then 
					mac_ok <= '1';
				end if; 
            -- Check if destination IP is valid for us
				if dst_ip_dd = HOST_IP or dst_ip_dd = LC_IP_BROADCAST or dst_ip_dd = (HOST_IP or not HOST_NET_MASK) then
					ip_ok <= '1';
				end if;
				-- ARP IP ok
				-- if arp_dst_ip_d = HOST_IP or arp_dst_ip_d = LC_IP_BROADCAST or arp_dst_ip_d = (HOST_IP or not HOST_NET_MASK) then
				if arp_dst_ip_dd = HOST_IP then  -- ARP request should be on specific IP address (broadcast not possible)
					arp_ip_ok <= '1';
				end if;				
			end if;
         end if;
      end process;  
   
  
   ----------------------------------------
   -------   Delay part of stream ---------
   ----------------------------------------
   -- This process delay part of AXI Stream because rest parts belongs to header
   -- and we need to write 512 bits of data into the FIFO 
   -- It also checks if there is end of current packet
   delay_part_of_stream_proc: process(CLK)
      variable tkeep_ddd_rest  : std_logic_vector(tkeep_rest'range) := (others => '0');
      begin
         if rising_edge(CLK) then
            tkeep_ddd_rest := tkeep_ddd(tkeep_ddd'left downto LC_HDR_SIZE/8);
            if RESET = '1' then
               tkeep_rest <= (others => '0');
               tdata_rest <= (others => '0');
               tuser_rest <= '0';
               tlast_rest <= '0';
               new_packet <= '1';
               tkeep_rest_int <= (others => '0');
            else
               -- Delay part of incoming data stream when data are valid
               -- precalculate something so it hasn't to be done in combination part
               -- and FIFOs are not full
               if tvalid_ddd = '1' then
                  tkeep_rest  <= tkeep_ddd(tkeep_dd'length - 1 downto LC_HDR_SIZE/8);
                  tdata_rest  <= tdata_ddd(tdata_dd'length - 1 downto LC_HDR_SIZE);
                  tuser_rest  <= tuser_ddd;
                  tlast_rest  <= tlast_ddd;
                  tkeep_rest_int  <= nat2vec(tkeep2num(tkeep_ddd_rest), tkeep_rest_int'length);
                  rest_not_empty <= or_vec(tkeep_ddd_rest);
               end if;
               -- Next valid data belongs to new packet
               if tvalid_ddd = '1' and tlast_ddd = '1' then
                  new_packet <= '1';
               elsif tvalid_ddd = '1' and tlast_ddd = '0' then
                  new_packet <= '0';
               end if;
            end if;
         end if;
      end process;

   ----------------------------------------
   --------   Validate packet       -------
   ----------------------------------------
   -- Here we check if packet is for us and if it contains data encapsulated
   -- in supported protocol
   validate_packet_proc: process(CLK)
      begin
         if rising_edge(CLK) then
            if RESET = '1' then
               packet_valid <= '0';
               src_udp_reg <= (others => '0');
               dst_udp_reg <= (others => '0');
               src_ip_reg <= (others => '0');
               src_mac_reg <= (others => '0');
               data_len_reg <= (others => '0');
               store_tag_reg <= '0';
            else
               ARP_REQUEST <= '0';
               ARP_SRC_MAC <= (others => '0');
               ARP_SRC_IP <= (others => '0');
            
               if store_tag = '1' then
                  store_tag_reg <= '1';
               elsif rx_tag_wren = '1' then
                  store_tag_reg <= '0';
               end if;
            
               -- Wait for valid data
               if tvalid_ddd = '1' and new_packet = '1' and fifo_not_full = '1' then
                  -- Check if incoming packet is for us and if it has >= 0 length
                  if mac_ok = '1' then
                     -- IPV4 is supported
                     if ethertype_ip = '1' and chksum_ok = '1' and ip_ok = '1' then
                        -- UDP is only supported IP protocol
                        if ip_proto_udp = '1' and data_len_ok = '1' then
                           -- Let others know there is valid packet
                           packet_valid <= '1';
                           -- Save packet addresses, ports and length
                           src_udp_reg <= src_udp_ddd;
                           dst_udp_reg <= dst_udp_ddd;
                           src_ip_reg  <= src_ip_ddd; 
                           src_mac_reg <= src_mac_ddd;
                           data_len_reg <= data_len;
                           -- prepare tkeep for short packets here so it hasn't to be done in combination part
                           short_pkt_tkeep <= num2tkeep(to_integer(unsigned(data_len)))(short_pkt_tkeep'left downto 0);
                           if data_len > 18 then 
                             short_packet <= '0';
                           else 
                             short_packet <= '1';
                           end if;
                        end if;
                     -- ARP is supported
                     elsif ethertype_arp = '1' and arp_ip_ok = '1' then
                        ARP_REQUEST <= '1';
                        ARP_SRC_MAC <= arp_src_mac_ddd;
                        ARP_SRC_IP <= arp_src_ip_ddd;
                     end if;
                  end if;
               elsif rx_tag_wren = '1' or discard_packet = '1' then
                  packet_valid <= '0';
               end if;
            end if;
         end if;
      end process;

   ----------------------------------------
   -------      Write to FIFO      --------
   ----------------------------------------
   -- Write UDP data to the FIFO
   rx_data_tag_proc: process(fifo_not_full, packet_valid, tvalid_ddd, tdata_ddd, tkeep_ddd, tlast_rest,  
                        tdata_rest, tkeep_rest, tuser_rest, byte_cnt, data_len_reg, src_udp_reg, dst_udp_reg, 
                        src_ip_reg, src_mac_reg, store_tag_reg, tag_fifo_full, data_fifo_full, short_packet,
                        tkeep_rest_int, rest_not_empty, short_pkt_tkeep)
         variable final_size     : std_logic_vector(15 downto 0);
         variable tmp_final_size : std_logic_vector(15 downto 0);
         variable packet_ok      : std_logic := '0';
      begin
         -- Assign default value to all signals and variables
         rx_data <= (others => '0');
         rx_tag <= (others => '0');
         rx_data_wren <= '0';
         rx_tag_wren <= '0';
         discard_packet <= '0';
         store_tag <= '0';
         
         -- Count final size of packet and check if its length is okay
         tmp_final_size := tkeep_rest_int + byte_cnt;
         if (tmp_final_size = data_len_reg or (data_len_reg < 18 and tmp_final_size = 18)) and tuser_rest = '0' then
            packet_ok := '1';
            final_size := data_len_reg;
         else
            packet_ok := '0';
            final_size := tmp_final_size;
         end if; 

         -- Assign values to rx_data signal
         if tvalid_ddd = '1' and tlast_rest = '0' then 
            rx_data(511 downto 0) <= tdata_ddd(LC_HDR_SIZE - 1 downto 0) & tdata_rest;
            rx_data(rx_data'length - 1 downto 512) <= tkeep_ddd(LC_HDR_SIZE/8 - 1 downto 0) & tkeep_rest;
         elsif tlast_rest = '1' then
            if rest_not_empty = '1' then
               rx_data(tdata_rest'length - 1 downto 0) <= tdata_rest;
               -- Write only data_cnt bytes into FIFO because rest is padding
               if short_packet = '1' then
                  rx_data(tdata_ddd'length + tkeep_rest'length - 1 downto tdata_ddd'length) <= short_pkt_tkeep;
               else
                  rx_data(tdata_ddd'length + tkeep_rest'length - 1 downto tdata_ddd'length) <= tkeep_rest;
               end if;
            end if;       
         else
            rx_data <= (others => '0');
         end if;

         -- Write data if packet is valid, there is space in FIFOs and counted packet size
         -- doest exceed received packet size
         if packet_valid = '1' and tag_fifo_full = '1' then
            discard_packet <= '1';
            store_tag <= '1';
         elsif store_tag_reg = '1' and tag_fifo_full = '0' then
            rx_tag(rx_tag'left - 1 downto rx_tag'left - byte_cnt'length)  <= byte_cnt;
            rx_tag_wren <= '1';
         elsif packet_valid = '1' and tag_fifo_full = '0' and (data_fifo_full = '1' or byte_cnt > data_len_reg) then
            rx_tag(rx_tag'left - 1 downto rx_tag'left - byte_cnt'length)  <= byte_cnt;
            rx_tag_wren <= '1';
            discard_packet <= '1';            
         elsif packet_valid = '1' and fifo_not_full = '1' and byte_cnt <= data_len_reg then
            rx_data_wren <= (not tlast_rest and tvalid_ddd) or (tlast_rest and rest_not_empty);
            rx_tag_wren <= tlast_rest;
            rx_tag <= packet_ok & final_size & src_udp_reg & dst_udp_reg & src_ip_reg & src_mac_reg;
         end if;
      end process;

   -- Count written bytes
   process(CLK) 
      variable tmp : std_logic_vector(63 downto 0) := (others => '0');
      begin 
         if rising_edge(CLK) then
            tmp := tkeep_ddd(tkeep_ddd'length - tkeep_rest'length - 1 downto 0) & tkeep_rest;
            if RESET = '1' then
               byte_cnt <= (others => '0');
            else
               -- Increment pkt_size_cnt when data are being written to the FIFO
               -- reset when tag is being written 
               if rx_data_wren = '1' and rx_tag_wren = '0' then
                  byte_cnt <= byte_cnt + tkeep2num(tmp);
               elsif rx_tag_wren = '1' then
                  byte_cnt <= (others => '0');
               end if;
            end if;
         end if;
      end process;

   ----------------------------------------
   -------      Assign outputs      -------
   ----------------------------------------
         
   -- AXI Stream Ready
   S_AXIS_TREADY <= fifo_not_full;
   -- Data FIFO write control
   RD_FIFO_WREN <= rx_data_wren;
--   RD_FIFO_WREN <= rd_fifo_wren_i;
   RD_FIFO_DATA <= rx_data;
   -- Tag FIFO write control
   RT_FIFO_WREN <= rx_tag_wren;
--   RT_FIFO_WREN <= rt_fifo_wren_i;
   RT_FIFO_DATA <= rx_tag;
   -- RX overflow - when FIFO is full and there is no free space for next packet
   RX_OVERFLOW <= not fifo_not_full;
   
end;
   
