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

Library xpm;
use xpm.vcomponents.all;

entity pcie_adc_app is
   generic (
      -- if there is need to finish packet of packet length
      -- before it's stopped
--      g_packet_mode        : boolean := true;
      -- should be MAX_UDP_PACKET - 1 (because of RoCE headers)
      g_batch_length       : natural range 1 to 140 := 140;
      g_data_pcie_clk_same : boolean := false;
      g_debug              : boolean := false
   );
   port (
      -- PCIE clocks and reset
      PCIE_CLK             : in  std_logic;
      PCIE_RESET           : in  std_logic;
      -- JESD clocks and reset
      DATA_CLK             : in  std_logic;
      DATA_RESET           : in  std_logic;
      -- AXI Registers
		S_AXI_AWADDR	      : in std_logic_vector(9 downto 0);
		S_AXI_AWPROT	      : in std_logic_vector(2 downto 0);
		S_AXI_AWVALID	      : in std_logic;
		S_AXI_AWREADY        : out std_logic;
		S_AXI_WDATA	         : in std_logic_vector(31 downto 0);
		S_AXI_WSTRB	         : in std_logic_vector(3 downto 0);
		S_AXI_WVALID	      : in std_logic;
		S_AXI_WREADY	      : out std_logic;
		S_AXI_BRESP	         : out std_logic_vector(1 downto 0);
		S_AXI_BVALID	      : out std_logic;
		S_AXI_BREADY	      : in std_logic;
		S_AXI_ARADDR         : in std_logic_vector(9 downto 0);
		S_AXI_ARPROT	      : in std_logic_vector(2 downto 0);
		S_AXI_ARVALID	      : in std_logic;
		S_AXI_ARREADY	      : out std_logic;
		S_AXI_RDATA          : out std_logic_vector(31 downto 0);
		S_AXI_RRESP          : out std_logic_vector(1 downto 0);
		S_AXI_RVALID	      : out std_logic;
		S_AXI_RREADY	      : in std_logic;
      -- Data input
      -- channel 0
      S_AXIS_CH0_TVALID    : in  std_logic;
      S_AXIS_CH0_TLAST     : in  std_logic;
      S_AXIS_CH0_TREADY    : out std_logic;
      S_AXIS_CH0_TDATA     : in  std_logic_vector(511 downto 0);
      S_AXIS_CH0_TKEEP     : in  std_logic_vector(63 downto 0);
      -- channel 1
      S_AXIS_CH1_TVALID    : in  std_logic;
      S_AXIS_CH1_TLAST     : in  std_logic;
      S_AXIS_CH1_TREADY    : out std_logic;
      S_AXIS_CH1_TDATA     : in  std_logic_vector(511 downto 0);
      S_AXIS_CH1_TKEEP     : in  std_logic_vector(63 downto 0);
      -- Data output to pcie xdma channel
      -- channel 0
      M_AXIS_CH0_TVALID    : out std_logic;
      M_AXIS_CH0_TLAST     : out std_logic;
      M_AXIS_CH0_TREADY    : in  std_logic;
      M_AXIS_CH0_TDATA     : out std_logic_vector(511 downto 0);
      M_AXIS_CH0_TKEEP     : out std_logic_vector(63 downto 0);
      -- channel 1
      M_AXIS_CH1_TVALID    : out std_logic;
      M_AXIS_CH1_TLAST     : out std_logic;
      M_AXIS_CH1_TREADY    : in  std_logic;
      M_AXIS_CH1_TDATA     : out std_logic_vector(511 downto 0);
      M_AXIS_CH1_TKEEP     : out std_logic_vector(63 downto 0);
      -- PCIE interrupt
      INTERRUPT            : out std_logic;
      -- DMA status
      CH0_DMA_STATUS       : in  std_logic_vector(7 downto 0);
      CH1_DMA_STATUS       : in  std_logic_vector(7 downto 0)
    );
end;

architecture Behavioral of pcie_adc_app is

   type channel_control_t is (IDLE_ST, RUN_ST, OVERFLOW_ST);
   signal common_fsm    : channel_control_t := IDLE_ST;
   signal ch0_fsm       : channel_control_t := IDLE_ST;
   signal ch1_fsm       : channel_control_t := IDLE_ST;

--   signal run                    : std_logic := '0';
--   signal run_control            : std_logic := '0';
--   signal clear_control          : std_logic := '0';
--   signal clear_cdc              : std_logic := '0';
--   signal clear                  : std_logic := '0';
--   signal reset                  : std_logic := '1';
   signal pcie_resetn            : std_logic := '0';
   signal data_resetn            : std_logic := '0';

   
   signal ch0_s_axis_tvalid      : std_logic := '0';     
   signal ch0_s_axis_tlast       : std_logic := '0';     
   signal ch0_s_axis_tready      : std_logic := '0';         
   signal ch0_s_axis_tdata       : std_logic_vector(511 downto 0) := (others => '0');   
   signal ch0_s_axis_tkeep       : std_logic_vector(63 downto 0) := (others => '0');    
   signal ch0_s_axis_tvalid_cdc  : std_logic := '0';     
   signal ch0_s_axis_tlast_cdc   : std_logic := '0';     
   signal ch0_s_axis_tready_cdc  : std_logic := '0';         
   signal ch0_s_axis_tdata_cdc   : std_logic_vector(511 downto 0) := (others => '0');   
   signal ch0_s_axis_tkeep_cdc   : std_logic_vector(63 downto 0) := (others => '0');        
   signal ch0_fifo_cnt           : std_logic_vector(31 downto 0) := (others => '0');  
   signal ch0_run                : std_logic := '0';    
   signal ch0_clear              : std_logic := '0';  
   signal ch0_clear_cdc          : std_logic := '0';  
   signal ch0_overflow           : std_logic := '0'; 
   signal ch0_overflow_cdc       : std_logic := '0';
   signal ch0_rstn               : std_logic := '0';
   signal ch0_full               : std_logic := '0';  

   signal ch1_s_axis_tvalid      : std_logic := '0';     
   signal ch1_s_axis_tlast       : std_logic := '0';     
   signal ch1_s_axis_tready      : std_logic := '0';         
   signal ch1_s_axis_tdata       : std_logic_vector(511 downto 0) := (others => '0');   
   signal ch1_s_axis_tkeep       : std_logic_vector(63 downto 0) := (others => '0');    
   signal ch1_s_axis_tvalid_cdc  : std_logic := '0';     
   signal ch1_s_axis_tlast_cdc   : std_logic := '0';     
   signal ch1_s_axis_tready_cdc  : std_logic := '0';         
   signal ch1_s_axis_tdata_cdc   : std_logic_vector(511 downto 0) := (others => '0');   
   signal ch1_s_axis_tkeep_cdc   : std_logic_vector(63 downto 0) := (others => '0');     
   signal ch1_fifo_cnt           : std_logic_vector(31 downto 0) := (others => '0');    
   signal ch1_run                : std_logic := '0';  
   signal ch1_clear              : std_logic := '0';  
   signal ch1_clear_cdc          : std_logic := '0';  
   signal ch1_overflow           : std_logic := '0';
   signal ch1_overflow_cdc       : std_logic := '0';
   signal ch1_rstn               : std_logic := '0';  
   signal ch1_full               : std_logic := '0';  

   signal common_sync_cdc        : std_logic := '0';
   signal common_sync_d          : std_logic := '0';
   signal common_full            : std_logic := '0';
   signal common_overflow        : std_logic := '0';
   signal common_overflow_cdc    : std_logic := '0';
   signal common_clear           : std_logic := '0';
   signal common_clear_cdc       : std_logic := '0';
   signal common_running         : std_logic := '0';
   signal ch0_running            : std_logic := '0';
   signal ch1_running            : std_logic := '0';
  
   
 
   -- PCIE registerse
   -- channel 0
   signal ctrl_ch0_clear         : std_logic := '0';
   signal ctrl_ch0_overflow      : std_logic := '0';
   signal ctrl_ch0_word_cnt      : std_logic_vector(63 downto 0) := (others => '0');
   -- channel 
   signal ctrl_ch1_clear         : std_logic := '0';
   signal ctrl_ch1_overflow      : std_logic := '0';
   signal ctrl_ch1_word_cnt      : std_logic_vector(63 downto 0) := (others => '0');
   -- commo
   signal ctrl_common_sync       : std_logic := '0';
   signal ctrl_common_trig       : std_logic := '0';
   signal ctrl_common_trig_ch    : std_logic := '0';
   signal ctrl_common_trig_edge  : std_logic := '0'; -- 0 rising / 1 falling
   signal ctrl_common_trig_lvl   : std_logic_vector(13 downto 0) := (others => '0');
   signal ctrl_common_clear      : std_logic := '0';
   signal ctrl_common_overflow   : std_logic := '0';

   signal common_axis_tready     : std_logic := '0';    
   signal tmp_rst_cnt            : std_logic_vector(3 downto 0) := (others => '0');
   constant LC_TMP_RST_CNT       : std_logic_vector(tmp_rst_cnt'range) := (others => '1');
   constant LC_AXIS_ILA          : std_logic_vector(63 downto 0) := (others => '0');

   signal sync_cdc               : std_logic := '0';
   signal trig_cdc               : std_logic := '0';
   signal trig_ch_cdc            : std_logic := '0';
   signal trig_edge_cdc          : std_logic := '0';
   signal trig_lvl_cdc           : std_logic_vector(13 downto 0) := (others => '0');



   -- or vector
   function round_up (num  : natural)
      return natural is
      begin
         for i in 0 to 9 loop
            if 2**i > num then
               return 2**i;
            end if;
         end loop;
         return 2**10;
      end round_up;

   -- read count width
   function rd_cnt_width (num  : natural)
      return natural is
      begin
         for i in 0 to 9 loop
            if 2**i = num then
               return i + 1;
            end if;
         end loop;
         return 11;
      end rd_cnt_width;
      
   constant LC_FIFO_DEPTH           : natural := round_up(140); --round_up(g_batch_length);
   constant LC_RD_CNT_WIDTH         : natural := rd_cnt_width(LC_FIFO_DEPTH);

   
   signal m_axis_ch0_tvalid_i       : std_logic := '0';     
   signal m_axis_ch0_tlast_i        : std_logic := '0';     
   signal m_axis_ch0_tready_i       : std_logic := '0';         
   signal m_axis_ch0_tdata_i        : std_logic_vector(511 downto 0) := (others => '0');   
   signal m_axis_ch0_tkeep_i        : std_logic_vector(63 downto 0) := (others => '0');    

   signal m_axis_ch1_tvalid_i       : std_logic := '0';     
   signal m_axis_ch1_tlast_i        : std_logic := '0';     
   signal m_axis_ch1_tready_i       : std_logic := '0';         
   signal m_axis_ch1_tdata_i        : std_logic_vector(511 downto 0) := (others => '0');   
   signal m_axis_ch1_tkeep_i        : std_logic_vector(63 downto 0) := (others => '0');    

   signal ch0_stat_compl            : std_logic;
   signal ch1_stat_compl            : std_logic;
   signal ch0_stat_run              : std_logic;
   signal ch1_stat_run              : std_logic;
   
   signal ch0_abort_run              : std_logic;
   signal ch1_abort_run              : std_logic;
   
   signal trig_done                 : std_logic;
   

   signal ch1_s_axis_tdata_d        : std_logic_vector(511 downto 0) := (others => '0');   
   signal ch0_s_axis_tdata_d        : std_logic_vector(511 downto 0) := (others => '0');   
   
   signal ch1_last_sample           : std_logic_vector(15 downto 0) := (others => '0');   
   signal ch0_last_sample           : std_logic_vector(15 downto 0) := (others => '0');   
   
   signal ch0_s_axis_tvalid_d       : std_logic := '0';     
   signal ch1_s_axis_tvalid_d       : std_logic := '0';     
   
   signal ch0_ready                 : std_logic := '0';
   signal ch1_ready                 : std_logic := '0';
begin

   -- negation of incoming resets
   pcie_resetn <= not PCIE_RESET;
   data_resetn <= not DATA_RESET;

   ch0_stat_compl <= CH0_DMA_STATUS(1);
   ch1_stat_compl <= CH1_DMA_STATUS(1);
   ch0_stat_run <= CH0_DMA_STATUS(6);
   ch1_stat_run <= CH1_DMA_STATUS(6);

      
   data_delay_proces: process(DATA_CLK) 
      begin
         if rising_edge(DATA_CLK) then
            if DATA_RESET = '1' then 
               ch0_s_axis_tvalid_d <= '0';
               ch1_s_axis_tvalid_d <= '0';
               ch0_s_axis_tdata_d <= (others => '0');
               ch1_s_axis_tdata_d <= (others => '0');
            else
               ch0_s_axis_tvalid_d <= S_AXIS_CH0_TVALID;
               ch1_s_axis_tvalid_d <= S_AXIS_CH1_TVALID;
               ch0_s_axis_tdata_d <= S_AXIS_CH0_TDATA;
               ch1_s_axis_tdata_d <= S_AXIS_CH1_TDATA;
               
               if S_AXIS_CH0_TVALID = '1' then
                  ch0_last_sample <= S_AXIS_CH0_TDATA(511 downto 496);
               end if;
               
               if S_AXIS_CH1_TVALID = '1' then
                  ch1_last_sample <= S_AXIS_CH1_TDATA(511 downto 496);
               end if;
               
            end if;
         end if;
      end process;

   find_trig_ch0_proc: process(ch0_last_sample, S_AXIS_CH0_TDATA, S_AXIS_CH0_TVALID,
                           ch1_last_sample, S_AXIS_CH1_TDATA, S_AXIS_CH1_TVALID) 
                           
      variable samples     : std_logic_vector(S_AXIS_CH0_TDATA'range);
      variable prev_sample : std_logic_vector(ch0_last_sample'range);
      variable samples_valid  : std_logic;
      begin 
      
         trig_done <= '0';
      
         -- choose which channel should be used for trigger
         if trig_ch_cdc = '0' then
            samples := S_AXIS_CH0_TDATA;
            prev_sample := ch0_last_sample;
            samples_valid := S_AXIS_CH0_TVALID;
         else
            samples := S_AXIS_CH1_TDATA;
            prev_sample := ch1_last_sample;
            samples_valid := S_AXIS_CH1_TVALID;
         end if;
      
         for i in 0 to ch0_s_axis_tdata'length/16-1 loop
         
            if samples_valid = '1' then
               -- Looking for rising edge
               if trig_edge_cdc = '0' then    
                  if i = 0 then 
                     if prev_sample(trig_lvl_cdc'range) <= trig_lvl_cdc and
                        samples(13 downto 0) > trig_lvl_cdc then
                        trig_done <= '1';
                     end if;
                  else
                     if samples((i-1)*16+13 downto (i-1)*16) <= trig_lvl_cdc and
                        samples(i*16+13 downto i*16) > trig_lvl_cdc then
                        trig_done <= '1';
                     end if;
                  end if;
                  
               -- Looking for falling edge
               else
                  if i = 0 then 
                     if prev_sample(trig_lvl_cdc'range) >= trig_lvl_cdc and
                        samples(13 downto 0) < trig_lvl_cdc then
                        trig_done <= '1';
                     end if;
                  else
                     if samples((i-1)*16+13 downto (i-1)*16) >= trig_lvl_cdc and
                        samples(i*16+13 downto i*16) < trig_lvl_cdc then
                        trig_done <= '1';
                     end if;
                  end if;
               end if;
            end if;
            
         end loop;
      end process;
   
   data_clk_dom_channel_ctrl_proc: process(DATA_CLK)
   begin
      if rising_edge(DATA_CLK) then
         if DATA_RESET = '1' then
            ch0_fsm <= IDLE_ST;
            ch1_fsm <= IDLE_ST;
            ch0_running <= '0';
            ch0_overflow <= '0';
            ch1_running <= '0';
            ch1_overflow <= '0';
         else
          
            -- channel 0 control
            case ch0_fsm is
               when IDLE_ST =>
                  if (((sync_cdc and ch1_s_axis_tready) or not sync_cdc) and ch0_s_axis_tready) = '1' then
                     if ((trig_cdc and trig_done) or not trig_cdc) = '1' then
                        ch0_fsm <= RUN_ST;
                        ch0_running <= '1';
                     end if;
                  end if;
               when RUN_ST =>
                  if ch0_s_axis_tready = '0' then
                     ch0_fsm <= IDLE_ST;
                     ch0_running <= '0';
                  elsif ch0_full = '1' then
                     ch0_fsm <= OVERFLOW_ST;
                     ch0_running <= '0';
                     ch0_overflow <= '1';
                  end if;
               when OVERFLOW_ST => 
                  if ch0_clear_cdc = '1' then
                     ch0_fsm <= IDLE_ST;
                     ch0_overflow <= '0';
                  end if;
           end case;
           
           -- channel 1 control
           case ch1_fsm is
              when IDLE_ST =>
                 if (((sync_cdc and ch0_s_axis_tready) or not sync_cdc) and ch1_s_axis_tready) = '1' then
                  if ((trig_cdc and trig_done) or not trig_cdc) = '1' then
                     ch1_fsm <= RUN_ST;
                     ch1_running <= '1';
                    end if;
                 end if;
              when RUN_ST =>
                 if ch1_s_axis_tready = '0' then
                    ch1_fsm <= IDLE_ST;
                    ch1_running <= '0';
                 elsif ch1_full = '1' then
                    ch1_fsm <= OVERFLOW_ST;
                    ch1_running <= '0';
                    ch1_overflow <= '1';
                 end if;
              when OVERFLOW_ST => 
                 if ch1_clear_cdc = '1' then
                    ch1_fsm <= IDLE_ST;
                    ch1_overflow <= '0';
                 end if;
           end case;
        end if;
      end if;
   end process;

   -- is system ready?
   S_AXIS_CH0_TREADY <= ch0_s_axis_tready;
   S_AXIS_CH1_TREADY <= ch1_s_axis_tready;
   
   -- is data valid?
   ch0_s_axis_tvalid <= ch0_s_axis_tvalid_d and ch0_running;
   ch0_s_axis_tlast  <= '0'; -- Is never ending stream so TLAST should never be '1'
   ch0_s_axis_tdata  <= ch0_s_axis_tdata_d;
   ch0_s_axis_tkeep  <= (others => '1'); -- Is never ending stream so TKEEP should never be all '1'

   ch1_s_axis_tvalid <= ch1_s_axis_tvalid_d and ch1_running;
   ch1_s_axis_tlast  <= '0'; -- Is never ending stream so TLAST should never be '1'                             
   ch1_s_axis_tdata  <= ch1_s_axis_tdata_d;                              
   ch1_s_axis_tkeep  <= (others => '1'); -- Is never ending stream so TKEEP should never be all '1'                             
      
   pcie_data_clk_same_gen: if g_data_pcie_clk_same = false generate


      axis_data_fifo_ch1_same_clk: xpm_fifo_axis
      generic map(
        CLOCKING_MODE         =>  "independent_clock",
        FIFO_DEPTH            =>  LC_FIFO_DEPTH,
        TDATA_WIDTH           =>  512,
        USE_ADV_FEATURES      => "1408",
        RD_DATA_COUNT_WIDTH   =>  LC_RD_CNT_WIDTH
      )
      port map(
        s_aresetn             => ch1_rstn,
        m_aclk                => PCIE_CLK,
        s_aclk                => DATA_CLK,
        s_axis_tvalid         => ch1_s_axis_tvalid,
        s_axis_tready         => ch1_s_axis_tready,
        s_axis_tdata          => ch1_s_axis_tdata, 
        s_axis_tstrb          => x"ffffffffffffffff",      
        s_axis_tkeep          => ch1_s_axis_tkeep, 
        s_axis_tlast          => ch1_s_axis_tlast, 
        s_axis_tid            => "0",                     
        s_axis_tdest          => "0",                     
        s_axis_tuser          => "0",                     
        m_axis_tvalid         => ch1_s_axis_tvalid_cdc,
        m_axis_tready         => ch1_s_axis_tready_cdc,
        m_axis_tdata          => ch1_s_axis_tdata_cdc,
        m_axis_tstrb          => open,
        m_axis_tkeep          => ch1_s_axis_tkeep_cdc,
        m_axis_tlast          => ch1_s_axis_tlast_cdc,
        m_axis_tid            => open,
        m_axis_tdest          => open,
        m_axis_tuser          => open,
        almost_full_axis      => ch1_full,
        rd_data_count_axis    => ch1_fifo_cnt(LC_RD_CNT_WIDTH-1 downto 0),
        injectsbiterr_axis    => '0',
        injectdbiterr_axis    => '0',
        sbiterr_axis          => open,
        dbiterr_axis          => open
      );
   
      axis_data_fifo_ch0_same_clk: xpm_fifo_axis
      generic map(
        CLOCKING_MODE         =>  "independent_clock",
        FIFO_DEPTH            =>  LC_FIFO_DEPTH,
        TDATA_WIDTH           =>  512,
        USE_ADV_FEATURES      => "1408",
        RD_DATA_COUNT_WIDTH   =>  LC_RD_CNT_WIDTH
      )
      port map(
        s_aresetn             => ch0_rstn,
        m_aclk                => PCIE_CLK,
        s_aclk                => DATA_CLK,
        s_axis_tvalid         => ch0_s_axis_tvalid,
        s_axis_tready         => ch0_s_axis_tready,
        s_axis_tdata          => ch0_s_axis_tdata, 
        s_axis_tstrb          => x"ffffffffffffffff",      
        s_axis_tkeep          => ch0_s_axis_tkeep, 
        s_axis_tlast          => ch0_s_axis_tlast, 
        s_axis_tid            => "0",                     
        s_axis_tdest          => "0",                     
        s_axis_tuser          => "0",                     
        m_axis_tvalid         => ch0_s_axis_tvalid_cdc,
        m_axis_tready         => ch0_s_axis_tready_cdc,
        m_axis_tdata          => ch0_s_axis_tdata_cdc,
        m_axis_tstrb          => open,
        m_axis_tkeep          => ch0_s_axis_tkeep_cdc,
        m_axis_tlast          => ch0_s_axis_tlast_cdc,
        m_axis_tid            => open,
        m_axis_tdest          => open,
        m_axis_tuser          => open,
        almost_full_axis      => ch0_full,
        rd_data_count_axis    => ch0_fifo_cnt(LC_RD_CNT_WIDTH-1 downto 0),
        injectsbiterr_axis    => '0',
        injectdbiterr_axis    => '0',
        sbiterr_axis          => open,
        dbiterr_axis          => open
      );   
   
      sync_cdc_inst: entity work.xcdc_bit
      port map(
         RSTN        => data_resetn,
         DCLK        => DATA_CLK,
         DATA_IN     => ctrl_common_sync,
         DATA_OUT    => sync_cdc
      );
   
      trig_cdc_inst: entity work.xcdc_bit
      port map(
         RSTN        => data_resetn,
         DCLK        => DATA_CLK,
         DATA_IN     => ctrl_common_trig,
         DATA_OUT    => trig_cdc
      );
      
      trig_channel_cdc_inst: entity work.xcdc_bit
      port map(
         RSTN        => data_resetn,
         DCLK        => DATA_CLK,
         DATA_IN     => ctrl_common_trig_ch,
         DATA_OUT    => trig_ch_cdc
      );
       
      trig_edge_cdc_inst: entity work.xcdc_bit
      port map(
         RSTN        => data_resetn,
         DCLK        => DATA_CLK,
         DATA_IN     => ctrl_common_trig_edge,
         DATA_OUT    => trig_edge_cdc
      );
             
      trig_level_cdc_inst: entity work.xcdc
      generic map(
         g_data_width   => ctrl_common_trig_lvl'length
      )
      port map(
         RSTN        => data_resetn,
         DCLK        => DATA_CLK,
         DATA_IN     => ctrl_common_trig_lvl,
         DATA_OUT    => trig_lvl_cdc
      );
                           
      ch0_clear_cdc_inst: entity work.xcdc_bit
      port map(
         RSTN        => data_resetn,
         DCLK        => DATA_CLK,
         DATA_IN     => ch0_clear,
         DATA_OUT    => ch0_clear_cdc
      );

      ch1_clear_cdc_inst: entity work.xcdc_bit
      port map(
         RSTN        => data_resetn,
         DCLK        => DATA_CLK,
         DATA_IN     => ch1_clear,
         DATA_OUT    => ch1_clear_cdc
      );

      ch0_overflow_cdc_inst: entity work.xcdc_bit
      port map(
         RSTN        => pcie_resetn,
         DCLK        => PCIE_CLK,
         DATA_IN     => ch0_overflow,
         DATA_OUT    => ch0_overflow_cdc
      );
      
      ch1_overflow_cdc_inst: entity work.xcdc_bit
      port map(
         RSTN        => pcie_resetn,
         DCLK        => PCIE_CLK,
         DATA_IN     => ch1_overflow,
         DATA_OUT    => ch1_overflow_cdc
      );
   end generate;
   
   pcie_data_clk_different_gen: if g_data_pcie_clk_same = true generate

      axis_data_fifo_ch1_same_clk: xpm_fifo_axis
      generic map(
        CLOCKING_MODE         =>  "common_clock",
        FIFO_DEPTH            =>  LC_FIFO_DEPTH,
        TDATA_WIDTH           =>  512,
        USE_ADV_FEATURES      => "1408",
        RD_DATA_COUNT_WIDTH   =>  LC_RD_CNT_WIDTH
      )
      port map(
        s_aresetn             => ch1_rstn,
        m_aclk                => PCIE_CLK,
        s_aclk                => PCIE_CLK,
        s_axis_tvalid         => ch1_s_axis_tvalid,
        s_axis_tready         => ch1_s_axis_tready,
        s_axis_tdata          => ch1_s_axis_tdata, 
        s_axis_tstrb          => x"ffffffffffffffff",      
        s_axis_tkeep          => ch1_s_axis_tkeep, 
        s_axis_tlast          => ch1_s_axis_tlast, 
        s_axis_tid            => "0",                     
        s_axis_tdest          => "0",                     
        s_axis_tuser          => "0",                     
        m_axis_tvalid         => ch1_s_axis_tvalid_cdc,
        m_axis_tready         => ch1_s_axis_tready_cdc,
        m_axis_tdata          => ch1_s_axis_tdata_cdc,
        m_axis_tstrb          => open,
        m_axis_tkeep          => ch1_s_axis_tkeep_cdc,
        m_axis_tlast          => ch1_s_axis_tlast_cdc,
        m_axis_tid            => open,
        m_axis_tdest          => open,
        m_axis_tuser          => open,
        almost_full_axis      => ch1_full,
        rd_data_count_axis    => ch1_fifo_cnt(LC_RD_CNT_WIDTH-1 downto 0),
        injectsbiterr_axis    => '0',
        injectdbiterr_axis    => '0',
        sbiterr_axis          => open,
        dbiterr_axis          => open
      );
   
      axis_data_fifo_ch0_same_clk: xpm_fifo_axis
      generic map(
        CLOCKING_MODE         =>  "common_clock",
        FIFO_DEPTH            =>  LC_FIFO_DEPTH,
        TDATA_WIDTH           =>  512,
        USE_ADV_FEATURES      => "1408",
        RD_DATA_COUNT_WIDTH   =>  LC_RD_CNT_WIDTH
      )
      port map(
        s_aresetn             => ch0_rstn,
        m_aclk                => PCIE_CLK,
        s_aclk                => PCIE_CLK,
        s_axis_tvalid         => ch0_s_axis_tvalid,
        s_axis_tready         => ch0_s_axis_tready,
        s_axis_tdata          => ch0_s_axis_tdata, 
        s_axis_tstrb          => x"ffffffffffffffff",      
        s_axis_tkeep          => ch0_s_axis_tkeep, 
        s_axis_tlast          => ch0_s_axis_tlast, 
        s_axis_tid            => "0",                     
        s_axis_tdest          => "0",                     
        s_axis_tuser          => "0",                     
        m_axis_tvalid         => ch0_s_axis_tvalid_cdc,
        m_axis_tready         => ch0_s_axis_tready_cdc,
        m_axis_tdata          => ch0_s_axis_tdata_cdc,
        m_axis_tstrb          => open,
        m_axis_tkeep          => ch0_s_axis_tkeep_cdc,
        m_axis_tlast          => ch0_s_axis_tlast_cdc,
        m_axis_tid            => open,
        m_axis_tdest          => open,
        m_axis_tuser          => open,
        almost_full_axis      => ch0_full,
        rd_data_count_axis    => ch0_fifo_cnt(LC_RD_CNT_WIDTH-1 downto 0),
        injectsbiterr_axis    => '0',
        injectdbiterr_axis    => '0',
        sbiterr_axis          => open,
        dbiterr_axis          => open
      );

      sync_cdc <= ctrl_common_sync;
      ch0_clear_cdc <= ch0_clear;
      ch1_clear_cdc <= ch1_clear;
      ch0_overflow_cdc <= ch0_overflow;
      ch1_overflow_cdc <= ch1_overflow;
      common_overflow_cdc <= ch0_overflow_cdc or ch1_overflow_cdc;
      trig_cdc <= ctrl_common_trig;
      trig_ch_cdc <= ctrl_common_trig_ch;
      trig_edge_cdc <= ctrl_common_trig_edge;
      trig_lvl_cdc <= ctrl_common_trig_lvl;
      
   end generate;
   
   pcie_channel_ready_proc: process(PCIE_CLK)
      begin  
         if rising_edge(PCIE_CLK) then
            if PCIE_RESET = '1' then 
               ch0_ready <= '0';
               ch1_ready <= '0';
            else 
               if ch0_abort_run = '1' then 
                  ch0_ready <= '0';
               elsif (ch0_stat_run and M_AXIS_CH0_TREADY) = '1' then
                  ch0_ready <= '1';
               end if;
               
               if ch1_abort_run = '1' then 
                  ch1_ready <= '0';
               elsif (ch1_stat_run and M_AXIS_CH1_TREADY) = '1' then
                  ch1_ready <= '1';
               end if;
            end if;
         end if;
      end process;
   
   ch0_abort_run <= ch0_stat_compl or ch0_clear;
   ch1_abort_run <= ch1_stat_compl or ch1_clear;
   
   ch0_control_inst: entity work.channel_control
   generic map(
      -- if there is need to finish packet of packet length
      -- before it's stopped
      g_packet_mode     => false,
      -- should be MAX_UDP_PACKET - 1 (because of RoCE headers)
      g_batch_length    => g_batch_length,
      g_insert_seq_num  => false,
      g_abort_en        => true
   )
   port map(
      CLK               => PCIE_CLK,
      RESET             => PCIE_RESET,
      -- Control signals
      RUN               => ch0_run,
      FIFO_RSTN         => ch0_rstn,
      OVERFLOW          => ch0_overflow_cdc,
      ABORT             => ch0_abort_run,
      -- Status signals
      SEND_CNT          => ctrl_ch0_word_cnt,
      -- Data input
      S_AXIS_TVALID     => ch0_s_axis_tvalid_cdc,
      S_AXIS_TLAST      => ch0_s_axis_tlast_cdc, 
      S_AXIS_TREADY     => ch0_s_axis_tready_cdc,
      S_AXIS_TDATA      => ch0_s_axis_tdata_cdc, 
      S_AXIS_TKEEP      => ch0_s_axis_tkeep_cdc, 
      FIFO_DATA_CNT     => ch0_fifo_cnt,
      -- Data output
      M_AXIS_TVALID     => m_axis_ch0_tvalid_i,
      M_AXIS_TLAST      => m_axis_ch0_tlast_i,
      M_AXIS_TREADY     => M_AXIS_CH0_TREADY,
      M_AXIS_TDATA      => m_axis_ch0_tdata_i,
      M_AXIS_TKEEP      => m_axis_ch0_tkeep_i
    );

   ch1_control_inst: entity work.channel_control
   generic map(
      -- if there is need to finish packet of packet length
      -- before it's stopped
      g_packet_mode     => false,
      -- should be MAX_UDP_PACKET - 1 (because of RoCE headers)
      g_batch_length    => g_batch_length,
      g_insert_seq_num  => false,
      g_abort_en        => true
   )
   port map(
      CLK               => PCIE_CLK,
      RESET             => PCIE_RESET,
      -- Control signals
      RUN               => ch1_run,
      FIFO_RSTN         => ch1_rstn,
      OVERFLOW          => ch1_overflow_cdc,
      ABORT             => ch1_abort_run,
      -- Status signals
      SEND_CNT          => ctrl_ch1_word_cnt,
      -- Data input
      S_AXIS_TVALID     => ch1_s_axis_tvalid_cdc,
      S_AXIS_TLAST      => ch1_s_axis_tlast_cdc, 
      S_AXIS_TREADY     => ch1_s_axis_tready_cdc,
      S_AXIS_TDATA      => ch1_s_axis_tdata_cdc, 
      S_AXIS_TKEEP      => ch1_s_axis_tkeep_cdc,  
      FIFO_DATA_CNT     => ch1_fifo_cnt,
      -- Data output
      M_AXIS_TVALID     => m_axis_ch1_tvalid_i,
      M_AXIS_TLAST      => m_axis_ch1_tlast_i,
      M_AXIS_TREADY     => M_AXIS_CH1_TREADY,
      M_AXIS_TDATA      => m_axis_ch1_tdata_i, 
      M_AXIS_TKEEP      => m_axis_ch1_tkeep_i 
    );

   
   free_to_sync_mode_rst_proc: process(PCIE_CLK)   
      begin
         if rising_edge(PCIE_CLK) then
            if PCIE_RESET = '1' then 
               tmp_rst_cnt <= (others => '0');
               common_sync_d <= '1';
            else
               common_sync_d <= ctrl_common_sync;
               if tmp_rst_cnt(tmp_rst_cnt'left) = '0' then
                  tmp_rst_cnt <= tmp_rst_cnt + 1;   
               elsif (not common_sync_d and ctrl_common_sync) = '1' then 
                  tmp_rst_cnt <= (others => '0');
               end if;
            end if;
         end if;
      end process;

   -- channel run if synchronization is disabled and run is enabled
   -- or if synchronization is enabled and synchronized run is enabled
   ch0_run <= ch0_ready and not ch0_clear and not ch0_stat_compl;
   ch1_run <= ch1_ready and not ch1_clear and not ch1_stat_compl;
   
   -- Channel clear if synchnonization is disabled and channel clear enabled
   -- or if synchronization is enabled and synchnonized clear is enabled too
   ch0_clear <= (ctrl_ch0_clear and not ctrl_common_sync) or common_clear;
   ch1_clear <= (ctrl_ch1_clear and not ctrl_common_sync) or common_clear;
   
   -- synchronized clear should be generate in case there was jump from free 
   -- mode to synchronized without clear - to make channels really synchronized
   common_clear <= ctrl_common_sync and (ctrl_common_clear or not tmp_rst_cnt(tmp_rst_cnt'left) or not common_sync_d);
   
   -- overflow feedback from data clock domain - set appropriate registers
   ctrl_ch0_overflow <= ch0_overflow_cdc;
   ctrl_ch1_overflow <= ch1_overflow_cdc;
   ctrl_common_overflow <= common_overflow_cdc;

   pcie_axi_registers_inst: entity work.axi_registers
	generic map(
		g_s_axi_addr_width => 10
	)
	port map(
		-- Global Clock Signal
		S_AXI_ACLK         => PCIE_CLK,
		-- Global Reset Signal. This Signal is Active LOW
		S_AXI_ARESETN      => pcie_resetn,
		-- Write address (issued by master, acceped by Slave)
		S_AXI_AWADDR	=> S_AXI_AWADDR,	
		S_AXI_AWPROT	=> S_AXI_AWPROT,	
		S_AXI_AWVALID	=> S_AXI_AWVALID,
		S_AXI_AWREADY	=> S_AXI_AWREADY,
		S_AXI_WDATA	   => S_AXI_WDATA,	 
		S_AXI_WSTRB	   => S_AXI_WSTRB,	 
		S_AXI_WVALID	=> S_AXI_WVALID,	
		S_AXI_WREADY	=> S_AXI_WREADY,	
		S_AXI_BRESP	   => S_AXI_BRESP,	 
		S_AXI_BVALID	=> S_AXI_BVALID,	
		S_AXI_BREADY	=> S_AXI_BREADY,	
		S_AXI_ARADDR	=> S_AXI_ARADDR, 
		S_AXI_ARPROT	=> S_AXI_ARPROT,	
		S_AXI_ARVALID	=> S_AXI_ARVALID,
		S_AXI_ARREADY	=> S_AXI_ARREADY,
		S_AXI_RDATA	   => S_AXI_RDATA,  
		S_AXI_RRESP	   => S_AXI_RRESP,  
		S_AXI_RVALID	=> S_AXI_RVALID,	
		S_AXI_RREADY	=> S_AXI_RREADY,	
		
		-- PCIE registerse
		-- channel 0
		CH0_CLEAR       => ctrl_ch0_clear,      
		CH0_OVERFLOW    => ctrl_ch0_overflow,   
		CH0_WORD_CNT    => ctrl_ch0_word_cnt,   
		-- channel 1               
		CH1_CLEAR       => ctrl_ch1_clear,      
		CH1_OVERFLOW    => ctrl_ch1_overflow,   
		CH1_WORD_CNT    => ctrl_ch1_word_cnt,   
		-- common                  
		COMMON_SYNC     => ctrl_common_sync,    
		COMMON_CLEAR    => ctrl_common_clear,  
		COMMON_TRIG     => ctrl_common_trig,   
		COMMON_TRIG_CH  => ctrl_common_trig_ch,  
		COMMON_TRIG_LVL => ctrl_common_trig_lvl,  
		COMMON_TRIG_EDGE => ctrl_common_trig_edge, 
		COMMON_OVERFLOW => ctrl_common_overflow
	);
   
   -- pcie interrupt in case of fifo overflow
   INTERRUPT <= ctrl_ch0_overflow or ctrl_ch1_overflow or ctrl_common_overflow;

   M_AXIS_CH0_TVALID <= m_axis_ch0_tvalid_i;
   M_AXIS_CH0_TLAST  <= m_axis_ch0_tlast_i;
   M_AXIS_CH0_TDATA  <= m_axis_ch0_tdata_i;
   M_AXIS_CH0_TKEEP  <= m_axis_ch0_tkeep_i;
   
   M_AXIS_CH1_TVALID <= m_axis_ch1_tvalid_i;
   M_AXIS_CH1_TLAST  <= m_axis_ch1_tlast_i;
   M_AXIS_CH1_TDATA  <= m_axis_ch1_tdata_i;
   M_AXIS_CH1_TKEEP  <= m_axis_ch1_tkeep_i;


   debug_gen: if g_debug = true generate
   
      pcie_adc_app_ch0_fifo_axis_ila: entity work.axis_ila
         port map(
            clk         => PCIE_CLK,
            probe0(0)   => ch0_s_axis_tvalid_cdc,
            probe1      => ch0_s_axis_tdata_cdc,
            probe2      => ch0_s_axis_tkeep_cdc,
            probe3(0)   => ch0_s_axis_tready_cdc,
            probe4(0)   => ch0_s_axis_tlast_cdc,
            probe5(0)   => ch0_run,
            probe6      => LC_AXIS_ILA(63 downto 18) & ctrl_common_trig_lvl & ctrl_common_trig & 
                        ctrl_common_trig_ch & ctrl_common_trig_edge & ch0_overflow_cdc,
            probe7(0)   => ch0_rstn,
            probe8(0)   => ch0_stat_compl
         );
         
      pcie_adc_app_ch1_fifo_axis_ila: entity work.axis_ila
         port map(
            clk         => PCIE_CLK,
            probe0(0)   => ch1_s_axis_tvalid_cdc,
            probe1      => ch1_s_axis_tdata_cdc,
            probe2      => ch1_s_axis_tkeep_cdc,
            probe3(0)   => ch1_s_axis_tready_cdc,
            probe4(0)   => ch1_s_axis_tlast_cdc,
            probe5(0)   => ch1_run,
            probe6      => LC_AXIS_ILA(63 downto 18) & ctrl_common_trig_lvl & ctrl_common_trig & 
                        ctrl_common_trig_ch & ctrl_common_trig_edge & ch1_overflow_cdc,
            probe7(0)   => ch1_rstn,
            probe8(0)   => ch1_stat_compl
         );
         
      pcie_adc_app_ch0_axis_ila: entity work.axis_ila
         port map(
            clk         => PCIE_CLK,
            probe0(0)   => m_axis_ch0_tvalid_i,
            probe1      => m_axis_ch0_tdata_i,
            probe2      => m_axis_ch0_tkeep_i,
            probe3(0)   => M_AXIS_CH0_TREADY,
            probe4(0)   => m_axis_ch0_tlast_i,
            probe5(0)   => '0',
            probe6      => LC_AXIS_ILA,
            probe7(0)   => '0',
            probe8(0)   => '0'
         );
         
      pcie_adc_app_ch1_axis_ila: entity work.axis_ila
         port map(
            clk         => PCIE_CLK,
            probe0(0)   => m_axis_ch1_tvalid_i,
            probe1      => m_axis_ch1_tdata_i,
            probe2      => m_axis_ch1_tkeep_i,
            probe3(0)   => M_AXIS_CH1_TREADY,
            probe4(0)   => m_axis_ch1_tlast_i,
            probe5(0)   => '0',
            probe6      => LC_AXIS_ILA,
            probe7(0)   => '0',
            probe8(0)   => '0'
         );
 
      pcie_adc_app_vio: entity work.input_vio
         port map(
            clk            => PCIE_CLK,
            probe_in0(0)   => ch0_stat_run,
            probe_in1(0)   => ctrl_ch0_clear,
            probe_in2(0)   => ctrl_ch0_overflow,
            probe_in3(0)   => ch1_stat_run,
            probe_in4(0)   => ctrl_ch1_clear,
            probe_in5(0)   => ctrl_ch1_overflow,
            probe_in6(0)   => ctrl_common_sync,   
            probe_in7(0)   => ctrl_common_trig,    
            probe_in8(0)   => ctrl_common_clear,  
            probe_in9(0)   => ctrl_common_overflow,
            probe_in10(0)  => ch0_run,
            probe_in11(0)  => ch1_run,
            probe_in12(0)  => ch0_clear,
            probe_in13(0)  => ch1_clear,
            probe_in14(0)  => common_clear,
            probe_in15(0)  => ch0_rstn,
            probe_in16(0)  => ch1_rstn
         );
            
   end generate;
   
end;
