// Copyright lowRISC contributors (OpenTitan project).
// Copyright zeroRISC Inc.
// Modified by Authors of "Towards ML-KEM & ML-DSA on OpenTitan" (https://eprint.iacr.org/2024/1192).
// Copyright "Towards ML-KEM & ML-DSA on OpenTitan" Authors.
// Modified by Ruben Niederhagen and Hoang Nguyen Hien Pham - authors of
// "Improving ML-KEM & ML-DSA on OpenTitan - Efficient Multiplication Vector Instructions for OTBN"
// (https://eprint.iacr.org/2025/2028).
// Copyright Ruben Niederhagen and Hoang Nguyen Hien Pham.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

`include "prim_assert.sv"

/**
 * ACC Controller
 */
module acc_controller
  import acc_pkg::*;
#(
  // Size of the instruction memory, in bytes
  parameter int ImemSizeByte = 4096,
  // Size of the data memory, in bytes
  parameter int DmemSizeByte = 4096,

  // Enabling PQC hardware support with vector ISA extension
  parameter bit AccPQCEn = 1'b1,

  localparam int ImemAddrWidth = prim_util_pkg::vbits(ImemSizeByte),
  localparam int DmemAddrWidth = prim_util_pkg::vbits(DmemSizeByte),
  localparam int Share         = 2
) (
  input logic clk_i,
  input logic rst_ni,

  input  logic start_i,   // start the processing at address zero
  output logic locking_o, // Controller is in or is entering the locked state
  input  logic err_bit_clear_i,

  input prim_mubi_pkg::mubi4_t fatal_escalate_en_i,
  input prim_mubi_pkg::mubi4_t recov_escalate_en_i,
  input prim_mubi_pkg::mubi4_t rma_req_i,
  output controller_err_bits_t err_bits_o,
  output logic                 recoverable_err_o,

  // Next instruction selection (to instruction fetch)
  output logic                     insn_fetch_req_valid_o,
  output logic                     insn_fetch_req_valid_raw_o,
  output logic [ImemAddrWidth-1:0] insn_fetch_req_addr_o,
  output logic                     insn_fetch_resp_clear_o,

  // Fetched/decoded instruction
  input logic                     insn_valid_i,
  input logic                     insn_illegal_i,
  input logic [ImemAddrWidth-1:0] insn_addr_i,

  // Decoded instruction data
  input insn_dec_base_t   insn_dec_base_i,
  input insn_dec_bignum_t insn_dec_bignum_i,
  input insn_dec_shared_t insn_dec_shared_i,

  // Base register file
  output logic [4:0]               rf_base_wr_addr_o,
  output logic                     rf_base_wr_en_o,
  output logic                     rf_base_wr_commit_o,
  output logic [31:0]              rf_base_wr_data_no_intg_o,
  output logic [BaseIntgWidth-1:0] rf_base_wr_data_intg_o,
  output logic                     rf_base_wr_data_intg_sel_o,

  output logic [4:0]               rf_base_rd_addr_a_o,
  output logic                     rf_base_rd_en_a_o,
  input  logic [BaseIntgWidth-1:0] rf_base_rd_data_a_intg_i,
  output logic [4:0]               rf_base_rd_addr_b_o,
  output logic                     rf_base_rd_en_b_o,
  input  logic [BaseIntgWidth-1:0] rf_base_rd_data_b_intg_i,
  output logic                     rf_base_rd_commit_o,

  input logic rf_base_call_stack_sw_err_i,
  input logic rf_base_call_stack_hw_err_i,

  // Bignum register file (WDRs)
  output logic [4:0]         rf_bignum_wr_addr_o,
  output logic [1:0]         rf_bignum_wr_en_o,
  output logic               rf_bignum_wr_commit_o,
  output logic [WLEN-1:0]    rf_bignum_wr_data_no_intg_o,
  output logic [ExtWLEN-1:0] rf_bignum_wr_data_intg_o,
  output logic               rf_bignum_wr_data_intg_sel_o,

  output logic [4:0]         rf_bignum_rd_addr_a_o,
  output logic               rf_bignum_rd_en_a_o,
  input  logic [ExtWLEN-1:0] rf_bignum_rd_data_a_intg_i,

  output logic [4:0]         rf_bignum_rd_addr_b_o,
  output logic               rf_bignum_rd_en_b_o,
  input  logic [ExtWLEN-1:0] rf_bignum_rd_data_b_intg_i,

  input logic rf_bignum_intg_err_i,
  input logic rf_bignum_spurious_we_err_i,

  output logic [NWdr-1:0] rf_bignum_rd_a_indirect_onehot_o,
  output logic [NWdr-1:0] rf_bignum_rd_b_indirect_onehot_o,
  output logic [NWdr-1:0] rf_bignum_wr_indirect_onehot_o,
  output logic            rf_bignum_indirect_en_o,

  // Execution units

  // Base ALU
  output alu_base_operation_t  alu_base_operation_o,
  output alu_base_comparison_t alu_base_comparison_o,
  input  logic [31:0]          alu_base_operation_result_i,
  input  logic                 alu_base_comparison_result_i,

  // Bignum ALU
  output alu_bignum_operation_t alu_bignum_operation_o,
  output logic                  alu_bignum_operation_valid_o,
  output logic                  alu_bignum_operation_commit_o,
  input  logic [WLEN-1:0]       alu_bignum_operation_result_i,
  input  logic                  alu_bignum_selection_flag_i,

  // Bignum MAC
  output mac_bignum_operation_t mac_bignum_operation_o,
  input  logic [WLEN-1:0]       mac_bignum_operation_result_i,
  output logic                  mac_bignum_en_o,
  output logic                  mac_bignum_commit_o,

  // LSU
  output logic                     lsu_load_req_o,
  output logic                     lsu_store_req_o,
  output insn_subset_e             lsu_req_subset_o,
  output logic [DmemAddrWidth-1:0] lsu_addr_o,
  input  logic                     lsu_addr_en_predec_i,

  output logic [BaseIntgWidth-1:0] lsu_base_wdata_o,
  output logic [ExtWLEN-1:0]       lsu_bignum_wdata_o,

  input  logic [BaseIntgWidth-1:0] lsu_base_rdata_i,
  input  logic [ExtWLEN-1:0]       lsu_bignum_rdata_i,

  // Internal Special-Purpose Registers (ISPRs)
  output ispr_e                       ispr_addr_o,
  output logic [31:0]                 ispr_base_wdata_o,
  output logic [BaseWordsPerWLEN-1:0] ispr_base_wr_en_o,
  output logic [ExtWLEN-1:0]          ispr_bignum_wdata_intg_o,
  output logic                        ispr_bignum_wr_en_o,
  output logic [NFlagGroups-1:0]      ispr_flags_wr_o,
  output logic                        ispr_wr_commit_o,
  input  logic [ExtWLEN-1:0]          ispr_rdata_intg_i,
  output logic                        ispr_rd_en_o,

  // RND interface
  output logic rnd_req_o,
  output logic rnd_prefetch_req_o,
  input  logic rnd_valid_i,

  input  logic urnd_reseed_err_i,

  // KMAC interface
  input  logic kmac_msg_write_ready_i   [Share],
  input  logic kmac_msg_pending_write_i [Share],
  input  logic kmac_digest_valid_i,

  // Secure Wipe
  output logic secure_wipe_req_o,
  input  logic secure_wipe_ack_i,
  input  logic sec_wipe_zero_i,
  input  logic secure_wipe_running_i,
  input  logic sec_wipe_err_i,

  input  logic        state_reset_i,
  output logic [31:0] insn_cnt_o,
  input  logic        insn_cnt_clear_ext_i,
  input  logic        insn_cnt_clear_int_i,
  output logic        mems_sec_wipe_o,

  input  logic        software_errs_fatal_i,

  input logic [1:0] sideload_key_shares_valid_i,

  // Prefetch stage control
  output logic                     prefetch_en_o,
  output logic                     prefetch_loop_active_o,
  output logic [31:0]              prefetch_loop_iterations_o,
  output logic [ImemAddrWidth:0]   prefetch_loop_end_addr_o,
  output logic [ImemAddrWidth-1:0] prefetch_loop_jump_addr_o,
  output logic                     prefetch_ignore_errs_o,

  // Predecoded control
  input  ctrl_flow_predec_t        ctrl_flow_predec_i,
  input  logic [ImemAddrWidth-1:0] ctrl_flow_target_predec_i,
  output logic                     predec_error_o
);
  import prim_mubi_pkg::*;

  generate
    if (!AccPQCEn) begin : gen_unused_ports
      // Tie off unused inputs
      logic unused_bits;
      assign unused_bits = ^{kmac_msg_write_ready_i[0], kmac_msg_pending_write_i[0],
                             kmac_msg_write_ready_i[1], kmac_msg_pending_write_i[1],
                             kmac_digest_valid_i};
    end
  endgenerate

  acc_state_e state_q, state_d;


  controller_err_bits_t err_bits_q, err_bits_d;

  // The specific error signals that go into err_bits_d
  logic fatal_software_err, bad_internal_state_err, reg_intg_violation_err, key_invalid_err;
  logic illegal_insn_err, bad_data_addr_err, call_stack_sw_err, bad_insn_addr_err;

  logic err;
  logic internal_err;
  logic recoverable_err;
  logic software_err;
  logic non_insn_addr_software_err;
  /* verilator lint_off UNOPTFLAT */
  logic fatal_err;
  logic internal_fatal_err;
  logic done_complete;
  logic executing;
  logic state_error, state_error_d, state_error_q;
  logic spurious_secure_wipe_ack_q, spurious_secure_wipe_ack_d;
  logic sec_wipe_err_q, sec_wipe_err_d;
  logic mubi_err_q, mubi_err_d;

  logic                     insn_fetch_req_valid_raw;
  logic [ImemAddrWidth-1:0] insn_fetch_req_addr_last;

  logic stall;
  logic ispr_stall;

  generate
    if (AccPQCEn) begin : gen_kmac_nets
      logic kmac_write_stall;
      logic kmac_msg0_stall;
      logic kmac_msg1_stall;
      logic kmac_msg0_write_req_raw;
      logic kmac_msg1_write_req_raw;
      logic kmac_digest_req_raw;
      logic kmac_msg_partial_raw;
    end
  endgenerate

  logic mem_stall;
  logic rf_indirect_stall;
  logic jump_or_branch;
  logic branch_taken;
  logic insn_executing;
  logic ld_insn_with_addr_from_call_stack, st_insn_with_addr_from_call_stack;
  logic [ImemAddrWidth-1:0] branch_target;
  logic                     branch_target_overflow;
  logic [ImemAddrWidth:0]   next_insn_addr_wide;
  logic [ImemAddrWidth-1:0] next_insn_addr;

  csr_e                                csr_addr;
  logic [$clog2(BaseWordsPerWLEN)-1:0] csr_sub_addr;
  logic [31:0]                         csr_rdata_raw;
  logic [31:0]                         csr_rdata;
  logic [BaseWordsPerWLEN-1:0]         csr_rdata_mux [32];
  logic [31:0]                         csr_wdata_raw;
  logic [31:0]                         csr_wdata;

  wsr_e                                wsr_addr;
  logic [WLEN-1:0]                     wsr_wdata;

  ispr_e                               ispr_addr_base;
  logic [$clog2(BaseWordsPerWLEN)-1:0] ispr_word_addr_base;
  logic [BaseWordsPerWLEN-1:0]         ispr_word_sel_base;

  ispr_e                               ispr_addr_bignum;

  logic                                ispr_wr_insn, ispr_rd_insn;
  logic                                ispr_wr_base_insn;
  logic                                ispr_wr_bignum_insn;
  logic                                ispr_rd_bignum_insn;

  logic                     lsu_load_req_raw;
  logic                     lsu_store_req_raw;
  logic [DmemAddrWidth-1:0] lsu_addr, lsu_addr_blanked, lsu_addr_saved_d, lsu_addr_saved_q;
  logic                     lsu_addr_saved_sel;
  logic                     expected_lsu_addr_en;

  logic                     expected_call_stack_push, expected_call_stack_pop;
  logic                     lsu_predec_error, branch_target_predec_error, ctrl_predec_error;

  logic rnd_req_raw;

  // Register read data with integrity stripped off
  logic [31:0]     rf_base_rd_data_a_no_intg;
  logic [31:0]     rf_base_rd_data_b_no_intg;
  logic [WLEN-1:0] rf_bignum_rd_data_a_no_intg;
  logic [WLEN-1:0] rf_bignum_rd_data_b_no_intg;

  logic [ExtWLEN-1:0] rf_bignum_rd_data_b_intg_blanked;
  logic [ExtWLEN-1:0] selection_result;

  logic [1:0] rf_bignum_wr_en_unbuf;
  logic [4:0] rf_bignum_wr_addr_unbuf;
  logic [4:0] rf_bignum_rd_addr_a_unbuf;
  logic       rf_bignum_rd_en_a_unbuf;
  logic [4:0] rf_bignum_rd_addr_b_unbuf;
  logic       rf_bignum_rd_en_b_unbuf;

  logic rf_bignum_rd_a_indirect_en;
  logic rf_bignum_rd_b_indirect_en;
  logic rf_bignum_wr_indirect_en;

  // Computed increments for indirect register index and memory address in BN.LID/BN.SID/BN.MOVR
  // instructions.
  logic [5:0]  rf_base_rd_data_a_inc;
  logic [5:0]  rf_base_rd_data_b_inc;
  logic [26:0] rf_base_rd_data_a_wlen_word_inc;

  // Read/Write enables for base register file before illegal instruction encoding are factored in
  logic rf_base_rd_en_a_raw, rf_base_rd_en_b_raw, rf_base_wr_en_raw;

  // Output of mux taking the above increments as inputs and choosing one to write back to base
  // register file with appropriate zero extension and padding to give a 32-bit result.
  logic [31:0]              increment_out;

  // Loop control, used to start a new loop
  logic        loop_start_req;
  logic        loop_start_commit;
  logic        loop_reset;
  logic [11:0] loop_bodysize;
  logic [31:0] loop_iterations;

  // Loop generated jumps. The loop controller asks to jump when execution reaches the end of a
  // loop body that hasn't completed all of its iterations.
  logic                     loop_jump;
  logic [ImemAddrWidth-1:0] loop_jump_addr;

  logic [WLEN-1:0] mac_bignum_rf_wr_data;

  logic loop_hw_err, loop_predec_err;
  logic csr_illegal_addr, wsr_illegal_addr, ispr_illegal_addr;
  logic imem_addr_err, loop_sw_err, ispr_err;
  logic dmem_addr_err_check, dmem_addr_err;
  logic dmem_addr_unaligned_base, dmem_addr_unaligned_bignum, dmem_addr_overflow;
  logic illegal_insn_static;
  logic key_invalid;

  logic rf_a_indirect_err, rf_b_indirect_err, rf_d_indirect_err, rf_indirect_err;

  // If we are doing an indirect access to the bignum register file, it's possible that the
  // address that we use for the access is architecturally unknown. This happens if it came from x1
  // and we've underflowed the call stack. When this happens, we want to ignore any read data
  // integrity errors and spurious write enable errors since the access to the bignum register file
  // didn't happen architecturally anyway.
  logic ignore_rf_bignum_intg_errs;
  logic rf_bignum_intg_err;
  logic ignore_rf_bignum_spurious_we_errs;
  logic rf_bignum_spurious_we_err;

  logic ispr_rdata_intg_err;

  logic [31:0] insn_cnt_d, insn_cnt_q;
  logic        insn_cnt_clear;

  logic [4:0] insn_bignum_rd_addr_a_q, insn_bignum_rd_addr_b_q, insn_bignum_wr_addr_q;

  logic       start_secure_wipe;
  logic       secure_wipe_running_q, secure_wipe_running_d;

  assign secure_wipe_running_d = start_secure_wipe | (secure_wipe_running_q & ~secure_wipe_ack_i);

  always_ff @(posedge clk_i or negedge rst_ni) begin
    if (!rst_ni) begin
      secure_wipe_running_q <= 1'b0;
    end else begin
      secure_wipe_running_q <= secure_wipe_running_d;
    end
  end
  assign secure_wipe_req_o = start_secure_wipe | secure_wipe_running_q;

  // Spot spurious acks on the secure wipe interface. There is a an ack at the end of the initial
  // secure wipe, and as `secure_wipe_running_q` is only high during secure wipes triggered by this
  // controller, we have to ignore acks before the initial secure wipe is done.  Register this
  // signal to break a circular path (a secure wipe can be triggered by a stop, and a spurious
  // secure wipe ack can trigger a stop).
  always_ff @(posedge clk_i or negedge rst_ni) begin
    if (!rst_ni) begin
      spurious_secure_wipe_ack_q <= 1'b0;
    end else begin
      spurious_secure_wipe_ack_q <= spurious_secure_wipe_ack_d;
    end
  end
  assign spurious_secure_wipe_ack_d = spurious_secure_wipe_ack_q |
                                      (secure_wipe_ack_i      &
                                       ~secure_wipe_running_q &
                                       ~secure_wipe_running_i);

  // Detect and latch unexpected secure wipe signals.
  always_ff @(posedge clk_i or negedge rst_ni) begin
    if (!rst_ni) begin
      sec_wipe_err_q <= 1'b0;
    end else begin
      sec_wipe_err_q <= sec_wipe_err_d;
    end
  end
  assign sec_wipe_err_d = sec_wipe_err_q |
                          sec_wipe_err_i |
                          (sec_wipe_zero_i & ~secure_wipe_running_i);

  // Stall a cycle on loads to allow load data writeback to happen the following cycle. Stall not
  // required on stores as there is no response to deal with.
  assign mem_stall = lsu_load_req_raw;

  assign rf_indirect_stall = insn_valid_i &
                             (state_q != AccStateStall) &
                             (insn_dec_shared_i.subset == InsnSubsetBignum) &
                             (insn_dec_bignum_i.rf_a_indirect |
                              insn_dec_bignum_i.rf_b_indirect |
                              insn_dec_bignum_i.rf_d_indirect |
                              insn_dec_shared_i.ld_insn);

  // Reads to RND must stall until data is available.
  // Also, writes to the KMAC_MSG register are only
  // allowed if it can accept new data.
  generate
    if (AccPQCEn) begin : gen_ispr_stall_pqc
      assign ispr_stall = (rnd_req_raw & ~rnd_valid_i) |
                          (gen_kmac_nets.kmac_digest_req_raw & ~kmac_digest_valid_i);

      assign gen_kmac_nets.kmac_msg0_stall =
              (gen_kmac_nets.kmac_msg0_write_req_raw & ~kmac_msg_write_ready_i[0]) |
              (gen_kmac_nets.kmac_msg_partial_raw & kmac_msg_pending_write_i[0]);

      assign gen_kmac_nets.kmac_msg1_stall =
              (gen_kmac_nets.kmac_msg1_write_req_raw & ~kmac_msg_write_ready_i[1]) |
              (gen_kmac_nets.kmac_msg_partial_raw & kmac_msg_pending_write_i[1]);

      assign gen_kmac_nets.kmac_write_stall = gen_kmac_nets.kmac_msg0_stall |
                                              gen_kmac_nets.kmac_msg1_stall;

      assign stall = mem_stall | ispr_stall | rf_indirect_stall | gen_kmac_nets.kmac_write_stall;
    end else begin : gen_ispr_stall
      assign ispr_stall = rnd_req_raw & ~rnd_valid_i;
      assign stall = mem_stall | ispr_stall | rf_indirect_stall;
    end
  endgenerate

  // ACC is done when it was executing something (in state AccStateRun or AccStateStall)
  // and either it executes an ecall or an error occurs. A pulse on the done signal raises the
  // 'done' interrupt and also tells the top-level to update its ERR_BITS status
  // register. The calculation that ecall triggered done is factored out as `done_complete` to
  // avoid logic loops in the error handling logic.
  assign done_complete = (insn_valid_i & insn_dec_shared_i.ecall_insn);
  assign executing = (state_q == AccStateRun) ||
                     (state_q == AccStateStall);

  // Set the *locking* output when the next state is the *locked* state and no secure wipe is
  // running or there is a URND reseed error.  `locking_o` is thus set only after the secure wipe
  // has completed or if it cannot complete due to an URND reseed error (in which case
  // `secure_wipe_req_o` and `urnd_reseed_err_i` will remain high).  The condition for secure wipe
  // running involves `secure_wipe_running_i`, which is high for the initial secure wipe, and
  // `secure_wipe_req_o`, which is high for post-execution secure wipes.
  assign locking_o = (state_d == AccStateLocked) & (~(secure_wipe_running_i | secure_wipe_req_o) |
                                                     urnd_reseed_err_i | mubi_err_d);

  assign start_secure_wipe = executing & (done_complete | err);

  assign jump_or_branch = (insn_valid_i &
                          (insn_dec_shared_i.branch_insn | insn_dec_shared_i.jump_insn));

  // Branch taken when there is a valid branch instruction and comparison passes or a valid jump
  // instruction (which is always taken)
  assign branch_taken = insn_valid_i &
                        ((insn_dec_shared_i.branch_insn & alu_base_comparison_result_i) |
                         insn_dec_shared_i.jump_insn);
  // Branch target computed by base ALU (PC + imm)
  assign branch_target = alu_base_operation_result_i[ImemAddrWidth-1:0];
  assign branch_target_overflow = |alu_base_operation_result_i[31:ImemAddrWidth];

  assign next_insn_addr_wide = {1'b0, insn_addr_i} + 'd4;
  assign next_insn_addr = next_insn_addr_wide[ImemAddrWidth-1:0];

  // Record address for fetch request so it can be retried when an invalid response is received
  always_ff @(posedge clk_i) begin
    if (insn_fetch_req_valid_raw) begin
      insn_fetch_req_addr_last <= insn_fetch_req_addr_o;
    end
  end

  always_comb begin
    state_d                  = state_q;
    // `insn_fetch_req_valid_raw` is the value `insn_fetch_req_valid_o` before any errors are
    // considered.
    insn_fetch_req_valid_raw = 1'b0;
    insn_fetch_req_addr_o    = '0;
    insn_fetch_resp_clear_o  = 1'b1;
    prefetch_en_o            = 1'b0;

    state_error = 1'b0;

    unique case (state_q)
      AccStateHalt: begin
        if (start_i) begin
          state_d = AccStateRun;

          insn_fetch_req_addr_o    = '0;
          insn_fetch_req_valid_raw = 1'b1;
          prefetch_en_o            = 1'b1;
        end
      end
      AccStateRun: begin
        insn_fetch_req_valid_raw = 1'b1;
        prefetch_en_o            = 1'b1;

        if (!insn_valid_i) begin
          insn_fetch_req_addr_o = insn_fetch_req_addr_last;
        end else if (done_complete) begin
          state_d                  = AccStateHalt;
          insn_fetch_req_valid_raw = 1'b0;
          prefetch_en_o            = 1'b0;
        end else begin
          if (stall) begin
            // When stalling don't request a new fetch and don't clear response either to keep
            // current instruction.
            state_d                  = AccStateStall;
            insn_fetch_req_valid_raw = 1'b0;
            insn_fetch_resp_clear_o  = 1'b0;
          end else begin
            if (branch_taken) begin
              insn_fetch_req_addr_o = branch_target;
            end else if (loop_jump) begin
              insn_fetch_req_addr_o = loop_jump_addr;
            end else begin
              insn_fetch_req_addr_o = next_insn_addr;
            end
          end
        end
      end
      AccStateStall: begin
        prefetch_en_o = 1'b1;
        // When stalling refetch the same instruction to keep decode inputs constant
        if (stall) begin
          state_d                  = AccStateStall;
          //insn_fetch_req_addr_o = insn_addr_i;
          insn_fetch_req_valid_raw = 1'b0;
          insn_fetch_resp_clear_o  = 1'b0;
        end else begin
          insn_fetch_req_valid_raw = 1'b1;

          if (loop_jump) begin
            insn_fetch_req_addr_o = loop_jump_addr;
          end else begin
            insn_fetch_req_addr_o = next_insn_addr;
          end

          state_d = AccStateRun;
        end
      end
      AccStateLocked: begin
        insn_fetch_req_valid_raw = 1'b0;
        state_d                  = AccStateLocked;
      end
      default: begin
        // We should never get here. If we do (e.g. via a malicious glitch), error out immediately.
        // SEC_CM: CONTROLLER.FSM.LOCAL_ESC
        state_d = AccStateLocked;
        state_error = 1'b1;
      end
    endcase

    // On any error immediately halt, either going to AccStateLocked or AccStateHalt depending on
    // whether it was a fatal error.
    if (err) begin
      insn_fetch_resp_clear_o = 1'b1;

      if (fatal_err) begin
        // SEC_CM: CONTROLLER.FSM.GLOBAL_ESC
        state_d = AccStateLocked;
      end else begin
        state_d = AccStateHalt;
      end
    end

    // Regardless of what happens above enforce staying in OtnbStateLocked.
    if (state_q == AccStateLocked) begin
      state_d = AccStateLocked;
    end
  end

  assign state_error_d = state_error | state_error_q;

  prim_flop #(
    .Width(1),
    .ResetValue('0)
  ) u_state_error_flop (
    .clk_i,
    .rst_ni,

    .d_i(state_error_d),
    .q_o(state_error_q)
  );

  `ASSERT(InsnAlwaysValidInStall, state_q == AccStateStall |-> insn_valid_i)

  // Anything that moves us or keeps us in the stall state should cause `stall` to be asserted
  `ASSERT(StallIfNextStateStall, insn_valid_i & (state_d == AccStateStall) |-> stall)

  // The raw signal is needed by the instruction fetch stage for generating instruction address
  // errors (where instruction fetch and prefetch disagree on address). `err` will factor this in so
  // using the qualified signal results in a combinational loop.
  assign insn_fetch_req_valid_raw_o = insn_fetch_req_valid_raw;
  assign insn_fetch_req_valid_o     = err ? 1'b0 : insn_fetch_req_valid_raw;

  // Determine if there are any errors related to the Imem fetch address.
  always_comb begin
    imem_addr_err = 1'b0;

    if (insn_fetch_req_valid_raw) begin
      if (|insn_fetch_req_addr_o[1:0]) begin
        // Imem address is unaligned
        imem_addr_err = 1'b1;
      end else if (branch_taken) begin
        imem_addr_err = branch_target_overflow;
      end else begin
        imem_addr_err = next_insn_addr_wide[ImemAddrWidth] & insn_valid_i;
      end
    end
  end

  // Signal error if MuBi input signals take on invalid values as this means something bad is
  // happening. Register the error signal to break circular paths (instruction fetch errors factor
  // into fatal_escalate_en_i, RND errors factor into recov_escalate_en_i).
  assign mubi_err_d = |{mubi4_test_invalid(fatal_escalate_en_i),
                        mubi4_test_invalid(recov_escalate_en_i),
                        mubi_err_q};
  always_ff @(posedge clk_i or negedge rst_ni) begin
    if (!rst_ni) begin
      mubi_err_q <= 1'b0;
    end else begin
      mubi_err_q <= mubi_err_d;
    end
  end

  // Instruction is illegal based on the static properties of the instruction bits (illegal encoding
  // or illegal WSR/CSR referenced).
  assign illegal_insn_static = insn_illegal_i | ispr_err;

  assign fatal_software_err       = software_err & software_errs_fatal_i;
  assign bad_internal_state_err   = |{state_error_d, loop_hw_err, rf_base_call_stack_hw_err_i,
                                      rf_bignum_spurious_we_err, spurious_secure_wipe_ack_q,
                                      sec_wipe_err_q, mubi_err_q};
  assign reg_intg_violation_err   = rf_bignum_intg_err | ispr_rdata_intg_err;
  assign key_invalid_err          = ispr_rd_bignum_insn & insn_valid_i & key_invalid;
  assign illegal_insn_err         = illegal_insn_static | rf_indirect_err;
  assign call_stack_sw_err        = rf_base_call_stack_sw_err_i;

  // Flag a bad data address error if the data memory address is invalid and it does not come from
  // an empty call stack.  The second case cannot be decided as bad data address because the address
  // on top of the empty call stack may or may not be valid.  (Also, in most RTL simulators an empty
  // call stack that has never been pushed contains an unknown value, so this error bit would become
  // unknown.)  Thus, a data memory address coming from an empty call stack raises a call stack
  // error but never a bad data address error.
  assign bad_data_addr_err = dmem_addr_err &
                             ~(call_stack_sw_err &
                               (ld_insn_with_addr_from_call_stack |
                                st_insn_with_addr_from_call_stack));

  // Identify load instructions that take the memory address from the call stack.
  assign ld_insn_with_addr_from_call_stack = insn_valid_i               &
                                             insn_dec_shared_i.ld_insn  &
                                             insn_dec_base_i.rf_ren_a   &
                                             (insn_dec_base_i.a == 5'd1);

  // Identify store instructions that take the memory address from the call stack.
  assign st_insn_with_addr_from_call_stack = insn_valid_i               &
                                             insn_dec_shared_i.st_insn  &
                                             insn_dec_base_i.rf_ren_a   &
                                             (insn_dec_base_i.a == 5'd1);

  // All software errors that aren't bad_insn_addr. Factored into bad_insn_addr so it is only raised
  // if other software errors haven't occurred. As bad_insn_addr relates to the next instruction
  // begin fetched it cannot occur if the current instruction has seen an error and failed to
  // execute.
  assign non_insn_addr_software_err = |{key_invalid_err,
                                        loop_sw_err,
                                        illegal_insn_err,
                                        call_stack_sw_err,
                                        bad_data_addr_err};

  assign bad_insn_addr_err = imem_addr_err & ~non_insn_addr_software_err;

  assign err_bits_d = '{
    fatal_software:     fatal_software_err,
    bad_internal_state: bad_internal_state_err,
    reg_intg_violation: reg_intg_violation_err,
    key_invalid:        key_invalid_err,
    loop:               loop_sw_err,
    illegal_insn:       illegal_insn_err,
    call_stack:         call_stack_sw_err,
    bad_data_addr:      bad_data_addr_err,
    bad_insn_addr:      bad_insn_addr_err
  };

  always_ff @(posedge clk_i or negedge rst_ni) begin
    if (!rst_ni) begin
      err_bits_q <= '0;
    end else begin
      if (err_bit_clear_i && !locking_o) begin
        err_bits_q <= '0;
      end else begin
        err_bits_q <= err_bits_q | err_bits_d;
      end
    end
  end
  assign err_bits_o = err_bits_q;

  assign software_err = non_insn_addr_software_err | bad_insn_addr_err;

  assign recoverable_err = mubi4_test_true_loose(recov_escalate_en_i);

  assign internal_fatal_err = |{fatal_software_err,
                                bad_internal_state_err,
                                reg_intg_violation_err};

  // In case of an RMA request, just lock up the controller. This triggers the rotation of the
  // scrambling keys. The start/stop controller takes care of initiating the internal secure wipe
  // and eventually acknowledging the RMA request.
  assign fatal_err = |{internal_fatal_err,
                       mubi4_test_true_loose(fatal_escalate_en_i),
                       mubi4_test_true_strict(rma_req_i)};

  assign recoverable_err_o = recoverable_err | (software_err & ~software_errs_fatal_i);
  assign mems_sec_wipe_o   = (state_d == AccStateLocked) & (state_q != AccStateLocked);

  assign internal_err = software_err | internal_fatal_err;
  assign err          = software_err | recoverable_err | fatal_err;

  assign prefetch_ignore_errs_o = internal_err;

  // Instructions must not execute if there is an error
  assign insn_executing = insn_valid_i & ~err;

  `ASSERT(ErrBitSetOnErr,
      err & (mubi4_test_false_strict(fatal_escalate_en_i) &
             mubi4_test_false_strict(recov_escalate_en_i) &
             mubi4_test_false_loose(rma_req_i)) |=>
          err_bits_o)
  `ASSERT(ErrSetOnFatalErr, fatal_err |-> err)
  `ASSERT(SoftwareErrIfNonInsnAddrSoftwareErr, non_insn_addr_software_err |-> software_err)

  `ASSERT(ControllerStateValid,
          state_q inside {AccStateHalt, AccStateRun, AccStateStall, AccStateLocked})
  // Branch only takes effect in AccStateRun so must not go into stall state for branch
  // instructions.
  `ASSERT(NoStallOnBranch,
      insn_valid_i & insn_dec_shared_i.branch_insn |-> state_q != AccStateStall)

  // SEC_CM: CONTROLLER.FSM.SPARSE
  `PRIM_FLOP_SPARSE_FSM(u_state_regs, state_d, state_q, acc_state_e, AccStateHalt)

  // SEC_CM: CTRL_FLOW.COUNT
  // Two explicit clear controls, one comes from external to acc_core and the other is generated
  // internally (by acc_start_stop_control).
  assign insn_cnt_clear =
    (state_q == AccStateLocked) | insn_cnt_clear_ext_i | insn_cnt_clear_int_i;

  always_comb begin
    if (insn_cnt_clear) begin
      insn_cnt_d = 32'd0;
    end else if (insn_executing & ~stall & (insn_cnt_q != 32'hffffffff)) begin
      insn_cnt_d = insn_cnt_q + 32'd1;
    end else begin
      insn_cnt_d = insn_cnt_q;
    end
  end

  always_ff @(posedge clk_i or negedge rst_ni) begin
    if (!rst_ni) begin
      insn_cnt_q <= 32'd0;
    end else begin
      insn_cnt_q <= insn_cnt_d;
    end
  end

  assign insn_cnt_o = insn_cnt_q;

  assign loop_reset = state_reset_i | sec_wipe_zero_i;

  acc_loop_controller #(
    .ImemAddrWidth(ImemAddrWidth)
  ) u_acc_loop_controller (
    .clk_i,
    .rst_ni,

    .state_reset_i(loop_reset),

    .insn_valid_i,
    .insn_addr_i,
    .next_insn_addr_i(next_insn_addr),

    .loop_start_req_i       (loop_start_req),
    .loop_start_commit_i    (loop_start_commit),
    .loop_bodysize_i        (loop_bodysize),
    .loop_iterations_i      (loop_iterations),
    .loop_end_addr_predec_i (ctrl_flow_target_predec_i),

    .loop_jump_o     (loop_jump),
    .loop_jump_addr_o(loop_jump_addr),

    .sw_err_o     (loop_sw_err),
    .hw_err_o     (loop_hw_err),
    .predec_err_o (loop_predec_err),

    .jump_or_branch_i(jump_or_branch),
    .acc_stall_i    (stall),

    .prefetch_loop_active_o,
    .prefetch_loop_iterations_o,
    .prefetch_loop_end_addr_o,
    .prefetch_loop_jump_addr_o
  );

  // loop_start_req indicates the instruction wishes to start a loop, loop_start_commit confirms it
  // should occur.
  assign loop_start_req    = insn_valid_i & insn_dec_shared_i.loop_insn;
  assign loop_start_commit = insn_executing;
  assign loop_bodysize     = insn_dec_base_i.loop_bodysize;
  assign loop_iterations   = insn_dec_base_i.loop_immediate ? insn_dec_base_i.i :
                                                              rf_base_rd_data_a_no_intg;

  // Compute increments which can be optionally applied to indirect register accesses and memory
  // addresses in BN.LID/BN.SID/BN.MOVR instructions.
  assign rf_base_rd_data_a_inc           = rf_base_rd_data_a_no_intg[4:0] + 1'b1;
  assign rf_base_rd_data_b_inc           = rf_base_rd_data_b_no_intg[4:0] + 1'b1;
  // We can avoid a full 32-bit adder here because the offset is 32-bit aligned, so we know the
  // load/store address will only be valid if rf_base_rd_data_a_no_intg[4:0] is zero.
  assign rf_base_rd_data_a_wlen_word_inc = rf_base_rd_data_a_no_intg[31:5] + 27'h1;

  // Choose increment to write back to base register file, only one increment can be written as
  // there is only one write port. Note that where an instruction is incrementing the indirect
  // reference to its destination register (insn_dec_bignum_i.d_inc) that reference is read on the
  // B read port so the B increment is written back.
  always_comb begin
    unique case (1'b1)
      insn_dec_bignum_i.a_inc: begin
        increment_out = {26'b0, rf_base_rd_data_a_inc};
      end
      insn_dec_bignum_i.b_inc: begin
        increment_out = {26'b0, rf_base_rd_data_b_inc};
      end
      insn_dec_bignum_i.d_inc: begin
        increment_out = {26'b0, rf_base_rd_data_b_inc};
      end
      insn_dec_bignum_i.a_wlen_word_inc: begin
        increment_out = {rf_base_rd_data_a_wlen_word_inc, 5'b0};
      end
      default: begin
        // Whenever increment_out is written back to the register file, exactly one of the
        // increment selector signals is high. To prevent the automatic inference of latches in
        // case nothing is written back (rf_wdata_sel != RfWdSelIncr) and to save logic, we choose
        // a valid output as default.
        increment_out = {26'b0, rf_base_rd_data_a_inc};
      end
    endcase
  end

  // Base RF read/write address, enable and commit control
  always_comb begin
    rf_base_rd_addr_a_o = insn_dec_base_i.a;
    rf_base_rd_addr_b_o = insn_dec_base_i.b;
    rf_base_wr_addr_o   = insn_dec_base_i.d;

    // Only commit read or write if the instruction is executing (in particular a read commit pops
    // the call stack so must not occur where a valid instruction sees an error and doesn't
    // execute).
    rf_base_rd_commit_o = insn_executing;
    rf_base_wr_commit_o = insn_executing;

    rf_base_rd_en_a_raw = 1'b0;
    rf_base_rd_en_b_raw = 1'b0;
    rf_base_wr_en_raw   = 1'b0;

    if (insn_valid_i) begin
      if (insn_dec_shared_i.st_insn) begin
        // For stores, both base reads happen in the first cycle of the store instruction. For base
        // stores this is the same cycle as the request. For bignum stores this is the cycle before
        // the request (as the indirect register read to get the store data occurs the following
        // cycle).
        rf_base_rd_en_a_raw = insn_dec_base_i.rf_ren_a &
          (rf_indirect_stall | (insn_dec_shared_i.subset == InsnSubsetBase) |
           (insn_dec_shared_i.st_insn & ~insn_dec_bignum_i.rf_b_indirect));
        rf_base_rd_en_b_raw = insn_dec_base_i.rf_ren_b &
          (rf_indirect_stall | (insn_dec_shared_i.subset == InsnSubsetBase));

        // Bignum stores can update the base register file where an increment is used.
        rf_base_wr_en_raw   = (insn_dec_shared_i.subset == InsnSubsetBignum) &
                              insn_dec_base_i.rf_we                          &
                              (insn_dec_bignum_i.rf_b_indirect ? rf_indirect_stall : 1'b1);
      end else if (insn_dec_shared_i.ld_insn) begin
        // For loads, both base reads happen in the same cycle as the request. The address is
        // required for the request and the indirect destination register (only used for Bignum
        // loads) is flopped in ld_insn_bignum_wr_addr_q to correctly deal with the case where it's
        // updated by an increment.
        rf_base_rd_en_a_raw = insn_dec_base_i.rf_ren_a & lsu_load_req_raw;
        rf_base_rd_en_b_raw = insn_dec_base_i.rf_ren_b & lsu_load_req_raw;

        if (insn_dec_shared_i.subset == InsnSubsetBignum) begin
          // Bignum loads can update the base register file where an increment is used. This must
          // always happen in the same cycle as the request as this is where both registers are
          // read.
          rf_base_wr_en_raw = insn_dec_base_i.rf_we & lsu_load_req_raw & rf_indirect_stall;
        end else begin
          // For Base loads write the base register file when the instruction is unstalled (meaning
          // the load data is available).
          rf_base_wr_en_raw = insn_dec_base_i.rf_we & ~stall;
        end
      end else if (insn_dec_bignum_i.rf_wdata_sel == RfWdSelMovSel) begin
        // For MOVR base register reads occur in the first cycle of the instruction. The indirect
        // register read for the bignum data occurs in the following cycle.
        rf_base_rd_en_a_raw = insn_dec_base_i.rf_ren_a & rf_indirect_stall;
        rf_base_rd_en_b_raw = insn_dec_base_i.rf_ren_b & rf_indirect_stall;
        rf_base_wr_en_raw   = insn_dec_base_i.rf_we    & rf_indirect_stall;
      end else begin
        // For all other instructions the read and write happen when the instruction is unstalled.
        rf_base_rd_en_a_raw = insn_dec_base_i.rf_ren_a & ~stall;
        rf_base_rd_en_b_raw = insn_dec_base_i.rf_ren_b & ~stall;
        rf_base_wr_en_raw   = insn_dec_base_i.rf_we    & ~stall;
      end
    end

    if (insn_dec_shared_i.subset == InsnSubsetBignum) begin
      unique case (1'b1)
        insn_dec_bignum_i.a_inc,
        insn_dec_bignum_i.a_wlen_word_inc: begin
          rf_base_wr_addr_o = insn_dec_base_i.a;
        end

        insn_dec_bignum_i.b_inc,
        insn_dec_bignum_i.d_inc: begin
          rf_base_wr_addr_o = insn_dec_base_i.b;
        end
        default: ;
      endcase
    end

    rf_base_rd_en_a_o = rf_base_rd_en_a_raw & ~illegal_insn_static;
    rf_base_rd_en_b_o = rf_base_rd_en_b_raw & ~illegal_insn_static;
    rf_base_wr_en_o   = rf_base_wr_en_raw   & ~illegal_insn_static;
  end

  // Base ALU Operand A MUX
  always_comb begin
    unique case (insn_dec_base_i.op_a_sel)
      OpASelRegister: alu_base_operation_o.operand_a = rf_base_rd_data_a_no_intg;
      OpASelZero:     alu_base_operation_o.operand_a = '0;
      OpASelCurrPc:   alu_base_operation_o.operand_a = {{(32 - ImemAddrWidth){1'b0}}, insn_addr_i};
      default:        alu_base_operation_o.operand_a = rf_base_rd_data_a_no_intg;
    endcase
  end

  // Base ALU Operand B MUX
  always_comb begin
    unique case (insn_dec_base_i.op_b_sel)
      OpBSelRegister:  alu_base_operation_o.operand_b = rf_base_rd_data_b_no_intg;
      OpBSelImmediate: alu_base_operation_o.operand_b = insn_dec_base_i.i;
      default:         alu_base_operation_o.operand_b = rf_base_rd_data_b_no_intg;
    endcase
  end

  assign alu_base_operation_o.op = insn_dec_base_i.alu_op;

  assign alu_base_comparison_o.operand_a = rf_base_rd_data_a_no_intg;
  assign alu_base_comparison_o.operand_b = rf_base_rd_data_b_no_intg;
  assign alu_base_comparison_o.op = insn_dec_base_i.comparison_op;

  assign rf_base_rd_data_a_no_intg = rf_base_rd_data_a_intg_i[31:0];
  assign rf_base_rd_data_b_no_intg = rf_base_rd_data_b_intg_i[31:0];

  logic unused_rf_base_rd_a_intg_bits;

  // TODO(#18266): Implement GPR to ISPR end to end integrity path (ISPR writes from GPR take data
  // from base RF port A)
  assign unused_rf_base_rd_a_intg_bits = |rf_base_rd_data_a_intg_i[38:32];

  // Base register file write MUX. Depending on the data source, integrity bits do or don't have to
  // be appended:
  // - Data sources that require appending integrity bits go into `rf_base_wr_data_no_intg_o` and
  //   `rf_base_wr_data_intg_sel_o` is low.
  // - Data sources that already come with integrity bits go into `rf_base_wr_data_intg_o` and
  //   `rf_base_wr_data_intg_sel_o` is high.
  always_comb begin
    // Default values
    rf_base_wr_data_no_intg_o  = alu_base_operation_result_i;
    rf_base_wr_data_intg_o     = '0;
    rf_base_wr_data_intg_sel_o = 1'b0;

    unique case (insn_dec_base_i.rf_wdata_sel)
      RfWdSelEx: begin
        rf_base_wr_data_no_intg_o  = alu_base_operation_result_i;
      end
      RfWdSelNextPc: begin
        rf_base_wr_data_no_intg_o  = {{(32-(ImemAddrWidth+1)){1'b0}}, next_insn_addr_wide};
      end
      RfWdSelIspr: begin
        rf_base_wr_data_no_intg_o  = csr_rdata;
      end
      RfWdSelIncr: begin
        rf_base_wr_data_no_intg_o  = increment_out;
      end
      RfWdSelLsu: begin
        rf_base_wr_data_intg_sel_o = 1'b1;
        rf_base_wr_data_intg_o     = lsu_base_rdata_i;
      end
      default: ;
    endcase
  end

  for (genvar i = 0; i < BaseWordsPerWLEN; ++i) begin : g_rf_bignum_rd_data
    assign rf_bignum_rd_data_a_no_intg[i*32+:32] = rf_bignum_rd_data_a_intg_i[i*39+:32];
    assign rf_bignum_rd_data_b_no_intg[i*32+:32] = rf_bignum_rd_data_b_intg_i[i*39+:32];
  end

  // Bignum RF control signals from the controller aren't actually used, instead the predecoded
  // one-hot versions are. The predecoded versions get checked against the signals produced here.
  // Buffer them to ensure they don't get optimised away (with a functionally correct ACC they will
  // always be identical).
  assign rf_bignum_rd_addr_a_unbuf = insn_dec_bignum_i.rf_a_indirect ? insn_bignum_rd_addr_a_q :
                                                                       insn_dec_bignum_i.a;

  prim_buf #(
    .Width(WdrAw)
  ) u_rf_bignum_rd_addr_a_buf (
    .in_i (rf_bignum_rd_addr_a_unbuf),
    .out_o(rf_bignum_rd_addr_a_o)
  );

  generate
    if (AccPQCEn) begin : gen_bignum_rd_unbuf_pqc
      assign rf_bignum_rd_en_a_unbuf = insn_dec_bignum_i.rf_ren_a
          & insn_valid_i
          & (~stall | gen_kmac_nets.kmac_write_stall);
    end else begin : gen_bignum_rd_unbuf
      assign rf_bignum_rd_en_a_unbuf = insn_dec_bignum_i.rf_ren_a & insn_valid_i & ~stall;
    end
  endgenerate

  prim_buf #(
    .Width(1)
  ) u_rf_bignum_rd_en_a_buf (
    .in_i (rf_bignum_rd_en_a_unbuf),
    .out_o(rf_bignum_rd_en_a_o)
  );

  assign rf_bignum_rd_addr_b_unbuf = insn_dec_bignum_i.rf_b_indirect ? insn_bignum_rd_addr_b_q :
                                                                       insn_dec_bignum_i.b;

  prim_buf #(
    .Width(WdrAw)
  ) u_rf_bignum_rd_addr_b_buf (
    .in_i (rf_bignum_rd_addr_b_unbuf),
    .out_o(rf_bignum_rd_addr_b_o)
  );

  assign rf_bignum_rd_en_b_unbuf = insn_dec_bignum_i.rf_ren_b & insn_valid_i & ~stall;

  prim_buf #(
    .Width(1)
  ) u_rf_bignum_rd_en_b_buf (
    .in_i (rf_bignum_rd_en_b_unbuf),
    .out_o(rf_bignum_rd_en_b_o)
  );

  assign alu_bignum_operation_o.operand_a = rf_bignum_rd_data_a_no_intg;

  // Base ALU Operand B MUX
  always_comb begin
    unique case (insn_dec_bignum_i.alu_op_b_sel)
      OpBSelRegister:  alu_bignum_operation_o.operand_b = rf_bignum_rd_data_b_no_intg;
      OpBSelImmediate: alu_bignum_operation_o.operand_b = insn_dec_bignum_i.i;
      default:         alu_bignum_operation_o.operand_b = rf_bignum_rd_data_b_no_intg;
    endcase
  end

  assign alu_bignum_operation_o.op          = insn_dec_bignum_i.alu_op;
  assign alu_bignum_operation_o.shift_right = insn_dec_bignum_i.alu_shift_right;
  assign alu_bignum_operation_o.shift_amt   = insn_dec_bignum_i.alu_shift_amt;
  assign alu_bignum_operation_o.flag_group  = insn_dec_bignum_i.alu_flag_group;
  assign alu_bignum_operation_o.sel_flag    = insn_dec_bignum_i.alu_sel_flag;
  assign alu_bignum_operation_o.alu_flag_en = insn_dec_bignum_i.alu_flag_en & insn_valid_i;
  assign alu_bignum_operation_o.mac_flag_en = insn_dec_bignum_i.mac_flag_en & insn_valid_i;

  assign alu_bignum_operation_valid_o  = insn_valid_i;
  assign alu_bignum_operation_commit_o = insn_executing;

  assign mac_bignum_operation_o.operand_a         = rf_bignum_rd_data_a_no_intg;
  assign mac_bignum_operation_o.operand_b         = rf_bignum_rd_data_b_no_intg;
  assign mac_bignum_operation_o.operand_a_qw_sel  = insn_dec_bignum_i.mac_op_a_qw_sel;
  assign mac_bignum_operation_o.operand_b_qw_sel  = insn_dec_bignum_i.mac_op_b_qw_sel;
  assign mac_bignum_operation_o.wr_hw_sel_upper   = insn_dec_bignum_i.mac_wr_hw_sel_upper;
  assign mac_bignum_operation_o.pre_acc_shift_imm = insn_dec_bignum_i.mac_pre_acc_shift;
  assign mac_bignum_operation_o.zero_acc          = insn_dec_bignum_i.mac_zero_acc;
  assign mac_bignum_operation_o.shift_acc         = insn_dec_bignum_i.mac_shift_out;

  // Vector instruction flags for the PQC extension are driven by default values in the decoder
  assign alu_bignum_operation_o.vector_type   = insn_dec_bignum_i.vector_type;
  assign alu_bignum_operation_o.vector_sel    = insn_dec_bignum_i.vector_sel;
  assign alu_bignum_operation_o.trn_type      = insn_dec_bignum_i.alu_trn_type;
  assign mac_bignum_operation_o.mulv          = insn_dec_bignum_i.mac_mulv;
  assign mac_bignum_operation_o.data_type     = insn_dec_bignum_i.mac_data_type;
  assign mac_bignum_operation_o.sel           = insn_dec_bignum_i.mac_sel;
  assign mac_bignum_operation_o.lane_mode     = insn_dec_bignum_i.mac_lane_mode;
  assign mac_bignum_operation_o.lane_word_32  = insn_dec_bignum_i.mac_lane_word_32;
  assign mac_bignum_operation_o.lane_word_16  = insn_dec_bignum_i.mac_lane_word_16;
  assign mac_bignum_operation_o.exec_mode     = insn_dec_bignum_i.mac_exec_mode;

  assign mac_bignum_en_o     = insn_valid_i & insn_dec_bignum_i.mac_en;
  assign mac_bignum_commit_o = insn_executing;

  // Move / Conditional Select. Only select B register data when a selection instruction is being
  // executed and the selection flag isn't set. To avoid undesirable SCA leakage between the two
  // registers for non-selection instructions, the B register is blanked except for selection
  // instructions.
  // Note that blanking both registers is not feasible nor absolutely required because:
  // - The flag group selection and flag selection are known in the predecoder stage but the actual
  //   flag isn't.
  // - Selecting the flag in the predecoder stage using combinatorial inputs may lead to SCA leakage
  //   between the still combinatorial flag groups and flags within a group which might be
  //   undesirable as well.
  // - When executing a selection instruction, programmers can expected that there will be some SCA
  //   leakage between the two options. But it may be much less expected for such leakage to occur
  //   for other instructions.
  `ASSERT(SelFlagValid, insn_valid_i & insn_dec_bignum_i.sel_insn |->
    insn_dec_bignum_i.alu_sel_flag inside {FlagC, FlagL, FlagM, FlagZ})

  // SEC_CM: DATA_REG_SW.SCA
  prim_blanker #(.Width(ExtWLEN)) u_rf_bignum_rd_data_b_intg_blanker (
    .in_i (rf_bignum_rd_data_b_intg_i),
    .en_i (ctrl_flow_predec_i.sel_insn),
    .out_o(rf_bignum_rd_data_b_intg_blanked)
  );

  `ASSERT(BlankingBignumRdDataBSel,
    ~(insn_valid_i & insn_dec_bignum_i.sel_insn) |-> rf_bignum_rd_data_b_intg_blanked == '0,
    clk_i, !rst_ni || ctrl_predec_error || !insn_executing)

  assign selection_result =
    ~ctrl_flow_predec_i.sel_insn | alu_bignum_selection_flag_i ? rf_bignum_rd_data_a_intg_i :
                                                                 rf_bignum_rd_data_b_intg_blanked;

  // Bignum Register file write control
  generate
    if (AccPQCEn) begin : gen_bignum_wr_ctrl_pqc
      always_comb begin
        // By default write nothing
        rf_bignum_wr_en_unbuf = 2'b00;

        // Only write if valid instruction wants a bignum rf write and it isn't stalled.
        // If instruction doesn't execute (e.g. due to an error) the write won't commit.
        if (insn_valid_i && insn_dec_bignum_i.rf_we && !rf_indirect_stall &&
           !gen_kmac_nets.kmac_write_stall)
        begin
          if (insn_dec_bignum_i.mac_en && insn_dec_bignum_i.mac_shift_out) begin
            // Special handling for BN.MULQACC.SO, only enable upper or lower half depending on
            // mac_wr_hw_sel_upper.
            rf_bignum_wr_en_unbuf = insn_dec_bignum_i.mac_wr_hw_sel_upper ? 2'b10 : 2'b01;
          end else begin
            // For everything else write both halves immediately.
            rf_bignum_wr_en_unbuf = 2'b11;
          end
        end
      end
    end else begin : gen_bignum_wr_ctrl
      always_comb begin
        // By default write nothing
        rf_bignum_wr_en_unbuf = 2'b00;

        // Only write if valid instruction wants a bignum rf write and it isn't stalled.
        // If instruction doesn't execute (e.g. due to an error) the write won't commit.
        if (insn_valid_i && insn_dec_bignum_i.rf_we && !rf_indirect_stall) begin
          if (insn_dec_bignum_i.mac_en && insn_dec_bignum_i.mac_shift_out) begin
            // Special handling for BN.MULQACC.SO, only enable upper or lower half depending on
            // mac_wr_hw_sel_upper.
            rf_bignum_wr_en_unbuf = insn_dec_bignum_i.mac_wr_hw_sel_upper ? 2'b10 : 2'b01;
          end else begin
            // For everything else write both halves immediately.
            rf_bignum_wr_en_unbuf = 2'b11;
          end
        end
      end
    end
  endgenerate

  // Bignum RF control signals from the controller aren't actually used, instead the predecoded
  // one-hot versions are. The predecoded versions get checked against the signals produced here.
  // Buffer them to ensure they don't get optimised away (with a functionally correct ACC they
  // will always be identical).
  prim_buf #(
    .Width(2)
  ) u_bignum_wr_en_buf (
    .in_i (rf_bignum_wr_en_unbuf),
    .out_o(rf_bignum_wr_en_o)
  );


  assign rf_bignum_wr_commit_o = |rf_bignum_wr_en_o & insn_executing & !stall;

  assign rf_bignum_indirect_en_o    = insn_executing & rf_indirect_stall;
  assign rf_bignum_rd_a_indirect_en = insn_executing & insn_dec_bignum_i.rf_a_indirect;
  assign rf_bignum_rd_b_indirect_en = insn_executing & insn_dec_bignum_i.rf_b_indirect;
  assign rf_bignum_wr_indirect_en   = insn_executing & insn_dec_bignum_i.rf_d_indirect;

  prim_onehot_enc #(
    .OneHotWidth(NWdr)
  ) rf_bignum_rd_a_idirect_onehot__enc (
    .in_i  (rf_base_rd_data_a_no_intg[4:0]),
    .en_i  (rf_bignum_rd_a_indirect_en),
    .out_o (rf_bignum_rd_a_indirect_onehot_o)
  );

  prim_onehot_enc #(
    .OneHotWidth(NWdr)
  ) rf_bignum_rd_b_indirect_onehot_enc (
    .in_i  (rf_base_rd_data_b_no_intg[4:0]),
    .en_i  (rf_bignum_rd_b_indirect_en),
    .out_o (rf_bignum_rd_b_indirect_onehot_o)
  );

  prim_onehot_enc #(
    .OneHotWidth(NWdr)
  ) rf_bignum_wr_indirect_onehot_enc (
    .in_i  (insn_dec_bignum_i.rf_d_indirect ? rf_base_rd_data_b_no_intg[4:0] : insn_dec_bignum_i.b),
    .en_i  (rf_bignum_wr_indirect_en | insn_dec_shared_i.ld_insn),
    .out_o (rf_bignum_wr_indirect_onehot_o)
  );

  // For BN.LID sample the indirect destination register index in first cycle as an increment might
  // change it for the second cycle where the load data is written to the bignum register file.
  always_ff @(posedge clk_i) begin
    if (insn_dec_bignum_i.rf_d_indirect) begin
      insn_bignum_wr_addr_q <= rf_base_rd_data_b_no_intg[4:0];
    end

    if (insn_dec_bignum_i.rf_a_indirect) begin
      insn_bignum_rd_addr_a_q <= rf_base_rd_data_a_no_intg[4:0];
    end

    if (insn_dec_bignum_i.rf_b_indirect) begin
      insn_bignum_rd_addr_b_q <= rf_base_rd_data_b_no_intg[4:0];
    end
  end

  // Bignum RF control signals from the controller aren't actually used, instead the predecoded
  // one-hot versions are. The predecoded versions get checked against the signals produced here.
  // Buffer them to ensure they don't get optimised away (with a functionally correct ACC they will
  // always be identical).
  assign rf_bignum_wr_addr_unbuf = insn_dec_bignum_i.rf_d_indirect ?
                                      insn_bignum_wr_addr_q :
                                      (insn_dec_shared_i.ld_insn ?
                                        insn_dec_bignum_i.b : insn_dec_bignum_i.d);

  prim_buf #(
    .Width(WdrAw)
  ) u_rf_bignum_wr_addr_buf (
    .in_i (rf_bignum_wr_addr_unbuf),
    .out_o(rf_bignum_wr_addr_o)
  );

  // For the shift-out variant of BN.MULQACC the bottom half of the MAC result is written to one
  // half of a desintation register specified by the instruction (mac_wr_hw_sel_upper). The bottom
  // half of the MAC result must be placed in the appropriate half of the write data (the RF only
  // accepts write data for the top half in the top half of the write data input). Otherwise
  // (shift-out to bottom half and all other BN.MULQACC instructions) simply pass the MAC result
  // through unchanged as write data.
  assign mac_bignum_rf_wr_data[WLEN-1:WLEN/2] =
      insn_dec_bignum_i.mac_wr_hw_sel_upper &&
      insn_dec_bignum_i.mac_shift_out          ? mac_bignum_operation_result_i[WLEN/2-1:0] :
                                                 mac_bignum_operation_result_i[WLEN-1:WLEN/2];

  assign mac_bignum_rf_wr_data[WLEN/2-1:0] = mac_bignum_operation_result_i[WLEN/2-1:0];

  // Bignum register file write MUX. Depending on the data source, integrity bits do or don't have
  // to be appended; see comments on the "Base register file write MUX" for details.
  always_comb begin
    // Default values
    rf_bignum_wr_data_intg_sel_o = 1'b0;
    rf_bignum_wr_data_intg_o     = '0;
    rf_bignum_wr_data_no_intg_o  = alu_bignum_operation_result_i;

    unique case (insn_dec_bignum_i.rf_wdata_sel)
      RfWdSelEx: begin
        rf_bignum_wr_data_no_intg_o  = alu_bignum_operation_result_i;
      end
      RfWdSelMac: begin
        rf_bignum_wr_data_no_intg_o  = mac_bignum_rf_wr_data;
      end
      RfWdSelIspr: begin
        rf_bignum_wr_data_intg_sel_o = 1'b1;
        rf_bignum_wr_data_intg_o     = ispr_rdata_intg_i;
      end
      RfWdSelMovSel: begin
        rf_bignum_wr_data_intg_sel_o = 1'b1;
        rf_bignum_wr_data_intg_o     = selection_result;
      end
      RfWdSelLsu: begin
        rf_bignum_wr_data_intg_sel_o = 1'b1;
        //SEC_CM: BUS.INTEGRITY
        rf_bignum_wr_data_intg_o     = lsu_bignum_rdata_i;
      end
      default: ;
    endcase
  end

  assign rf_a_indirect_err = insn_dec_bignum_i.rf_a_indirect    &
                             (|rf_base_rd_data_a_no_intg[31:5]) &
                             ~rf_base_call_stack_sw_err_i       &
                             rf_base_rd_en_a_o;

  assign rf_b_indirect_err = insn_dec_bignum_i.rf_b_indirect    &
                             (|rf_base_rd_data_b_no_intg[31:5]) &
                             ~rf_base_call_stack_sw_err_i       &
                             rf_base_rd_en_b_o;

  assign rf_d_indirect_err = insn_dec_bignum_i.rf_d_indirect    &
                             (|rf_base_rd_data_b_no_intg[31:5]) &
                             ~rf_base_call_stack_sw_err_i       &
                             rf_base_rd_en_b_o;

  assign rf_indirect_err =
      insn_valid_i & (rf_a_indirect_err | rf_b_indirect_err | rf_d_indirect_err);


  // If the source registers are indirectly indexed and there is a stack error, the source
  // register indices were illegal due to a stack pop error. In this case, ignore bignum RF read
  // integrity errors.
  assign ignore_rf_bignum_intg_errs = (insn_dec_bignum_i.rf_a_indirect |
                                       insn_dec_bignum_i.rf_b_indirect) &
                                      rf_base_call_stack_sw_err_i;

  assign rf_bignum_intg_err = rf_bignum_intg_err_i & ~ignore_rf_bignum_intg_errs;

  // If the destination register is indirectly indexed and there is a stack error, the destination
  // register index was illegal due to a stack pop error. In this case, ignore bignum RF
  // write-enable errors.
  assign ignore_rf_bignum_spurious_we_errs = insn_dec_bignum_i.rf_d_indirect &
                                             rf_base_call_stack_sw_err_i;

  assign rf_bignum_spurious_we_err = rf_bignum_spurious_we_err_i &
                                     ~ignore_rf_bignum_spurious_we_errs;

  // CSR/WSR/ISPR handling
  // ISPRs (Internal Special Purpose Registers) are the internal registers. CSRs and WSRs are the
  // ISA visible versions of those registers in the base and bignum ISAs respectively.

  assign csr_addr     = csr_e'(insn_dec_base_i.i[11:0]);
  assign csr_sub_addr = insn_dec_base_i.i[$clog2(BaseWordsPerWLEN)-1:0];

  always_comb begin
    ispr_addr_base      = IsprMod;
    ispr_word_addr_base = '0;
    csr_illegal_addr    = 1'b0;

    unique case (csr_addr)
      CsrFlags, CsrFg0, CsrFg1: begin
        ispr_addr_base      = IsprFlags;
        ispr_word_addr_base = '0;
      end
      CsrMod0, CsrMod1, CsrMod2, CsrMod3, CsrMod4, CsrMod5, CsrMod6, CsrMod7: begin
        ispr_addr_base      = IsprMod;
        ispr_word_addr_base = csr_sub_addr;
      end
      CsrRndPrefetch: begin
        // Reading from RND_PREFETCH results in 0, there is no ISPR to read so no address is set.
        // The csr_rdata mux logic takes care of producing the 0.
      end
      CsrKmacCfg: begin
        if (AccPQCEn) begin
          ispr_addr_base      = IsprKmacCfg;
          ispr_word_addr_base = '0;
        end else begin
          csr_illegal_addr = 1'b1;
        end
      end
      CsrKmacStatus: begin
        if (AccPQCEn) begin
          ispr_addr_base      = IsprKmacStatus;
          ispr_word_addr_base = '0;
        end else begin
          csr_illegal_addr = 1'b1;
        end
      end
      CsrKmacPartialW: begin
        if (AccPQCEn) begin
          ispr_addr_base      = IsprKmacPartialW;
          ispr_word_addr_base = '0;
        end else begin
          csr_illegal_addr = 1'b1;
        end
      end
      CsrRnd: begin
        ispr_addr_base      = IsprRnd;
        ispr_word_addr_base = '0;
      end
      CsrUrnd: begin
        ispr_addr_base      = IsprUrnd;
        ispr_word_addr_base = '0;
      end
      default: csr_illegal_addr = 1'b1;
    endcase
  end

  for (genvar i_word = 0; i_word < BaseWordsPerWLEN; i_word++) begin : g_ispr_word_sel_base
    assign ispr_word_sel_base[i_word] = ispr_word_addr_base == i_word;
  end

  // Decode wide ISPR read data.
  logic [WLEN-1:0]                ispr_rdata;
  logic [2*BaseWordsPerWLEN-1:0]  ispr_rdata_intg_err_wide;
  logic [BaseWordsPerWLEN-1:0]    ispr_rdata_intg_err_narrow;
  for (genvar i_word = 0; i_word < BaseWordsPerWLEN; i_word++) begin : g_ispr_rdata_dec
    prim_secded_inv_39_32_dec i_secded_dec (
      .data_i     (ispr_rdata_intg_i[i_word*39+:39]),
      .data_o     (/* unused because we abort on any integrity error */),
      .syndrome_o (/* unused */),
      .err_o      (ispr_rdata_intg_err_wide[i_word*2+:2])
    );
    assign ispr_rdata[i_word*32+:32] = ispr_rdata_intg_i[i_word*39+:32];
    assign ispr_rdata_intg_err_narrow[i_word] = |(ispr_rdata_intg_err_wide[i_word*2+:2]);
  end

  // Propagate integrity error only if wide ISPR is used.

  // Handle ISPR integrity error detection. We've got a bitmask of ISPR words that failed their
  // integrity check (ispr_rdata_intg_err_narrow), but a nonzero entry may not be a problem if we
  // don't actually use the data.
  //
  // The situations when the data is actually used are:
  //
  //   (1) This is a bignum instruction that writes back to the bignum register file by reading an
  //       ISPR. In this case, we actually pass the data through with integrity bits, but it
  //       shouldn't hurt to add fault detection at this point.
  //
  //   (2) This instruction consumes the data by selecting a word from an ISPR and then writing it
  //       back. This happens for things like CSRRS instructions, where the data flows to the base
  //       register file through rf_base_wr_data_no_intg_o and back to the ISPR through
  //       ispr_base_wdata_o. The word used is given by the onehot ispr_word_sel_base mask.
  //
  // In both cases, there's a special case for the RND_PREFETCH register, which doesn't actually
  // have any backing data. It reads as zero with invalid integrity bits which we want to ignore.

  // Are we reading all the ISPR data? (case (1) above)
  logic all_ispr_words_used;
  assign all_ispr_words_used = (insn_dec_bignum_i.rf_wdata_sel == RfWdSelIspr);

  // Are we reading just one word of the ISPR data? (case (2) above).
  logic one_ispr_word_used;
  assign one_ispr_word_used = ispr_rd_insn & (insn_dec_shared_i.subset == InsnSubsetBase);

  // A bit-mask giving which ISPR words are being read
  logic [BaseWordsPerWLEN-1:0] ispr_read_mask;
  assign ispr_read_mask = all_ispr_words_used ? '1 :
                          one_ispr_word_used  ? ispr_word_sel_base : '0;

  // Use ispr_read_mask to qualify the error bit-mask that came out of the integrity decoder.
  logic [BaseWordsPerWLEN-1:0] ispr_rdata_used_intg_err;
  assign ispr_rdata_used_intg_err = ispr_read_mask & ispr_rdata_intg_err_narrow;

  // We only architecturally read the ISPR when there's a non-stalled instruction. This is also the
  // place where we factor in the special RND_PREFETCH behaviour. We also need to squash any
  // integrity errors if we're reading a sideload key which isn't currently valid (this will
  // generate a key_invalid error, but we shouldn't have any behaviour that depends on what happens
  // to be on the pins)
  logic non_prefetch_insn_running;
  assign non_prefetch_insn_running = (insn_valid_i & ~stall &
                                      (csr_addr != CsrRndPrefetch) & ~key_invalid);

  assign ispr_rdata_intg_err = non_prefetch_insn_running & |(ispr_rdata_used_intg_err);

  `ASSERT_KNOWN(IsprRdataIntgErrKnown_A, ispr_rdata_intg_err)

  for (genvar i_bit = 0; i_bit < 32; i_bit++) begin : g_csr_rdata_mux
    for (genvar i_word = 0; i_word < BaseWordsPerWLEN; i_word++) begin : g_csr_rdata_mux_inner
      assign csr_rdata_mux[i_bit][i_word] =
          ispr_rdata[i_word*32 + i_bit] & ispr_word_sel_base[i_word];
    end

    assign csr_rdata_raw[i_bit] = |csr_rdata_mux[i_bit];
  end

  // Specialised read data handling for CSR reads where raw read data needs modification.
  always_comb begin
    csr_rdata = csr_rdata_raw;

    unique case (csr_addr)
      // For FG0/FG1 select out appropriate bits from FLAGS ISPR and pad the rest with zeros.
      CsrFg0:         csr_rdata = {28'b0, csr_rdata_raw[3:0]};
      CsrFg1:         csr_rdata = {28'b0, csr_rdata_raw[7:4]};
      CsrRndPrefetch: csr_rdata = '0;
      default: ;
    endcase
  end

  assign csr_wdata_raw = insn_dec_shared_i.ispr_rs_insn ? csr_rdata | rf_base_rd_data_a_no_intg :
                                                          rf_base_rd_data_a_no_intg;

  // Specialised write data handling for CSR writes where raw write data needs modification.
  always_comb begin
    csr_wdata = csr_wdata_raw;

    unique case (csr_addr)
      // For FG0/FG1 only modify relevant part of FLAGS ISPR.
      CsrFg0: csr_wdata = {24'b0, csr_rdata_raw[7:4], csr_wdata_raw[3:0]};
      CsrFg1: csr_wdata = {24'b0, csr_wdata_raw[3:0], csr_rdata_raw[3:0]};
      default: ;
    endcase
  end

  // ISPR RS (read and set) must not be combined with ISPR RD or WR (read or write). ISPR RD and
  // WR (read and write) is allowed.
  `ASSERT(NoIsprRorWAndRs, insn_valid_i |-> ~(insn_dec_shared_i.ispr_rs_insn   &
                                              (insn_dec_shared_i.ispr_rd_insn |
                                               insn_dec_shared_i.ispr_wr_insn)))


  assign wsr_addr = wsr_e'(insn_dec_bignum_i.i[WsrNumWidth-1:0]);

  always_comb begin
    ispr_addr_bignum = IsprMod;
    wsr_illegal_addr = 1'b0;
    key_invalid      = 1'b0;

    unique case (wsr_addr)
      WsrMod:  ispr_addr_bignum = IsprMod;
      WsrRnd:  ispr_addr_bignum = IsprRnd;
      WsrUrnd: ispr_addr_bignum = IsprUrnd;
      WsrAcc:  ispr_addr_bignum = IsprAcc;
      WsrKmacMsg0: begin
        if (AccPQCEn) begin
          ispr_addr_bignum = IsprKmacMsg0;
        end else begin
          wsr_illegal_addr = 1'b1;
        end
      end
      WsrKmacMsg1: begin
        if (AccPQCEn) begin
          ispr_addr_bignum = IsprKmacMsg1;
        end else begin
          wsr_illegal_addr = 1'b1;
        end
      end
      WsrKmacCfg: begin
        if (AccPQCEn) begin
          ispr_addr_bignum = IsprKmacCfg;
        end else begin
          wsr_illegal_addr = 1'b1;
        end
      end
      WsrKmacDigest0: begin
        if (AccPQCEn) begin
          ispr_addr_bignum = IsprKmacDigest0;
        end else begin
          wsr_illegal_addr = 1'b1;
        end
      end
      WsrKmacDigest1: begin
        if (AccPQCEn) begin
          ispr_addr_bignum = IsprKmacDigest1;
        end else begin
          wsr_illegal_addr = 1'b1;
        end
      end
      WsrAccH: begin
        if (AccPQCEn) begin
          ispr_addr_bignum = IsprAccH;
        end else begin
          wsr_illegal_addr = 1'b1;
        end
      end
      WsrKeyS0L: begin
        ispr_addr_bignum = IsprKeyS0L;
        key_invalid = ~sideload_key_shares_valid_i[0];
      end
      WsrKeyS0H: begin
        ispr_addr_bignum = IsprKeyS0H;
        key_invalid = ~sideload_key_shares_valid_i[0];
      end
      WsrKeyS1L: begin
        ispr_addr_bignum = IsprKeyS1L;
        key_invalid = ~sideload_key_shares_valid_i[1];
      end
      WsrKeyS1H: begin
        ispr_addr_bignum = IsprKeyS1H;
        key_invalid = ~sideload_key_shares_valid_i[1];
      end
      default: wsr_illegal_addr = 1'b1;
    endcase

    // BN.WSRR/WSRW encoding allows 8-bit immediate but only declared WSR are valid
    if (insn_dec_bignum_i.i[7:0] >= (1 << WsrNumWidth)) begin
      wsr_illegal_addr = 1'b1;
    end
  end

  assign wsr_wdata = insn_dec_shared_i.ispr_rs_insn ? ispr_rdata | rf_bignum_rd_data_a_no_intg :
                                                      rf_bignum_rd_data_a_no_intg;

  assign ispr_illegal_addr = insn_dec_shared_i.subset == InsnSubsetBase ? csr_illegal_addr :
                                                                          wsr_illegal_addr;

  assign ispr_err = ispr_illegal_addr & insn_valid_i & (insn_dec_shared_i.ispr_rd_insn |
                                                        insn_dec_shared_i.ispr_wr_insn |
                                                        insn_dec_shared_i.ispr_rs_insn);

  assign ispr_wr_insn = insn_dec_shared_i.ispr_wr_insn | insn_dec_shared_i.ispr_rs_insn;
  assign ispr_rd_insn = insn_dec_shared_i.ispr_rd_insn | insn_dec_shared_i.ispr_rs_insn;

  assign ispr_flags_wr_o = insn_dec_shared_i.ispr_flags_wr;

  // Write to RND_PREFETCH must not produce ISR write
  assign ispr_wr_base_insn =
    ispr_wr_insn & (insn_dec_shared_i.subset == InsnSubsetBase) & (csr_addr != CsrRndPrefetch);

  assign ispr_wr_bignum_insn = ispr_wr_insn & (insn_dec_shared_i.subset == InsnSubsetBignum);
  assign ispr_rd_bignum_insn = ispr_rd_insn & (insn_dec_shared_i.subset == InsnSubsetBignum);

  assign ispr_addr_o         = insn_dec_shared_i.subset == InsnSubsetBase ? ispr_addr_base :
                                                                            ispr_addr_bignum;
  assign ispr_base_wdata_o   = csr_wdata;
  assign ispr_base_wr_en_o   = {BaseWordsPerWLEN{ispr_wr_base_insn & insn_valid_i}} &
                               ispr_word_sel_base;

  for (genvar i_word = 0; i_word < BaseWordsPerWLEN; i_word++) begin : g_ispr_bignum_wdata_enc
    prim_secded_inv_39_32_enc i_secded_enc (
      .data_i(wsr_wdata[i_word*32+:32]),
      .data_o(ispr_bignum_wdata_intg_o[i_word*39+:39])
    );
  end
  assign ispr_bignum_wr_en_o = ispr_wr_bignum_insn & insn_valid_i;

  assign ispr_wr_commit_o = ispr_wr_insn & insn_executing;
  assign ispr_rd_en_o     = ispr_rd_insn & insn_valid_i &
    ~((insn_dec_shared_i.subset == InsnSubsetBase) & (csr_addr == CsrRndPrefetch));

  // For BN.SID the LSU address is computed in the first cycle by the base ALU. The store request
  // itself occurs in the second cycle when the store data is available (from the indirect register
  // read). The calculated address is saved in a flop here so it's available for use in the second
  // cycle.
  assign lsu_addr_saved_d = alu_base_operation_result_i[DmemAddrWidth-1:0];
  always_ff @(posedge clk_i) begin
    lsu_addr_saved_q <= lsu_addr_saved_d;
  end

  //assign expected_lsu_addr_en_predec = insn_valid & insn_dec_shared_i.ld_insn

  // lsu_load_req_raw/lsu_store_req_raw indicate an instruction wishes to perform a store or a load.
  // lsu_load_req_o/lsu_store_req_o factor in whether an instruction is actually executing (it may
  // be suppressed due an error) and command the load or store to happen when asserted.
  assign lsu_load_req_raw = insn_valid_i & insn_dec_shared_i.ld_insn & (state_q == AccStateRun);
  assign lsu_load_req_o   = insn_executing & lsu_load_req_raw;

  assign lsu_store_req_raw = insn_valid_i & insn_dec_shared_i.st_insn & ~rf_indirect_stall;
  assign lsu_store_req_o   = insn_executing & lsu_store_req_raw;

  assign lsu_req_subset_o = insn_dec_shared_i.subset;

  // To simplify blanking logic all two cycle memory operations (BN.LID, BN.SID, LW) present the
  // calculated address in their first cycle and the saved address in the second cycle. This results
  // in lsu_addr_o remaining stable for the entire instruction. Only SW is a single cycle
  // instruction so it only presents the calculated address. The stability property is checked by an
  // assertion.
  assign lsu_addr_saved_sel =
    insn_valid_i & (((insn_dec_shared_i.subset == InsnSubsetBignum) &
                      insn_dec_shared_i.st_insn & insn_dec_bignum_i.rf_b_indirect
                    ) || insn_dec_shared_i.ld_insn ? ~stall : 1'b0);

  assign lsu_addr = lsu_addr_saved_sel ? lsu_addr_saved_q                                :
                                         alu_base_operation_result_i[DmemAddrWidth-1:0];

  // SEC_CM: CTRL.REDUN
  assign expected_lsu_addr_en =
    insn_valid_i & (insn_dec_shared_i.ld_insn | insn_dec_shared_i.st_insn);

  assign lsu_predec_error = expected_lsu_addr_en != lsu_addr_en_predec_i;

  assign expected_call_stack_push =
    insn_valid_i & insn_dec_base_i.rf_we & rf_base_wr_addr_o == 5'd1;

  assign expected_call_stack_pop = insn_valid_i &
                                   ((insn_dec_base_i.rf_ren_a & rf_base_rd_addr_a_o == 5'd1) |
                                    (insn_dec_base_i.rf_ren_b & rf_base_rd_addr_b_o == 5'd1));

  // Check branch target against the precalculated target from pre-decode. Pre-decode cannot
  // calculate the jump target of a JALR as it requires a register read so this is excluded from the
  // check (by looking at the ALU op a selection).
  assign branch_target_predec_error =
    insn_dec_shared_i.branch_insn                                            &
    insn_dec_shared_i.jump_insn & insn_dec_base_i.op_a_sel != OpASelRegister &
    (ctrl_flow_target_predec_i != branch_target);

  assign ctrl_predec_error =
    |{ctrl_flow_predec_i.jump_insn       != (insn_dec_shared_i.jump_insn   & insn_valid_i),
      ctrl_flow_predec_i.loop_insn       != (insn_dec_shared_i.loop_insn   & insn_valid_i),
      ctrl_flow_predec_i.branch_insn     != (insn_dec_shared_i.branch_insn & insn_valid_i),
      ctrl_flow_predec_i.sel_insn        != (insn_dec_bignum_i.sel_insn    & insn_valid_i),
      ctrl_flow_predec_i.call_stack_push != expected_call_stack_push,
      ctrl_flow_predec_i.call_stack_pop  != expected_call_stack_pop,
      branch_target_predec_error,
      loop_predec_err};

  assign predec_error_o = lsu_predec_error | ctrl_predec_error;

  // SEC_CM: DATA_REG_SW.SCA
  prim_blanker #(.Width(DmemAddrWidth)) u_lsu_addr_blanker (
    .in_i (lsu_addr),
    .en_i (lsu_addr_en_predec_i),
    .out_o(lsu_addr_blanked)
  );

  // Check stability property described above (see the lsu_addr_saved_sel signal) holds.
  `ASSERT(LsuAddrBlankedStable_A, insn_valid_i & stall & ~err |=> $stable(lsu_addr_blanked))

  assign lsu_addr_o = lsu_addr_blanked;

  assign lsu_base_wdata_o   = rf_base_rd_data_b_intg_i;
  assign lsu_bignum_wdata_o = rf_bignum_rd_data_b_intg_i;

  assign dmem_addr_unaligned_bignum =
      (lsu_req_subset_o == InsnSubsetBignum) & (|lsu_addr_o[$clog2(WLEN/8)-1:0]);
  assign dmem_addr_unaligned_base   =
      (lsu_req_subset_o == InsnSubsetBase)   & (|lsu_addr_o[1:0]);
  assign dmem_addr_overflow         = |alu_base_operation_result_i[31:DmemAddrWidth];

  // A dmem address is checked the cycle it is available. For bignum stores this is the first cycle
  // where the base register file read occurs, with the store request occurring the following cycle.
  // For all other loads and stores the dmem address is available the same cycle as the request.
  assign dmem_addr_err_check =
    (lsu_req_subset_o == InsnSubsetBignum) &
    insn_dec_shared_i.st_insn              &
    insn_dec_bignum_i.rf_b_indirect         ? rf_indirect_stall :
                                              lsu_load_req_raw | lsu_store_req_raw;

  assign dmem_addr_err =
      insn_valid_i & dmem_addr_err_check & (dmem_addr_overflow         |
                                            dmem_addr_unaligned_bignum |
                                            dmem_addr_unaligned_base);

  generate
    if (AccPQCEn) begin : gen_kmac_raw
      assign gen_kmac_nets.kmac_digest_req_raw     = insn_valid_i & ispr_rd_insn &
                                                     ((ispr_addr_o == IsprKmacDigest0)
                                                     |(ispr_addr_o == IsprKmacDigest1));
      assign gen_kmac_nets.kmac_msg0_write_req_raw = insn_valid_i & ispr_wr_insn &
                                                     (ispr_addr_o == IsprKmacMsg0);
      assign gen_kmac_nets.kmac_msg1_write_req_raw = insn_valid_i & ispr_wr_insn &
                                                     (ispr_addr_o == IsprKmacMsg1);
      assign gen_kmac_nets.kmac_msg_partial_raw    = insn_valid_i & ispr_wr_insn &
                                                     (ispr_addr_o == IsprKmacPartialW);
    end
  endgenerate

  assign rnd_req_raw = insn_valid_i & ispr_rd_insn & (ispr_addr_o == IsprRnd);
  // Don't factor rnd_rep/fips_err_i into rnd_req_o. This would lead to a combo loop.
  assign rnd_req_o = rnd_req_raw & insn_valid_i & ~(software_err | fatal_err);

  assign rnd_prefetch_req_o = insn_executing & ispr_wr_insn &
      (insn_dec_shared_i.subset == InsnSubsetBase) & (csr_addr == CsrRndPrefetch);
endmodule
