-------------------------------------------------------------------------------
--
-- 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_gen is
   port (
      -- Interface reset
      RESET                : in  std_logic;
      -- Interface clock
      CLK                  : in  std_logic;
      -- TX to CMAC
      M_AXIS_TVALID        : out std_logic;
      M_AXIS_TREADY        : in  std_logic;
      M_AXIS_TUSER         : out 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);
      -- TX from data FIFO
      TD_FIFO_RDEN         : out std_logic;
      TD_FIFO_EMPTY        : in  std_logic;
      TD_FIFO_DATA         : in  std_logic_vector(575 downto 0);
      TD_FIFO_RST_BUSY     : in  std_logic;
      -- TX from tag FIFO
      TT_FIFO_RDEN         : out std_logic;
      TT_FIFO_EMPTY        : in  std_logic;
      TT_FIFO_DATA         : in  std_logic_vector(130 downto 0);
      TT_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);
      -- ARP request
      ARP_REQUEST          : in  std_logic;
      ARP_ACCEPTED         : out std_logic;
      ARP_SRC_MAC          : in  std_logic_vector(47 downto 0);
      ARP_SRC_IP           : in  std_logic_vector(31 downto 0)
      
   );
end;

architecture Behavioral of frame_gen 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
      begin
         for i in 0 to vector'length-1 loop
            if vector(i) = '0' then
               return i;
            end if;
         end loop;
         return vector'length;
      end tkeep2num;
   
   -- 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 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;

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


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

   
   -- TX state machine states
--   type tx_state_type is (TX_IDLE_ST, TX_HEADER_ST, TX_DATA_ST, TX_EOP_ST, TX_PAUSE_ST);
--   signal tx_fsm_cur          : tx_state_type := TX_IDLE_ST;
--   signal tx_fsm_next         : tx_state_type := TX_IDLE_ST;
   
   type tx_state_type is (TX_HEADER_ST, TX_DATA_ST, TX_EOP_ST);
   signal tx_fsm_cur          : tx_state_type := TX_HEADER_ST;
   signal tx_fsm_next         : tx_state_type := TX_HEADER_ST;
   
   -- Headers sizes 
   constant LC_ETH_HEADER_SIZE      : natural := 14*8;
   constant LC_IP_HEADER_SIZE       : natural := 20*8;
   constant LC_UDP_HEADER_SIZE      : natural := 8*8;
   constant LC_HEADER_SIZE          : natural := LC_ETH_HEADER_SIZE + LC_IP_HEADER_SIZE + LC_UDP_HEADER_SIZE;
   constant LC_MINIMAL_PACKET_SIZE  : natural := 64;
   constant LC_PKT_HDR_KEEP   : std_logic_vector(LC_HEADER_SIZE/8-1 downto 0) := (others => '1');
   
   -- IP header constants 
   constant LC_IP_HDR_ID            : std_logic_vector(15 downto 0) := x"0000";
   constant LC_IP_HDR_FLAG_FRAGOFF  : std_logic_vector(15 downto 0) := x"4000";
   constant LC_IP_HDR_VER_IHL_TOS   : std_logic_vector(15 downto 0) := x"4500";
   constant LC_IP_HDR_TTL_PROTO     : std_logic_vector(15 downto 0) := x"ff11";

   -- UDP header checksum constant
   -- UDP checksum is not used so set it to zeros
   constant LC_UDP_HDR_CHECKSUM     : std_logic_vector(15 downto 0) := (others => '0');
   
   
   -- Packet route infos
   signal src_udp                : std_logic_vector(15 downto 0) := (others => '0');
   signal dst_udp                : std_logic_vector(15 downto 0) := (others => '0');
   signal dst_ip                 : std_logic_vector(31 downto 0) := (others => '0');
   signal dst_mac                : std_logic_vector(47 downto 0) := (others => '0'); 

   -- number of bytes to send
   signal pkt_data_length        : std_logic_vector(15 downto 0) := (others => '0');
   signal pkt_length             : std_logic_vector(15 downto 0) := (others => '0');
   signal pkt_length_reg         : std_logic_vector(15 downto 0) := (others => '0');
   
   -- IP and UDP header lengths
   signal ip_hdr_len             : std_logic_vector(15 downto 0) := (others => '0');
   signal udp_hdr_len            : std_logic_vector(15 downto 0) := (others => '0');

   signal half_ip_hdr_chksum     : std_logic_vector(15 downto 0) := (others => '0');
   signal ip_hdr_chksum          : std_logic_vector(15 downto 0) := (others => '0');
   signal udp_hdr_chksum         : std_logic_vector(15 downto 0) := (others => '0');
   signal ip_hdr_chksum_tmp      : std_logic_vector(19 downto 0) := (others => '0');
   signal udp_hdr_chksum_tmp     : std_logic_vector(19 downto 0) := (others => '0');   
   
   -- Separated data and keep signals
   signal tx_data                : std_logic_vector(511 downto 0) := (others => '0');
   signal tx_keep                : std_logic_vector(63 downto 0) := (others => '0');
   -- Delayed part of data and keep signals
   signal tx_data_rest              : std_logic_vector(LC_HEADER_SIZE-1 downto 0) := (others => '0');
   signal tx_keep_rest              : std_logic_vector(LC_HEADER_SIZE/8-1 downto 0) := (others => '0');
   -- Precalculated part of IP header
   signal half_ip_header_tmp     : std_logic_vector(79 downto 0) := (others => '0'); 
   
   -- Packet headers
   signal mac_header             : std_logic_vector(LC_HEADER_SIZE-1 downto 0) := (others => '0');
   signal ip_header              : std_logic_vector(LC_IP_HEADER_SIZE-1 downto 0) := (others => '0'); 
   signal udp_header             : std_logic_vector(LC_UDP_HEADER_SIZE-1 downto 0) := (others => '0'); 
   signal pkt_header             : std_logic_vector(LC_HEADER_SIZE-1 downto 0) := (others => '0');

   -- Internal FIFO read enable
   signal td_fifo_rden_i : std_logic := '0';
   signal tt_fifo_rden_i  : std_logic := '0';
   
   -- Internal AXI Stream signals
   signal m_axis_tvalid_i  : std_logic;                                   
   signal m_axis_tready_i  : std_logic;                                   
   signal m_axis_tuser_i   : std_logic;                                   
   signal m_axis_tlast_i   : std_logic;                                   
   signal m_axis_tkeep_i   : std_logic_vector(63 downto 0);
   signal m_axis_tdata_i   : std_logic_vector(511 downto 0); 
   
   -- Upper layer signal that actual packet is invalid
   signal discard_packet      : std_logic := '0';
   -- Counter used to count sent bytes
   signal sent_byte_cnt       : std_logic_vector(15 downto 0) := (others => '0');
   -- FIFO empty signals 
   signal data_fifo_empty     : std_logic := '0';
   signal data_fifo_empty_d   : std_logic := '0';
   signal data_fifo_empty_dd  : std_logic := '0';
   signal data_fifo_empty_ddd : std_logic := '0';
   signal tag_fifo_empty      : std_logic := '0';
   
   -- ARP packet
   constant LC_ARP_PACKET_LENGTH    : natural := 42*8;
   constant LC_ARP_ETHER_TYPE       : std_logic_vector(15 downto 0) := x"0806";
   constant LC_ARP_HW_TYPE          : std_logic_vector(15 downto 0) := x"0001";
   constant LC_ARP_IPV4_PROTO_TYPE       : std_logic_vector(15 downto 0) := x"0800";
   constant LC_ARP_HLEN             : std_logic_vector(7 downto 0)  := x"06";
   constant LC_ARP_PLEN             : std_logic_vector(7 downto 0)  := x"04";
   constant LC_ARP_OP_RESPOND       : std_logic_vector(15 downto 0) := x"0002";
   
   signal arp_packet       : std_logic_vector(LC_ARP_PACKET_LENGTH-1 downto 0) := (others => '0');
   
   signal data_length_comp     : std_logic_vector(1 downto 0) := (others => '0');   
   signal more_to_send        : std_logic := '0';

   signal tag_d                     : std_logic_vector(TT_FIFO_DATA'range);
   signal tag_dd                    : std_logic_vector(TT_FIFO_DATA'range);
   signal tag_ddd                   : std_logic_vector(TT_FIFO_DATA'range);
   
   signal tag_empty_d                : std_logic;
   signal tag_empty_dd               : std_logic;
   signal tag_empty_ddd              : std_logic;
   
   constant LC_CONST_HDR_PART    : std_logic_vector(15 downto 0) := checksum_part(swap_bytes(LC_IP_HDR_VER_IHL_TOS & LC_IP_HDR_ID & LC_IP_HDR_FLAG_FRAGOFF & LC_IP_HDR_TTL_PROTO), x"0000");
   signal ip_hdr_checksum_tmp    : std_logic_vector(15 downto 0) := (others => '0');
   signal ip_hdr_checksum        : std_logic_vector(15 downto 0) := (others => '0');
   
   signal ip_hdr_prelen          : std_logic_vector(15 downto 0) := (others => '0');
begin

   -- FIFO is not empty signal - is made valid by or with rst_busy
   data_fifo_empty <=  TD_FIFO_EMPTY or TD_FIFO_RST_BUSY;
   tag_fifo_empty <= TT_FIFO_EMPTY or TT_FIFO_RST_BUSY;
   
   -- Separate data and byte enable
   tx_data <= TD_FIFO_DATA(tx_data'range);
   tx_keep <= TD_FIFO_DATA(tx_data'length + tx_keep'left downto tx_data'length);
 
   -- Register incomming data and pipeline CRC computation
   process(CLK)
      variable dst_ip_v    : std_logic_vector(31 downto 0);
      begin
         if rising_edge(CLK) then
            if RESET = '1' then    
               tag_d    <= (others => '0');        
               tag_dd   <= (others => '0');        
               tag_ddd  <= (others => '0');     
               tag_empty_d    <= '1';
               tag_empty_dd   <= '1';
               tag_empty_ddd  <= '1';   
               ip_hdr_len <= (others => '0');
               ip_hdr_prelen <= (others => '0');
               ip_hdr_checksum_tmp <= (others => '0');
               ip_hdr_chksum <= (others => '0');
            else
               dst_ip_v := tag_d(79 downto 48);  
               
               -- One clock delay
               if (tag_empty_d or tag_empty_dd or tag_empty_ddd or tt_fifo_rden_i) = '1' then
                  tag_empty_d <= TT_FIFO_EMPTY or TT_FIFO_RST_BUSY;
                  tag_d <= TT_FIFO_DATA;   
               end if; 
                       
               -- Two clocks delay
               if (tag_empty_dd or tag_empty_ddd or tt_fifo_rden_i) = '1' then
                  tag_empty_dd <= tag_empty_d;
                  tag_dd <= tag_d;
                  ip_hdr_checksum_tmp <= checksum_part(swap_bytes(HOST_IP & dst_ip_v), LC_CONST_HDR_PART);
                  ip_hdr_prelen <= tag_d(127 downto 112) + LC_IP_HEADER_SIZE/8 + LC_UDP_HEADER_SIZE/8;
               end if;
               
               -- Three clocks delay
               if (tag_empty_ddd or tt_fifo_rden_i) = '1' then
                  tag_empty_ddd <= tag_empty_dd;
                  tag_ddd <= tag_dd;
                  ip_hdr_chksum <= full_checksum(swap_bytes(ip_hdr_prelen), ip_hdr_checksum_tmp);
                  ip_hdr_len <= ip_hdr_prelen;
               end if;
            
            end if;
         end if;
      end process;
 
   -- ARP_SRC_IP & ARP_SRC_MAC & HOST_IP & HOST_MAC & LC_ARP_OP_RESPOND & LC_ARP_PLEN & LC_ARP_HLEN & LC_ARP_PROTO_TYPE & LC_ARP_HW_TYPE
   arp_packet <= swap_bytes(ARP_SRC_MAC & HOST_MAC & LC_ARP_ETHER_TYPE & LC_ARP_HW_TYPE & LC_ARP_IPV4_PROTO_TYPE
                           & LC_ARP_HLEN & LC_ARP_PLEN & LC_ARP_OP_RESPOND & HOST_MAC & HOST_IP & ARP_SRC_MAC & ARP_SRC_IP);
   


   -- Parse packet route infos and packet length from tag data
   data_length_comp <= tag_ddd(130 downto 129);
   discard_packet <= not tag_ddd(128);
   pkt_data_length   <= tag_ddd(127 downto 112);
   src_udp        <= tag_ddd(111 downto 96);
   dst_udp        <= tag_ddd(95 downto 80);
   dst_ip         <= tag_ddd(79 downto 48);
   dst_mac        <= tag_ddd(47 downto 0);
   pkt_length     <= pkt_data_length + LC_HEADER_SIZE/8;
   
   -- IP header with calculated checksum
   ip_header <= swap_bytes(LC_IP_HDR_VER_IHL_TOS & ip_hdr_len & LC_IP_HDR_ID & LC_IP_HDR_FLAG_FRAGOFF & LC_IP_HDR_TTL_PROTO & ip_hdr_chksum & HOST_IP & dst_ip);
                                                   
   -- UDP header length
   udp_hdr_len <= pkt_data_length + LC_UDP_HEADER_SIZE/8;
   
   -- UDP header with disabled checksum
   udp_header <= swap_bytes(src_udp & dst_udp & udp_hdr_len & LC_UDP_HDR_CHECKSUM); 
   
   -- PACKET headers
   pkt_header(LC_ETH_HEADER_SIZE-1 downto 0) <= swap_bytes(dst_mac & HOST_MAC & x"0800");
   pkt_header(LC_IP_HEADER_SIZE+LC_ETH_HEADER_SIZE-1 downto LC_ETH_HEADER_SIZE) <= ip_header;
   pkt_header(LC_UDP_HEADER_SIZE+LC_IP_HEADER_SIZE+LC_ETH_HEADER_SIZE-1 downto LC_ETH_HEADER_SIZE+LC_IP_HEADER_SIZE) <= udp_header;                                               

   -- Change tx fsm state
   tx_fsm_change_proc: process(CLK)
      begin 
         if rising_edge(CLK) then 
            if RESET = '1' then
               tx_fsm_cur <= TX_HEADER_ST;
            else
               tx_fsm_cur <= tx_fsm_next;
            end if;
         end if;
      end process;

   -- Delay part of data which are not going to be send in this cycle (due to packet header)
   tx_delay_part_of_tdata: process(CLK)
      begin
         if rising_edge(CLK) then
            if RESET = '1' then
               tx_data_rest <= (others => '0');
               tx_keep_rest <= (others => '0');
            else
               if td_fifo_rden_i = '1' then
                  tx_data_rest <= tx_data(tx_data'length - 1 downto tx_data'length - LC_HEADER_SIZE);
                  tx_keep_rest <= tx_keep(tx_keep'length - 1 downto tx_keep'length - LC_HEADER_SIZE/8);
               end if;
            end if;
         end if;
      end process;

   -- This process counts bytes readed from fifo
   fifo_data_cnt_proc: process(CLK)
      begin
         if rising_edge(CLK) then
            if RESET = '1' then
               pkt_length_reg <= (others => '0');
               sent_byte_cnt <= (others => '0');
            else
               -- register packet length
               if tx_fsm_cur = TX_HEADER_ST and M_AXIS_TREADY = '1' then
                  pkt_length_reg <= pkt_length;
                  if (to_integer(unsigned(pkt_length)) mod 64) <= LC_HEADER_SIZE/8 then 
                     more_to_send <= '0';
                  else
                     more_to_send <= '1';
                  end if;
               end if;
               -- count bytes read from FIFO
               if td_fifo_rden_i = '1' and tt_fifo_rden_i = '0' then 
                  sent_byte_cnt <= sent_byte_cnt + 64;
               elsif tt_fifo_rden_i = '1' then 
                  sent_byte_cnt <= (others => '0');
               end if;
            end if;
         end if;
      end process;

   -- TX Main state machine process
   tx_fsm_proc: process(tx_fsm_cur, data_fifo_empty, tag_empty_ddd, tx_data, tx_keep, tx_data_rest, tx_keep_rest, ARP_REQUEST,
                        M_AXIS_TREADY, pkt_header, pkt_data_length, pkt_length, pkt_length_reg, discard_packet, sent_byte_cnt, arp_packet,
                        data_length_comp, more_to_send)
         variable tmp_cnt           : std_logic_vector(15 downto 0);
      begin
         -- Assign default value to all signals and variables
         tx_fsm_next <= tx_fsm_cur;
         m_axis_tvalid_i <= '0';
         m_axis_tkeep_i <= (others => '0');
         m_axis_tuser_i <= '0';
         m_axis_tlast_i <= '0';
         m_axis_tdata_i <= (others => '0');
         td_fifo_rden_i <= '0';
         tt_fifo_rden_i <= '0';
         tmp_cnt := (others => '0');
         ARP_ACCEPTED <= '0';
         
         case tx_fsm_cur is
            -- Send packet header and some data   
            when TX_HEADER_ST =>
               -- There is packet in the FIFO and is not invalid
               if ARP_REQUEST = '1' then 
                  m_axis_tvalid_i <= '1';
                  m_axis_tlast_i <= '1';
                  m_axis_tkeep_i(LC_HEADER_SIZE/8 + 18 - 1 downto 0) <= (others => '1');
                  m_axis_tdata_i(arp_packet'left downto 0) <= arp_packet;
                  ARP_ACCEPTED <= M_AXIS_TREADY;
               elsif data_fifo_empty = '0' and tag_empty_ddd = '0' and discard_packet = '0' then
                  td_fifo_rden_i <= M_AXIS_TREADY;
                  m_axis_tvalid_i <= '1';
                  m_axis_tdata_i <= tx_data(tx_data'length - tx_data_rest'length - 1 downto 0) & pkt_header; 
                  -- everything is gonna be send in this cycle
                  if data_length_comp = "01" then   
                     m_axis_tkeep_i <= tx_keep(tx_keep'length - tx_keep_rest'length - 1 downto 0) & LC_PKT_HDR_KEEP;                               
                     m_axis_tlast_i <= '1';
                     tt_fifo_rden_i <= M_AXIS_TREADY;
                  -- everything will be send in the next cycle
                  elsif data_length_comp = "10" then --unsigned(pkt_length) <= 2*64 then
                     m_axis_tkeep_i <= (others => '1');
                     if M_AXIS_TREADY = '1' then 
                        tx_fsm_next <= TX_EOP_ST;
                     end if;
                  -- There are more data to send
                  else
                     m_axis_tkeep_i <= (others => '1');
                     if M_AXIS_TREADY = '1' then 
                        tx_fsm_next <= TX_DATA_ST;
                     end if;
                  end if;
               -- There is packet in the FIFO but is invalid, so discard it
               elsif data_fifo_empty = '0' and tag_empty_ddd = '0' and discard_packet = '1' then
                  td_fifo_rden_i <= '1';
                  if sent_byte_cnt + 64 >= pkt_data_length then
                     tt_fifo_rden_i <= '1';
                  end if;
               end if;
            -- Send data
            when TX_DATA_ST => 
               m_axis_tvalid_i <= '1';
               m_axis_tdata_i <= tx_data(tx_data'length - tx_data_rest'length - 1 downto 0) & tx_data_rest;
               m_axis_tkeep_i <= tx_keep(tx_keep'length - tx_keep_rest'length - 1 downto 0) & tx_keep_rest;             
               td_fifo_rden_i <= M_AXIS_TREADY and not data_fifo_empty;
               -- CMAC is ready to receive
               if M_AXIS_TREADY = '1' and data_fifo_empty = '0' and (sent_byte_cnt + 2*64) >= pkt_length_reg then
                  tx_fsm_next <= TX_EOP_ST;
               end if;
  
            -- End of packet
            when TX_EOP_ST => 
               -- All bytes to send minus already sent bytes 
               -- Line under is used instead of pkt_length_reg - sent_byte_cnt because it should be more 
               -- efficient than subtraction (only operation performed is OR). Can be used because we are
               -- going to send maximum of 64 bytes
               m_axis_tvalid_i <= '1';
               m_axis_tlast_i <= '1';
               if more_to_send = '0' then 
                  m_axis_tdata_i(tx_data_rest'range) <= tx_data_rest;
                  m_axis_tkeep_i(tx_keep_rest'range) <= tx_keep_rest;
               else
                  m_axis_tdata_i <= tx_data(tx_data'left - tx_data_rest'length downto 0) & tx_data_rest;
                  m_axis_tkeep_i <= tx_keep(tx_keep'left - tx_keep_rest'length downto 0) & tx_keep_rest;
                  td_fifo_rden_i <= M_AXIS_TREADY and not data_fifo_empty;
               end if;
               tt_fifo_rden_i <= M_AXIS_TREADY and not tag_empty_ddd;
               -- Go to IDLE state if there is no other packet to send
               if M_AXIS_TREADY = '1' then
                  tx_fsm_next <= TX_HEADER_ST;
               end if;

         end case;
      end process;

   -- Enable read from tag FIFO
   TT_FIFO_RDEN <= not tag_fifo_empty and (tt_fifo_rden_i or tag_empty_d or tag_empty_dd or tag_empty_ddd);
   -- Enable read from data FIFO
   TD_FIFO_RDEN <= td_fifo_rden_i;
      
   M_AXIS_TVALID <= m_axis_tvalid_i;
   M_AXIS_TUSER  <= m_axis_tuser_i;
   M_AXIS_TLAST  <= m_axis_tlast_i;
   M_AXIS_TKEEP  <= m_axis_tkeep_i;
   M_AXIS_TDATA  <= m_axis_tdata_i;
   
end;
