// Copyright 2018 ETH Zurich and University of Bologna.
// Copyright and related rights are licensed under the Solderpad Hardware
// License, Version 0.51 (the "License"); you may not use this file except in
// compliance with the License.  You may obtain a copy of the License at
// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law
// or agreed to in writing, software, hardware and materials distributed under
// this 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.

////////////////////////////////////////////////////////////////////////////////
// Engineer:       Renzo Andri - andrire@student.ethz.ch                      //
//                                                                            //
// Additional contributions by:                                               //
//                 Igor Loi - igor.loi@unibo.it                               //
//                 Andreas Traber - atraber@student.ethz.ch                   //
//                 Sven Stucki - svstucki@student.ethz.ch                     //
//                 Michael Gautschi - gautschi@iis.ee.ethz.ch                 //
//                 Davide Schiavone - pschiavo@iis.ee.ethz.ch                 //
//                 Halfdan Bechmann - halfdan.bechmann@silabs.com             //
//                 Øystein Knauserud - oystein.knauserud@silabs.com           //
//                 Michael Platzer - michael.platzer@tuwien.ac.at             //
//                                                                            //
// Design Name:    Instruction Decode Stage                                   //
// Project Name:   RI5CY                                                      //
// Language:       SystemVerilog                                              //
//                                                                            //
// Description:    Decode stage of the core. It decodes the instructions      //
//                 and hosts the register file.                               //
//                                                                            //
////////////////////////////////////////////////////////////////////////////////

module cv32e40s_id_stage import cv32e40s_pkg::*;
#(
  parameter rv32_e       RV32                   = RV32I,
  parameter b_ext_e      B_EXT                  = B_NONE,
  parameter m_ext_e      M_EXT                  = M,
  parameter int unsigned REGFILE_NUM_READ_PORTS = 2,
  parameter bit          CLIC                   = 1
)
(
  input  logic        clk,                    // Gated clock
  input  logic        rst_n,

  // Jumps and branches
  output logic [31:0] jmp_target_o,

  // IF/ID pipeline
  input  if_id_pipe_t if_id_pipe_i,

  // ID/EX pipeline
  output id_ex_pipe_t id_ex_pipe_o,

  // EX/WB pipeline
  input  ex_wb_pipe_t ex_wb_pipe_i,

  // Controller
  input  ctrl_byp_t   ctrl_byp_i,
  input  ctrl_fsm_t   ctrl_fsm_i,

  input  mstatus_t    mstatus_i,

  input  xsecure_ctrl_t xsecure_ctrl_i,
  input  mcause_t     mcause_i,
  input  logic [JVT_ADDR_WIDTH-1:0] jvt_addr_i,

  // Register file write data from WB stage
  input  logic [31:0] rf_wdata_wb_i,

  // Register file write data from EX stage
  input  logic [31:0] rf_wdata_ex_i,

  output logic        alu_en_o,
  output logic        alu_jmp_o,        // Jump (JAL, JALR)
  output logic        alu_jmpr_o,       // Jump register (JALR)

  output logic        sys_mret_insn_o,
  output logic        sys_wfi_insn_o,
  output logic        sys_wfe_insn_o,
  output logic        last_sec_op_o,
  output logic        csr_en_raw_o,

  output logic        sys_en_o,

  output logic        first_op_o,
  output logic        last_op_o,
  output logic        abort_op_o,

  // RF interface -> controller
  output logic [REGFILE_NUM_READ_PORTS-1:0] rf_re_o,
  output rf_addr_t    rf_raddr_o[REGFILE_NUM_READ_PORTS],

  // Register file
  input  rf_data_t    rf_rdata_i[REGFILE_NUM_READ_PORTS],

  // Stage ready/valid
  output logic        id_ready_o,     // ID stage is ready for new data
  output logic        id_valid_o,     // ID stage has valid (non-bubble) data for next stage
  input  logic        ex_ready_i,     // EX stage is ready for new data

  output logic        lfsr_shift_o
);

  // Source/Destination register instruction index
  localparam REG_S1_MSB = 19;
  localparam REG_S1_LSB = 15;

  localparam REG_S2_MSB = 24;
  localparam REG_S2_LSB = 20;

  localparam REG_S3_MSB = 31;
  localparam REG_S3_LSB = 27;

  localparam REG_D_MSB  = 11;
  localparam REG_D_LSB  = 7;

  logic [31:0] instr;

  // Register Read/Write Control
  logic [1:0]           rf_re;                  // Decoder only supports rs1, rs2
  logic                 rf_we;
  logic                 rf_we_dec;
  rf_addr_t             rf_waddr;
  logic [REGFILE_NUM_READ_PORTS-1:0] rf_illegal_raddr;

  // ALU Control
  logic                 alu_en;
  logic                 alu_bch;
  logic                 alu_jmp;
  logic                 alu_jmpr;
  alu_opcode_e          alu_operator;

  // Multiplier Control
  logic                 mul_en;                 // Multiplication is used instead of ALU
  mul_opcode_e          mul_operator;           // Multiplication operation selection
  logic [1:0]           mul_signed_mode;        // Signed mode multiplication at the output of the controller, and before the pipe registers

  // Divider control
  logic                 div_en;
  div_opcode_e          div_operator;

  // LSU
  logic                 lsu_en;
  logic                 lsu_we;
  logic [1:0]           lsu_size;
  logic                 lsu_sext;

  // CSR
  logic                 csr_en;
  logic                 csr_en_raw;
  csr_opcode_e          csr_op;

  // SYS
  logic                 sys_en;
  logic                 sys_fence_insn;
  logic                 sys_fencei_insn;
  logic                 sys_ecall_insn;
  logic                 sys_ebrk_insn;
  logic                 sys_mret_insn;
  logic                 sys_dret_insn;
  logic                 sys_wfi_insn;
  logic                 sys_wfe_insn;

  // Operands and forwarding
  logic [31:0]          operand_a;
  logic [31:0]          operand_b;
  logic [31:0]          operand_c;
  logic [31:0]          operand_a_fw;
  logic [31:0]          operand_b_fw;
  logic [31:0]          jalr_fw;
  alu_op_a_mux_e        alu_op_a_mux_sel;
  alu_op_b_mux_e        alu_op_b_mux_sel;
  op_c_mux_e            op_c_mux_sel;
  imm_a_mux_e           imm_a_mux_sel;
  imm_b_mux_e           imm_b_mux_sel;
  bch_jmp_mux_e         bch_jmp_mux_sel;

  // Immediates
  logic [31:0]          imm_a;                  // Immediate for operand A
  logic [31:0]          imm_b;                  // Immediate for operand B
  logic [31:0]          imm_i_type;
  logic [31:0]          imm_s_type;
  logic [31:0]          imm_sb_type;
  logic [31:0]          imm_u_type;
  logic [31:0]          imm_uj_type;
  logic [31:0]          imm_z_type;

  // Branch target address
  logic [31:0]          bch_target;

  // Stall for multi operation ID instructions
  logic                 multi_op_id_stall;

  // Indicate last part of a multi operation instruction
  logic                 last_sec_op;
  logic                 first_sec_op;

  logic                 illegal_insn;

  // Local instruction valid qualifier
  logic                 instr_valid;

  // Last operation of instruction (including secure operations)
  logic                 last_op;

  // First operation fo instruction (including secure operations)
  logic                 first_op;

  // Signal for detection of first operation (of two) of table jumps.
  logic                 tbljmp_first;

  // Current index for JVT instructions
  logic [7:0]           jvt_index;

  // Next-PC (+2/4, for use in EX in case of dataindependent non-taken branches)
  logic [31:0]          pc_next;

  assign instr_valid = if_id_pipe_i.instr_valid && !ctrl_fsm_i.kill_id && !ctrl_fsm_i.halt_id;

  assign sys_mret_insn_o = sys_mret_insn;
  assign sys_wfi_insn_o  = sys_wfi_insn;
  assign sys_wfe_insn_o  = sys_wfe_insn;



  // Ensures one shift of the operand LFSRs for each dummy or hint instruction in ID
  assign lfsr_shift_o    = (id_valid_o && ex_ready_i) && (if_id_pipe_i.instr_meta.dummy || if_id_pipe_i.instr_meta.hint) && last_sec_op;

  assign instr = if_id_pipe_i.instr.bus_resp.rdata;

  // Immediate extraction and sign extension
  assign imm_i_type   = { {20 {instr[31]}}, instr[31:20] };
  assign imm_s_type   = { {20 {instr[31]}}, instr[31:25], instr[11:7] };
  assign imm_sb_type  = { {19 {instr[31]}}, instr[31], instr[7], instr[30:25], instr[11:8], 1'b0 };
  assign imm_u_type   = { instr[31:12], 12'b0 };
  assign imm_uj_type  = { {12 {instr[31]}}, instr[19:12], instr[20], instr[30:21], 1'b0 };

  // Immediate for CSR manipulation (zero extended)
  assign imm_z_type  = { 27'b0, instr[REG_S1_MSB:REG_S1_LSB] };

  //---------------------------------------------------------------------------
  // Source register selection
  //---------------------------------------------------------------------------
  assign rf_raddr_o[0] = instr[REG_S1_MSB:REG_S1_LSB];
  assign rf_raddr_o[1] = instr[REG_S2_MSB:REG_S2_LSB];

  //---------------------------------------------------------------------------
  // Destination register seclection
  //---------------------------------------------------------------------------
  assign rf_waddr = instr[REG_D_MSB:REG_D_LSB];

  // Detect first half of table jumps
  assign tbljmp_first = if_id_pipe_i.instr_meta.tbljmp ? !if_id_pipe_i.last_op : 1'b0;

  //////////////////////////////////////////////////////////////////
  //      _                         _____                    _    //
  //     | |_   _ _ __ ___  _ __   |_   _|_ _ _ __ __ _  ___| |_  //
  //  _  | | | | | '_ ` _ \| '_ \    | |/ _` | '__/ _` |/ _ \ __| //
  // | |_| | |_| | | | | | | |_) |   | | (_| | | | (_| |  __/ |_  //
  //  \___/ \__,_|_| |_| |_| .__/    |_|\__,_|_|  \__, |\___|\__| //
  //                       |_|                    |___/           //
  //////////////////////////////////////////////////////////////////

  assign jvt_index = if_id_pipe_i.instr.bus_resp.rdata[19:12];

  cv32e40s_pc_target cv32e40s_pc_target_i
  (
    .bch_jmp_mux_sel_i ( bch_jmp_mux_sel                   ),
    .pc_id_i           ( if_id_pipe_i.pc                   ),
    .imm_uj_type_i     ( imm_uj_type                       ),
    .imm_sb_type_i     ( imm_sb_type                       ),
    .imm_i_type_i      ( imm_i_type                        ),
    .jalr_fw_i         ( jalr_fw                           ),
    .jvt_addr_i        ( jvt_addr_i                        ),
    .jvt_index_i       ( jvt_index                         ),
    .compressed_i      ( if_id_pipe_i.instr_meta.compressed),
    .dummy_i           ( if_id_pipe_i.instr_meta.dummy     ),
    .bch_target_o      ( bch_target                        ),
    .jmp_target_o      ( jmp_target_o                      ),
    .pc_next_o         ( pc_next                           )
  );

  ////////////////////////////////////////////////////////
  //   ___                                 _      _     //
  //  / _ \ _ __   ___ _ __ __ _ _ __   __| |    / \    //
  // | | | | '_ \ / _ \ '__/ _` | '_ \ / _` |   / _ \   //
  // | |_| | |_) |  __/ | | (_| | | | | (_| |  / ___ \  //
  //  \___/| .__/ \___|_|  \__,_|_| |_|\__,_| /_/   \_\ //
  //       |_|                                          //
  ////////////////////////////////////////////////////////

  // Operand A Mux
  always_comb begin : operand_a_mux
    case (alu_op_a_mux_sel)
      OP_A_REGA_OR_FWD:  operand_a = operand_a_fw;
      OP_A_CURRPC:       operand_a = if_id_pipe_i.pc;
      OP_A_IMM:          operand_a = imm_a;
      default:           operand_a = operand_a_fw;
    endcase; // case (alu_op_a_mux_sel)
  end

  always_comb begin : immediate_a_mux
    unique case (imm_a_mux_sel)
      IMMA_Z:      imm_a = imm_z_type;
      IMMA_ZERO:   imm_a = '0;
      default:     imm_a = '0;
    endcase
  end

  // Operand A forwarding mux
  always_comb begin : operand_a_fw_mux
    case (ctrl_byp_i.operand_a_fw_mux_sel)
      SEL_FW_EX:    operand_a_fw = rf_wdata_ex_i;
      SEL_FW_WB:    operand_a_fw = rf_wdata_wb_i;
      SEL_REGFILE:  operand_a_fw = rf_rdata_i[0];
      SEL_LFSR:     operand_a_fw = xsecure_ctrl_i.lfsr1;
      default:      operand_a_fw = rf_rdata_i[0];
    endcase;
  end

  always_comb begin: jalr_fw_mux
    case (ctrl_byp_i.jalr_fw_mux_sel)
      SELJ_FW_WB:   jalr_fw = ex_wb_pipe_i.rf_wdata;
      SELJ_REGFILE: jalr_fw = rf_rdata_i[0];
      default:      jalr_fw = rf_rdata_i[0];
    endcase
  end

  //////////////////////////////////////////////////////
  //   ___                                 _   ____   //
  //  / _ \ _ __   ___ _ __ __ _ _ __   __| | | __ )  //
  // | | | | '_ \ / _ \ '__/ _` | '_ \ / _` | |  _ \  //
  // | |_| | |_) |  __/ | | (_| | | | | (_| | | |_) | //
  //  \___/| .__/ \___|_|  \__,_|_| |_|\__,_| |____/  //
  //       |_|                                        //
  //////////////////////////////////////////////////////

  // Immediate Mux for operand B
  always_comb begin : immediate_b_mux
    unique case (imm_b_mux_sel)
      IMMB_I:      imm_b = imm_i_type;
      IMMB_S:      imm_b = imm_s_type;
      IMMB_U:      imm_b = imm_u_type;
      IMMB_PCINCR: imm_b = if_id_pipe_i.instr_meta.compressed ? 32'h2 : 32'h4;
      default:     imm_b = imm_i_type;
    endcase
  end

  // Operand B Mux
  always_comb begin : operand_b_mux
    case (alu_op_b_mux_sel)
      OP_B_REGB_OR_FWD:  operand_b = operand_b_fw;
      OP_B_IMM:          operand_b = imm_b;
      default:           operand_b = operand_b_fw;
    endcase // case (alu_op_b_mux_sel)
  end

  // Operand B forwarding mux
  always_comb begin : operand_b_fw_mux
    case (ctrl_byp_i.operand_b_fw_mux_sel)
      SEL_FW_EX:    operand_b_fw = rf_wdata_ex_i;
      SEL_FW_WB:    operand_b_fw = rf_wdata_wb_i;
      SEL_REGFILE:  operand_b_fw = rf_rdata_i[1];
      SEL_LFSR:     operand_b_fw = xsecure_ctrl_i.lfsr2;
      default:      operand_b_fw = rf_rdata_i[1];
    endcase;
  end

  //////////////////////////////////////////////////////
  //   ___                                 _    ____  //
  //  / _ \ _ __   ___ _ __ __ _ _ __   __| |  / ___| //
  // | | | | '_ \ / _ \ '__/ _` | '_ \ / _` | | |     //
  // | |_| | |_) |  __/ | | (_| | | | | (_| | | |___  //
  //  \___/| .__/ \___|_|  \__,_|_| |_|\__,_|  \____| //
  //       |_|                                        //
  //////////////////////////////////////////////////////

  // ALU OP C Mux
  always_comb begin : operand_c_mux
    case (op_c_mux_sel)
      OP_C_REGB_OR_FWD:  operand_c = operand_b_fw;
      OP_C_BCH:          operand_c = bch_target;
      default:           operand_c = operand_b_fw;
    endcase // case (op_c_mux_sel)
  end


  ///////////////////////////////////////////////
  //  ____  _____ ____ ___  ____  _____ ____   //
  // |  _ \| ____/ ___/ _ \|  _ \| ____|  _ \  //
  // | | | |  _|| |  | | | | | | |  _| | |_) | //
  // | |_| | |__| |__| |_| | |_| | |___|  _ <  //
  // |____/|_____\____\___/|____/|_____|_| \_\ //
  //                                           //
  ///////////////////////////////////////////////

  cv32e40s_decoder
  #(
    .RV32                            ( RV32                      ),
    .REGFILE_NUM_READ_PORTS          ( REGFILE_NUM_READ_PORTS    ),
    .B_EXT                           ( B_EXT                     ),
    .M_EXT                           ( M_EXT                     ),
    .CLIC                            ( CLIC                      )
  )
  decoder_i
  (
    // controller related signals
    .deassert_we_i                   ( ctrl_byp_i.deassert_we    ),

    // SYS signals
    .sys_en_o                        ( sys_en                    ),
    .illegal_insn_o                  ( illegal_insn              ),
    .sys_ebrk_insn_o                 ( sys_ebrk_insn             ),
    .sys_mret_insn_o                 ( sys_mret_insn             ),
    .sys_dret_insn_o                 ( sys_dret_insn             ),
    .sys_ecall_insn_o                ( sys_ecall_insn            ),
    .sys_wfi_insn_o                  ( sys_wfi_insn              ),
    .sys_wfe_insn_o                  ( sys_wfe_insn              ),
    .sys_fence_insn_o                ( sys_fence_insn            ),
    .sys_fencei_insn_o               ( sys_fencei_insn           ),

    // from IF/ID pipeline
    .if_id_pipe_i                    ( if_id_pipe_i              ),

    // ALU
    .alu_en_o                        ( alu_en                    ),
    .alu_bch_o                       ( alu_bch                   ),
    .alu_jmp_o                       ( alu_jmp                   ),
    .alu_jmpr_o                      ( alu_jmpr                  ),
    .alu_operator_o                  ( alu_operator              ),
    .alu_op_a_mux_sel_o              ( alu_op_a_mux_sel          ),
    .alu_op_b_mux_sel_o              ( alu_op_b_mux_sel          ),

    // MUL
    .mul_en_o                        ( mul_en                    ),
    .mul_operator_o                  ( mul_operator              ),
    .mul_signed_mode_o               ( mul_signed_mode           ),

    // DIV
    .div_en_o                        ( div_en                    ),
    .div_operator_o                  ( div_operator              ),

    // CSR
    .csr_en_o                        ( csr_en                    ),
    .csr_en_raw_o                    ( csr_en_raw                ),
    .csr_op_o                        ( csr_op                    ),
    .mstatus_i                       ( mstatus_i                 ),

    // LSU
    .lsu_en_o                        ( lsu_en                    ),
    .lsu_we_o                        ( lsu_we                    ),
    .lsu_size_o                      ( lsu_size                  ),
    .lsu_sext_o                      ( lsu_sext                  ),

    // Register file control signals
    .rf_re_o                         ( rf_re                     ),
    .rf_we_o                         ( rf_we_dec                 ),
    .rf_raddr_i                      ( rf_raddr_o                ),
    .rf_waddr_i                      ( rf_waddr                  ),
    .rf_illegal_raddr_o              ( rf_illegal_raddr          ),

    // Mux selects
    .imm_a_mux_sel_o                 ( imm_a_mux_sel             ),
    .imm_b_mux_sel_o                 ( imm_b_mux_sel             ),
    .op_c_mux_sel_o                  ( op_c_mux_sel              ),
    .bch_jmp_mux_sel_o               ( bch_jmp_mux_sel           ),

    // From controller fsm
    .ctrl_fsm_i                      ( ctrl_fsm_i                ),

    // Table jump related signals
    .tbljmp_first_i                  ( tbljmp_first              )
  );


  assign rf_re_o        = rf_re;

  // Register writeback is enabled by the decoder
  assign rf_we          = rf_we_dec;


  /////////////////////////////////////////////////////////////////////////////////
  //   ___ ____        _______  __  ____ ___ ____  _____ _     ___ _   _ _____   //
  //  |_ _|  _ \      | ____\ \/ / |  _ \_ _|  _ \| ____| |   |_ _| \ | | ____|  //
  //   | || | | |_____|  _|  \  /  | |_) | || |_) |  _| | |    | ||  \| |  _|    //
  //   | || |_| |_____| |___ /  \  |  __/| ||  __/| |___| |___ | || |\  | |___   //
  //  |___|____/      |_____/_/\_\ |_|  |___|_|   |_____|_____|___|_| \_|_____|  //
  //                                                                             //
  /////////////////////////////////////////////////////////////////////////////////

  always_ff @(posedge clk, negedge rst_n)
  begin : ID_EX_PIPE_REGISTERS
    if (rst_n == 1'b0)
    begin
      id_ex_pipe_o.instr_valid            <= 1'b0;
      id_ex_pipe_o.alu_en                 <= 1'b0;
      id_ex_pipe_o.alu_bch                <= 1'b0;
      id_ex_pipe_o.alu_jmp                <= 1'b0;
      id_ex_pipe_o.alu_operator           <= ALU_SLTU;
      id_ex_pipe_o.alu_operand_a          <= 32'b0;
      id_ex_pipe_o.alu_operand_b          <= 32'b0;

      id_ex_pipe_o.operand_c              <= 32'b0;

      id_ex_pipe_o.mul_en                 <= 1'b0;
      id_ex_pipe_o.mul_operator           <= MUL_M32;
      id_ex_pipe_o.mul_signed_mode        <= 2'b0;

      id_ex_pipe_o.div_en                 <= 1'b0;
      id_ex_pipe_o.div_operator           <= DIV_DIVU;

      id_ex_pipe_o.muldiv_operand_a       <= 32'b0;
      id_ex_pipe_o.muldiv_operand_b       <= 32'b0;

      id_ex_pipe_o.csr_en                 <= 1'b0;
      id_ex_pipe_o.csr_op                 <= CSR_OP_READ;

      id_ex_pipe_o.lsu_en                 <= 1'b0;
      id_ex_pipe_o.lsu_we                 <= 1'b0;
      id_ex_pipe_o.lsu_size               <= 2'b0;
      id_ex_pipe_o.lsu_sext               <= 1'b0;

      id_ex_pipe_o.sys_en                <= 1'b0;
      id_ex_pipe_o.sys_dret_insn         <= 1'b0;
      id_ex_pipe_o.sys_ebrk_insn         <= 1'b0;
      id_ex_pipe_o.sys_ecall_insn        <= 1'b0;
      id_ex_pipe_o.sys_fence_insn        <= 1'b0;
      id_ex_pipe_o.sys_fencei_insn       <= 1'b0;
      id_ex_pipe_o.sys_mret_insn         <= 1'b0;
      id_ex_pipe_o.sys_wfi_insn          <= 1'b0;
      id_ex_pipe_o.sys_wfe_insn          <= 1'b0;

      id_ex_pipe_o.priv_lvl               <= PRIV_LVL_M;
      id_ex_pipe_o.illegal_insn           <= 1'b0;

      id_ex_pipe_o.rf_we                  <= 1'b0;
      id_ex_pipe_o.rf_waddr               <= '0;

        // Exceptions and debug
      id_ex_pipe_o.pc_next                <= 32'b0;
      id_ex_pipe_o.pc                     <= 32'b0;
      id_ex_pipe_o.instr                  <= INST_RESP_RESET_VAL;
      id_ex_pipe_o.instr_meta             <= '0;
      id_ex_pipe_o.trigger_match          <= '0;

      id_ex_pipe_o.first_op               <= 1'b0;
      id_ex_pipe_o.last_op                <= 1'b0;
      id_ex_pipe_o.last_sec_op            <= 1'b0;
      id_ex_pipe_o.abort_op               <= 1'b0;
    end else begin
      // normal pipeline unstall case
      if (id_valid_o && ex_ready_i) begin
        id_ex_pipe_o.priv_lvl     <= if_id_pipe_i.priv_lvl;
        id_ex_pipe_o.instr_valid  <= 1'b1;
        id_ex_pipe_o.last_op      <= last_op_o;
        id_ex_pipe_o.last_sec_op  <= last_sec_op;
        id_ex_pipe_o.first_op     <= first_op_o;
        id_ex_pipe_o.abort_op     <= abort_op_o;

        // Operands
        if (alu_op_a_mux_sel != OP_A_NONE) begin
          id_ex_pipe_o.alu_operand_a        <= operand_a;               // Used by most ALU, CSR and LSU instructions
        end
        if (alu_op_b_mux_sel != OP_B_NONE) begin
          id_ex_pipe_o.alu_operand_b        <= operand_b;               // Used by most ALU, CSR and LSU instructions
        end

        if (op_c_mux_sel != OP_C_NONE)
        begin
          id_ex_pipe_o.operand_c            <= operand_c;               // Used by LSU stores and some ALU instructions
        end

        id_ex_pipe_o.alu_en                 <= alu_en;
        if (alu_en) begin                                               // Branch comparison and jump link computation are done in ALU
          id_ex_pipe_o.alu_bch              <= alu_bch;
          id_ex_pipe_o.alu_jmp              <= alu_jmp;
        end
        if (alu_en || div_en) begin                                     // ALU and DIV use alu_operator (DIV uses the shifter in the ALU)
          id_ex_pipe_o.alu_operator         <= alu_operator;
        end

        id_ex_pipe_o.div_en                 <= div_en;
        if (div_en) begin
          id_ex_pipe_o.div_operator         <= div_operator;
        end

        id_ex_pipe_o.mul_en                 <= mul_en;
        if (mul_en) begin
          id_ex_pipe_o.mul_operator         <= mul_operator;
          id_ex_pipe_o.mul_signed_mode      <= mul_signed_mode;
        end

        if (mul_en || div_en) begin
          id_ex_pipe_o.muldiv_operand_a     <= operand_a_fw;            // Only register file operand (or forward) is required
          id_ex_pipe_o.muldiv_operand_b     <= operand_b_fw;            // Only register file operand (or forward) is required
        end

        id_ex_pipe_o.csr_en                 <= csr_en;
        if (csr_en) begin
          id_ex_pipe_o.csr_op               <= csr_op;
        end

        id_ex_pipe_o.lsu_en                 <= lsu_en;
        if (lsu_en) begin
          id_ex_pipe_o.lsu_we               <= lsu_we;
          id_ex_pipe_o.lsu_size             <= lsu_size;
          id_ex_pipe_o.lsu_sext             <= lsu_sext;
        end

        // Special instructions
        id_ex_pipe_o.sys_en                 <= sys_en;
        if (sys_en) begin
          id_ex_pipe_o.sys_dret_insn        <= sys_dret_insn;
          id_ex_pipe_o.sys_ebrk_insn        <= sys_ebrk_insn;
          id_ex_pipe_o.sys_ecall_insn       <= sys_ecall_insn;
          id_ex_pipe_o.sys_fence_insn       <= sys_fence_insn;
          id_ex_pipe_o.sys_fencei_insn      <= sys_fencei_insn;
          id_ex_pipe_o.sys_mret_insn        <= sys_mret_insn;
          id_ex_pipe_o.sys_wfi_insn         <= sys_wfi_insn;
          id_ex_pipe_o.sys_wfe_insn         <= sys_wfe_insn;
        end

        id_ex_pipe_o.illegal_insn           <= illegal_insn;

        id_ex_pipe_o.rf_we                  <= rf_we;
        if (rf_we) begin
          id_ex_pipe_o.rf_waddr             <= rf_waddr;
        end

        // Exceptions and debug
        id_ex_pipe_o.pc                     <= if_id_pipe_i.pc;
        id_ex_pipe_o.instr_meta             <= if_id_pipe_i.instr_meta;

        // Next PC (pc_if) is only needed for branches in case of
        // dataindependent timing branches to the next instruction
        if (alu_en && alu_bch) begin
          id_ex_pipe_o.pc_next              <= pc_next;
        end

        if (if_id_pipe_i.instr_meta.compressed) begin
          // Overwrite instruction word in case of compressed instruction
          id_ex_pipe_o.instr.bus_resp.rdata         <= {16'h0, if_id_pipe_i.compressed_instr};
          id_ex_pipe_o.instr.bus_resp.err           <= if_id_pipe_i.instr.bus_resp.err;
          id_ex_pipe_o.instr.bus_resp.integrity_err <= if_id_pipe_i.instr.bus_resp.integrity_err;
          id_ex_pipe_o.instr.mpu_status             <= if_id_pipe_i.instr.mpu_status;
        end else begin
          id_ex_pipe_o.instr                <= if_id_pipe_i.instr;
        end

        id_ex_pipe_o.trigger_match          <= if_id_pipe_i.trigger_match;

      end else if (ex_ready_i) begin
        id_ex_pipe_o.instr_valid            <= 1'b0;
      end
    end
  end

  generate
    if (SECURE) begin : secure_ctrl_flow
      // Flag for jumps, mret and branch instructions
      logic jmp_bch_insn;

      // Counter for finished sub operations
      logic [MULTI_OP_CNT_WIDTH-1:0] multi_op_cnt;

      // Detect jumps (including mret) and branches.
      assign jmp_bch_insn = ((alu_jmp || alu_bch) && alu_en) || (sys_mret_insn && sys_en);

      // Detect last operation of current instruction.
      // Only when pc_hardening is enabled, otherwise no instruction will be split for pc_hardening.
      assign last_sec_op = (jmp_bch_insn && xsecure_ctrl_i.cpuctrl.pc_hardening) ? (multi_op_cnt == JMP_BCH_CYCLES - 1)
                                                                                 : 1'b1;

      assign first_sec_op = (jmp_bch_insn && xsecure_ctrl_i.cpuctrl.pc_hardening) ? (multi_op_cnt == '0) : 1'b1;

      // Count number of operations performed by an instruction.
      always_ff @(posedge clk, negedge rst_n) begin
        if (rst_n == 1'b0) begin
          multi_op_cnt <= MULTI_OP_CNT_WIDTH'(0);
        end else begin
          if(id_valid_o && ex_ready_i) begin
            if(last_sec_op) begin
              // Last operation is done, reset counter
              multi_op_cnt <= MULTI_OP_CNT_WIDTH'(0);
            end else begin
              // Suboperation done, increment counter
              multi_op_cnt <= multi_op_cnt + MULTI_OP_CNT_WIDTH'(1);
            end
          end

          // Reset multi op counter if stage is killed
          if(ctrl_fsm_i.kill_id) begin
            multi_op_cnt <= MULTI_OP_CNT_WIDTH'(0);
          end
        end
      end

    end else begin : nonsecure_ctrl_flow // !SECURE
      assign last_sec_op = 1'b1;
      assign first_sec_op = 1'b1;
    end
  endgenerate

  // Only signal 'last_op' when the incoming instruction is a last_op AND the hardening logic for jumps and
  // branches also have a 'last_sec_op'
  assign last_op = if_id_pipe_i.last_op && last_sec_op;

  assign first_op = if_id_pipe_i.first_op && first_sec_op;

  assign alu_en_o     = alu_en;
  assign sys_en_o     = sys_en;
  assign alu_jmp_o    = alu_jmp;
  assign alu_jmpr_o   = alu_jmpr;

  assign csr_en_raw_o = csr_en_raw;

  assign last_sec_op_o = last_sec_op;

  // stall control for multi operation ID instructions (currently only jumps and branches if SECURE=1)
  // Using if_id_pipe_i.instr_valid instead of the local instr_valid, as halt_id and kill_id are
  // factored into id_valid_o and id_ready_o regardless of muli_op_id_stall.
  assign multi_op_id_stall = !last_sec_op && (if_id_pipe_i.instr_valid);

  // Stage ready/valid
  //
  // Most stall conditions are factored into halt_id (and will force both ready and valid to 0).
  //
  // Multi operation instruction related stalls are different; in that case ready will be 0 to avoid
  // following instructions to enter ID at the same time as id_valid will be allowed to go high to update
  // operands in EX for operations within the same instruction.

  assign id_ready_o = ctrl_fsm_i.kill_id || (!multi_op_id_stall && ex_ready_i && !ctrl_fsm_i.halt_id);

  assign id_valid_o = instr_valid || (multi_op_id_stall && !ctrl_fsm_i.kill_id && !ctrl_fsm_i.halt_id);

  assign first_op_o  = first_op;

  // An mret with mcause.minhv set will cause a pointer fetch, and that pointer fetch is the last operation of the mret.
  // Mrets with the mcause conditions not true will be normal single operation instructions.
  // Using CSR signals below is safe, as any implicit CSR read in ID stage is halted if there is an implicit or explicit CSR write
  // in either EX or WB at the same time.
  assign last_op_o   = (sys_en && sys_mret_insn && mcause_i.minhv) ? 1'b0 : last_op;
  assign abort_op_o  = if_id_pipe_i.abort_op || ctrl_byp_i.id_stage_abort;

  logic unused_signals;
  assign unused_signals = |rf_illegal_raddr;

endmodule
