-------------------------------------------------------------------------------
--
-- 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 append CRC from CRC32 generator 
-- to it's end. 
-- 
-------------------------------------------------------------------------------



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_append_crc 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        : out 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);
      S_EXTRA_DATA         : in  std_logic_vector(g_extra_data_width - 1 downto 0) := (others => '0');
      -- AXI Stream output
      M_AXIS_TVALID        : out std_logic;
      M_AXIS_TREADY        : in  std_logic;
      M_AXIS_TLAST         : out std_logic;
      M_AXIS_TKEEP         : out std_logic_vector(63 downto 0);
      M_AXIS_TDATA         : out std_logic_vector(511 downto 0);
      M_EXTRA_DATA         : out std_logic_vector(g_extra_data_width - 1 downto 0);
      -- CRC 
      CRC                  : in  std_logic_vector(31 downto 0);
      CRC_VALID            : in  std_logic;
      CRC_DONE             : out std_logic
   );
end;

architecture Behavioral of roce_append_crc is

   -- 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 tkeep2num(tkeep : std_logic_vector)
      return natural is
      begin
         for i in 0 to tkeep'length-1 loop
            if tkeep(i) = '0' then
               return i;
            end if;
         end loop;
         return tkeep'length;
      end tkeep2num;

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

   type main_fsm_t is (DATA_ST, WAIT_ST, CRC_REST_ST);                       
   signal main_fsm_nxt : main_fsm_t := DATA_ST;                        
   signal main_fsm     : main_fsm_t := DATA_ST;      
   
   signal m_axis_tvalid_i     : std_logic;     
   signal m_axis_tready_i     : std_logic;    
   signal m_axis_tlast_i      : std_logic;   
   signal m_axis_tkeep_i      : std_logic_vector(M_AXIS_TKEEP'range);    
   signal m_axis_tdata_i      : std_logic_vector(M_AXIS_TDATA'range);            
   
   signal crc_rest            : std_logic_vector(31 downto 0);
   signal crc_rest_tkeep      : std_logic_vector(3 downto 0);
   
   signal crc_rest_reg        : std_logic_vector(31 downto 0);
   signal crc_rest_tkeep_reg  : std_logic_vector(3 downto 0);

   signal only_crc            : std_logic;
   signal only_crc_reg        : std_logic;
   signal test                : std_logic_vector(1 downto 0);
   signal crc_valid_i         : std_logic;
   signal crc_valid_d         : std_logic;
   signal crc_d               : std_logic_vector(31 downto 0);
   signal crc_hton            : std_logic_vector(31 downto 0);
begin

   crc_hton <= HTON(CRC);

   process(CLK) 
      begin 
         if rising_edge(CLK) then
            if RESET = '1' then 
               main_fsm <= DATA_ST;
               only_crc_reg <= '0';
               crc_rest_tkeep_reg <= (others => '0');
            else
               main_fsm <= main_fsm_nxt;
               if main_fsm = DATA_ST then
                  only_crc_reg <= only_crc;
                  crc_valid_d <= '0';
               elsif main_fsm = WAIT_ST then 
                  crc_rest_reg <= crc_rest;
                  crc_rest_tkeep_reg <= crc_rest_tkeep;
                  -- register crc_valid and crc signals
                  if CRC_VALID = '1' then
                     crc_valid_d <= '1';
                     crc_d <= crc_hton;
                  end if;
               end if;
            end if;
         end if;
      end process;
        

   crc_valid_i <= crc_valid_d or CRC_VALID;
         
   process(S_AXIS_TVALID, S_AXIS_TDATA, S_AXIS_TKEEP, S_AXIS_TLAST, M_AXIS_TREADY, crc_rest_reg, 
            crc_rest_tkeep_reg, only_crc_reg, crc_hton, CRC_VALID, main_fsm, crc_valid_i, crc_d) 
      variable tmp : natural range 0 to 64;
      variable tmp_vec  : std_logic_vector(CRC'length/8 - 1 downto 0);
      variable crc_tmp  : std_logic_vector(CRC'range);
      begin   
            -- choose between actual or delayed CRC
            if CRC_VALID = '1' then
               crc_tmp := crc_hton;
            else
               crc_tmp := crc_d;
            end if; 
            tmp := tkeep2num(S_AXIS_TKEEP);
            tmp_vec := S_AXIS_TKEEP(S_AXIS_TKEEP'left downto S_AXIS_TKEEP'length - tmp_vec'length);
            m_axis_tvalid_i <= '0';
            m_axis_tdata_i <= (others => '0');
            m_axis_tkeep_i <= (others => '0');
            m_axis_tlast_i <= '0';            
            S_AXIS_TREADY <= '0';
            crc_rest <= (others => '0');
            crc_rest_tkeep <= (others => '0');
            main_fsm_nxt <= main_fsm;
            only_crc <= '0';
            CRC_DONE <= '0';
            
            case main_fsm is 
               -- Let data go through 
               when DATA_ST => 
                  m_axis_tvalid_i <= S_AXIS_TVALID and not S_AXIS_TLAST;
                  m_axis_tdata_i <= S_AXIS_TDATA;
                  m_axis_tkeep_i <= S_AXIS_TKEEP;
                  m_axis_tlast_i <= '0';
                  S_AXIS_TREADY <= M_AXIS_TREADY and not S_AXIS_TLAST;
                  if S_AXIS_TVALID = '1' and S_AXIS_TLAST = '1' then 
                     if and_vec(S_AXIS_TKEEP) = '1' then 
                        m_axis_tvalid_i <= S_AXIS_TVALID;
                        S_AXIS_TREADY <= M_AXIS_TREADY;
                        only_crc <= '1';
                        if M_AXIS_TREADY = '1' then                      
                           main_fsm_nxt <= WAIT_ST;
                        end if;
                     end if;
                     main_fsm_nxt <= WAIT_ST;
                  end if;
               -- Wait untial CRC computation is done and send it 
               -- together with the rest of data   
               when WAIT_ST =>       
                  if crc_valid_i = '1' then 
                     -- Only CRC is gonna be sent
                     if only_crc_reg = '1' then 
                        m_axis_tdata_i(CRC'range) <= crc_tmp;
                        m_axis_tkeep_i(CRC'length/8 - 1 downto 0) <= x"f";
                        m_axis_tvalid_i <= '1';
                        m_axis_tlast_i <= '1';
                        if M_AXIS_TREADY = '1' then 
                           main_fsm_nxt <= DATA_ST;
                           CRC_DONE <= '1';
                        end if;
                     -- There is rest of data which is supposed to be send too
                     else
                        S_AXIS_TREADY <= M_AXIS_TREADY;
                        m_axis_tvalid_i <= S_AXIS_TVALID;
                        -- Everything is gonna be send in this cycle
                        if or_vec(tmp_vec) = '0' then 
                           m_axis_tlast_i <= '1';
                           m_axis_tdata_i <= S_AXIS_TDATA;
                           m_axis_tdata_i(tmp*8 + CRC'left downto tmp*8) <= crc_tmp;
                           m_axis_tkeep_i <= S_AXIS_TKEEP;
                           m_axis_tkeep_i(tmp + CRC'length/8 - 1 downto tmp) <= (others => '1');
                           if M_AXIS_TREADY = '1' then 
                              main_fsm_nxt <= DATA_ST;
                              CRC_DONE <= '1';
                           end if;
                        -- CRC has to be split
                        else
                           case tmp_vec is 
                           when "0001" =>
                              m_axis_tdata_i(m_axis_tdata_i'left downto m_axis_tdata_i'length - 24) <= crc_tmp(23 downto 0);
                              crc_rest(7 downto 0) <= crc_hton(crc_hton'left downto 24);
                              crc_rest_tkeep(0 downto 0) <= (others => '1');
                           when "0011" =>
                              m_axis_tdata_i(m_axis_tdata_i'left downto m_axis_tdata_i'length - 16) <= crc_tmp(15 downto 0);
                              crc_rest(15 downto 0) <= crc_hton(crc_hton'left downto 16);
                              crc_rest_tkeep(1 downto 0) <= (others => '1');
                           when "0111" =>
                              m_axis_tdata_i(m_axis_tdata_i'left downto m_axis_tdata_i'length - 8) <= crc_tmp(7 downto 0);
                              crc_rest(23 downto 0) <= crc_hton(crc_hton'left downto 8);
                              crc_rest_tkeep(2 downto 0) <= (others => '1');
                           when others => -- this should never happen. if it happens, invalid tkeep is gonna 
                                          --be send in next cycle which cause that messege will be discarded
                              crc_rest_tkeep(3 downto 0) <= "1100"; 
                           end case;
                           m_axis_tlast_i <= '0';
                           m_axis_tkeep_i <= (others => '1');
                           if M_AXIS_TREADY = '1' then 
                              main_fsm_nxt <= CRC_REST_ST;
                           end if;
                        end if;
                     end if;
                  end if;
               
               when CRC_REST_ST =>
                  m_axis_tdata_i(crc_rest_reg'range) <= crc_rest_reg;
                  m_axis_tkeep_i(crc_rest_tkeep_reg'range) <= crc_rest_tkeep_reg;
                  m_axis_tvalid_i <= '1';
                  m_axis_tlast_i <= '1';
                  if M_AXIS_TREADY = '1' then 
                     CRC_DONE <= '1';
                     main_fsm_nxt <= DATA_ST;
                  end if;
               
            end case;  
      end process;
      
   M_AXIS_TVALID <= m_axis_tvalid_i;   
   M_AXIS_TLAST <= m_axis_tlast_i;   
   M_AXIS_TKEEP <= m_axis_tkeep_i;   
   M_AXIS_TDATA <= m_axis_tdata_i;   
   M_EXTRA_DATA <= S_EXTRA_DATA;
   
end;
