library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

use work.typedef_package.all;

-- Xillybus Link
-- This entity is the link between the xillybus IP core, and the rest of the FPGA logic.
-- This entity contains asynchronous FIFOs and RAMs handling the clock domain crossing between
-- the xillybus and the FPGA logic.

-- The current xillybus IP core is configured for a 32-bit FPGA-to-HOST FIFO used for sending debug
-- stats to the host every "DEBUG_SEND_INTERVAL" clock cycles, and 32-bit HOST-to-FPGA 16-bit address
-- memory for configuration. The 32-bit wide 16-bit address interface is converted into 2 32-bit wide
-- 15-bit address RAMs, effectively increasing the internal config to 64-bits.

-- NOTE: It has to be made sure that the read and write port to not access the smae address at the same 
-- time

entity xillybus_link is
    generic (
        DEBUG_SEND_INTERVAL : integer := 20000000
    );
    port (
        --***XILLYBUS IF***
        xillybus_clk    : in std_logic;
        -- FIFO
        fifo_rd_data    : out std_logic_vector(DEBUG_FIFO_DATA_WIDTH-1 downto 0);
        fifo_ren        : in std_logic;
        fifo_empty      : out std_logic;
        -- RAM
        mem_addr        : in std_logic_vector(CONFIG_MEM_ADDR_WIDTH downto 0);
        mem_wr_data     : in std_logic_vector(CONFIG_MEM_DATA_WIDTH-1 downto 0);
        mem_wen         : in std_logic;
        --***FPGA IF***
        fpga_clk        : in std_logic;
        reset           : in std_logic;
        -- Dynamic Configuration
        config_addr     : in std_logic_vector(CONFIG_MEM_ADDR_WIDTH-1 downto 0);
        config_ren      : in std_logic;
        config_data     : out std_logic_vector(CONFIG_DATA_WIDTH-1 downto 0);
        -- DEBUG
        adc_data1_max   : in std_logic_vector(ADC_DATA_WIDTH-1 downto 0);
        adc_data2_max   : in std_logic_vector(ADC_DATA_WIDTH-1 downto 0);
        scaler_max      : in std_logic_vector(DAC_DATA_WIDTH-1 downto 0);
        dac_max         : in std_logic_vector(DAC_DATA_WIDTH-1 downto 0)
    );
end entity;

architecture arch of xillybus_link is
    
    --*****COMPONENT DECLARATION*****
    component dual_port_ram is
        generic (
            ADDR_WIDTH      : integer := 8;
            DATA_WIDTH      : integer := 16
        );
        port (
            wr_clk      : in std_logic;
            rd_clk      : in std_logic;
            rd_addr     : in std_logic_vector(ADDR_WIDTH-1 downto 0);
            wr_addr     : in std_logic_vector(ADDR_WIDTH-1 downto 0);
            wen         : in std_logic;
            ren         : in std_logic;
            wr_data     : in std_logic_vector(DATA_WIDTH-1 downto 0);
            rd_data     : out std_logic_vector(DATA_WIDTH-1 downto 0)
        );
    end component;
    
    component async_fifo is
        generic (
            DATA_WIDTH          : integer := 32;
            FIFO_DEPTH          : integer := 16;    -- (16 - 4194304)
            PROG_EMPTY_THRESH   : integer := 3;     -- MIN:3    MAX:(FIFO_DEPTH-3)
            PROG_FULL_THRESH    : integer := 13     -- MIN:3+CDC_SYNC_STAGES    MAX:(FIFO_DEPTH-3)
        );
        port (
            wr_clk      : in std_logic;
            rd_clk      : in std_logic;
            reset       : in std_logic; --wr_clk synchronous
            wen         : in std_logic;
            ren         : in std_logic;
            empty       : out std_logic;
            almost_empty: out std_logic;
            prog_empty  : out std_logic;
            full        : out std_logic;
            almost_full : out std_logic;
            prog_full   : out std_logic;
            wr_data     : in std_logic_vector(DATA_WIDTH-1 downto 0);
            rd_data     : out std_logic_vector(DATA_WIDTH-1 downto 0)
        );
    end component;
    
    --*****TYPE DEFINITION*****
     type STAGE_TYPE is (WAIT_COUNTER, WAIT_FIFO, SEND1, SEND2);
    
    --*****SIGNAL DEFINITION*****
    signal cnt, cnt_next : integer range 0 to DEBUG_SEND_INTERVAL := 0;
    signal stage, stage_next : STAGE_TYPE := WAIT_COUNTER;
    signal fifo_almost_full, fifo_wen : std_logic := '0';
    signal fifo_wr_data : std_logic_vector(DEBUG_FIFO_DATA_WIDTH-1 downto 0) := (others => '0');
    
begin
    
    --*****COMPONENT INSTANTIATION*****
    config_mem_high : dual_port_ram
        generic map (
            ADDR_WIDTH      => CONFIG_MEM_ADDR_WIDTH,
            DATA_WIDTH      => CONFIG_MEM_DATA_WIDTH
        )
        port map (
            wr_clk      => xillybus_clk,
            rd_clk      => fpga_clk,
            rd_addr     => config_addr,
            wr_addr     => mem_addr(CONFIG_MEM_ADDR_WIDTH downto 1),
            wen         => mem_wen and (mem_addr(0)), -- Only odd adresses
            ren         => config_ren,
            wr_data     => mem_wr_data,
            rd_data     => config_data(CONFIG_DATA_WIDTH-1 downto CONFIG_MEM_DATA_WIDTH)
        );
    
    config_mem_low : dual_port_ram
        generic map (
            ADDR_WIDTH      => CONFIG_MEM_ADDR_WIDTH,
            DATA_WIDTH      => CONFIG_MEM_DATA_WIDTH
        )
        port map (
            wr_clk      => xillybus_clk,
            rd_clk      => fpga_clk,
            rd_addr     => config_addr,
            wr_addr     => mem_addr(CONFIG_MEM_ADDR_WIDTH downto 1),
            wen         => mem_wen and (not mem_addr(0)), -- Only even adresses
            ren         => config_ren,
            wr_data     => mem_wr_data,
            rd_data     => config_data(CONFIG_MEM_DATA_WIDTH-1 downto 0)
        );
    
    
    debug_fifo : async_fifo
        generic map (
            DATA_WIDTH  => DEBUG_FIFO_DATA_WIDTH,
            FIFO_DEPTH  => DEBUG_FIFO_DEPTH
        )
        port map(
            wr_clk      => fpga_clk,
            rd_clk      => xillybus_clk,
            reset       => reset,
            wen         => fifo_wen,
            ren         => fifo_ren,
            empty       => fifo_empty,
            almost_empty=> open,
            prog_empty  => open,
            full        => open,
            almost_full => fifo_almost_full,
            prog_full   => open,
            wr_data     => fifo_wr_data,
            rd_data     => fifo_rd_data
        );
    
    debug_prc : process(all)
    begin
        -- DEFAULT VALUES
        stage_next  <= stage;
        cnt_next    <= cnt;
        fifo_wen    <= '0';
        fifo_wr_data<= (others => '0');
        
        case (stage) is
            when WAIT_COUNTER => 
                if(cnt = DEBUG_SEND_INTERVAL) then
                    if(fifo_almost_full = '1') then
                        stage_next <= WAIT_FIFO;
                    else
                        stage_next <= SEND1;
                    end if;
                else
                    cnt_next    <= cnt + 1;
                end if;
            when WAIT_FIFO =>
                if(fifo_almost_full = '0') then
                    stage_next  <= SEND1;
                end if;
            when SEND1 =>
                fifo_wen                    <= '1';
                fifo_wr_data(11 downto 0)   <= adc_data1_max;
                fifo_wr_data(27 downto 16)  <= adc_data2_max;
                
                stage_next                  <= SEND2;
            when SEND2 =>
                fifo_wen                    <= '1';
                fifo_wr_data(15 downto 0)   <= dac_max;
                fifo_wr_data(31 downto 16)  <= scaler_max;
                
                stage_next                  <= WAIT_COUNTER;
                cnt_next                    <= 0;
        end case;
    end process;
   
   
    sync : process(fpga_clk)
    begin
        if rising_edge(fpga_clk) then
            if(reset = '1') then
                stage   <= WAIT_COUNTER;
                cnt     <= 0;
            else
                stage   <= stage_next;
                cnt     <= cnt_next;
            end if;
        end if;
    end process;
    
end architecture;