-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--
-- 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_exec is
  generic ( RISCV_M    : sl_t := '0';        -- RV mul / div extension
            RISCV_Zba  : sl_t := '0';        -- address generation (shXadd)
            RISCV_ZB   : sl_t := '1' );      -- ZB-extension + Zicond
  port ( clk_i           : in  std_logic;
         xrst_i          : in  std_logic;
         --
         pline_stall_i   : in  std_logic;    -- ISSUE.PIPE.stall_o
         rs1_dat_i       : in  slv_31_00;
         rs2_dat_i       : in  slv_31_00;
         -- alu
         alu_op_i        : in  alu_op_t;
         iword_p2_i      : in  slv_31_00;
         pc_e1_i         : in  slv_31_00;
         alu_wb_o        : out slv_31_00;
         -- multiplier
         mul_op_p1_i     : in  mul_op_t;
         mul_vld_i       : in  std_logic;
         mul_wb_o        : out slv_31_00;
         --  divider
         div_op_p1_i     : in  div_op_t;
         div_vld_i       : in  std_logic;
         div_wb_vld_o    : out std_logic;
         div_wb_o        : out slv_31_00;
         --
         iword_p1_i      : in  slv_31_00;
         opc_vld_i       : in  std_logic;
         opc_pc_i        : in  slv_31_00;
         branch_op_i     : in  branch_op_t;
         branch_req_o    : out std_logic;
         branch_pc_o     : out slv_31_00
         );
end entity rv_exec;


architecture rtl of rv_exec is

  component rv_alu
    port ( alu_op_i : in  slv_03_00;
           alu_a_i  : in  slv_31_00;
           alu_b_i  : in  slv_31_00;
           alu_p_o  : out slv_31_00 );
  end component;

  component rv_multiplier
    port ( clk_i          : in  std_logic;
           xrst_i         : in  std_logic;
           --
           mul_op_p1_i    : in  mul_op_t;
           pline_stall_i  : in  std_logic;
           valid_i        : in  std_logic;
           rs1_dat_i      : in  slv_31_00;
           rs2_dat_i      : in  slv_31_00;
           --
           wb_value_o     : out slv_31_00 );
  end component;

  component rv_divider
    port ( clk_i        : in  std_logic;
           xrst_i       : in  std_logic;
           --
           div_op_p1_i  : in  div_op_t;
           valid_i      : in  std_logic;
           rs1_dat_i    : in  slv_31_00;
           rs2_dat_i    : in  slv_31_00;
           --
           wb_valid_o   : out std_logic;
           wb_value_o   : out slv_31_00 );
  end component;

  -- instruction decode
  signal opc_p2_w     : alu_op_t;
  signal imm20_q      : slv_31_00;
  signal imm12_q      : slv_31_00;

  signal rs1_dat_q    : slv_31_00;           -- alu / alu zb input reg
  signal rs2_dat_q    : slv_31_00;           -- alu / alu zb input reg

  --  exExecute - ALU operations
  signal alu_a_w      : slv_31_00;
  signal alu_b_w      : slv_31_00;

  signal mul_dout_w   : slv_31_00;
  signal div_dout_w   : slv_31_00;

  signal alu_wb_o_w   : slv_31_00;

  signal zb_wb_o_w    : slv_31_00;

begin


  -- MULHSU | MULH          :  a  <= ra_i(31)  &  ra_i(31:0)
  -- MULHU  | MUL           :  a  <= '0'       &  ra_i(31:0)
  --
  -- MULH                   :  b  <= rb_i(31)  &  rb_i(31:0)
  -- MULHU  | MUL | MULHSU  :  b  <= '0'       &  rb_i(31:0)
  u_mul : rv_multiplier
    port map ( clk_i          => clk_i,
               xrst_i         => xrst_i,
               -- inputs
               mul_op_p1_i    => mul_op_p1_i,
               valid_i        => mul_vld_i,
               pline_stall_i  => pline_stall_i,
               rs1_dat_i      => rs1_dat_i,
               rs2_dat_i      => rs2_dat_i,
               -- outputs
               wb_value_o     => mul_dout_w );

  mul_wb_o  <= mul_dout_w  when ( RISCV_M = '1' ) else X"00000000";



  -- ra : dividend[31:0]
  -- rb : divisor[62:0]
  --
  -- DIV | REM ==> signed op
  --
  -- (DIV | REM) & ra_i(31) = '1'   :  dividend[31:0]  <=  -ra_i
  -- else (DIVU | REMU | !ra_i(31)) :  dividend[31:0]  <=   ra_i
  --
  -- (DIV | REM) & rb_i(31) = '1'   :  divisor[62:0]   <= -rb_i &  (30:0 => '0')
  -- else (DIVU | REMU | !rb_i(31)) :  divisor[62:0]   <=  rb_i &  (30:0 => '0')
  u_div : rv_divider
    port map ( clk_i        => clk_i,
               xrst_i       => xrst_i,
               -- inputs
               div_op_p1_i  => div_op_p1_i,
               valid_i      => div_vld_i,
               rs1_dat_i    => rs1_dat_i,
               rs2_dat_i    => rs2_dat_i,
               -- outputs
               wb_valid_o   => div_wb_vld_o,
               wb_value_o   => div_dout_w );

  div_wb_o  <= div_dout_w  when ( RISCV_M = '1' ) else X"00000000";
  --
  -- invert divider output when
  --   invert_res <= '1' :   DIV & (ra__i(31) /= rb_i(31)) and rb_i(31:0) /= 0
  --                       or  REM and ra_i(31) = '1'
  -- i.e. inver output when
  --  1) DIV op  and  ra/rb are different sign and rb is not zero vector
  --  2) REM op  and  ra    is negative (dividend)




  -- - - - - - - - - - - - - - - -
  --  opcode decode (immediates)
  -- - - - - - - - - - - - - - - -
  opc_p2_w  <= alu_op_i;

  imm20_q   <= ( iword_p2_i(IMM20_R'range) & X"000" );         -- 31:12
  imm12_q   <= ( (19 downto 0 => iword_p2_i(IMM12_R'left)) &   -- sign
                 iword_p2_i(IMM12_R'range) );                  -- 31:20


  p_aluireg : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        rs1_dat_q <= (others => '0');
        rs2_dat_q <= (others => '0');
      else
        if (pline_stall_i = '0') then
          rs1_dat_q <= rs1_dat_i;
          rs2_dat_q <= rs2_dat_i;
        end if;
      end if;
    end if;
  end process p_aluireg;


  -- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  -- execute - ALU operations
  -- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  --
  -- select ALU A input
  --
  --   NOTE:  load upper immediate is in b-bus (opc_p2_w.i_lui, less mux)
  --   rs1_dat_q : alu op / add immediate
  --   pc_e1_i     : add upper immediate to pc / jump and link [register]
  --
  --   Zba - address generation: sh1add / sh2add / sh3add
  --
  alu_a_w <= rs1_dat_q(30 downto 0) & '0'   when (opc_p2_w.i_sh1add = '1') else
             rs1_dat_q(29 downto 0) & "00"  when (opc_p2_w.i_sh2add = '1') else
             rs1_dat_q(28 downto 0) & "000" when (opc_p2_w.i_sh3add = '1') else
             rs1_dat_q when ( opc_p2_w.i_2operand  = '1'  or
                              opc_p2_w.i_immediate = '1'  ) else
             pc_e1_i   when ( opc_p2_w.i_auipc     = '1'  or
                              opc_p2_w.i_jal       = '1'  or
                              opc_p2_w.i_jalr      = '1' )   else
             X"00000000";


  --
  -- select ALU B input
  --
  --   rs2_dat_q : alu op / add immediate / sh*add
  --   imm12_q     : immediate op, (shamt_q : shift amount (slli, srli, srai))
  --   imm20_q     : ! ! load upper immediate, NOTE: ON B BUS
  --   X"00000004" : jal / jalr: (indirect jump, jump and link register)
  alu_b_w <= rs2_dat_q when ( opc_p2_w.i_2operand = '1' or
                              opc_p2_w.i_sh1add   = '1' or
                              opc_p2_w.i_sh2add   = '1' or
                              opc_p2_w.i_sh3add   = '1' ) else
             imm12_q  when (opc_p2_w.i_immediate  = '1')  else  -- immediate add
             imm20_q  when (opc_p2_w.i_lui = '1' or opc_p2_w.i_auipc = '1') else
             X"00000004" when (opc_p2_w.i_jal = '1' or opc_p2_w.i_jalr = '1')
             else X"00000000";



  -- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  u_alu : rv_alu
    port map ( alu_op_i => opc_p2_w.alu_func4,
               alu_a_i  => alu_a_w,     -- rs1_dat_q / pc_e1_i / X"00000000"
               alu_b_i  => alu_b_w,     -- rs2_dat_q / imm-- / shamt / +4
               alu_p_o  => alu_wb_o_w );


  --         enable    valid              opcode
  p_alu_zb ( RISCV_ZB, alu_op_i.zb_valid, alu_op_i.zb_func5,
             imm12_q, rs1_dat_q, rs2_dat_q, zb_wb_o_w );


  alu_wb_o <= alu_wb_o_w when ( alu_op_i.zb_valid = '0' ) else
              zb_wb_o_w;
  -- - - - - - - - - - - - - - - - - - - - - - - - - - - - -






  -- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  --  execute - branch operations
  -- - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  -- instr   branch_w     branch_go_w               description
  -- -----   --------   -----------------           -------------
  --  beq   '1'  eq(rs1_dat_i, rs2_dat_i)           EQUAL
  --  bne   '1'  not (eq(rs1_dat_i, rs2_dat_i))     NOT EQUAL
  --  blt   '1'  lt_signed(rs1_dat_i, rs2_dat_i)    LESS THAN
  --  bge   '1'  gt_signed(rs1_dat_i, rs2_dat_i)    GREATER OR EQUAL = ! LT
  --                         OR
  --                eq(rs1_dat_i, rs2_dat_i)
  --  bltu  '1'   lt(rs1_dat_i,rs2_dat_i);          LESS THAN UNSIGNED
  --  bgeu  '1'   not lt(rs1_dat_i, rs2_dat_i)      GREATER OR EQUAL UNSIGNED
  --

  p_btarget ( iword_p1_i, branch_op_i, opc_pc_i, rs1_dat_i,
              rs2_dat_i, opc_vld_i, branch_pc_o, branch_req_o );

end rtl;
