-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--
-- 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_pipe_ctrl is
  generic ( BYPASS_LOAD  : std_logic := '1';
            BYPASS_MULT  : std_logic := '1' );
  port ( clk_i                      : in  std_logic;
         xrst_i                     : in  std_logic;    -- syncr reset
         --
         --  issue
         issue_accept_i             : in  std_logic;
         pline_stall_i              : in  std_logic;
         issue_lsu_i                : in  std_logic;
         issue_csr_i                : in  std_logic;
         issue_csr_reg_i            : in  std_logic;    -- reg rd/wr
         issue_div_i                : in  std_logic;
         issue_mul_i                : in  std_logic;
         issue_branch_i             : in  std_logic;
         issue_rd_valid_i           : in  std_logic;
         issue_rd_i                 : in  slv_04_00;
         issue_exception_i          : in  slv_05_00;    -- fault fetch / page
         take_irq_i                 : in  std_logic;
         issue_branch_taken_i       : in  std_logic;
         issue_branch_target_i      : in  slv_31_00;    -- 31:2 NC
         issue_pc_i                 : in  slv_31_00;
         iword_p1_i                 : in  slv_31_00;
         -- execution stage 1: ALU result
         alu_wb_i                   : in  slv_31_00;    -- e1 result
         -- execution stage 1: CSR read result / early exceptions
         csr_result_value_e1_i      : in  slv_31_00;
         csr_result_exception_e1_i  : in  slv_05_00;
         --
         csr_wr_e1_i                : in  std_logic;
         csr_wdata_e1_i             : in  slv_31_00;
         csr_write_wb_o             : out std_logic;
         csr_waddr_wb_o             : out slv_11_00;
         csr_wdata_wb_o             : out slv_31_00;
         csr_wb_o                   : out std_logic;
         --
         -- execution stage 1
         rd_e1_o                    : out slv_04_00;
         load_e1_o                  : out std_logic;
         store_e1_o                 : out std_logic;
         mul_e1_o                   : out std_logic;
         pc_e1_o                    : out slv_31_00;
         -- execution stage 2 : other results
         mem_ld_fault_i             : in  std_logic;
         mem_st_fault_i             : in  std_logic;
         mem_ack_i                  : in  std_logic;
         mem_err_i                  : in  std_logic;
         dmem_rdata_i               : in  slv_31_00;
         dmem_ldtype_i              : in dmem_ldtyp_t;
         mul_wb_i                   : in  slv_31_00;
         -- execution stage 2
         load_e2_o                  : out std_logic;
         mul_e2_o                   : out std_logic;
         rd_e2_o                    : out slv_04_00;
         result_e2_o                : out slv_31_00;    -- bypass wb result
         --
         iword_p2_o                 : out slv_31_00;
         --
         -- out of pipe : divide result
         div_wb_vld_i               : in  std_logic;
         div_wb_i                   : in  slv_31_00;
         -- commit
         rd_wb_o                    : out slv_04_00;
         result_wb_o                : out slv_31_00;    -- register e2 result
         pc_wb_o                    : out slv_31_00;
         exception_wb_o             : out slv_05_00;
         --
         stall_o                    : out std_logic;
         squash_e1_e2_o             : out std_logic );
end entity rv_pipe_ctrl;


architecture rtl of rv_pipe_ctrl is

  -- - - - - - - - - - - - -
  -- E1 / Address
  -- - - - - - - - - - - - -
  constant PCINFO_W           : natural := 10;
  constant PCINFO_ALU         : natural :=  0;
  constant PCINFO_LOAD        : natural :=  1;
  constant PCINFO_STORE       : natural :=  2;
  constant PCINFO_CSR         : natural :=  3;
  constant PCINFO_DIV         : natural :=  4;
  constant PCINFO_MUL         : natural :=  5;
  constant PCINFO_BRANCH      : natural :=  6;
  constant PCINFO_RD_VALID    : natural :=  7;
  constant PCINFO_INTR        : natural :=  8;
  constant PCINFO_COMPLETE    : natural :=  9;

  signal squash_e1_e2_w       : std_logic;
  signal branch_misaligned_w  : std_logic;

  -- E1 / address
  signal valid_e1_q           : std_logic;
  signal ctrl_e1_q            : slv_t(PCINFO_W-1 downto 0);
  signal pc_e1_q              : slv_31_00;
  signal iword_e1_q           : slv_31_00;
  signal exception_e1_q       : slv_t(EXCEPTION_W-1 downto 0);
  -- E2 / mem result
  signal valid_e2_q           : std_logic;
  signal ctrl_e2_q            : slv_t(PCINFO_W-1 downto 0);
  signal csr_wr_e2_q          : std_logic;
  signal csr_wdata_e2_q       : slv_31_00;
  signal result_e2_q          : slv_31_00;
  signal pc_e2_q              : slv_31_00;
  signal iword_e2_q           : slv_31_00;                  -- NC  19:12, 6:0
  signal exception_e2_q       : slv_t(EXCEPTION_W-1 downto 0);

  signal valid_e2_w           : std_logic;
  signal exception_e2_w       : slv_t(EXCEPTION_W-1 downto 0);
  signal squash_e1_e2_q       : std_logic;
  -- writeback / commit
  signal valid_wb_q           : std_logic;
  signal ctrl_wb_q            : slv_t(PCINFO_W-1 downto 0); -- NC 9-8, 6-4, 2-0
  signal csr_wr_wb_q          : std_logic;
  signal csr_wdata_wb_q       : slv_31_00;
  signal result_wb_q          : slv_31_00;
  signal pc_wb_q              : slv_31_00;
  signal iword_wb_q           : slv_31_00;                  -- NC  19:12, 6:0
  signal exception_wb_q       : slv_t(EXCEPTION_W-1 downto 0);  -- 5:0

  signal load_sel_w           : std_logic;
  signal mul_sel_w            : std_logic;

  signal dmem_valid_w         : std_logic;
  signal dmem_excp_e2_w       : slv_05_00;
  signal dmem_dat_e2_w        : slv_31_00;

  -- canot read output
  signal squash_e1_e2_o_w     : std_logic;
  signal stall_o_w            : std_logic;
  signal valid_wb_o_w         : std_logic;

begin

  branch_misaligned_w  <= issue_branch_taken_i and
                          not_zero(issue_branch_target_i(1 downto 0));

  -- - - - - - - - - - - - -
  --  E1 / address
  -- - - - - - - - - - - - -
  p_e1 : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        valid_e1_q      <= '0';
        ctrl_e1_q       <= (PCINFO_W-1 downto 0 => '0');
        pc_e1_q         <= X"00000000";
        iword_e1_q      <= X"00000000";
        exception_e1_q  <= (EXCEPTION_W-1 downto 0 => '0');  -- 5:0

      else
        if (pline_stall_i = '1') then        -- stall - no change in E1 state
          null;
        elsif ( issue_accept_i = '1' and squash_e1_e2_o_w = '0' ) then
          valid_e1_q                 <= '1';
          ctrl_e1_q(PCINFO_ALU)      <= not(issue_lsu_i or issue_csr_i or
                                            issue_div_i or issue_mul_i);
          ctrl_e1_q(PCINFO_LOAD)     <= issue_lsu_i and issue_rd_valid_i and
                                        (not take_irq_i);
          ctrl_e1_q(PCINFO_STORE)    <= issue_lsu_i and
                                        (not issue_rd_valid_i) and
                                        (not take_irq_i);
          ctrl_e1_q(PCINFO_CSR)      <= issue_csr_i and (not take_irq_i);
          ctrl_e1_q(PCINFO_DIV)      <= issue_div_i and (not take_irq_i);
          ctrl_e1_q(PCINFO_MUL)      <= issue_mul_i and (not take_irq_i);
          ctrl_e1_q(PCINFO_BRANCH)   <= issue_branch_i and
                                        (not take_irq_i);
          ctrl_e1_q(PCINFO_RD_VALID) <= issue_rd_valid_i and
                                        (not take_irq_i);
          ctrl_e1_q(PCINFO_INTR)     <= take_irq_i;
          ctrl_e1_q(PCINFO_COMPLETE) <= '1';

          pc_e1_q                    <= issue_pc_i;
          iword_e1_q      <= iword_p1_i;
          if ( not_zero(issue_exception_i) ) then         -- fault fetch / page
            exception_e1_q <= issue_exception_i;          -- 5:0
          elsif (branch_misaligned_w  = '1') then
            exception_e1_q <= EXCEPTION_MISALIGNED_FETCH;
          else
            exception_e1_q <= (EXCEPTION_W-1 downto 0 => '0');
          end if;
        else  -- no valid instruction (or pipeline flush event)
          valid_e1_q      <= '0';
          ctrl_e1_q       <= (PCINFO_W-1 downto 0 => '0');
          pc_e1_q         <= X"00000000";
          iword_e1_q      <= X"00000000";
          exception_e1_q  <= (EXCEPTION_W-1 downto 0 => '0');
        end if;
      end if;
    end if;
  end process p_e1;

  load_e1_o       <= ctrl_e1_q(PCINFO_LOAD);
  store_e1_o      <= ctrl_e1_q(PCINFO_STORE);
  mul_e1_o        <= ctrl_e1_q(PCINFO_MUL);
  rd_e1_o         <= (4 downto 0 => ctrl_e1_q(PCINFO_RD_VALID)) and
                     iword_e1_q(RD_R'range);            -- 11:7 destination reg
  pc_e1_o         <= pc_e1_q;

  iword_p2_o      <= iword_e1_q;


  -- - - - - - - - - - - - -
  --  E2 / mem result
  -- - - - - - - - - - - - -
  p_e2_mres : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        valid_e2_q          <= '0';
        ctrl_e2_q           <= (PCINFO_W-1 downto 0 => '0');
        csr_wr_e2_q         <= '0';
        csr_wdata_e2_q      <= X"00000000";
        pc_e2_q             <= X"00000000";
        iword_e2_q          <= X"00000000";
        result_e2_q         <= X"00000000";
        exception_e2_q      <= (EXCEPTION_W-1 downto 0 => '0');
      -- stall - no change in E2 state
      else
        if (pline_stall_i = '1') then
          null;
          -- Pipeline flush
        elsif ( squash_e1_e2_o_w = '1' ) then
          valid_e2_q        <= '0';
          ctrl_e2_q         <= (PCINFO_W-1 downto 0 => '0');
          csr_wr_e2_q       <= '0';
          csr_wdata_e2_q    <= X"00000000";
          pc_e2_q           <= X"00000000";
          iword_e2_q        <= X"00000000";
          result_e2_q       <= X"00000000";
          exception_e2_q    <= (EXCEPTION_W-1 downto 0 => '0');
          -- normal pipeline advance
        else
          valid_e2_q        <= valid_e1_q;
          ctrl_e2_q         <= ctrl_e1_q;
          csr_wr_e2_q       <= csr_wr_e1_i;
          csr_wdata_e2_q    <= csr_wdata_e1_i;
          pc_e2_q           <= pc_e1_q;
          iword_e2_q        <= iword_e1_q;

          -- launch interrupt
          if (ctrl_e1_q(PCINFO_INTR) = '1') then
            exception_e2_q  <= EXCEPTION_INTERRUPT;
            -- if frontend reports bad instruction, ignore later CSR errors...
          elsif ( not_zero(exception_e1_q) ) then
            valid_e2_q      <= '0';
            exception_e2_q  <= exception_e1_q;
          else
            exception_e2_q  <= csr_result_exception_e1_i;
          end if;

          result_e2_q       <= alu_wb_i;
          if (ctrl_e1_q(PCINFO_DIV) = '1') then
            result_e2_q     <= div_wb_i;
          elsif (ctrl_e1_q(PCINFO_CSR) = '1') then
            result_e2_q     <= csr_result_value_e1_i;
          end if;
        end if;
      end if;
    end if;
  end process p_e2_mres;


  -- - - - - - - - - - - - -
  -- LSU: load response
  -- - - - - - - - - - - - -

  dmem_valid_w   <= mem_ack_i or dmem_ldtype_i.ualigned_e2;

  -- from lsu
  dmem_excp_e2_w <= EXCEPTION_MISALIGNED_LOAD
                    when (dmem_ldtype_i.ualigned_e2 = '1' and
                          dmem_ldtype_i.ld = '1') else
                    EXCEPTION_MISALIGNED_STORE
                    when (dmem_ldtype_i.ualigned_e2 = '1' and
                          dmem_ldtype_i.ld = '0') else
                    EXCEPTION_PAGE_FAULT_LOAD
                    when (mem_err_i = '1' and mem_ld_fault_i = '1') else
                    EXCEPTION_PAGE_FAULT_STORE
                    when (mem_err_i = '1' and mem_st_fault_i = '1') else
                    EXCEPTION_FAULT_LOAD
                    when (mem_err_i = '1' and  dmem_ldtype_i.ld = '1') else
                    EXCEPTION_FAULT_STORE
                    when (mem_err_i = '1' and dmem_ldtype_i.ld = '0') else
                    (EXCEPTION_W-1 downto 0 => '0');

  --        ack, err, ldtype, idata, odata
  p_ld_resp(mem_ack_i, mem_err_i, dmem_ldtype_i, dmem_rdata_i, dmem_dat_e2_w);

  valid_e2_w  <= valid_e2_q and (not pline_stall_i);
  load_sel_w  <= valid_e2_w and ( ctrl_e2_q(PCINFO_LOAD) or
                                  ctrl_e2_q(PCINFO_STORE) );
  mul_sel_w   <= valid_e2_w and ctrl_e2_q(PCINFO_MUL);


  p_c_e2_res : process(result_e2_q, load_sel_w, dmem_dat_e2_w,
                       mul_sel_w, mul_wb_i)
  begin
    -- default: ALU result
    result_e2_o <= result_e2_q;                       -- div / csr / alu
    --
    if (load_sel_w = '1' and BYPASS_LOAD = '1') then  -- SIGN EXTEND
      -- [sign extended] byte | half word | word
      result_e2_o <= dmem_dat_e2_w;                   -- from dmem, long path
    end if;
    if (mul_sel_w = '1' and BYPASS_MULT = '1') then
      result_e2_o <=  mul_wb_i;
    end if;
  end process p_c_e2_res;


  load_e2_o       <= ctrl_e2_q(PCINFO_LOAD);
  mul_e2_o        <= ctrl_e2_q(PCINFO_MUL);                   -- 7
  rd_e2_o         <= (4 downto 0 => ( valid_e2_w and ctrl_e2_q(PCINFO_RD_VALID)
                                      and (not stall_o_w) )) and
                     iword_e2_q(RD_R'range);                  --  11:7

  -- load store result not ready when reaching E2
  stall_o_w       <= ( ctrl_e1_q(PCINFO_DIV) and (not div_wb_vld_i) ) or
                     ( (ctrl_e2_q(PCINFO_LOAD) or ctrl_e2_q(PCINFO_STORE) ) and
                       (not dmem_valid_w) );
  stall_o         <= stall_o_w;

  exception_e2_w  <= dmem_excp_e2_w  when (valid_e2_q               = '1' and
                                           (ctrl_e2_q(PCINFO_LOAD)  = '1' or
                                            ctrl_e2_q(PCINFO_STORE) = '1')
                                           and dmem_valid_w = '1')
                     else exception_e2_q;

  squash_e1_e2_w <= not_zero(exception_e2_w);


  p_squash : process (clk_i)
  begin
    if rising_edge ( clk_i ) then
      if (xrst_i = '0') then
        squash_e1_e2_q  <= '0';
      elsif (pline_stall_i = '0') then
        squash_e1_e2_q  <= squash_e1_e2_w;
      end if;
    end if;
  end process p_squash;

  squash_e1_e2_o_w  <= squash_e1_e2_w or squash_e1_e2_q;
  squash_e1_e2_o    <= squash_e1_e2_o_w;


  -- - - - - - - - - - - - -
  -- writeback / commit
  -- - - - - - - - - - - - -
  p_wb : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        valid_wb_q      <= '0';
        ctrl_wb_q       <= (PCINFO_W-1 downto 0 => '0');
        csr_wr_wb_q     <= '0';
        csr_wdata_wb_q  <= X"00000000";
        pc_wb_q         <= X"00000000";
        iword_wb_q      <= X"00000000";
        result_wb_q     <= X"00000000";
        exception_wb_q  <= (EXCEPTION_W-1 downto 0 => '0');
      else
        if (pline_stall_i = '1') then      -- stall - no change in WB state
          null;
        else
          -- squash instruction valid on memory faults
          case exception_e2_w is
            when  EXCEPTION_MISALIGNED_LOAD |
              EXCEPTION_FAULT_LOAD          |
              EXCEPTION_MISALIGNED_STORE    |
              EXCEPTION_FAULT_STORE         |
              EXCEPTION_PAGE_FAULT_LOAD     |
              EXCEPTION_PAGE_FAULT_STORE =>
              valid_wb_q      <= '0';
            when others =>
              valid_wb_q      <= valid_e2_q;
          end case;

          csr_wr_wb_q     <= csr_wr_e2_q;
          csr_wdata_wb_q  <= csr_wdata_e2_q;

          ctrl_wb_q       <= ctrl_e2_q;
          -- exception - squash writeback
          if ( not_zero(exception_e2_w) ) then
            ctrl_wb_q(PCINFO_RD_VALID) <= '0';
          end if;
          pc_wb_q         <= pc_e2_q;
          iword_wb_q      <= iword_e2_q;
          exception_wb_q  <= exception_e2_w;
          --
          --
          result_wb_q <= result_e2_q;
          if (load_sel_w = '1') then
            result_wb_q <= dmem_dat_e2_w;    -- from dcache / excp addr
          elsif (mul_sel_w = '1') then
            result_wb_q <= mul_wb_i;
          end if;
        end if;
      end if;
    end if;
  end process p_wb;


  -- instruction completion (for debug)

  valid_wb_o_w    <= valid_wb_q and (not pline_stall_i);

  rd_wb_o         <= (4 downto 0 => (valid_wb_o_w and
                                     ctrl_wb_q(PCINFO_RD_VALID) and
                                     (not stall_o_w)))
                     and iword_wb_q(RD_R'range);        -- 11:7
  result_wb_o     <= result_wb_q;


  pc_wb_o         <= pc_wb_q;

  exception_wb_o  <= exception_wb_q;

  csr_wb_o        <= ctrl_wb_q(PCINFO_CSR) and (not pline_stall_i);
  csr_write_wb_o  <= csr_wr_wb_q;
  csr_waddr_wb_o  <= iword_wb_q(IMM12_R'range);         -- 31:20
  csr_wdata_wb_o  <= csr_wdata_wb_q;

end rtl;
