-----------------------------------------------------------------------------
--! @file
--! @copyright Copyright 2016 GNSS Sensor Ltd. All right reserved.
--! @author    Sergey Khabarov - sergeykhbr@gmail.com
--! @brief     CPU Memory Access stage.
------------------------------------------------------------------------------

library ieee;
use ieee.std_logic_1164.all;
library commonlib;
use commonlib.types_common.all;
--! RIVER CPU specific library.
library riverlib;
--! RIVER CPU configuration constants.
use riverlib.river_cfg.all;


entity MemAccess is
  port (
    i_clk  : in std_logic;
    i_nrst : in std_logic;
    i_e_valid : in std_logic;                                         -- Execution stage outputs are valid
    i_e_pc : in std_logic_vector(BUS_ADDR_WIDTH-1 downto 0);          -- Execution stage instruction pointer
    i_e_instr : in std_logic_vector(31 downto 0);                     -- Execution stage instruction value

    i_res_addr : in std_logic_vector(4 downto 0);                     -- Register address to be written (0=no writing)
    i_res_data : in std_logic_vector(RISCV_ARCH-1 downto 0);          -- Register value to be written
    i_memop_sign_ext : in std_logic;                                  -- Load data with sign extending (if less than 8 Bytes)
    i_memop_load : in std_logic;                                      -- Load data from memory and write to i_res_addr
    i_memop_store : in std_logic;                                     -- Store i_res_data value into memory
    i_memop_size : in std_logic_vector(1 downto 0);                   -- Encoded memory transaction size in bytes: 0=1B; 1=2B; 2=4B; 3=8B
    i_memop_addr : in std_logic_vector(BUS_ADDR_WIDTH-1 downto 0);    -- Memory access address
    o_wena : out std_logic;                                           -- Write enable signal
    o_waddr : out std_logic_vector(4 downto 0);                       -- Output register address (0 = x0 = no write)
    o_wdata : out std_logic_vector(RISCV_ARCH-1 downto 0);            -- Register value

    -- Memory interface:
    i_mem_req_ready : in std_logic;
    o_mem_valid : out std_logic;                                      -- Memory request is valid
    o_mem_write : out std_logic;                                      -- Memory write request
    o_mem_sz : out std_logic_vector(1 downto 0);                      -- Encoded data size in bytes: 0=1B; 1=2B; 2=4B; 3=8B
    o_mem_addr : out std_logic_vector(BUS_ADDR_WIDTH-1 downto 0);     -- Data path requested address
    o_mem_data : out std_logic_vector(BUS_DATA_WIDTH-1 downto 0);     -- Data path requested data (write transaction)
    i_mem_data_valid : in std_logic;                                  -- Data path memory response is valid
    i_mem_data_addr : in std_logic_vector(BUS_ADDR_WIDTH-1 downto 0); -- Data path memory response address
    i_mem_data : in std_logic_vector(BUS_DATA_WIDTH-1 downto 0);      -- Data path memory response value
    o_mem_resp_ready : out std_logic;

    o_hold : out std_logic;                                           -- Memory operation is more than 1 clock
    o_valid : out std_logic;                                          -- Output is valid
    o_pc : out std_logic_vector(BUS_ADDR_WIDTH-1 downto 0);           -- Valid instruction pointer
    o_instr : out std_logic_vector(31 downto 0)                       -- Valid instruction value
  );
end; 
 
architecture arch_MemAccess of MemAccess is

  type RegistersType is record
      valid : std_logic;
      pc : std_logic_vector(BUS_ADDR_WIDTH-1 downto 0);
      instr : std_logic_vector(31 downto 0);

      wena : std_logic;
      waddr : std_logic_vector(4 downto 0);
      sign_ext : std_logic;
      size : std_logic_vector(1 downto 0);
      wdata : std_logic_vector(RISCV_ARCH-1 downto 0);
      wait_req : std_logic;
      wait_req_write : std_logic;
      wait_req_sz : std_logic_vector(1 downto 0);
      wait_req_addr : std_logic_vector(BUS_ADDR_WIDTH-1 downto 0);
      wait_req_wdata : std_logic_vector(RISCV_ARCH-1 downto 0);
      wait_resp : std_logic;
  end record;

  signal r, rin : RegistersType;

begin


  comb : process(i_nrst, i_mem_req_ready, i_e_valid, i_e_pc, i_e_instr, i_res_addr, i_res_data,
                i_memop_sign_ext, i_memop_load, i_memop_store, i_memop_size,
                i_memop_addr, i_mem_data_valid, i_mem_data_addr, i_mem_data, r)
    variable v : RegistersType;
    variable w_req_fire : std_logic;
    variable w_o_mem_valid : std_logic;
    variable w_o_mem_write : std_logic;
    variable wb_o_mem_sz : std_logic_vector(1 downto 0);
    variable wb_o_mem_addr : std_logic_vector(BUS_ADDR_WIDTH-1 downto 0);
    variable wb_o_mem_wdata : std_logic_vector(RISCV_ARCH-1 downto 0);
    variable wb_res_wdata : std_logic_vector(RISCV_ARCH-1 downto 0);
    variable w_memop : std_logic;
    variable w_o_valid : std_logic;
    variable w_o_wena : std_logic;
    variable w_o_hold : std_logic;
    variable w_mem_fire : std_logic;
  begin

    v := r;

    w_o_mem_valid := '0';
    w_o_mem_write := '0';
    wb_o_mem_sz := (others => '0');
    wb_o_mem_addr := (others => '0');
    wb_o_mem_wdata := (others => '0');
    v.valid := '0';
    w_o_hold := '0';
    
    w_memop := i_memop_load or i_memop_store;

    if r.wait_req = '1' then
        if i_mem_req_ready = '1' then
            v.wait_req := '0';
            v.wait_resp := '1';
        end if;
        w_o_mem_valid := '1';
        w_o_mem_write := r.wait_req_write;
        wb_o_mem_sz := r.wait_req_sz;
        wb_o_mem_addr := r.wait_req_addr;
        wb_o_mem_wdata := r.wait_req_wdata;
    elsif i_e_valid = '1' then
      v.valid := not w_memop;
      v.pc := i_e_pc;
      v.instr := i_e_instr;
      v.waddr := i_res_addr;
      v.wdata := i_res_data;
      if i_res_addr = "00000" then
          v.wena := '0';
      else
          v.wena := '1';
      end if;

      if w_memop = '1' then
        w_o_mem_valid := '1';
        w_o_mem_write := i_memop_store;
        wb_o_mem_sz := i_memop_size;
        wb_o_mem_addr := i_memop_addr;
        wb_o_mem_wdata := i_res_data;
        v.sign_ext := i_memop_sign_ext;
        v.size := i_memop_size;

        v.wait_resp := i_mem_req_ready;
        v.wait_req := not i_mem_req_ready;
        v.wait_req_write := i_memop_store;
        v.wait_req_sz := i_memop_size;
        v.wait_req_addr := i_memop_addr;
        v.wait_req_wdata := i_res_data;
      else
        w_o_mem_valid := '0';
        w_o_mem_write := '0';
        wb_o_mem_sz := (others => '0');
        wb_o_mem_addr := (others => '0');
        wb_o_mem_wdata := (others => '0');
        v.sign_ext := '0';
        v.size := (others => '0');
        v.wait_req_addr := (others => '0');
        v.wait_req := '0';
        v.wait_resp := '0';
      end if;
    elsif i_mem_data_valid = '1' then
      v.wait_resp := '0';
    end if;

    w_o_hold := (i_e_valid and w_memop) or r.wait_req 
            or (r.wait_resp and not i_mem_data_valid);

    w_mem_fire := i_mem_data_valid;
    
    if w_mem_fire = '1' then
        if r.sign_ext = '1' then
            case r.size is
            when MEMOP_1B =>
                wb_res_wdata := i_mem_data;
                wb_res_wdata(63 downto 8) := (others => i_mem_data(7));
            when MEMOP_2B =>
                wb_res_wdata := i_mem_data;
                wb_res_wdata(63 downto 16) := (others => i_mem_data(15));
            when MEMOP_4B =>
                wb_res_wdata := i_mem_data;
                wb_res_wdata(63 downto 32) := (others => i_mem_data(31));
            when others =>
                wb_res_wdata := i_mem_data;
            end case;
        else
            wb_res_wdata := i_mem_data;
        end if;
    else
        wb_res_wdata := r.wdata;
    end if;

    w_o_valid := r.valid or w_mem_fire;
    w_o_wena := r.wena and w_o_valid;

    if i_nrst = '0' then
        v.valid := '0';
        v.pc := (others => '0');
        v.instr := (others => '0');
        v.waddr := (others => '0');
        v.wdata := (others => '0');
        v.wena := '0';
        v.size := (others => '0');
        v.sign_ext := '0';
        v.wait_req := '0';
        v.wait_req_write := '0';
        v.wait_req_sz := (others => '0');
        v.wait_req_addr := (others => '0');
        v.wait_req_wdata := (others => '0');
        v.wait_resp := '0';
    end if;

    o_mem_resp_ready <= '1';

    o_mem_valid <= w_o_mem_valid;
    o_mem_write <= w_o_mem_write;
    o_mem_sz <= wb_o_mem_sz;
    o_mem_addr <= wb_o_mem_addr;
    o_mem_data <= wb_o_mem_wdata;

    o_wena <= w_o_wena;
    o_waddr <= r.waddr;
    o_wdata <= wb_res_wdata;
    o_valid <= w_o_valid;
    o_pc <= r.pc;
    o_instr <= r.instr;
    o_hold <= w_o_hold;
    
    rin <= v;
  end process;

  -- registers:
  regs : process(i_clk)
  begin 
     if rising_edge(i_clk) then 
        r <= rin;
     end if; 
  end process;

end;
