-- Adapted from AXI4 Peripheral template generated by "Create and Package New IP" Wizard in Vivado.
-- Changes:
--   * Fixed register width to 32 bits;
--   * Changed AXI4LITE interface to record;
--   * Parameterizable number of registers (generic G_S_AXI_NUM_REGISTERS);
--   * Exposed written data as reg_wdata array, and write event pulse as reg_wpulse;
--   * Implemented status registers (Read Only from Master PoV):
--       > Enabled through G_S_AXI_REG_IS_STATUS generic;
--       > Data provided through reg_rdata array.

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

library work;
use work.pkg_general.all;  -- log2, slv32d_array
use work.pkg_axi.all;      -- axi4lite_mout_sin, axi4lite_min_sout

entity axi4lite_reg_file is
    generic (
        G_S_AXI_NUM_REGISTERS : integer          := 4;
        G_S_AXI_REG_IS_STATUS : std_logic_vector := "0000"               -- if G_S_AXI_REG_IS_STATUS(i) = '1', register i will be read from reg_rdata(i)
        );
    port (
        -- AXI4-Lite Bus
        S_AXI_ACLK    : in  std_logic;
        S_AXI_ARESETN : in  std_logic;
        S_AXI_IN      : in  axi4lite_mout_sin;
        S_AXI_OUT     : out axi4lite_min_sout;
        -- register interface to/from fabric logic
        reg_rdata     : in  slv32d_array(0 to G_S_AXI_NUM_REGISTERS-1);  -- applicable only for status registers
        reg_wdata     : out slv32d_array(0 to G_S_AXI_NUM_REGISTERS-1);
        reg_wpulse    : out std_logic_vector(0 to G_S_AXI_NUM_REGISTERS-1)
        );
end axi4lite_reg_file;

architecture arch_imp of axi4lite_reg_file is

    constant C_S_AXI_DATA_WIDTH : integer := 32;
    constant C_S_AXI_ADDR_WIDTH : integer := log2(G_S_AXI_NUM_REGISTERS)+2;

    -- AXI4LITE signals
    signal axi_awaddr  : std_logic_vector(C_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(C_S_AXI_ADDR_WIDTH-1 downto 0);
    signal axi_arready : std_logic;
    signal axi_rdata   : std_logic_vector(C_S_AXI_DATA_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 := (C_S_AXI_DATA_WIDTH/32)+ 1;
    constant OPT_MEM_ADDR_BITS : integer := log2(G_S_AXI_NUM_REGISTERS)-1;

    --------------------------------------------------
    ---- Signals for user logic register space example
    --------------------------------------------------
    ---- Number of Slave Registers G_S_AXI_NUM_REGISTERS
    signal slv_reg      : slv32d_array(0 to G_S_AXI_NUM_REGISTERS-1) := (others => (others => '0'));
    signal slv_reg_rden : std_logic;
    signal slv_reg_wren : std_logic;
    signal reg_data_out : std_logic_vector(C_S_AXI_DATA_WIDTH-1 downto 0);
    signal byte_index   : integer;
    signal aw_en        : std_logic;

begin

    assert G_S_AXI_REG_IS_STATUS'ascending report "G_S_AXI_REG_IS_STATUS must be ascending!" severity FAILURE;
    assert G_S_AXI_REG_IS_STATUS'low = 0 report "G_S_AXI_REG_IS_STATUS'low must be 0!" severity FAILURE;
    assert G_S_AXI_REG_IS_STATUS'high = G_S_AXI_NUM_REGISTERS-1 report "G_S_AXI_REG_IS_STATUS'high must be G_S_AXI_NUM_REGISTERS-1!" severity FAILURE;

    -- I/O Connections assignments
    S_AXI_OUT.AWREADY <= axi_awready;
    S_AXI_OUT.WREADY  <= axi_wready;
    S_AXI_OUT.BRESP   <= axi_bresp;
    S_AXI_OUT.BVALID  <= axi_bvalid;
    S_AXI_OUT.ARREADY <= axi_arready;
    S_AXI_OUT.RDATA   <= axi_rdata;
    S_AXI_OUT.RRESP   <= axi_rresp;
    S_AXI_OUT.RVALID  <= axi_rvalid;

    -- Implement axi_awready generation
    -- axi_awready is asserted for one S_AXI_ACLK clock cycle when both
    -- S_AXI_IN.AWVALID and S_AXI_IN.WVALID are asserted. axi_awready is
    -- de-asserted when reset is low.
    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_IN.AWVALID = '1' and S_AXI_IN.WVALID = '1' and aw_en = '1') then
                    -- slave is ready to accept write address when
                    -- there is a valid write address and write data
                    -- on the write address and data bus. This design 
                    -- expects no outstanding transactions. 
                    axi_awready <= '1';
                elsif (S_AXI_IN.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;

    -- Implement axi_awaddr latching
    -- This process is used to latch the address when both 
    -- S_AXI_IN.AWVALID and S_AXI_IN.WVALID are valid. 
    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_IN.AWVALID = '1' and S_AXI_IN.WVALID = '1' and aw_en = '1') then
                    -- Write Address latching
                    axi_awaddr <= S_AXI_IN.AWADDR(axi_awaddr'range);
                end if;
            end if;
        end if;
    end process;

    -- Implement axi_wready generation
    -- axi_wready is asserted for one S_AXI_ACLK clock cycle when both
    -- S_AXI_IN.AWVALID and S_AXI_IN.WVALID are asserted. axi_wready is 
    -- de-asserted when reset is low. 
    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_IN.WVALID = '1' and S_AXI_IN.AWVALID = '1' and aw_en = '1') then
                    -- slave is ready to accept write data when 
                    -- there is a valid write address and write data
                    -- on the write address and data bus. This design 
                    -- expects no outstanding transactions.           
                    axi_wready <= '1';
                else
                    axi_wready <= '0';
                end if;
            end if;
        end if;
    end process;

    -- Implement memory mapped register select and write logic generation
    -- The write data is accepted and written to memory mapped registers when
    -- axi_awready, S_AXI_IN.WVALID, axi_wready and S_AXI_IN.WVALID are asserted. Write strobes are used to
    -- select byte enables of slave registers while writing.
    -- These registers are cleared when reset (active low) is applied.
    -- Slave register write enable is asserted when valid address and data are available
    -- and the slave is ready to accept the write address and write data.
    slv_reg_wren <= axi_wready and S_AXI_IN.WVALID and axi_awready and S_AXI_IN.AWVALID;

    process (S_AXI_ACLK)
        variable loc_addr : std_logic_vector(OPT_MEM_ADDR_BITS downto 0);
    begin
        if rising_edge(S_AXI_ACLK) then
            reg_wpulse <= (others => '0');
            loc_addr   := axi_awaddr(ADDR_LSB + OPT_MEM_ADDR_BITS downto ADDR_LSB);
            if (slv_reg_wren = '1') then
                for reg_index in slv_reg'range loop
                    if reg_index = unsigned(loc_addr) then
                        reg_wpulse(reg_index) <= '1';
                        for byte_index in 0 to (C_S_AXI_DATA_WIDTH/8-1) loop
                            if (S_AXI_IN.WSTRB(byte_index) = '1') then
                                -- Respective byte enables are asserted as per write strobes                   
                                -- slave register reg_index
                                slv_reg(reg_index)(byte_index*8+7 downto byte_index*8) <= S_AXI_IN.WDATA(byte_index*8+7 downto byte_index*8);
                            end if;
                        end loop;
                    end if;
                end loop;
            end if;
        end if;
    end process;

    reg_wdata <= slv_reg;

    -- Implement write response logic generation
    -- The write response and response valid signals are asserted by the slave 
    -- when axi_wready, S_AXI_IN.WVALID, axi_wready and S_AXI_IN.WVALID are asserted.  
    -- This marks the acceptance of address and indicates the status of 
    -- write transaction.
    process (S_AXI_ACLK)
    begin
        if rising_edge(S_AXI_ACLK) then
            if S_AXI_ARESETN = '0' then
                axi_bvalid <= '0';
                axi_bresp  <= "00";                                      --need to work more on the responses
            else
                if (axi_awready = '1' and S_AXI_IN.AWVALID = '1' and axi_wready = '1' and S_AXI_IN.WVALID = '1' and axi_bvalid = '0') then
                    axi_bvalid <= '1';
                    axi_bresp  <= "00";
                elsif (S_AXI_IN.BREADY = '1' and axi_bvalid = '1') then  --check if bready is asserted while bvalid is high)
                    axi_bvalid <= '0';                                   -- (there is a possibility that bready is always asserted high)
                end if;
            end if;
        end if;
    end process;

    -- Implement axi_arready generation
    -- axi_arready is asserted for one S_AXI_ACLK clock cycle when
    -- S_AXI_IN.ARVALID is asserted. axi_awready is 
    -- de-asserted when reset (active low) is asserted. 
    -- The read address is also latched when S_AXI_IN.ARVALID is 
    -- asserted. axi_araddr is reset to zero on reset assertion.
    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_IN.ARVALID = '1') then
                    -- indicates that the slave has acceped the valid read address
                    axi_arready <= '1';
                    -- Read Address latching 
                    axi_araddr  <= S_AXI_IN.ARADDR(axi_araddr'range);
                else
                    axi_arready <= '0';
                end if;
            end if;
        end if;
    end process;

    -- Implement axi_arvalid generation
    -- axi_rvalid is asserted for one S_AXI_ACLK clock cycle when both 
    -- S_AXI_IN.ARVALID and axi_arready are asserted. The slave registers 
    -- data are available on the axi_rdata bus at this instance. The 
    -- assertion of axi_rvalid marks the validity of read data on the 
    -- bus and axi_rresp indicates the status of read transaction.axi_rvalid 
    -- is deasserted on reset (active low). axi_rresp and axi_rdata are 
    -- cleared to zero on reset (active low).  
    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_IN.ARVALID = '1' and axi_rvalid = '0') then
                                         -- Valid read data is available at the read data bus
                    axi_rvalid <= '1';
                    axi_rresp  <= "00";  -- 'OKAY' response
                elsif (axi_rvalid = '1' and S_AXI_IN.RREADY = '1') then
                                         -- Read data is accepted by the master
                    axi_rvalid <= '0';
                end if;
            end if;
        end if;
    end process;

    -- Implement memory mapped register select and read logic generation
    -- Slave register read enable is asserted when valid address is available
    -- and the slave is ready to accept the read address.
    slv_reg_rden <= axi_arready and S_AXI_IN.ARVALID and (not axi_rvalid);

    process (slv_reg, axi_araddr)
        variable loc_addr : std_logic_vector(OPT_MEM_ADDR_BITS downto 0);
    begin
        -- Address decoding for reading registers
        loc_addr     := axi_araddr(ADDR_LSB + OPT_MEM_ADDR_BITS downto ADDR_LSB);
        reg_data_out <= (others => '0');
        for reg_index in slv_reg'range loop
            if reg_index = unsigned(loc_addr) then
                if G_S_AXI_REG_IS_STATUS(reg_index) = '1' then
                    reg_data_out <= reg_rdata(reg_index);
                else
                    reg_data_out <= slv_reg(reg_index);
                end if;
            end if;
        end loop;
    end process;

    -- Output register or memory read data
    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
                                                -- When there is a valid read address (S_AXI_IN.ARVALID) with 
                                                -- acceptance of read address by the slave (axi_arready), 
                                                -- output the read data 
                                                -- Read address mux
                    axi_rdata <= reg_data_out;  -- register read data
                end if;
            end if;
        end if;
    end process;

end arch_imp;
