-------------------------------------------------------------------------------
--
-- 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;
use ieee.numeric_std.all;

entity axi_registers is
	generic (
		g_s_axi_addr_width    : integer	:= 9
	);
	port (
		-- Global Clock Signal
		S_AXI_ACLK            : in std_logic;
		-- Global Reset Signal. This Signal is Active LOW
		S_AXI_ARESETN         : in std_logic;
		-- Write address (issued by master, acceped by Slave)
		S_AXI_AWADDR          : in std_logic_vector(g_s_axi_addr_width-1 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(g_s_axi_addr_width-1 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;
		
		-- PCIE registerse
		-- channel 0
		CH0_CLEAR             : out std_logic;
		CH0_OVERFLOW          : in  std_logic;
		CH0_WORD_CNT          : in  std_logic_vector(63 downto 0);
		-- channel 1
		CH1_CLEAR             : out std_logic;
		CH1_OVERFLOW          : in  std_logic;
		CH1_WORD_CNT          : in  std_logic_vector(63 downto 0);
		-- common
		COMMON_SYNC           : out std_logic;
		COMMON_CLEAR          : out std_logic;
		COMMON_TRIG           : out std_logic;
		COMMON_TRIG_CH        : out std_logic;
		COMMON_TRIG_EDGE      : out std_logic;
		COMMON_TRIG_LVL       : out std_logic_vector(13 downto 0);
		COMMON_OVERFLOW       : in  std_logic
	);
end;

architecture synthesis of axi_registers is

   function write2register (reg     : std_logic_vector(31 downto 0);
                            data    : std_logic_vector(31 downto 0); 
                            strb    : std_logic_vector(3 downto 0))
      return std_logic_vector is
      variable ret : std_logic_vector(reg'range) := reg;
   begin
      for byte_index in 0 to (data'length/8-1) loop
	     if ( strb(byte_index) = '1' ) then
	        ret(byte_index*8+7 downto byte_index*8) := data(byte_index*8+7 downto byte_index*8);
	     end if;
	  end loop;
	  return ret;
   end write2register;

   
   -- channel control register
      -- clear channel
   -- channel status register
      -- send counter
      -- overflow
   -- common control register
      -- synchronized (use this register insted of channel registers)
      -- clear all
      -- trigger on/off
      -- trigger edge
      -- trigger level
   -- common status register
      -- overflow (set if one or more channels are overflowed)
      -- waiting for trigger
   
   -- Registers channel 0
   signal ch0_control_reg     : std_logic_vector(31 downto 0) := (others => '0');
   signal ch0_status_reg0     : std_logic_vector(31 downto 0) := (others => '0');
   signal ch0_status_reg1     : std_logic_vector(31 downto 0) := (others => '0');
   signal ch0_status_reg2     : std_logic_vector(31 downto 0) := (others => '0');
   -- Registers channel 1
   signal ch1_control_reg     : std_logic_vector(31 downto 0) := (others => '0');
   signal ch1_status_reg0     : std_logic_vector(31 downto 0) := (others => '0');
   signal ch1_status_reg1     : std_logic_vector(31 downto 0) := (others => '0');
   signal ch1_status_reg2     : std_logic_vector(31 downto 0) := (others => '0');
   -- Registers common for all channels
   signal common_control_reg  : std_logic_vector(31 downto 0) := (others => '0');
   signal common_status_reg   : std_logic_vector(31 downto 0) := (others => '0');
   
   
   constant register_width    : integer := 32;
   
	-- AXI4LITE signals
	signal axi_awaddr	: std_logic_vector(g_s_axi_addr_width-1 downto 0);
	signal axi_awready	: std_logic;
	signal axi_wready	: std_logic;
	signal axi_bresp	: std_logic_vector(1 downto 0);
	signal axi_bvalid	: std_logic;
	signal axi_araddr	: std_logic_vector(g_s_axi_addr_width-1 downto 0);
	signal axi_arready	: std_logic;
	signal axi_rdata	: std_logic_vector(register_width-1 downto 0);
	signal axi_rresp	: std_logic_vector(1 downto 0);
	signal axi_rvalid	: std_logic;

	-- Example-specific design signals
	-- local parameter for addressing 32 bit / 64 bit C_S_AXI_DATA_WIDTH
	-- ADDR_LSB is used for addressing 32/64 bit registers/memories
	-- ADDR_LSB = 2 for 32 bits (n downto 2)
	-- ADDR_LSB = 3 for 64 bits (n downto 3)
	constant ADDR_LSB  : integer := (register_width/32)+ 1;
	constant OPT_MEM_ADDR_BITS : integer := g_s_axi_addr_width - ADDR_LSB;

	signal slv_reg_rden	: std_logic;
	signal slv_reg_wren	: std_logic;
	signal reg_data_out	:std_logic_vector(register_width-1 downto 0);
	signal byte_index	: integer;
	signal aw_en	: std_logic;
	
	constant LC_ID_REG        : std_logic_vector(31 downto 0) := x"DFC0CEC0";

begin

   -- Assing values to control signals
	-- channel 0
	CH0_CLEAR       <= ch0_control_reg(1);
	ch0_status_reg0(0) <= CH0_OVERFLOW;
	ch0_status_reg1 <= CH0_WORD_CNT(31 downto 0);
	ch0_status_reg2 <= CH0_WORD_CNT(63 downto 32);
	-- channel 1
	CH1_CLEAR       <= ch1_control_reg(1);
	ch1_status_reg0(0) <= CH1_OVERFLOW;
	ch1_status_reg1 <= CH1_WORD_CNT(31 downto 0);
	ch1_status_reg2 <= CH1_WORD_CNT(63 downto 32);
	-- common
	COMMON_SYNC     <= common_control_reg(0);
	COMMON_TRIG  <= common_control_reg(1);
	COMMON_CLEAR    <= common_control_reg(2);
	COMMON_TRIG_CH <= common_control_reg(3);
	COMMON_TRIG_EDGE <= common_control_reg(4);
	COMMON_TRIG_LVL <= common_control_reg(18 downto 5);
	common_status_reg(0) <= COMMON_OVERFLOW;

      
	-- I/O Connections assignments
	S_AXI_AWREADY	<= axi_awready;
	S_AXI_WREADY	<= axi_wready;
	S_AXI_BRESP	<= axi_bresp;
	S_AXI_BVALID	<= axi_bvalid;
	S_AXI_ARREADY	<= axi_arready;
	S_AXI_RDATA	<= axi_rdata;
	S_AXI_RRESP	<= axi_rresp;
	S_AXI_RVALID	<= axi_rvalid;


   ---------------------------------------------------------------
   --                   AXI Lite Write
   ---------------------------------------------------------------
	process (S_AXI_ACLK)
	begin
	  if rising_edge(S_AXI_ACLK) then 
	    if S_AXI_ARESETN = '0' then
	      axi_awready <= '0';
	      aw_en <= '1';
	    else
	      if (axi_awready = '0' and S_AXI_AWVALID = '1' and S_AXI_WVALID = '1' and aw_en = '1') then
	        axi_awready <= '1';
	        elsif (S_AXI_BREADY = '1' and axi_bvalid = '1') then
	            aw_en <= '1';
	        	axi_awready <= '0';
	      else
	        axi_awready <= '0';
	      end if;
	    end if;
	  end if;
	end process;

	process (S_AXI_ACLK)
	begin
	  if rising_edge(S_AXI_ACLK) then 
	    if S_AXI_ARESETN = '0' then
	      axi_awaddr <= (others => '0');
	    else
	      if (axi_awready = '0' and S_AXI_AWVALID = '1' and S_AXI_WVALID = '1' and aw_en = '1') then
	        axi_awaddr <= S_AXI_AWADDR;
	      end if;
	    end if;
	  end if;                   
	end process; 

	process (S_AXI_ACLK)
	begin
	  if rising_edge(S_AXI_ACLK) then 
	    if S_AXI_ARESETN = '0' then
	      axi_wready <= '0';
	    else
	      if (axi_wready = '0' and S_AXI_WVALID = '1' and S_AXI_AWVALID = '1' and aw_en = '1') then          
	          axi_wready <= '1';
	      else
	        axi_wready <= '0';
	      end if;
	    end if;
	  end if;
	end process; 

	slv_reg_wren <= axi_wready and S_AXI_WVALID and axi_awready and S_AXI_AWVALID ;

	process (S_AXI_ACLK)
	variable loc_addr :std_logic_vector(OPT_MEM_ADDR_BITS-1 downto 0); 
	begin
	  if rising_edge(S_AXI_ACLK) then 
	     if S_AXI_ARESETN = '0' then
	     else
	        -- Address decode for writing into registers
	        loc_addr := axi_awaddr(ADDR_LSB + OPT_MEM_ADDR_BITS-1 downto ADDR_LSB);
	        if (slv_reg_wren = '1') then	      
	           -- Write into control register 
	           case to_integer(unsigned(loc_addr)) is
	           when 0 =>   -- ch0 control reg
                  ch0_control_reg <= write2register(ch0_control_reg, S_AXI_WDATA, S_AXI_WSTRB);
	           when 1 =>   -- ch1 control reg
                 ch1_control_reg <= write2register(ch1_control_reg, S_AXI_WDATA, S_AXI_WSTRB);
	           when 2 =>   -- common control reg
                 common_control_reg <= write2register(common_control_reg, S_AXI_WDATA, S_AXI_WSTRB);
	           when 3 =>   -- ch0 status reg0
	              null;
	           when 4 =>   -- ch0 status reg1
	              null;
	           when 5 =>   -- ch0 status reg2
	              null;
	           when 6 =>   -- ch1 status reg0
	              null;
	           when 7 =>   -- ch1 status reg1
	              null;
	           when 8 =>   -- ch1 status reg2
	              null;
	           when 9 =>   -- common status reg0
	              null;
	           when 10 =>   -- common status reg0
	              null;
	           when others => 
	              null;
	           end case;
	        end if;
	     end if;
	  end if;                   
	end process; 

	process (S_AXI_ACLK)
	begin
	  if rising_edge(S_AXI_ACLK) then 
	    if S_AXI_ARESETN = '0' then
	      axi_bvalid  <= '0';
	      axi_bresp   <= "00"; 
	    else
	      if (axi_awready = '1' and S_AXI_AWVALID = '1' and axi_wready = '1' and S_AXI_WVALID = '1' and axi_bvalid = '0'  ) then
	        axi_bvalid <= '1';
	        axi_bresp  <= "00"; 
	      elsif (S_AXI_BREADY = '1' and axi_bvalid = '1') then 
	        axi_bvalid <= '0';                                 
	      end if;
	    end if;
	  end if;                   
	end process; 
	
   ---------------------------------------------------------------
   --                   AXI Lite Read
   ---------------------------------------------------------------	
	process (S_AXI_ACLK)
	begin
	  if rising_edge(S_AXI_ACLK) then 
	    if S_AXI_ARESETN = '0' then
	      axi_arready <= '0';
	      axi_araddr  <= (others => '1');
	    else
	      if (axi_arready = '0' and S_AXI_ARVALID = '1') then
	        axi_arready <= '1';
	        axi_araddr  <= S_AXI_ARADDR;           
	      else
	        axi_arready <= '0';
	      end if;
	    end if;
	  end if;                   
	end process; 
 
	process (S_AXI_ACLK)
	begin
	  if rising_edge(S_AXI_ACLK) then
	    if S_AXI_ARESETN = '0' then
	      axi_rvalid <= '0';
	      axi_rresp  <= "00";
	    else
	      if (axi_arready = '1' and S_AXI_ARVALID = '1' and axi_rvalid = '0') then
	        axi_rvalid <= '1';
	        axi_rresp  <= "00";
	      elsif (axi_rvalid = '1' and S_AXI_RREADY = '1') then
	        axi_rvalid <= '0';
	      end if;            
	    end if;
	  end if;
	end process;

	slv_reg_rden <= axi_arready and S_AXI_ARVALID and (not axi_rvalid) ;

   -- Read register value
	process (ch0_control_reg, ch1_control_reg, common_control_reg, ch0_status_reg0, 
	        ch0_status_reg1, ch0_status_reg2, ch1_status_reg0, ch1_status_reg1, 
	        ch1_status_reg2, common_status_reg, axi_araddr, S_AXI_ARESETN, slv_reg_rden)
	variable loc_addr :std_logic_vector(OPT_MEM_ADDR_BITS-1 downto 0);
	begin
	   -- Decode address 
	   loc_addr := axi_araddr(ADDR_LSB + OPT_MEM_ADDR_BITS-1 downto ADDR_LSB);
      case to_integer(unsigned(loc_addr)) is
      when 0 =>   -- ch0 control reg
         reg_data_out <= ch0_control_reg;
      when 1 =>   -- ch1 control reg
         reg_data_out <= ch1_control_reg;
      when 2 =>   -- common control reg
         reg_data_out <= common_control_reg;
      when 3 =>   -- ch0 status reg0
         reg_data_out <= ch0_status_reg0;
      when 4 =>   -- ch0 status reg1
         reg_data_out <= ch0_status_reg1;
      when 5 =>   -- ch0 status reg2
         reg_data_out <= ch0_status_reg2;
      when 6 =>   -- ch1 status reg0
         reg_data_out <= ch1_status_reg0;
      when 7 =>   -- ch1 status reg1
         reg_data_out <= ch1_status_reg1;
      when 8 =>   -- ch1 status reg2
         reg_data_out <= ch1_status_reg2;
      when 9 =>   -- common status reg0
         reg_data_out <= common_status_reg;
      when 10 =>   -- common status reg0
         reg_data_out <= LC_ID_REG;
      when others => 
         reg_data_out <= (others => '0');
      end case;	     
	end process; 

	process( S_AXI_ACLK ) is
	begin
	  if (rising_edge (S_AXI_ACLK)) then
	    if ( S_AXI_ARESETN = '0' ) then
	      axi_rdata  <= (others => '0');
	    else
	      if (slv_reg_rden = '1') then
	          axi_rdata <= reg_data_out;     -- register read data
	      end if;   
	    end if;
	  end if;
	end process;
   
end;
