-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--
-- Copyright 2025 VLSI Solution Oy
-- SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1
--
-- Licensed under the Solderpad Hardware License v 2.1 (the "License");
-- you may not use this file except in compliance with the License, or,
-- at your option, the Apache License version 2.0.
-- You may obtain a copy of the License at
--
--    https://solderpad.org/licenses/SHL-2.1/
--
-- Unless required by applicable law or agreed to in writing, any work
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--                               RISCV core
--                                 V1.1
--
--
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

-- - - - - - - -
--  libraries
-- - - - - - - -
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
use work.rv_pkg.all;

entity rv_lsu is
  generic ( CACHE_ADDR_L : slv_31_00 := X"80000000";
            CACHE_ADDR_H : slv_31_00 := X"8FFFFFFF" );
  port ( clk_i               : in  std_logic;
         xrst_i              : in  std_logic;
         --  inputs
         instr_valid_i       : in  std_logic;
         instr_i             : in  slv_31_00;
         lsu_op_p1_i         : in  lsu_op_t;
         amofunc_i           : in  slv_03_00;
         dmem_ldtype_o       : out dmem_ldtyp_t;
         stall_o             : out std_logic;
         --
         oprs1_dat_i         : in  slv_31_00;
         oprs2_dat_i         : in  slv_31_00;
         -- added for module load fix
         ifence_p1_i         : in  std_logic;     -- ifence instruction
         --
         mem_d_accept_d1_i   : in  std_logic;
         mem_d_accept_i      : in  std_logic;     -- NC
         mem_accept_i        : in  std_logic;
         mem_ack_i           : in  std_logic;
         mem_error_i         : in  std_logic;
         --  outputs
         dmem_atomic_o       : out std_logic;     -- lr / sc
         dmem_amofunc_o      : out slv_03_00;     -- amo
         dmem_rd_o           : out std_logic;
         dmem_wr_o           : out slv_03_00;
         dmem_addr_o         : out slv_31_00;
         dmem_wdata_o        : out slv_31_00;
         mem_cacheable_o     : out std_logic;
         mem_req_tag_o       : out slv_10_00;
         mem_invalidate_o    : out std_logic;
         mem_flush_o         : out std_logic;
         mem_wb_o            : out std_logic );
end entity rv_lsu;


architecture rtl of rv_lsu is

  type resp_data_t is
  record
    byte_data    : slv_31_00;
    half_data    : slv_31_00;
    word_data    : slv_31_00;
  end record;

  signal resp_data_w : resp_data_t;


  -- - - - - - - - - - - - -
  -- registers / wires
  -- - - - - - - - - - - - -
  signal mem_addr_q          : slv_31_00;
  signal mem_wdata_q         : slv_31_00;
  signal mem_rd_q            : std_logic;
  signal mem_wr_q            : slv_03_00;
  signal mem_cacheable_q     : std_logic;
  signal mem_invalidate_q    : std_logic;
  signal mem_wb_q            : std_logic;
  signal mem_flush_q         : std_logic;
  signal mem_unaligned_e1_q  : std_logic;
  signal mem_unaligned_e2_q  : std_logic;

  signal mem_load_q          : std_logic;
  signal mem_xb_q            : std_logic;
  signal mem_xh_q            : std_logic;
  signal mem_ls_q            : std_logic;

  -- outstanding access tracking
  signal pending_lsu_e2_q    : std_logic;

  signal issue_lsu_e1_w      : std_logic;
  signal complete_ok_e2_w    : std_logic;
  signal complete_err_e2_w   : std_logic;

  -- delay next instruction if outstanding response is late
  signal delay_lsu_e2_w      : std_logic;

  signal dmem_ld_signed_w    : std_logic;
  signal dmem_ld_op_w        : std_logic;  -- word, half word, byte


  signal req_lb_w            : std_logic;
  signal req_lh_w            : std_logic;
  signal req_sb_w            : std_logic;
  signal req_sh_w            : std_logic;
  signal req_sw_lw_w         : std_logic;
  signal req_sh_lh_w         : std_logic;
  signal mem_addr_w          : slv_31_00;
  signal mem_unaligned_w     : std_logic;
  signal mem_wdata_w         : slv_31_00;
  signal mem_rd_w            : std_logic;
  signal mem_wr_w            : slv_03_00;

  signal dcache_flush_w      : std_logic;
  signal dcache_wb_w         : std_logic;
  signal dcache_invalidate_w : std_logic;

  signal resp_addr_q         : slv_31_00;
  signal resp_load_q         : std_logic;
  signal resp_signed_q       : std_logic;
  signal resp_half_q         : std_logic;
  signal resp_byte_q         : std_logic;

  -- cannot read output
  signal mem_rd_o_w          : std_logic;
  signal mem_wr_o_w          : slv_03_00;

  signal is_csrrw_instr      : std_logic;

  signal opcode_p1_i_w       : slv_31_00;

  signal mem_atomic_w        : std_logic;
  signal mem_atomic_q        : std_logic;
  signal mem_amofunc_w       : slv_03_00;
  signal mem_amofunc_q       : slv_03_00;
  
begin

  opcode_p1_i_w <= instr_i;

  -- - - - - - - - - - - - - - - -
  -- outstanding access tracking
  -- - - - - - - - - - - - - - - -
  issue_lsu_e1_w    <= mem_accept_i and
                       ( mem_rd_o_w or
                         (not (eq(mem_wr_o_w, (mem_wr_o_w'range => '0')))) or
                         mem_wb_q or mem_invalidate_q or mem_flush_q ) ;
  complete_ok_e2_w  <= mem_ack_i and (not mem_error_i);
  complete_err_e2_w <= mem_ack_i and      mem_error_i;


  p_pend_e2 : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        pending_lsu_e2_q <= '0';
      else
        if (issue_lsu_e1_w = '1') then
          pending_lsu_e2_q <= '1';
        elsif (complete_ok_e2_w = '1' or complete_err_e2_w = '1') then
          pending_lsu_e2_q <= '0';
        end if;
      end if;
    end if;
  end process p_pend_e2;


  --  delay next instruction if outstanding response is late
  delay_lsu_e2_w <= pending_lsu_e2_q and (not complete_ok_e2_w);

  -- - - - - - - - - - - - -
  --  dummy ack (unaligned access /E2)
  -- - - - - - - - - - - - -
  p_ualigned_e2 : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        mem_unaligned_e2_q <= '0';
      else
        mem_unaligned_e2_q <= mem_unaligned_e1_q and (not delay_lsu_e2_w);
      end if;
    end if;
  end process p_ualigned_e2;


  -- - - - - - - - - - - - -
  --  opcode decode
  -- - - - - - - - - - - - -
  dmem_ld_signed_w    <= lsu_op_p1_i.i_lb or
                         lsu_op_p1_i.i_lh or
                         lsu_op_p1_i.i_lw;
  dmem_ld_op_w        <= dmem_ld_signed_w  or
                         lsu_op_p1_i.i_lbu or
                         lsu_op_p1_i.i_lhu or
                         lsu_op_p1_i.i_lwu;

  req_lb_w            <= lsu_op_p1_i.i_lb or lsu_op_p1_i.i_lbu; -- load byte
  req_lh_w            <= lsu_op_p1_i.i_lh or lsu_op_p1_i.i_lhu; -- ld half word
  req_sb_w            <= lsu_op_p1_i.i_sb;                      -- store byte
  req_sh_w            <= lsu_op_p1_i.i_sh;                      -- st half hword

  req_sw_lw_w         <= lsu_op_p1_i.i_sw or
                         lsu_op_p1_i.i_lw or
                         lsu_op_p1_i.i_lwu;
  req_sh_lh_w         <= lsu_op_p1_i.i_sh or
                         lsu_op_p1_i.i_lh or
                         lsu_op_p1_i.i_lhu;
  is_csrrw_instr      <= lsu_op_p1_i.i_csrrw;

  dcache_flush_w      <= ifence_p1_i or
                         ( is_csrrw_instr and
                           eq(opcode_p1_i_w(31 downto 20), CSR_DFLUSH) );
  dcache_wb_w         <= is_csrrw_instr and
                         eq(opcode_p1_i_w(31 downto 20), CSR_DWRITEBACK);
  dcache_invalidate_w <= is_csrrw_instr and
                         eq(opcode_p1_i_w(31 downto 20), CSR_DINVALIDATE);


  resp_data_w.byte_data(31 downto 24) <=  oprs2_dat_i(7 downto 0)
                                          when mem_addr_w(1 downto 0) = "11"
                                          else X"00";
  resp_data_w.byte_data(23 downto 16) <=  oprs2_dat_i(7 downto 0)
                                          when mem_addr_w(1 downto 0) = "10"
                                          else X"00";
  resp_data_w.byte_data(15 downto 8)  <=  oprs2_dat_i(7 downto 0)
                                          when mem_addr_w(1 downto 0) = "01"
                                          else X"00";
  resp_data_w.byte_data(7 downto 0)   <=  oprs2_dat_i(7 downto 0)
                                          when mem_addr_w(1 downto 0) = "00"
                                          else X"00";

  resp_data_w.half_data(31 downto 16) <= oprs2_dat_i(15 downto 0)
                                         when mem_addr_w(1 downto 0)  = "10"
                                         else X"0000";
  resp_data_w.half_data(15 downto 0)  <= oprs2_dat_i(15 downto 0)
                                         when mem_addr_w(1 downto 0) /= "10"
                                         else X"0000";

  resp_data_w.word_data               <= oprs2_dat_i;



  p_c_to_mem : process ( instr_valid_i,  opcode_p1_i_w, oprs1_dat_i,
                         dmem_ld_op_w, req_sw_lw_w, req_sh_lh_w,
                         lsu_op_p1_i, resp_data_w, amofunc_i)
    variable mem_addr_v       : slv_31_00;
    variable mem_unaligned_v  : std_logic;
    variable mem_atomic_v     : std_logic;
    variable mem_amofunc_v    : slv_03_00;
    variable op2_v            : slv_31_00;
    variable imm12_v          : slv_11_00;
  begin
    mem_addr_v       := X"00000000";
    mem_atomic_v     :=  '0';
    mem_amofunc_v    := X"0";
    mem_unaligned_v  :=  '0';
    mem_wr_w         <= X"0";
    mem_rd_w         <=  '0';
    mem_wdata_w      <= X"00000000";
    --
    imm12_v          := X"000";
    op2_v            := X"00000000";
    --
    -- calculate data address
    --
    if ( instr_valid_i = '1' ) then
      -- 1) is_csrrw_instr / atomic lr|sw :   op2_v := X"00000000"
      if ( dmem_ld_op_w = '1' and lsu_op_p1_i.i_atomic = '0' and
           lsu_op_p1_i.i_amo = '0' ) then
        -- 2) immediate for load address ( sext(iword[31:20]) )
        imm12_v    := opcode_p1_i_w(IMM12_R'range);
      elsif ( lsu_op_p1_i.i_atomic = '0' and      -- NOT lr/sc
              lsu_op_p1_i.i_amo = '0') then       -- NOT amo op
        -- 3) immediate for store address  ( sext(iword[31:25] & iword[11:7]) )
        imm12_v    := opcode_p1_i_w(31 downto 25) & opcode_p1_i_w(11 downto 7);
      end if;
      op2_v        := slv_t(resize(signed(imm12_v), 32));
      --
      mem_addr_v   := addu(oprs1_dat_i, op2_v);
      mem_atomic_v := (lsu_op_p1_i.i_lw or lsu_op_p1_i.i_sw) and -- load/store
                      lsu_op_p1_i.i_atomic;                      -- amo lr/sc
      mem_amofunc_v := amofunc_i;
    end if;
    --
    -- check data address is correctly aligned
    --
    if (instr_valid_i = '1') then
      mem_unaligned_v := (req_sw_lw_w and not_zero(mem_addr_v(1 downto 0))) or
                         (req_sh_lh_w and mem_addr_v(0));
    end if;
    --
    mem_rd_w <= (instr_valid_i and dmem_ld_op_w and (not mem_unaligned_v));
    --
    if ( instr_valid_i = '1') then
      if ( lsu_op_p1_i.i_amo = '1' and mem_unaligned_v = '0') then
        mem_wdata_w  <= resp_data_w.word_data;
      elsif ( lsu_op_p1_i.i_sw = '1'  and mem_unaligned_v = '0') then
        mem_wdata_w  <= resp_data_w.word_data;
        mem_wr_w    <= X"F";
      elsif ( lsu_op_p1_i.i_sh = '1'  and mem_unaligned_v = '0') then
        mem_wdata_w  <= resp_data_w.half_data;
        if (mem_addr_v(1 downto 0) = "10" ) then
          mem_wr_w    <= "1100";
        else
          mem_wr_w    <= "0011";
        end if;
      elsif ( lsu_op_p1_i.i_sb = '1' ) then
        mem_wdata_w  <= resp_data_w.byte_data;               -- mem_addr_v[1:0]
        mem_wr_w(3)  <=       mem_addr_v(1)  and      mem_addr_v(0);    -- "11"
        mem_wr_w(2)  <=       mem_addr_v(1)  and (not mem_addr_v(0));   -- "10"
        mem_wr_w(1)  <=  (not mem_addr_v(1)) and      mem_addr_v(0);    -- "01"
        mem_wr_w(0)  <=  (not mem_addr_v(1)) and (not mem_addr_v(0));   -- "00"
      end if;
    end if;
    --
    mem_addr_w       <= mem_addr_v;
    mem_unaligned_w  <= mem_unaligned_v;
    mem_atomic_w     <= mem_atomic_v;
    mem_amofunc_w    <= mem_amofunc_v;
  end process p_c_to_mem;


  -- - - - - - - - - - - - -
  --  sequential
  -- - - - - - - - - - - - -
  p_seq : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if ( xrst_i = '0') then -- or
        mem_atomic_q       <= '0';      -- lr/sc
        mem_amofunc_q      <= X"0";     -- amo
        mem_rd_q           <= '0';
        mem_wr_q           <= X"0";
        mem_addr_q         <= X"00000000";
        mem_wdata_q        <= X"00000000";
        mem_cacheable_q    <= '0';
        mem_invalidate_q   <= '0';
        mem_unaligned_e1_q <= '0';
        --
        mem_wb_q           <= '0';
        mem_flush_q        <= '0';
        mem_load_q         <= '0';
        mem_xb_q           <= '0';
        mem_xh_q           <= '0';
        mem_ls_q           <= '0';
        -- memory access fault - squash next operation (exception coming...)
      else
        --
        -- added for module load fix
        if (mem_d_accept_d1_i = '1') then
          mem_flush_q      <= '0';
        end if;
        --
        if (complete_err_e2_w = '1' or mem_unaligned_e2_q = '1') then
          --
          mem_atomic_q       <=  '0';      -- lr/sc
          mem_amofunc_q      <= X"0";      -- amo
          mem_rd_q           <=  '0';
          mem_wr_q           <= X"0";
          mem_addr_q         <= X"00000000";
          mem_wdata_q        <= X"00000000";
          mem_cacheable_q    <=  '0';
          mem_invalidate_q   <=  '0';
          mem_unaligned_e1_q <=  '0';
          --
          mem_wb_q           <=  '0';
          mem_flush_q        <=  '0';
          mem_load_q         <=  '0';
          mem_xb_q           <=  '0';
          mem_xh_q           <=  '0';
          mem_ls_q           <=  '0';
        elsif ((mem_rd_q = '1'or not_zero(mem_wr_q) or
                mem_unaligned_e1_q = '1') and delay_lsu_e2_w = '1') then
          null;
        elsif (not ( (mem_wb_q = '1' or mem_invalidate_q = '1' or
                      mem_flush_q = '1' or mem_rd_o_w = '1' or
                      not_zero(mem_wr_o_w)) and  mem_accept_i = '0')) then
          mem_atomic_q       <= mem_atomic_w;     -- lr/sc
          mem_amofunc_q      <= mem_amofunc_w;    -- amo
          mem_rd_q           <= mem_rd_w;
          mem_wr_q           <= mem_wr_w;
          mem_addr_q         <= mem_addr_w;
          mem_wdata_q        <= mem_wdata_w;
          mem_cacheable_q    <= (not lt(mem_addr_w, CACHE_ADDR_L) and
                                 not gt(mem_addr_w, CACHE_ADDR_H)) or
                                ( instr_valid_i and (dcache_invalidate_w or
                                                     dcache_wb_w or
                                                     dcache_flush_w) );
          mem_invalidate_q   <= instr_valid_i and dcache_invalidate_w;
          mem_unaligned_e1_q <= mem_unaligned_w;
          --
          mem_wb_q           <= instr_valid_i and dcache_wb_w;
          mem_flush_q        <= instr_valid_i and dcache_flush_w;
          mem_load_q         <= instr_valid_i and dmem_ld_op_w;
          mem_xb_q           <= req_lb_w or req_sb_w;  -- byte
          mem_xh_q           <= req_lh_w or req_sh_w;  -- half word
          mem_ls_q           <= dmem_ld_signed_w;      -- signed (sign extend)
        end if;
      end if;
    end if;
  end process p_seq;


  mem_rd_o_w        <= mem_rd_q and (not delay_lsu_e2_w);
  mem_wr_o_w        <= mem_wr_q and (not(3 downto 0 => delay_lsu_e2_w));

  dmem_atomic_o     <= mem_atomic_q;
  dmem_amofunc_o    <= mem_amofunc_q;
  dmem_rd_o         <= mem_rd_o_w;
  dmem_wr_o         <= mem_wr_o_w;
  dmem_addr_o       <= (mem_addr_q(31 downto 2) & "00");
  dmem_wdata_o      <= mem_wdata_q;

  mem_cacheable_o   <= mem_cacheable_q;
  mem_invalidate_o  <= mem_invalidate_q;
  mem_req_tag_o     <= (10 downto 0 => '0');
  mem_wb_o          <= mem_wb_q;
  mem_flush_o       <= mem_flush_q;



  -- stall upstream if dcache is busy
  stall_o  <= ( (mem_wb_q or mem_invalidate_q or mem_flush_q or
                 mem_rd_o_w or (not_zero(mem_wr_o_w)) ) and
                (not mem_accept_i) )  or  delay_lsu_e2_w or mem_unaligned_e1_q;


  -- - - - - - - - - - - - - - - -
  -- delay line fifo for load ops
  -- - - - - - - - - - - - - - - -
  --                               ___
  --                mem_addr_q    |   |    resp_addr_q
  -- load data      mem_load_q  ->|   |->  resp_load_q
  -- signed load    mem_ls_q      |   |    resp_signed_q
  -- half word op   mem_xh_q      |>  |    resp_half_q
  -- byte op        mem_xb_q      |___|    resp_byte_q
  --

  p_del : process (clk_i)
    variable push_v, pop_v : std_logic;
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        resp_addr_q    <= X"00000000";
        resp_load_q    <= '0';
        resp_signed_q  <= '0';
        resp_half_q    <= '0';
        resp_byte_q    <= '0';
      else
        -- push trigger for load request
        push_v := ((mem_rd_o_w or not_zero(mem_wr_o_w) or
                    mem_wb_q or mem_invalidate_q or mem_flush_q) and
                   mem_accept_i) or
                  (mem_unaligned_e1_q and (not delay_lsu_e2_w));
        --  push
        if (push_v = '1') then
          -- NOTE: atomic sc also loads a sucess/fail flag to rfile
          resp_load_q   <= mem_load_q or mem_atomic_q; -- load word / atomic sc
          resp_addr_q   <= mem_addr_q;
          resp_signed_q <= mem_ls_q;                   -- signed
          resp_half_q   <= mem_xh_q;                   -- half word op
          resp_byte_q   <= mem_xb_q;                   -- byte op
        end if;
        --
      end if;
    end if;
  end process p_del;


  -- dmem read response type
--dmem_dtype_o.ack         <= '0';   -- dmem ack
--dmem_dtype_o.err         <= '0';   -- dmem err
--dmem_dtype_o.exp         <= '0';
--dmem_dtype_o.din         <= X"00000000";
  dmem_ldtype_o.ualigned_e2 <= mem_unaligned_e2_q;
  dmem_ldtype_o.exp_addr    <= resp_addr_q;
  dmem_ldtype_o.ld          <= resp_load_q;
  dmem_ldtype_o.ldb         <= resp_byte_q;
  dmem_ldtype_o.ldh         <= resp_half_q;
  dmem_ldtype_o.sint        <= resp_signed_q;

end rtl;
