-------------------------------------------------------------------------------
--
-- Project name: ROCE_TO_100G_UDP
--
-- (C) Copyright 2019 DFC Design, s.r.o., Brno, Czech Republic
-- Author: Jakub Hadamek (j.hadamek@dfcdesign.cz)
--
-------------------------------------------------------------------------------
--
-- This file is part of ROCE_TO_100G_UDP IP core
--
-- It's purpose is to create interface between RoCE
-- packet and CRC32 generator. It inserts IP and UDP
-- headers on it's start and pass it to CRC generator. 
--
-- It has to be reseted before first data are passed to it.
-- 
-------------------------------------------------------------------------------



library IEEE;
use IEEE.STD_LOGIC_1164.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;
use IEEE.STD_LOGIC_UNSIGNED.ALL;


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

entity roce_crc_if is
   generic (
      g_extra_data_width   : natural
   );
   port (
      RESET                : in  std_logic;
      CLK                  : in  std_logic;
      -- AXI Stream input
      S_AXIS_TVALID        : in  std_logic;
      S_AXIS_TREADY        : 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);
      -- Routing informations
      HOST_MAC             : in  std_logic_vector(47 downto 0) := (others => '0');
      HOST_IP              : in  std_logic_vector(31 downto 0) := (others => '0');
      HOST_NET_MASK        : in  std_logic_vector(31 downto 0) := (others => '0');
      TX_SRC_UDP           : in  std_logic_vector(15 downto 0) := (others => '0');
      TX_DST_UDP           : in  std_logic_vector(15 downto 0) := (others => '0');
      TX_DST_IP            : in  std_logic_vector(31 downto 0) := (others => '0');
      TX_DST_MAC           : in  std_logic_vector(47 downto 0) := (others => '0');
      ROCE_PKT_LEN         : in  std_logic_vector(15 downto 0);
      -- CRC interface
      CRC_DATA             : out std_logic_vector(511 downto 0);
      CRC_DATA_VALID       : out std_logic;
      CRC_EOP              : out std_logic;
      CRC_MASK             : out std_logic_vector(5 downto 0);
      CRC_VALID            : in  std_logic;
      CRC_DONE             : in  std_logic
   );
end;

architecture Behavioral of roce_crc_if is

   -- Convert form HOST to NETWORK order
   -- It mix bytes only not bits
   -- Parameter must be vector of length divisible by 8
   function HTON(vec : std_logic_vector) return std_logic_vector is
      variable res   : std_logic_vector(vec'length - 1 downto 0);
      variable vec1  : std_logic_vector(vec'length - 1 downto 0);
      variable bytes : integer := vec'length/8;
      variable j     : integer;
   begin
      vec1 := vec;
      for i in 0 to bytes - 1 loop
         j := bytes - i - 1;
         res((i+1)*8 -1 downto i*8) := vec1((j+1)*8 -1 downto j*8);
      end loop;
      return res;
   end function;

   -- 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;

   -- convert tkeep to number
   function tkeep2crcmask(tkeep : in std_logic_vector; size : in natural)
      return std_logic_vector is
      variable ret   : std_logic_vector(size-1 downto 0);
      begin
         ret := (others => '0');
         for i in 0 to tkeep'length-1 loop
            if tkeep(i) = '0' then
               ret := std_logic_vector(to_unsigned(tkeep'length - i, ret'length));
               return ret;
            end if;
         end loop;
         return ret;
      end tkeep2crcmask; 

   -- crc internal signals
   signal crc_data_i                : std_logic_vector(511 downto 0);
   signal crc_data_valid_i          : std_logic;
   signal crc_eop_i                 : std_logic;
   signal crc_mask_i                : std_logic_vector(5 downto 0);


   -- RoCE headers
   constant LC_BTH_HEADER_SIZE      : natural := 12*8;
   constant LC_DETH_HEADER_SIZE     : natural := 8*8;
   constant LC_ROCE_HEADERS_SIZE    : natural := LC_BTH_HEADER_SIZE + LC_DETH_HEADER_SIZE;

   -- UDP headers
   constant LC_IP_HEADER_SIZE       : natural := 20*8;
   constant LC_UDP_HEADER_SIZE      : natural := 8*8;
   constant LC_UDPIP_HEADERS_SIZE   : natural := LC_IP_HEADER_SIZE + LC_UDP_HEADER_SIZE;
   signal ip_hdr     : std_logic_vector(LC_IP_HEADER_SIZE-1 downto 0);
   signal udp_hdr    : std_logic_vector(LC_UDP_HEADER_SIZE-1 downto 0);

   type udpip_hdr_fsm_t is (SOP_ST, DATA_ST, LAST_ST, WAIT_ST, CRC_RST_ST);                       
   signal udpip_hdr_fsm     : udpip_hdr_fsm_t := SOP_ST;                       
   signal udpip_hdr_fsm_nxt : udpip_hdr_fsm_t := SOP_ST;      

   signal roce_hdr         : std_logic_vector(20*8-1 downto 0);
   signal tdata_d          : std_logic_vector(LC_UDPIP_HEADERS_SIZE + 64 -1 downto 0);
   signal tkeep_d          : std_logic_vector((LC_UDPIP_HEADERS_SIZE + 64)/8-1 downto 0);

   signal ip_len           : std_logic_vector(15 downto 0);
   signal udp_len          : std_logic_vector(15 downto 0);
   signal data_valid       : std_logic;
   
   constant LC_CRC_DATA_DUMMY : std_logic_vector(CRC_DATA'range) := (others => '1');

   signal tmp_vec          : std_logic_vector(S_AXIS_TKEEP'range);

begin

   data_valid <= S_AXIS_TVALID and S_AXIS_TREADY;
   ip_len <= LC_UDPIP_HEADERS_SIZE/8 + ROCE_PKT_LEN;
   udp_len <= LC_UDP_HEADER_SIZE/8 + ROCE_PKT_LEN;

   -- IP and UDP headers for crc calc_lengths_proc
   ip_hdr <= HTON(TX_DST_IP) & HTON(HOST_IP)
               & x"ffff" -- Checksum replacement for iCRC
               & x"11ff" & x"0040"   -- This contains flags - should be "0040" -- but don't fragment is not sent by UDP/IP
               & x"0000" & HTON(ip_len) & x"ff45";
               
   udp_hdr <= x"ffff" & -- Checksum replaced for iCRC
               HTON(udp_len) & HTON(TX_DST_UDP) & HTON(TX_SRC_UDP);

     -- delay part of stream
   process(CLK) 
      variable tmp0     : std_logic_vector(tkeep_d'range);
      variable tmp1     : std_logic_vector(S_AXIS_TKEEP'range);
      begin 
         if rising_edge(CLK) then
            tmp0 := S_AXIS_TKEEP(S_AXIS_TKEEP'left downto S_AXIS_TKEEP'length - tkeep_d'length); 
            tmp1 := S_AXIS_TKEEP(S_AXIS_TKEEP'left - udp_hdr'length/8 - ip_hdr'length/8 - 8 downto 0) & tkeep_d;      
            if RESET = '1' then
               tdata_d <= (others => '0');   
               tkeep_d <= (others => '0');    
               udpip_hdr_fsm <= SOP_ST;
               crc_data_valid_i <= '0';
               crc_data_i <= (others => '0');
               crc_mask_i <= (others => '0');
               crc_eop_i <= '0';
            else      
               tdata_d <= S_AXIS_TDATA(S_AXIS_TDATA'left downto S_AXIS_TDATA'length - tdata_d'length);   
               tkeep_d <= S_AXIS_TKEEP(S_AXIS_TKEEP'left downto S_AXIS_TKEEP'length - tkeep_d'length); 
               
               -- output data
               case udpip_hdr_fsm is 
               when SOP_ST =>
                  crc_data_valid_i <= data_valid or (S_AXIS_TLAST and S_AXIS_TVALID);
                  -- 8 bits in BTH header has to be masked
                  crc_data_i <= S_AXIS_TDATA(S_AXIS_TDATA'left - udp_hdr'length - ip_hdr'length - 64 downto 40) &
                                 x"ff" & S_AXIS_TDATA(31 downto 0) & udp_hdr & ip_hdr & x"ffffffffffffffff";
                  tmp1 := S_AXIS_TKEEP(S_AXIS_TKEEP'left - udp_hdr'length/8 - ip_hdr'length/8 - 8 downto 0) & x"FFFFFFFFF";
                  crc_mask_i <= tkeep2crcmask(tmp1, crc_mask_i'length);
                  if data_valid = '1'  or (S_AXIS_TLAST and S_AXIS_TVALID) = '1' then 
                     if or_vec(tmp0) = '0' then 
                        crc_eop_i <= '1';
                        udpip_hdr_fsm <= WAIT_ST;
                     elsif S_AXIS_TLAST = '1' then 
                        crc_eop_i <= '0';
                        udpip_hdr_fsm <= LAST_ST;
                     else
                        crc_eop_i <= '0';
                        udpip_hdr_fsm <= DATA_ST;
                     end if;
                  end if;
                  
               when DATA_ST => 
                  crc_data_valid_i <= data_valid or (S_AXIS_TLAST and S_AXIS_TVALID);
                  crc_data_i <= S_AXIS_TDATA(S_AXIS_TDATA'left - udp_hdr'length - ip_hdr'length - 64 downto 0) & tdata_d;
                  tmp1 := S_AXIS_TKEEP(S_AXIS_TKEEP'left - udp_hdr'length/8 - ip_hdr'length/8 - 8 downto 0) & tkeep_d;
                  crc_mask_i <= tkeep2crcmask(tmp1, crc_mask_i'length);
                  if data_valid = '1' or (S_AXIS_TLAST and S_AXIS_TVALID) = '1' then
                     if or_vec(tmp0) = '0' then 
                        crc_eop_i <= '1';
                        udpip_hdr_fsm <= WAIT_ST;
                     elsif S_AXIS_TLAST = '1' then 
                        crc_eop_i <= '0';
                        udpip_hdr_fsm <= LAST_ST;
                     else
                        crc_eop_i <= '0';
                        udpip_hdr_fsm <= DATA_ST;
                     end if;
                  end if;
               
               when LAST_ST =>
                  crc_data_valid_i <= '1';
                  crc_data_i(CRC_DATA'left downto tdata_d'length) <= (others => '0');
                  crc_data_i(tdata_d'range) <= tdata_d;
                  crc_eop_i <= '1';
                  tmp1(S_AXIS_TKEEP'left downto tkeep_d'length) := (others => '0');
                  tmp1(tkeep_d'range) := tkeep_d;
                  crc_mask_i <= tkeep2crcmask(tmp1, crc_mask_i'length);
                  udpip_hdr_fsm <= WAIT_ST;
                  
               when WAIT_ST =>
                  crc_data_valid_i <= '0';
                  crc_eop_i <= '0';
                  if CRC_VALID = '1' then 
                     if CRC_DONE = '1' then
                        udpip_hdr_fsm <= SOP_ST;
                     else
                        udpip_hdr_fsm <= CRC_RST_ST;
                     end if;
                  end if;
                  
               when CRC_RST_ST =>
                  crc_eop_i <= '0';
                  crc_data_valid_i <= '0';      
                  crc_mask_i <= (others => '0');            
                  if CRC_DONE = '1' then 
                     udpip_hdr_fsm <= SOP_ST;
                  end if;
               end case;  
                 
            end if;
         end if;
      end process;

 
    crc_signals_reg: process(CLK) 
   begin
      if rising_edge(CLK) then 
         if RESET = '1' then
            CRC_DATA_VALID <= '0';
            CRC_DATA <= LC_CRC_DATA_DUMMY;
            CRC_MASK <= (others => '0');
            CRC_EOP <= '0';
         else
            CRC_DATA_VALID <= crc_data_valid_i;
            CRC_DATA <= crc_data_i;
            CRC_MASK <= crc_mask_i;
            CRC_EOP <= crc_eop_i;
         end if;  
      end if;
   end process;
 
 
   
end;
