-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--
-- 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_csr is
  generic ( RISCV_M   : sl_t := '1';  -- RV mul / div extension
            RISCV_A   : sl_t := '1';  -- lrsc / amo extension
            RISCV_E   : sl_t := '0';  -- RV rf embedded extension
            RISCV_S_U : sl_t := '1';
            RISCV_ZB  : sl_t := '0';  -- ZB-extension + Zicond
            BOOT_ADDR : slv_31_00;
            CPU_ID    : slv_31_00 );
  port ( clk_i                      : in  std_logic;
         xrst_i                     : in  std_logic;
         --
         extirq_i                   : in  std_logic;     -- external interrupt
         tmrirq_i                   : in  std_logic;     -- timer interrupt
         irq_block_i                : in  std_logic;
         take_irq_o                 : out std_logic;
         ifence_p1_o                : out std_logic;
         ifence_p2_o                : out std_logic;
         --
         branch_req_o               : out std_logic;
         branch_pc_o                : out slv_31_00;
         branch_priv_o              : out slv_01_00;
         --
         mmu_priv_d_o               : out slv_01_00;
         mmu_sum_o                  : out std_logic;
         mmu_mxr_o                  : out std_logic;
         mmu_flush_o                : out std_logic;
         mmu_satp_o                 : out slv_31_00;
         --
         csr_op_i                   : in  csr_op_t;
         opcode_valid_i             : in  std_logic;
         iword_p1_i                 : in  slv_31_00;
         opcode_invalid_i           : in  std_logic;
         oprs1_idx_i                : in  slv_04_00;
         oprs1_dat_i                : in  slv_31_00;
         csr_wb_write_i             : in  std_logic;
         csr_wb_waddr_i             : in  slv_11_00;
         csr_wb_wdata_i             : in  slv_31_00;
         csr_wb_exception_i         : in  slv_05_00;
         csr_wb_exception_pc_i      : in  slv_31_00;
         csr_wb_exception_addr_i    : in  slv_31_00;
         --
         csr_result_e1_value_o      : out slv_31_00;
         csr_wr_e1_o                : out std_logic;
         csr_wdata_e1_o             : out slv_31_00;
         csr_result_e1_exception_o  : out slv_05_00
         );
end entity rv_csr;


architecture rtl of rv_csr is

  component rv_csr_rf
    generic ( SUPPORT_MTIMECMP : std_logic := '1';
              RISCV_S_U        : std_logic := '1' );
    port ( clk_i             : in  std_logic;
           xrst_i            : in  std_logic;  -- syncr reset
           ext_irq_i         : in  std_logic;
           timer_irq_i       : in  std_logic;
           --
           cpu_id_i          : in  slv_31_00;
           misa_i            : in  slv_31_00;
           --
           exception_i       : in  slv_05_00;
           exception_pc_i    : in  slv_31_00;
           exception_addr_i  : in  slv_31_00;
           --   CSR read port
           csr_ren_i         : in  std_logic;
           csr_raddr_i       : in  slv_11_00;
           csr_rdata_o       : out slv_31_00;
           --   CSR write port
           csr_waddr_i       : in  slv_11_00;
           csr_wdata_i       : in  slv_31_00;
           --
           csr_branch_o      : out std_logic;
           csr_target_o      : out slv_31_00;
           --   CSR registers
           priv_o            : out slv_01_00;
           status_o          : out slv_31_00;
           satp_o            : out slv_31_00;
           --   masked interrupt output
           interrupt_o       : out slv_31_00 );
  end component;

  -- - - - - - - - - - - - -
  --  registers / wires
  -- - - - - - - - - - - - -
  signal ecall_w          : std_logic;
  signal ebreak_w         : std_logic;
  signal eret_w           : std_logic;
  signal eret_priv_w      : slv_01_00;
  signal csrrw_w          : std_logic;
  signal csrrs_w          : std_logic;
  signal csrrc_w          : std_logic;
  signal csrrwi_w         : std_logic;
  signal csrrsi_w         : std_logic;
  signal csrrci_w         : std_logic;
  signal sfence_w         : std_logic;
  signal ifence_w         : std_logic;

  -- CSR handling
  signal current_priv_w   : slv_01_00;
  signal csr_priv_w       : slv_01_00;
  signal csr_readonly_w   : std_logic;
  signal csr_write_w      : std_logic;
  signal set_w            : std_logic;
  signal clr_w            : std_logic;
  signal csr_fault_w      : std_logic;
  signal data_w           : slv_31_00;
  signal satp_update_w    : std_logic;

  -- CSR register file
  signal misa_w           : slv_31_00;
  signal csr_rdata_w      : slv_31_00;
  signal csr_branch_w     : std_logic;
  signal csr_target_w     : slv_31_00;

  signal interrupt_w      : slv_31_00;
  signal status_reg_w     : slv_31_00;
  signal satp_reg_w       : slv_31_00;
  signal csr_waddr_i_w    : slv_11_00;

  -- CSR read result (E1) / early exceptions
  signal rd_valid_e1_q    : std_logic;
  signal rd_result_e1_q   : slv_31_00;
  signal csr_wdata_e1_q   : slv_31_00;
  signal exception_e1_q   : slv_t(EXCEPTION_W-1 downto 0);
  signal eret_fault_w     : std_logic;

  --  interrupt launch enable
  signal take_irq_q       : std_logic;

  -- TLB flush
  signal tlb_flush_q      : std_logic;
  -- ifence
  signal ifence_q         : std_logic;

  -- execute - branch operations
  signal priv_x_q         : slv_01_00;  --  priv level
  signal branch_q         : std_logic;
  signal branch_target_q  : slv_31_00;
  signal reset_q          : std_logic;

begin

  ecall_w     <= csr_op_i.i_ecall;
  ebreak_w    <= csr_op_i.i_ebreak;
  eret_w      <= csr_op_i.i_eret;

  eret_priv_w <= iword_p1_i(29 downto 28);

  csrrw_w     <= csr_op_i.i_csrrw and opcode_valid_i;
  csrrs_w     <= csr_op_i.i_csrrs and opcode_valid_i;
  csrrc_w     <= csr_op_i.i_csrrc and opcode_valid_i;

  csrrwi_w    <= csr_op_i.i_csrrwi and opcode_valid_i;
  csrrsi_w    <= csr_op_i.i_csrrsi and opcode_valid_i;
  csrrci_w    <= csr_op_i.i_csrrci and opcode_valid_i;

  sfence_w    <= csr_op_i.i_sfence and opcode_valid_i;
  ifence_w    <= csr_op_i.i_ifence and opcode_valid_i;


  -- - - - - - - - - - - - -
  -- CSR handling
  -- - - - - - - - - - - - -
  set_w          <= csrrw_w or csrrs_w or csrrwi_w or csrrsi_w;
  clr_w          <= csrrw_w or csrrc_w or csrrwi_w or csrrci_w;

  csr_priv_w     <= iword_p1_i(29 downto 28);
  csr_readonly_w <= eq(iword_p1_i(31 downto 30),"11");
  csr_write_w    <= not_zero(oprs1_idx_i) or csrrw_w or csrrwi_w;

  data_w         <= ((26 downto 0 => '0') & oprs1_idx_i)
                    when (csrrwi_w = '1' or csrrsi_w = '1' or csrrci_w = '1' )
                    else oprs1_dat_i;

  -- detect access fault on CSR access
  csr_fault_w    <= (opcode_valid_i and (set_w or clr_w) and
                     ((csr_write_w and csr_readonly_w) or
                      lt(current_priv_w, csr_priv_w))) when (RISCV_S_U = '1')
                    else '0';


  satp_update_w <= (opcode_valid_i and (set_w or clr_w) and csr_write_w and
                    eq(iword_p1_i(31 downto 20), CSR_SATP));


  -- - - - - - - - - - - - -
  -- CSR register file
  -- - - - - - - - - - - - -

  misa_w <= ( MISA_RVA_B  => RISCV_A,       -- 0,  atomic
              MISA_RVB_B  => RISCV_ZB,      -- 1,  B is defined as ZbaZbbZbs
                                            --     (bitmanip)
              MISA_RVC_B  => '0',           -- 2,  compressed
              MISA_RVE_B  => RISCV_E,       -- 4,  RV32E base ISA
              MISA_RVI_B  => (not RISCV_E), -- 8,  RV32 base ISA  (if not E)
              MISA_RVM_B  => RISCV_M,       -- 12, integer multiply / divide
              MISA_RVS_B  => RISCV_S_U,     -- 18, supervisor mode implemented
              MISA_RVU_B  => RISCV_S_U,     -- 20, user mode implemented
              MISA_RV32_B => '1',           -- 30, 32-bit architecture (MXL lo)
              others => '0');


  csr_waddr_i_w <= csr_wb_waddr_i when (csr_wb_write_i = '1') else X"000";


  u_csr_rf : rv_csr_rf
    generic map ( RISCV_S_U        => RISCV_S_U,
                  SUPPORT_MTIMECMP => '1' )
    port map ( clk_i             => clk_i,
               xrst_i            => xrst_i,
               ext_irq_i         => extirq_i,  -- external intrrupt
               timer_irq_i       => tmrirq_i,  -- riscv timer intrrupt
               cpu_id_i          => CPU_ID,
               misa_i            => misa_w,
               --  exception (WB)
               exception_i       => csr_wb_exception_i,
               exception_pc_i    => csr_wb_exception_pc_i,
               exception_addr_i  => csr_wb_exception_addr_i,
               --   CSR register read port
               csr_ren_i         => opcode_valid_i,
               csr_raddr_i       => iword_p1_i(31 downto 20),
               csr_rdata_o       => csr_rdata_w,
               --  CSR register write port (WB)
               csr_waddr_i       => csr_waddr_i_w,
               csr_wdata_i       => csr_wb_wdata_i,
               --  CSR branches
               csr_branch_o      => csr_branch_w,
               csr_target_o      => csr_target_w,
               --  various CSR registers
               priv_o            => current_priv_w,
               status_o          => status_reg_w,
               satp_o            => satp_reg_w,
               --  masked interrupt output
               interrupt_o       => interrupt_w );


  -- - - - - - - - - - - - -
  -- CSR read result (E1) / early exceptions
  -- - - - - - - - - - - - -
  -- inappropriate xRET for the current exec priv level
  eret_fault_w <= opcode_valid_i and eret_w and
                  lt(current_priv_w, eret_priv_w);

  p_csr_e1 : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        rd_valid_e1_q   <= '0';
        rd_result_e1_q  <= X"00000000";
        csr_wdata_e1_q  <= X"00000000";
        exception_e1_q  <= (EXCEPTION_W-1 downto 0 => '0');
      else
        if (opcode_valid_i = '1') then
          rd_valid_e1_q     <= (set_w or clr_w) and (not csr_fault_w);
          -- invalid instruction / CSR access fault?
          -- record opcode for writing to csr_xtval later.
          if ( opcode_invalid_i = '1' or csr_fault_w = '1' or
               eret_fault_w = '1' ) then
            rd_result_e1_q  <= iword_p1_i;
          else
            rd_result_e1_q  <= csr_rdata_w;
          end if;
          -- E1 CSR exceptions
          if (ecall_w = '1') then
            exception_e1_q  <= addu(EXCEPTION_ECALL, (X"0" & current_priv_w));
            -- xRET for priv level above this one - fault
          elsif (eret_fault_w = '1') then
            exception_e1_q  <= EXCEPTION_ILLEGAL_INSTRUCTION;
          elsif (eret_w = '1') then
            exception_e1_q  <= addu(EXCEPTION_ERET_U, (X"0" & eret_priv_w));
          elsif (ebreak_w = '1') then
            exception_e1_q  <= EXCEPTION_BREAKPOINT;
          elsif (opcode_invalid_i = '1' or csr_fault_w = '1') then
            exception_e1_q  <= EXCEPTION_ILLEGAL_INSTRUCTION;
            -- fence / MMU settings cause a pipeline flush
          elsif (satp_update_w = '1' or ifence_w = '1' or sfence_w = '1') then
            exception_e1_q  <= EXCEPTION_FENCE;
          else
            exception_e1_q  <= (EXCEPTION_W-1 downto 0 => '0');
          end if;
          -- value to be written to CSR registers
          if (set_w = '1' and clr_w = '1') then
            csr_wdata_e1_q  <= data_w;
          elsif (set_w = '1') then
            csr_wdata_e1_q  <= csr_rdata_w or data_w;
          elsif (clr_w = '1') then
            csr_wdata_e1_q  <= csr_rdata_w and (not data_w);
          end if;
        else
          rd_valid_e1_q     <= '0';
          rd_result_e1_q    <= X"00000000";
          csr_wdata_e1_q    <= X"00000000";
          exception_e1_q    <= (EXCEPTION_W-1 downto 0 => '0');
        end if;
      end if;
    end if;
  end process p_csr_e1;

  csr_result_e1_value_o     <= rd_result_e1_q;
  csr_wr_e1_o               <= rd_valid_e1_q;
  csr_wdata_e1_o            <= csr_wdata_e1_q;
  csr_result_e1_exception_o <= exception_e1_q;


  -- - - - - - - - - - - - - -
  --  interrupt launch enable
  -- - - - - - - - - - - - - -
  p_int : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        take_irq_q <= '0';
      else
        take_irq_q <= not_zero(interrupt_w) and (not irq_block_i);
      end if;
    end if;
  end process p_int;


  take_irq_o <= take_irq_q;

  -- - - - - - - - - - - - -
  --  TLB flush
  -- - - - - - - - - - - - -
  p_dffr(clk_i, xrst_i, (satp_update_w or sfence_w or ifence_w), tlb_flush_q);


  -- - - - - - - - - - - - -
  --  ifence
  -- - - - - - - - - - - - -
  p_dffr(clk_i, xrst_i, ifence_w, ifence_q);


  ifence_p1_o <= ifence_w;
  ifence_p2_o <= ifence_q;


  -- - - - - - - - - - - - -
  -- execute - branch operations
  -- - - - - - - - - - - - -
  p_branch : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        branch_target_q   <= X"00000000";
        branch_q          <= '0';
        reset_q           <= '1';
      else
        if (reset_q = '1') then
          branch_target_q <= BOOT_ADDR;
          branch_q        <= '1';
          reset_q         <= '0';
        else
          branch_q        <= csr_branch_w;
          branch_target_q <= csr_target_w;
        end if;
      end if;
    end if;
  end process p_branch;


  -- - - - - - - - - - - - -
  -- priv level
  -- - - - - - - - - - - - -
  p_priv_lev : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        priv_x_q <= PRIV_MACHINE;
      else
        if (branch_q = '1') then             --  branch csr request
          priv_x_q   <= PRIV_MACHINE;
          if (satp_reg_w(SATP_MODE_R) = '1') then
            priv_x_q <= current_priv_w;
          end if;
        end if;
      end if;
    end if;
  end process p_priv_lev;


  branch_req_o  <= branch_q;
  branch_pc_o   <= branch_target_q;
  branch_priv_o <= PRIV_MACHINE when (satp_reg_w(SATP_MODE_R) = '0')
                   else current_priv_w when (branch_q = '1')
                   else priv_x_q;


  -- - - - - - - - - - - - -
  -- MMU
  -- - - - - - - - - - - - -
  mmu_priv_d_o  <=  status_reg_w(SR_MPP_R'range)
                    when (status_reg_w(SR_MPRV_R) = '1')
                    else current_priv_w;
  mmu_satp_o    <= satp_reg_w;
  mmu_flush_o   <= tlb_flush_q;
  mmu_sum_o     <= status_reg_w(SR_SUM_R);
  mmu_mxr_o     <= status_reg_w(SR_MXR_R);

end rtl;
