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

entity memory_mapped_registers is
    port (
        clk     : in std_logic;
        reset   : in std_logic;
        
        --***AXI4-LITE INTERFACE***
        -- WRITE ADDRESS CHANNEL
        AWVALID : in std_logic;
        AWREADY : out std_logic;
        AWADDR  : in std_logic_vector(1 downto 0);
        AWPROT  : in std_logic_vector(2 downto 0);
        -- WRITE DATA CHANNEL
        WVALID  : in std_logic;
        WREADY  : out std_logic;
        WDATA   : in std_logic_vector(31 downto 0);
        WSTRB   : in std_logic_vector(3 downto 0);
        -- WRITE RESPONSE CHANNEL
        BVALID  : out std_logic;
        BREADY  : in std_logic;
        BRESP   : out std_logic_vector(1 downto 0);
        -- READ ADDRESS CHANNEL
        ARVALID : in std_logic;
        ARREADY : out std_logic;
        ARADDR  : in std_logic_vector(1 downto 0);
        ARPROT  : in std_logic_vector(2 downto 0);
        -- READ DATA CHANNEL
        RVALID  : out std_logic;
        RREADY  : in std_logic;
        RDATA   : out std_logic_vector(31 downto 0);
        RRESP   : out std_logic_vector(1 downto 0);
        
        -- DEBUG
        reset_debug     : out std_logic;
        adc_data1_max   : in std_logic_vector(11 downto 0);
        adc_data2_max   : in std_logic_vector(11 downto 0);
        scaler_max      : in std_logic_vector(15 downto 0);
        dac_max         : in std_logic_vector(15 downto 0)
        
        -- DYNAMIC CONFIGURATION
        addsub_mode : out std_logic;
        delay       : out std_logic_vector(7 downto 0);
        factor      : out std_logic_vector(3 downto 0);
    );
end entity;

architecture arch of memory_mapped_registers is
    
    --*****TYPE DECLARATION*****
    type WRITE_STAGE_TYPE is (WRITE_ADDR, WRITE_DATA, WRITE_RESP);
    type READ_STAGE_TYPE is (READ_ADDR, READ_DATA);
    
    --*****SIGNAL DECLARATION*****
    signal read_stage, read_stage_next : READ_STAGE_TYPE := READ_ADDR;
    signal write_stage, write_stage_next : WRITE_STAGE_TYPE := WRITE_ADDR;
    signal arready_next, arready, addsub_mode_next, rvalid, rvalid_next : std_logic := '0';
    signal awready, awready_next, wready, wready_next, bvalid, bvalid_next : std_logic := '0';
    signal rdata, rdata_next : std_logic_vector(31 downto 0) := (others => '0');
    signal factor_next : std_logic_vector(3 downto 0) := (others => '0');
    signal delay_next : std_logic_vector(7 downto 0) := (others => '0');
    signal awaddr, awaddr_next : std_logic_vector(1 downto 0) := (others => '0');
    signal reset_debug : std_logic := '0';
    
begin
    
    BRESP   <= (others => '0');
    RRESP   <= (others => '0');
    AWREADY <= awready;
    AWADDR  <= awaddr;
    WREADY  <= wready;
    BVALID  <= bvalid;
    ARREADY <= arready;
    RVALID  <= rvalid;
    
    read_ctrl : process(all)
    begin
        --DEFAULT VALUES
        read_stage_next <= read_stage;
        arready_next    <= arready;
        rdata_next      <= rdata;
        rvalid_next     <= rvalid;
        
        case(read_stage)
            when READ_ADDR =>
                arready_next    <= '1';
                rvalid_next     <= '0';
                if (ARVALID = '1') then
                    case(to_integer(unsigned(ARADDR)))
                        when 0  =>
                            rdata_next                  <= (others => '0');
                            rdata_next(30)              <= addsub_mode_next;
                            rdata_next(19 downto 16)    <= factor_next;
                            rdata_next(7 downto 0)      <= delay_next;
                        when 1  => 
                            rdata_next              <= (others => '0');
                            rdata_next(11 downto 0) <= adc_data1_max;
                            rdata_next(27 downto 16)<= adc_data2_max;
                        when 2  => 
                            rdata_next              <= (others => '0');
                            rdata_next(15 downto 0) <= dac_max;
                            rdata_next(31 downto 16)<= scaler_max;
                        when others =>
                            rdata_next  <= (others => '0');
                    end case;
                    arready_next    <= '0';
                    rvalid_next     <= '1';
                    read_stage_next <= READ_DATA;
                end if;
            when READ_DATA =>
                arready_next    <= '0';
                rvalid_next     <= '1';
                if (RREADY = '1') then
                    arready_next    <= '1';
                    rvalid_next     <= '0';
                    read_stage_next <= READ_ADDR;
                end if;
        end case;
    end process;
    
    read_ctrl : process(all)
    begin
        --DEFAULT VALUES
        write_stage_next    <= write_stage;
        awready_next        <= awready;
        awaddr_next         <= awaddr;
        wready_next         <= wready;
        bvalid_next         <= bvalid;
        reset_debug_next    <= '0';
        
        case(write_stage)
            when WRITE_ADDR => 
                awready_next    <= '1';
                wready_next     <= '0';
                bvalid          <= '0';
                if (AWVALID = '1') then
                    awaddr_next         <= AWADDR;
                    awready_next        <= '0';
                    wready_next         <= '1';
                    write_stage_next    <= WRITE_DATA;
                end if;
            when WRITE_DATA => 
                awready_next    <= '0';
                wready_next     <= '1';
                bvalid          <= '0';
                if (WVALID = '1') then
                    case(to_integer(unsigned(awaddr)))
                        when 0 =>
                            reset_debug_next    <= WDATA(31);
                            addsub_mode_next    <= WDATA(30);
                            factor_next         <= WDATA(19 downto 16);
                            delay_next          <= WDATA(7 downto 0);
                        when others =>
                            null;
                    end case;
                    wready_next         <= '0';
                    bvalid_next         <= '1';
                    write_stage_next    <= WRITE_RESP;
                end if;
            when WRITE_RESP => 
                awready_next    <= '0';
                wready_next     <= '0';
                bvalid_next     <= '1';
                if (BREADY = '1') then
                    bvalid_next         <= '0';
                    awready_next        <= '1';
                    write_stage_next    <= WRITE_ADDR;
                end if;
        end case;
    end process;
    
    sync : process(clk)
    begin
        if (rising_edge(clk)) then
            if (reset = '1') then
                awready     <= '0';
                awaddr      <= (others => '0');
                wready      <= '0';
                bvalid      <= '0';
                arready     <= '0';
                rvalid      <= '0';
                
                read_stage  <= READ_ADDR;
                write_stage <= WRITE_ADDR;
                
                addsub_mode <= '0';
                factor      <= (others => '0');
                delay       <= (others => '0');
                
                rdata       <= (others => '0');
            else
                awready     <= awready_next;
                awaddr      <= awaddr_next;
                wready      <= wready_next;
                bvalid      <= bvalid_next;
                arready     <= arready_next;
                rvalid      <= rvalid_next;
                
                read_stage  <= read_stage_next;
                write_stage <= write_stage_next;
                
                addsub_mode <= addsub_mode_next;
                factor      <= factor_next;
                delay       <= delay_next;
                
                rdata       <= rdata_next;
            end if;
        end if;
    end process;
    
end architecture;