-------------------------------------------------------------------------------
--
-- Project name: PCIE_ADC_APP
--
-- (C) Copyright 2020 DFC Design, s.r.o., Brno, Czech Republic
-- Author: Jakub Hadamek (j.hadamek@dfcdesign.cz)
--
-------------------------------------------------------------------------------
--
-- This is part of the PCIE_ADC_APP IP Core
-- 
-------------------------------------------------------------------------------

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.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 channel_control is
   generic (
      -- batch size - number of words send continuously
      g_batch_length   : natural range 1 to 140 := 140;
      -- packet mode - enable tlast in the end of batch
      g_packet_mode     : boolean := true;
      -- insert seq number of the start of packet
      g_insert_seq_num  : boolean := true;
      -- enable abort signal to end sendig immediately
      g_abort_en        : boolean := true
   );
   port (
      CLK               : in  std_logic;
      RESET             : in  std_logic;
      -- Control signals
      RUN               : in  std_logic;
      OVERFLOW          : in  std_logic;
      FIFO_RSTN         : out std_logic;
      ABORT             : in  std_logic;
      -- Status signals
      SEND_CNT          : out std_logic_vector(63 downto 0);
      -- Data input
      S_AXIS_TVALID     : in  std_logic;
      S_AXIS_TLAST      : in  std_logic;
      S_AXIS_TREADY     : out std_logic;
      S_AXIS_TKEEP      : in  std_logic_vector(63 downto 0);
      S_AXIS_TDATA      : in  std_logic_vector(511 downto 0);
      FIFO_DATA_CNT     : in  std_logic_vector(31 downto 0);
      -- Data output
      M_AXIS_TVALID     : out std_logic;
      M_AXIS_TLAST      : out std_logic;
      M_AXIS_TREADY     : in  std_logic;
      M_AXIS_TKEEP      : out std_logic_vector(63 downto 0);
      M_AXIS_TDATA      : out std_logic_vector(511 downto 0)
    );
end;

architecture Behavioral of channel_control is
   signal send_cnt_i             : std_logic_vector(63 downto 0) := (others => '0');   
   signal not_idle               : std_logic := '0';
   signal split_packet           : std_logic := '0';
   signal packet_word_cnt        : natural range 0 to g_batch_length := 0;
   signal run_and_not_overflow   : std_logic := '0';
   constant LC_THRESHOLD_WORDS   : std_logic_vector(31 downto 0) := std_logic_vector(to_unsigned(g_batch_length, 32));
   signal threshold_exceed       : std_logic := '0';
   signal m_axis_tlast_i         : std_logic := '0';
   signal m_axis_tvalid_i        : std_logic := '0';
   
   
   signal s_axis_tvalid_i        : std_logic := '0';
   signal s_axis_tlast_i         : std_logic := '0';
   signal s_axis_tready_i        : std_logic := '0';
   signal s_axis_tkeep_i         : std_logic_vector(63 downto 0) := (others => '0'); 
   signal s_axis_tdata_i         : std_logic_vector(511 downto 0) := (others => '0'); 
   signal s_axis_tkeep_d         : std_logic_vector(63 downto 0) := (others => '0'); 
   signal s_axis_tdata_d         : std_logic_vector(511 downto 0) := (others => '0'); 
   signal pkt_start              : std_logic := '0';
   signal shift                  : std_logic_vector(31 downto 0) := (others => '0'); 
   signal pkt_seq                : std_logic_vector(15 downto 0) := (others => '0'); 
   
   signal abort_reg              : std_logic := '0';
begin

   run_and_not_overflow <= RUN and not OVERFLOW; 
   
   insert_seq_num_gen: if g_insert_seq_num = true generate
      insert_seq_num_proc: process(CLK)
         begin
            if rising_edge(CLK) then
               if RESET = '1' then 
                  s_axis_tvalid_i <= '0';
                  s_axis_tkeep_i <= (others => '0');  
                  s_axis_tdata_i <= (others => '0');  
                  s_axis_tkeep_d <= (others => '0');  
                  s_axis_tdata_d <= (others => '0'); 
                  shift <= x"80000000"; 
                  pkt_start <= '1';
                  pkt_seq <= (others => '0');
               else
              
                  if (run_and_not_overflow or not_idle) = '1' then 
                     if (s_axis_tready_i or not s_axis_tvalid_i) = '1' then
                        -- update tvalid and tlast
                        s_axis_tvalid_i <= S_AXIS_TVALID;
                        if m_axis_tlast_i = '1' then
                           pkt_start <= '1';
                        end if;
                        -- store data if it is valid so we dont lose it
                        -- update valid data
                        if S_AXIS_TVALID = '1' then
                           s_axis_tdata_d <= S_AXIS_TDATA;
                           s_axis_tkeep_d <= S_AXIS_TKEEP;
                           -- There is start of packet
                           -- Insert sequence number
                           update_data_gen: for i in 1 to shift'length loop
                              if (m_axis_tlast_i or pkt_start) = '1' then
                                 pkt_start <= '0';
                                 shift <=  shift(0) & shift(shift'left downto 1);
                                 pkt_seq <= pkt_seq + 1;
                                 if shift(i-1) = '1' then   
                                    -- update data
                                    s_axis_tdata_i <= S_AXIS_TDATA((i-1)*16-1 downto 0) &
                                       s_axis_tdata_d(s_axis_tdata_d'left downto i*16) &
                                       pkt_seq;
                                    -- update tkeep
                                    s_axis_tkeep_i <= S_AXIS_TKEEP((i-1)*2-1 downto 0) &
                                       s_axis_tkeep_d(s_axis_tkeep_d'left downto i*2) & "11";
                                 end if;
                              else
                                 if shift(i-1) = '1' then   
                                    -- update data
                                    s_axis_tdata_i <= S_AXIS_TDATA(i*16-1 downto 0) &
                                       s_axis_tdata_d(s_axis_tdata_d'left downto i*16);
                                    -- update tkeep
                                    s_axis_tkeep_i <= S_AXIS_TKEEP(i*2-1 downto 0) &
                                       s_axis_tkeep_d(s_axis_tkeep_d'left downto i*2);
                                 end if;
                              end if;
                           end loop;
                        end if;
                        
                     end if;
                  else
                     s_axis_tvalid_i <= '0';
                     s_axis_tkeep_i <= (others => '0');  
                     s_axis_tdata_i <= (others => '0');  
                     s_axis_tkeep_d <= (others => '0');  
                     s_axis_tdata_d <= (others => '0'); 
                     shift <= x"80000000"; 
                     pkt_start <= '1';
                  end if;
               end if;
            end if;
         end process;
  
  end generate;
  
  not_insert_seq_num_gen: if g_insert_seq_num = false generate
     s_axis_tvalid_i <= S_AXIS_TVALID and not ABORT;
     s_axis_tkeep_i <= S_AXIS_TKEEP;  
     s_axis_tdata_i <= S_AXIS_TDATA;  
     shift <= (others => '0');
  end generate;
   
   -- Count all words ever send
   -- Count words send in current packet
   -- split packet in case we are using packet mode and 
   -- packet length would overflow it's maximum length
   -- set/reset not_idle signal
   send_cnt_proc: process(CLK)
      variable tmp_pkt_cnt    : natural range 1 to g_batch_length := 1;
      begin
         if rising_edge(CLK) then
            tmp_pkt_cnt := packet_word_cnt + 1;
            if RESET = '1' then 
               send_cnt_i <= (others => '0');
               packet_word_cnt <= 0;
               split_packet <= '0';
               not_idle <= '0';
            else
               -- reset not_idle signal when no packet is being send right now
               -- set not_idle signal when packet is being send right now
               if (run_and_not_overflow and m_axis_tvalid_i and M_AXIS_TREADY) = '1' then
                  not_idle <= not m_axis_tlast_i;
               elsif (m_axis_tvalid_i and M_AXIS_TREADY and m_axis_tlast_i) = '1' then
                  not_idle <= '0';
               end if;
               
               -- count words
               if ABORT = '0' or g_abort_en = false then
                  if ((run_and_not_overflow or not_idle) and m_axis_tvalid_i and M_AXIS_TREADY) = '1' then
                     -- count every word aver send
                     send_cnt_i <= send_cnt_i + 1;
                     -- Check if there is end of packet or split request
                     if split_packet = '1' then 
                        packet_word_cnt <= 0;
                        split_packet <= '0';
                     -- otherwise count up and check for split condition
                     else
                        if tmp_pkt_cnt = g_batch_length - 1 then
                           split_packet <= '1';
                        end if;
                        packet_word_cnt <= tmp_pkt_cnt;
                     end if;
                  end if;
               else
                  packet_word_cnt <= 0;
                  split_packet <= '0';
                  not_idle <= '0';
               end if;

            end if;
         end if;
      end process;         
   
   m_axis_tvalid_i <= s_axis_tvalid_i and threshold_exceed and ((run_and_not_overflow and not RESET) or not_idle);      
   M_AXIS_TVALID <= m_axis_tvalid_i;
   M_AXIS_TDATA  <= s_axis_tdata_i  when ((run_and_not_overflow and not RESET) or not_idle) = '1' else (others => '0');
   M_AXIS_TKEEP  <= s_axis_tkeep_i  when ((run_and_not_overflow and not RESET) or not_idle) = '1' else (others => '0');
   s_axis_tready_i <= M_AXIS_TREADY and threshold_exceed when ((run_and_not_overflow and not RESET) or not_idle) = '1' else '0';
   S_AXIS_TREADY <= (s_axis_tready_i or not s_axis_tvalid_i) and not shift(0);   
--   S_AXIS_TREADY <= (s_axis_tready_i or not s_axis_tvalid_i) and not (shift(0) and m_axis_tlast_i);   

   -- If packet mode enable generate TLAST on the end of packet
   m_axis_tlast_i  <= split_packet and ((run_and_not_overflow and not RESET) or not_idle);
   tlast_en_gen: if g_packet_mode = true generate
   M_AXIS_TLAST <= m_axis_tlast_i;
   end generate;
   tlast_dis_gen: if g_packet_mode = false generate
   M_AXIS_TLAST <= '0';
   end generate;
   

   threshold_proc: process(CLK)
      begin
         if rising_edge(CLK) then
            if RESET = '1' then 
               threshold_exceed <= '0';
            else
               if ((run_and_not_overflow or not_idle) and S_AXIS_TVALID) = '1' and FIFO_DATA_CNT > LC_THRESHOLD_WORDS then
                  threshold_exceed <= '1';
               elsif ((run_and_not_overflow or not_idle) and M_AXIS_TREADY and ((S_AXIS_TVALID and m_axis_tlast_i) or not S_AXIS_TVALID)) = '1' then
                  threshold_exceed <= '0';
               end if;
            end if;
         end if;
      end process;   

   
   -- Output send words counter
   SEND_CNT <= send_cnt_i;
   -- Output FIFO reset
   FIFO_RSTN <= (run_and_not_overflow or not_idle) and not RESET and not ABORT;
         
end;