-- #################################################################################################
-- # << NEORV32 CPU - Central Operation Control Unit >>                                            #
-- # ********************************************************************************************* #
-- # CPU operations are controlled by several "engines" (modules). These engines operate in        #
-- # parallel to implement a tiny 2-stage pipeline:                                                #
-- #  + Fetch engine:    Fetches 32-bit chunks of instruction words (1st pipeline stage)           #
-- #  + Issue engine:    Decodes compressed instructions, aligns and queues instruction words      #
-- #  + Execute engine:  Multi-cycle execution of instructions (2nd pipeline stage)                #
-- #  + Trap controller: Handles interrupts and exceptions                                         #
-- #  + CSR module:      Read/write access to control and status registers                         #
-- #  + CPU counters:    Base and HPM counters                                                     #
-- #  + Debug module:    CPU debug mode handling (on-chip debugger)                                #
-- #  + Trigger module:  Hardware-assisted breakpoints (on-chip debugger)                          #
-- # ********************************************************************************************* #
-- # BSD 3-Clause License                                                                          #
-- #                                                                                               #
-- # The NEORV32 RISC-V Processor, https://github.com/stnolting/neorv32                            #
-- # Copyright (c) 2024, Stephan Nolting. All rights reserved.                                     #
-- #                                                                                               #
-- # Redistribution and use in source and binary forms, with or without modification, are          #
-- # permitted provided that the following conditions are met:                                     #
-- #                                                                                               #
-- # 1. Redistributions of source code must retain the above copyright notice, this list of        #
-- #    conditions and the following disclaimer.                                                   #
-- #                                                                                               #
-- # 2. Redistributions in binary form must reproduce the above copyright notice, this list of     #
-- #    conditions and the following disclaimer in the documentation and/or other materials        #
-- #    provided with the distribution.                                                            #
-- #                                                                                               #
-- # 3. Neither the name of the copyright holder nor the names of its contributors may be used to  #
-- #    endorse or promote products derived from this software without specific prior written      #
-- #    permission.                                                                                #
-- #                                                                                               #
-- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS   #
-- # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF               #
-- # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE    #
-- # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,     #
-- # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE #
-- # GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED    #
-- # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING     #
-- # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED  #
-- # OF THE POSSIBILITY OF SUCH DAMAGE.                                                            #
-- #################################################################################################

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

library neorv32;
use neorv32.neorv32_package.all;

entity neorv32_cpu_control is
  generic (
    -- General --
    HART_ID                    : std_ulogic_vector(31 downto 0); -- hardware thread ID
    VENDOR_ID                  : std_ulogic_vector(31 downto 0); -- vendor's JEDEC ID
    CPU_BOOT_ADDR              : std_ulogic_vector(31 downto 0); -- cpu boot address
    CPU_DEBUG_PARK_ADDR        : std_ulogic_vector(31 downto 0); -- cpu debug mode parking loop entry address, 4-byte aligned
    CPU_DEBUG_EXC_ADDR         : std_ulogic_vector(31 downto 0); -- cpu debug mode exception entry address, 4-byte aligned
    -- RISC-V CPU Extensions --
    CPU_EXTENSION_RISCV_A      : boolean; -- implement atomic memory operations extension?
    CPU_EXTENSION_RISCV_B      : boolean; -- implement bit-manipulation extension?
    CPU_EXTENSION_RISCV_C      : boolean; -- implement compressed extension?
    CPU_EXTENSION_RISCV_E      : boolean; -- implement embedded RF extension?
    CPU_EXTENSION_RISCV_M      : boolean; -- implement mul/div extension?
    CPU_EXTENSION_RISCV_U      : boolean; -- implement user mode extension?
    CPU_EXTENSION_RISCV_Zfinx  : boolean; -- implement 32-bit floating-point extension (using INT regs)
    CPU_EXTENSION_RISCV_Zicntr : boolean; -- implement base counters?
    CPU_EXTENSION_RISCV_Zicond : boolean; -- implement integer conditional operations?
    CPU_EXTENSION_RISCV_Zihpm  : boolean; -- implement hardware performance monitors?
    CPU_EXTENSION_RISCV_Zmmul  : boolean; -- implement multiply-only M sub-extension?
    CPU_EXTENSION_RISCV_Zxcfu  : boolean; -- implement custom (instr.) functions unit?
    CPU_EXTENSION_RISCV_Sdext  : boolean; -- implement external debug mode extension?
    CPU_EXTENSION_RISCV_Sdtrig : boolean; -- implement trigger module extension?
    CPU_EXTENSION_RISCV_Smpmp  : boolean; -- implement physical memory protection?
    -- Tuning Options --
    FAST_MUL_EN                : boolean; -- use DSPs for M extension's multiplier
    FAST_SHIFT_EN              : boolean; -- use barrel shifter for shift operations
    REGFILE_HW_RST             : boolean; -- implement full hardware reset for register file
    -- Hardware Performance Monitors (HPM) --
    HPM_NUM_CNTS               : natural range 0 to 13; -- number of implemented HPM counters (0..13)
    HPM_CNT_WIDTH              : natural range 0 to 64  -- total size of HPM counters (0..64)
  );
  port (
    -- global control --
    clk_i         : in  std_ulogic; -- global clock, rising edge
    clk_aux_i     : in  std_ulogic; -- always-on clock, rising edge
    rstn_i        : in  std_ulogic; -- global reset, low-active, async
    ctrl_o        : out ctrl_bus_t; -- main control bus
    -- instruction fetch interface --
    i_pmp_fault_i : in  std_ulogic; -- instruction fetch pmp fault
    bus_req_o     : out bus_req_t;  -- request
    bus_rsp_i     : in  bus_rsp_t;  -- response
    -- data path interface --
    alu_cp_done_i : in  std_ulogic; -- ALU iterative operation done
    cmp_i         : in  std_ulogic_vector(1 downto 0); -- comparator status
    alu_add_i     : in  std_ulogic_vector(XLEN-1 downto 0); -- ALU address result
    rs1_i         : in  std_ulogic_vector(XLEN-1 downto 0); -- rf source 1
    imm_o         : out std_ulogic_vector(XLEN-1 downto 0); -- immediate
    fetch_pc_o    : out std_ulogic_vector(XLEN-1 downto 0); -- instruction fetch address
    curr_pc_o     : out std_ulogic_vector(XLEN-1 downto 0); -- current PC (corresponding to current instruction)
    link_pc_o     : out std_ulogic_vector(XLEN-1 downto 0); -- link PC (return address)
    csr_rdata_o   : out std_ulogic_vector(XLEN-1 downto 0); -- CSR read data
    -- external CSR interface --
    xcsr_we_o     : out std_ulogic; -- global write enable
    xcsr_addr_o   : out std_ulogic_vector(11 downto 0); -- address
    xcsr_wdata_o  : out std_ulogic_vector(XLEN-1 downto 0); -- write data
    xcsr_rdata_i  : in  std_ulogic_vector(XLEN-1 downto 0); -- read data
    -- interrupts --
    db_halt_req_i : in  std_ulogic; -- debug mode (halt) request
    msi_i         : in  std_ulogic; -- machine software interrupt
    mei_i         : in  std_ulogic; -- machine external interrupt
    mti_i         : in  std_ulogic; -- machine timer interrupt
    firq_i        : in  std_ulogic_vector(15 downto 0); -- fast interrupts
    -- data access interface --
    lsu_wait_i    : in  std_ulogic; -- wait for data bus
    mar_i         : in  std_ulogic_vector(XLEN-1 downto 0); -- memory address register
    ma_load_i     : in  std_ulogic; -- misaligned load data address
    ma_store_i    : in  std_ulogic; -- misaligned store data address
    be_load_i     : in  std_ulogic; -- bus error on load data access
    be_store_i    : in  std_ulogic  -- bus error on store data access
  );
end neorv32_cpu_control;

architecture neorv32_cpu_control_rtl of neorv32_cpu_control is

  -- HPM counter auto-configuration --
  constant hpm_num_c          : natural := cond_sel_natural_f(CPU_EXTENSION_RISCV_Zihpm, HPM_NUM_CNTS, 0);
  constant hpm_cnt_lo_width_c : natural := cond_sel_natural_f(boolean(HPM_CNT_WIDTH < 32), HPM_CNT_WIDTH, 32); -- width low word
  constant hpm_cnt_hi_width_c : natural := natural(cond_sel_int_f(boolean(HPM_CNT_WIDTH > 32), HPM_CNT_WIDTH-32, 0)); -- width high word

  -- instruction fetch engine --
  type fetch_engine_state_t is (IF_RESTART, IF_REQUEST, IF_PENDING);
  type fetch_engine_t is record
    state   : fetch_engine_state_t;
    restart : std_ulogic; -- buffered restart request (after branch)
    pc      : std_ulogic_vector(XLEN-1 downto 0);
    reset   : std_ulogic; -- restart request (after branch)
    resp    : std_ulogic; -- bus response
    priv    : std_ulogic; -- fetch privilege level
  end record;
  signal fetch_engine : fetch_engine_t;

  -- instruction prefetch buffer (FIFO) interface --
  type ipb_data_t is array (0 to 1) of std_ulogic_vector(16 downto 0); -- bus_error & 16-bit instruction
  type ipb_t is record
    wdata, rdata : ipb_data_t;
    we,    re    : std_ulogic_vector(1 downto 0);
    free,  avail : std_ulogic_vector(1 downto 0);
  end record;
  signal ipb : ipb_t;

  -- instruction issue engine --
  type issue_engine_t is record
    align     : std_ulogic;
    align_set : std_ulogic;
    align_clr : std_ulogic;
    ci_i16    : std_ulogic_vector(15 downto 0);
    ci_i32    : std_ulogic_vector(31 downto 0);
    data      : std_ulogic_vector((2+32)-1 downto 0); -- is_compressed & bus_error & 32-bit instruction
    valid     : std_ulogic_vector(1 downto 0); -- data word is valid
    ack       : std_ulogic;
  end record;
  signal issue_engine : issue_engine_t;

  -- instruction decoding helper logic --
  type decode_aux_t is record
    opcode    : std_ulogic_vector(6 downto 0);
    is_a_lr   : std_ulogic;
    is_a_sc   : std_ulogic;
    is_f_op   : std_ulogic;
    is_m_mul  : std_ulogic;
    is_m_div  : std_ulogic;
    is_b_imm  : std_ulogic;
    is_b_reg  : std_ulogic;
    is_zicond : std_ulogic;
    rs1_zero  : std_ulogic;
    rd_zero   : std_ulogic;
  end record;
  signal decode_aux : decode_aux_t;

  -- instruction execution engine --
  -- make sure reset state is the first item in the list (discussion #415)
  type execute_engine_state_t is (DISPATCH, TRAP_ENTER, TRAP_EXIT, RESTART, FENCE, SLEEP,
                                  EXECUTE, ALU_WAIT, BRANCH, BRANCHED, SYSTEM, MEM_REQ, MEM_WAIT);
  type execute_engine_t is record
    state        : execute_engine_state_t;
    state_nxt    : execute_engine_state_t;
    ir           : std_ulogic_vector(31 downto 0);
    ir_nxt       : std_ulogic_vector(31 downto 0);
    is_ci        : std_ulogic; -- current instruction is de-compressed instruction
    is_ci_nxt    : std_ulogic;
    branch_taken : std_ulogic; -- branch condition fulfilled
    pc           : std_ulogic_vector(XLEN-1 downto 0); -- actual PC, corresponding to current executed instruction
    pc_we        : std_ulogic; -- PC update enabled
    next_pc      : std_ulogic_vector(XLEN-1 downto 0); -- next PC, corresponding to next instruction to be executed
    next_pc_inc  : std_ulogic_vector(XLEN-1 downto 0); -- increment to get next PC
    link_pc      : std_ulogic_vector(XLEN-1 downto 0); -- next PC for linking (return address)
  end record;
  signal execute_engine : execute_engine_t;

  -- execution monitor --
  type monitor_t is record
    cnt     : std_ulogic_vector(monitor_mc_tmo_c downto 0);
    cnt_add : std_ulogic_vector(monitor_mc_tmo_c downto 0);
    exc     : std_ulogic;
  end record;
  signal monitor : monitor_t;

  -- CPU sleep-mode --
  signal sleep_mode : std_ulogic;

  -- trap controller --
  type trap_ctrl_t is record
    exc_buf     : std_ulogic_vector(exc_width_c-1 downto 0); -- synchronous exception buffer (one bit per exception)
    exc_fire    : std_ulogic; -- set if there is a valid source in the exception buffer
    irq_pnd     : std_ulogic_vector(irq_width_c-1 downto 0); -- pending interrupt
    irq_buf     : std_ulogic_vector(irq_width_c-1 downto 0); -- asynchronous exception/interrupt buffer (one bit per interrupt source)
    irq_fire    : std_ulogic; -- set if an interrupt is actually kicking in
    cause       : std_ulogic_vector(6 downto 0); -- trap ID for mcause CSR & debug-mode entry identifier
    epc         : std_ulogic_vector(XLEN-1 downto 0); -- exception program counter
    --
    env_pending : std_ulogic; -- start of trap environment if pending
    env_enter   : std_ulogic; -- enter trap environment
    env_exit    : std_ulogic; -- leave trap environment
    wakeup      : std_ulogic; -- wakeup from sleep due to an enabled pending IRQ
    --
    instr_be    : std_ulogic; -- instruction fetch bus error
    instr_ma    : std_ulogic; -- instruction fetch misaligned address
    instr_il    : std_ulogic; -- illegal instruction
    ecall       : std_ulogic; -- ecall instruction
    ebreak      : std_ulogic; -- ebreak instruction
    hwtrig      : std_ulogic; -- hardware trigger module
  end record;
  signal trap_ctrl : trap_ctrl_t;

  -- CPU main control bus --
  signal ctrl, ctrl_nxt : ctrl_bus_t;

  -- control and status registers (CSRs) --
  type csr_t is record
    addr           : std_ulogic_vector(11 downto 0); -- csr address
    raddr          : std_ulogic_vector(11 downto 0); -- simplified csr read address
    we, we_nxt     : std_ulogic; -- csr write enable
    re, re_nxt     : std_ulogic; -- csr read enable
    wdata, rdata   : std_ulogic_vector(XLEN-1 downto 0); -- csr write/read data
    --
    mstatus_mie    : std_ulogic; -- machine-mode IRQ enable
    mstatus_mpie   : std_ulogic; -- previous machine-mode IRQ enable
    mstatus_mpp    : std_ulogic; -- machine previous privilege mode
    mstatus_mprv   : std_ulogic; -- effective privilege level for load/stores
    mstatus_tw     : std_ulogic; -- do not allow user mode to execute WFI instruction when set
    --
    mie_msi        : std_ulogic; -- machine software interrupt enable
    mie_mei        : std_ulogic; -- machine external interrupt enable
    mie_mti        : std_ulogic; -- machine timer interrupt enable
    mie_firq       : std_ulogic_vector(15 downto 0); -- fast interrupt enable
    mip_firq_wdata : std_ulogic_vector(15 downto 0); -- fast interrupt pending write data
    mip_firq_we    : std_ulogic; -- fast interrupt pending write enable
    --
    privilege      : std_ulogic; -- current privilege mode
    privilege_eff  : std_ulogic; -- current *effective* privilege mode
    --
    mepc           : std_ulogic_vector(XLEN-1 downto 0); -- machine exception PC
    mcause         : std_ulogic_vector(5 downto 0); -- machine trap cause
    mtvec          : std_ulogic_vector(XLEN-1 downto 0); -- machine trap-handler base address
    mtval          : std_ulogic_vector(XLEN-1 downto 0); -- machine bad address or instruction
    mtinst         : std_ulogic_vector(XLEN-1 downto 0); -- machine trap instruction
    mscratch       : std_ulogic_vector(XLEN-1 downto 0); -- machine scratch register
    mcounteren     : std_ulogic; -- machine counter access enable (from user-mode) for ALL counters
    mcountinhibit  : std_ulogic_vector(15 downto 0); -- inhibit counter auto-increment
    --
    dcsr_ebreakm   : std_ulogic; -- behavior of ebreak instruction in m-mode
    dcsr_ebreaku   : std_ulogic; -- behavior of ebreak instruction in u-mode
    dcsr_step      : std_ulogic; -- single-step mode
    dcsr_prv       : std_ulogic; -- current privilege level when entering debug mode
    dcsr_cause     : std_ulogic_vector(2 downto 0); -- why was debug mode entered
    dcsr_rd        : std_ulogic_vector(XLEN-1 downto 0); -- debug mode control and status register
    dpc            : std_ulogic_vector(XLEN-1 downto 0); -- mode program counter
    dscratch0      : std_ulogic_vector(XLEN-1 downto 0); -- debug mode scratch register 0
    --
    tdata1_hit_clr : std_ulogic; -- set to manually clear mcontrol6.hit0
    tdata1_execute : std_ulogic; -- enable instruction address match trigger
    tdata1_action  : std_ulogic; -- enter debug mode / ebreak exception when trigger fires
    tdata1_dmode   : std_ulogic; -- set to ignore tdata* CSR access from machine-mode
    tdata1_rd      : std_ulogic_vector(XLEN-1 downto 0); -- trigger register read-back
    tdata2         : std_ulogic_vector(XLEN-1 downto 0); -- address-match register
  end record;
  signal csr : csr_t;

  -- hpm event configuration CSRs --
  type hpmevent_cfg_t is array (3 to 15) of std_ulogic_vector(hpmcnt_event_size_c-1 downto 0);
  type hpmevent_rd_t  is array (3 to 15) of std_ulogic_vector(XLEN-1 downto 0);
  signal hpmevent_cfg : hpmevent_cfg_t;
  signal hpmevent_rd  : hpmevent_rd_t;
  signal hpmevent_we  : std_ulogic_vector(15 downto 0);

  -- counter CSRs --
  type cnt_dat_t is array (0 to 2+hpm_num_c) of std_ulogic_vector(XLEN-1 downto 0);
  type cnt_nxt_t is array (0 to 2+hpm_num_c) of std_ulogic_vector(XLEN downto 0);
  type cnt_ovf_t is array (0 to 2+hpm_num_c) of std_ulogic_vector(0 downto 0);
  type cnt_t is record
    we_lo : std_ulogic_vector(15 downto 0);
    we_hi : std_ulogic_vector(15 downto 0);
    inc   : std_ulogic_vector(15 downto 0);
    lo    : cnt_dat_t; -- counter word low
    hi    : cnt_dat_t; -- counter word high
    nxt   : cnt_nxt_t; -- increment, including carry bit
    ovf   : cnt_ovf_t; -- counter low-to-high-word overflow
  end record;
  signal cnt       : cnt_t;
  signal cnt_lo_rd : cnt_dat_t;
  signal cnt_hi_rd : cnt_dat_t;

  -- counter events --
  signal cnt_event : std_ulogic_vector(hpmcnt_event_size_c-1 downto 0);

  -- debug mode controller --
  type debug_ctrl_t is record
    running    : std_ulogic; -- CPU is in debug mode
    trig_hw    : std_ulogic; -- hardware trigger
    trig_break : std_ulogic; -- ebreak instruction trigger
    trig_halt  : std_ulogic; -- external request trigger
    trig_step  : std_ulogic; -- single-stepping mode trigger
  end record;
  signal debug_ctrl : debug_ctrl_t;

  -- illegal instruction check --
  signal illegal_cmd : std_ulogic;

  -- CSR access/privilege/read-write check --
  signal csr_reg_valid  : std_ulogic; -- CSR implemented at all
  signal csr_rw_valid   : std_ulogic; -- valid r/w access rights
  signal csr_priv_valid : std_ulogic; -- valid access privilege

  -- hardware trigger module --
  signal hw_trigger_match, hw_trigger_fired : std_ulogic;

  -- CSR read-back helpers --
  signal csr_rdata, xcsr_rdata : std_ulogic_vector(XLEN-1 downto 0);

begin

-- ****************************************************************************************************************************
-- Instruction Fetch (always fetch 32-bit-aligned 32-bit chunks of data)
-- ****************************************************************************************************************************

  -- Fetch Engine FSM -----------------------------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  fetch_engine_fsm: process(rstn_i, clk_i)
  begin
    if (rstn_i = '0') then
      fetch_engine.state   <= IF_RESTART;
      fetch_engine.restart <= '1'; -- set to reset IPB
      fetch_engine.pc      <= CPU_BOOT_ADDR(XLEN-1 downto 2) & "00"; -- 32-bit aligned boot address
      fetch_engine.priv    <= priv_mode_m_c; -- start in machine mode
    elsif rising_edge(clk_i) then
      -- restart request --
      if (fetch_engine.state = IF_RESTART) then -- restart done
        fetch_engine.restart <= '0';
      else -- buffer request
        fetch_engine.restart <= fetch_engine.restart or fetch_engine.reset;
      end if;

      -- fsm --
      case fetch_engine.state is

        when IF_REQUEST => -- request next 32-bit-aligned instruction word
        -- ------------------------------------------------------------
          if (ipb.free = "11") then -- wait for free IPB space
            fetch_engine.state <= IF_PENDING;
          elsif (fetch_engine.restart = '1') or (fetch_engine.reset = '1') then -- restart request due to branch
            fetch_engine.state <= IF_RESTART;
          end if;

        when IF_PENDING => -- wait for bus response and write instruction data to prefetch buffer
        -- ------------------------------------------------------------
          if (fetch_engine.resp = '1') then -- wait for bus response
            fetch_engine.pc    <= std_ulogic_vector(unsigned(fetch_engine.pc) + 4); -- next word
            fetch_engine.pc(1) <= '0'; -- (re-)align to 32-bit
            if (fetch_engine.restart = '1') or (fetch_engine.reset = '1') then -- restart request due to branch
              fetch_engine.state <= IF_RESTART;
            else -- request next linear instruction word
              fetch_engine.state <= IF_REQUEST;
            end if;
          end if;

        when others => -- IF_RESTART: set new start address
        -- ------------------------------------------------------------
          fetch_engine.pc    <= execute_engine.next_pc(XLEN-1 downto 1) & '0'; -- initialize from PC incl. 16-bit-alignment bit
          fetch_engine.priv  <= csr.privilege_eff; -- set new privilege level
          fetch_engine.state <= IF_REQUEST;

      end case;
    end if;
  end process fetch_engine_fsm;

  -- PC output for instruction fetch --
  bus_req_o.addr <= fetch_engine.pc(XLEN-1 downto 2) & "00"; -- word aligned
  fetch_pc_o     <= fetch_engine.pc(XLEN-1 downto 2) & "00"; -- word aligned

  -- instruction fetch (read) request if IPB not full --
  bus_req_o.stb <= '1' when (fetch_engine.state = IF_REQUEST) and (ipb.free = "11") else '0';

  -- instruction bus response --
  fetch_engine.resp <= bus_rsp_i.ack or bus_rsp_i.err;

  -- IPB instruction data and status --
  ipb.wdata(0) <= (bus_rsp_i.err or i_pmp_fault_i) & bus_rsp_i.data(15 downto 00);
  ipb.wdata(1) <= (bus_rsp_i.err or i_pmp_fault_i) & bus_rsp_i.data(31 downto 16);

  -- IPB write enable --
  ipb.we(0) <= '1' when (fetch_engine.state = IF_PENDING) and (fetch_engine.resp = '1') and
                        ((fetch_engine.pc(1) = '0') or (CPU_EXTENSION_RISCV_C = false)) else '0';
  ipb.we(1) <= '1' when (fetch_engine.state = IF_PENDING) and (fetch_engine.resp = '1') else '0';

  -- bus access type --
  bus_req_o.priv  <= fetch_engine.priv; -- current effective privilege level
  bus_req_o.data  <= (others => '0'); -- read-only
  bus_req_o.ben   <= (others => '0'); -- read-only
  bus_req_o.rw    <= '0'; -- read-only
  bus_req_o.src   <= '1'; -- source = instruction fetch
  bus_req_o.rvso  <= '0'; -- cannot be a reservation set operation
  bus_req_o.fence <= ctrl.lsu_fence; -- fence(.i) operation, valid without STB being set


  -- Instruction Prefetch Buffer (FIFO) -----------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  prefetch_buffer:
  for i in 0 to 1 generate -- low half-word + high half-word (incl. status bits)
    prefetch_buffer_inst: entity neorv32.neorv32_fifo
    generic map (
      FIFO_DEPTH => 2,                   -- number of fifo entries; has to be a power of two, min 2
      FIFO_WIDTH => ipb.wdata(i)'length, -- size of data elements in fifo
      FIFO_RSYNC => false,               -- we NEED to read data asynchronously
      FIFO_SAFE  => false,               -- no safe access required (ensured by FIFO-external logic)
      FULL_RESET => false                -- no HW reset, try to infer BRAM
    )
    port map (
      -- control --
      clk_i   => clk_i,                -- clock, rising edge
      rstn_i  => rstn_i,               -- async reset, low-active
      clear_i => fetch_engine.restart, -- sync reset, high-active
      half_o  => open,                 -- at least half full
      -- write port --
      wdata_i => ipb.wdata(i),         -- write data
      we_i    => ipb.we(i),            -- write enable
      free_o  => ipb.free(i),          -- at least one entry is free when set
      -- read port --
      re_i    => ipb.re(i),            -- read enable
      rdata_o => ipb.rdata(i),         -- read data
      avail_o => ipb.avail(i)          -- data available when set
    );
  end generate;


-- ****************************************************************************************************************************
-- Instruction Issue (decompress 16-bit instructions and assemble a 32-bit instruction word)
-- ****************************************************************************************************************************

  -- Compressed Instructions Decoder --------------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  neorv32_cpu_decompressor_inst_true:
  if CPU_EXTENSION_RISCV_C generate
    neorv32_cpu_decompressor_inst: entity neorv32.neorv32_cpu_decompressor
    port map (
      ci_instr16_i => issue_engine.ci_i16, -- compressed instruction
      ci_instr32_o => issue_engine.ci_i32  -- decompressed instruction
    );
  end generate;

  neorv32_cpu_decompressor_inst_false:
  if not CPU_EXTENSION_RISCV_C generate
    issue_engine.ci_i32 <= (others => '0');
  end generate;

  -- 16-bit instructions: half-word select --
  issue_engine.ci_i16 <= ipb.rdata(0)(15 downto 0) when (issue_engine.align = '0') else ipb.rdata(1)(15 downto 0);


  -- Issue Engine FSM (required if C extension is enabled) ----------------------------------
  -- -------------------------------------------------------------------------------------------
  issue_engine_enabled:
  if CPU_EXTENSION_RISCV_C generate

    issue_engine_fsm_sync: process(rstn_i, clk_i)
    begin
      if (rstn_i = '0') then
        issue_engine.align <= '0'; -- start aligned after reset
      elsif rising_edge(clk_i) then
        if (fetch_engine.restart = '1') then
          issue_engine.align <= execute_engine.next_pc(1); -- branch to unaligned address?
        elsif (issue_engine.ack = '1') then
          issue_engine.align <= (issue_engine.align and (not issue_engine.align_clr)) or issue_engine.align_set; -- "RS" flip-flop
        end if;
      end if;
    end process issue_engine_fsm_sync;

    issue_engine_fsm_comb: process(issue_engine, ipb)
    begin
      -- defaults --
      issue_engine.align_set <= '0';
      issue_engine.align_clr <= '0';
      issue_engine.valid     <= "00";
      -- start with LOW half-word --
      if (issue_engine.align = '0')  then
        if (ipb.rdata(0)(1 downto 0) /= "11") then -- compressed, use IPB(0) entry
          issue_engine.align_set <= ipb.avail(0); -- start of next instruction word is NOT 32-bit-aligned
          issue_engine.valid(0)  <= ipb.avail(0);
          issue_engine.data      <= '1' & ipb.rdata(0)(16) & issue_engine.ci_i32;
        else -- aligned uncompressed; use IPB(0) status flags only
          issue_engine.valid <= (others => (ipb.avail(0) and ipb.avail(1)));
          issue_engine.data  <= '0' & ipb.rdata(0)(16) & ipb.rdata(1)(15 downto 0) & ipb.rdata(0)(15 downto 0);
        end if;
      -- start with HIGH half-word --
      else
        if (ipb.rdata(1)(1 downto 0) /= "11") then -- compressed, use IPB(1) entry
          issue_engine.align_clr <= ipb.avail(1); -- start of next instruction word is 32-bit-aligned again
          issue_engine.valid(1)  <= ipb.avail(1);
          issue_engine.data      <= '1' & ipb.rdata(1)(16) & issue_engine.ci_i32;
        else -- unaligned uncompressed; use IPB(0) status flags only
          issue_engine.valid <= (others => (ipb.avail(0) and ipb.avail(1)));
          issue_engine.data  <= '0' & ipb.rdata(0)(16) & ipb.rdata(0)(15 downto 0) & ipb.rdata(1)(15 downto 0);
        end if;
      end if;
    end process issue_engine_fsm_comb;

  end generate; -- /issue_engine_enabled

  issue_engine_disabled: -- use IPB(0) status flags only
  if not CPU_EXTENSION_RISCV_C generate
    issue_engine.valid <= (others => ipb.avail(0));
    issue_engine.data  <= '0' & ipb.rdata(0)(16) & (ipb.rdata(1)(15 downto 0) & ipb.rdata(0)(15 downto 0));
  end generate; -- /issue_engine_disabled

  -- update IPB FIFOs --
  ipb.re(0) <= issue_engine.valid(0) and issue_engine.ack;
  ipb.re(1) <= issue_engine.valid(1) and issue_engine.ack;


-- ****************************************************************************************************************************
-- Instruction Execution
-- ****************************************************************************************************************************

  -- Immediate Generator --------------------------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  imm_gen: process(rstn_i, clk_i)
  begin
    if (rstn_i = '0') then
      imm_o <= (others => '0');
    elsif rising_edge(clk_i) then
      -- default I-immediate: ALU-immediate, load, jump-and-link with register --
      imm_o(XLEN-1 downto 11) <= (others => execute_engine.ir(31)); -- sign extension
      imm_o(10 downto 01)     <= execute_engine.ir(30 downto 21);
      imm_o(00)               <= execute_engine.ir(20);
      --
      case decode_aux.opcode is
        when opcode_store_c => -- S-immediate: store
          imm_o(XLEN-1 downto 11) <= (others => execute_engine.ir(31)); -- sign extension
          imm_o(10 downto 05)     <= execute_engine.ir(30 downto 25);
          imm_o(04 downto 00)     <= execute_engine.ir(11 downto 07);
        when opcode_branch_c => -- B-immediate: conditional branch
          imm_o(XLEN-1 downto 12) <= (others => execute_engine.ir(31)); -- sign extension
          imm_o(11)               <= execute_engine.ir(07);
          imm_o(10 downto 05)     <= execute_engine.ir(30 downto 25);
          imm_o(04 downto 01)     <= execute_engine.ir(11 downto 08);
          imm_o(00)               <= '0';
        when opcode_lui_c | opcode_auipc_c => -- U-immediate: lui, auipc
          imm_o(XLEN-1 downto 12) <= execute_engine.ir(31 downto 12);
          imm_o(11 downto 00)     <= (others => '0');
        when opcode_jal_c => -- J-immediate: unconditional jump
          imm_o(XLEN-1 downto 20) <= (others => execute_engine.ir(31)); -- sign extension
          imm_o(19 downto 12)     <= execute_engine.ir(19 downto 12);
          imm_o(11)               <= execute_engine.ir(20);
          imm_o(10 downto 01)     <= execute_engine.ir(30 downto 21);
          imm_o(00)               <= '0';
        when opcode_amo_c => -- atomic memory access
          if (CPU_EXTENSION_RISCV_A = true) then
            imm_o <= (others => '0');
          else
            NULL;
          end if;
        when others =>
          NULL;
      end case;
    end if;
  end process imm_gen;


  -- Branch Condition Check -----------------------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  branch_check: process(execute_engine.ir, cmp_i)
  begin
    if (execute_engine.ir(instr_opcode_lsb_c+2) = '0') then -- conditional branch
      if (execute_engine.ir(instr_funct3_msb_c) = '0') then -- beq / bne
        execute_engine.branch_taken <= cmp_i(cmp_equal_c) xor execute_engine.ir(instr_funct3_lsb_c);
      else -- blt(u) / bge(u)
        execute_engine.branch_taken <= cmp_i(cmp_less_c) xor execute_engine.ir(instr_funct3_lsb_c);
      end if;
    else -- unconditional branch
      execute_engine.branch_taken <= '1';
    end if;
  end process branch_check;


  -- Execute Engine FSM Sync ----------------------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  execute_engine_fsm_sync: process(rstn_i, clk_i)
  begin
    if (rstn_i = '0') then
      ctrl                   <= ctrl_bus_zero_c;
      execute_engine.state   <= RESTART;
      execute_engine.ir      <= (others => '0');
      execute_engine.is_ci   <= '0';
      execute_engine.pc      <= CPU_BOOT_ADDR(XLEN-1 downto 2) & "00"; -- 32-bit aligned boot address
      execute_engine.next_pc <= CPU_BOOT_ADDR(XLEN-1 downto 2) & "00"; -- 32-bit aligned boot address
      execute_engine.link_pc <= CPU_BOOT_ADDR(XLEN-1 downto 2) & "00"; -- 32-bit aligned boot address
    elsif rising_edge(clk_i) then
      -- control bus --
      ctrl <= ctrl_nxt;

      -- execute engine arbiter --
      execute_engine.state <= execute_engine.state_nxt;
      execute_engine.ir    <= execute_engine.ir_nxt;
      execute_engine.is_ci <= execute_engine.is_ci_nxt;

      -- current PC: address of instruction being executed --
      if (execute_engine.pc_we = '1') then
        execute_engine.pc <= execute_engine.next_pc(XLEN-1 downto 1) & '0';
      end if;

      -- link PC: return address --
      if (execute_engine.state = BRANCH) then
        execute_engine.link_pc <= execute_engine.next_pc(XLEN-1 downto 1) & '0';
      end if;

      -- next PC: address of next instruction --
      case execute_engine.state is

        when TRAP_ENTER => -- starting trap environment
          if (trap_ctrl.cause(5) = '1') and (CPU_EXTENSION_RISCV_Sdext = true) then -- debug mode (re-)entry
            execute_engine.next_pc <= CPU_DEBUG_PARK_ADDR(XLEN-1 downto 2) & "00"; -- debug mode enter; start at "parking loop" <normal_entry>
          elsif (debug_ctrl.running = '1') and (CPU_EXTENSION_RISCV_Sdext = true) then -- any other trap INSIDE debug mode
            execute_engine.next_pc <= CPU_DEBUG_EXC_ADDR(XLEN-1 downto 2) & "00"; -- debug mode enter: start at "parking loop" <exception_entry>
          else -- normal start of trap
            if (csr.mtvec(1 downto 0) = "01") and (trap_ctrl.cause(6) = '1') then -- vectored mode + interrupt
              execute_engine.next_pc <= csr.mtvec(XLEN-1 downto 7) & trap_ctrl.cause(4 downto 0) & "00"; -- pc = mtvec + 4 * mcause
            else
              execute_engine.next_pc <= csr.mtvec(XLEN-1 downto 2) & "00"; -- pc = mtvec
            end if;
          end if;

        when TRAP_EXIT => -- leaving trap environment
          if (debug_ctrl.running = '1') and (CPU_EXTENSION_RISCV_Sdext = true) then -- debug mode exit
            execute_engine.next_pc <= csr.dpc(XLEN-1 downto 1) & '0';
          else -- normal end of trap
            execute_engine.next_pc <= csr.mepc(XLEN-1 downto 1) & '0';
          end if;

        when BRANCH => -- control flow transfer
          if (trap_ctrl.exc_buf(exc_illegal_c) = '0') and (execute_engine.branch_taken = '1') then -- valid taken branch
            execute_engine.next_pc <= alu_add_i(XLEN-1 downto 1) & '0';
          end if;

        when EXECUTE => -- linear increment
          execute_engine.next_pc <= std_ulogic_vector(unsigned(execute_engine.pc) + unsigned(execute_engine.next_pc_inc));

        when others => -- no update
          NULL;

      end case;
    end if;
  end process execute_engine_fsm_sync;

  -- check if branch destination is misaligned --
  trap_ctrl.instr_ma <= '1' when (execute_engine.state = BRANCH) and (trap_ctrl.exc_buf(exc_illegal_c) = '0') and -- valid branch instruction
                                 (execute_engine.branch_taken = '1') and -- branch is taken
                                 (alu_add_i(1) = '1') and (CPU_EXTENSION_RISCV_C = false) else '0'; -- misaligned destination

  -- PC increment for next LINEAR instruction (+2 for compressed instr., +4 otherwise) --
  execute_engine.next_pc_inc(XLEN-1 downto 4) <= (others => '0');
  execute_engine.next_pc_inc(3 downto 0) <= x"4" when ((execute_engine.is_ci = '0') or (CPU_EXTENSION_RISCV_C = false)) else x"2";

  -- PC output --
  curr_pc_o <= execute_engine.pc(XLEN-1 downto 1) & '0'; -- current PC
  link_pc_o <= execute_engine.link_pc(XLEN-1 downto 1) & '0'; -- return address


  -- Decoding Helper Logic ------------------------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  decode_helper: process(execute_engine)
  begin
    -- defaults --
    decode_aux.is_f_op   <= '0';
    decode_aux.is_a_lr   <= '0';
    decode_aux.is_a_sc   <= '0';
    decode_aux.is_m_mul  <= '0';
    decode_aux.is_m_div  <= '0';
    decode_aux.is_b_imm  <= '0';
    decode_aux.is_b_reg  <= '0';
    decode_aux.is_zicond <= '0';

    -- ATOMIC instructions --
    if (CPU_EXTENSION_RISCV_A = true) and -- implemented at all?
       (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = "010") and
       (execute_engine.ir(instr_funct7_msb_c downto instr_funct7_lsb_c+3) = "0001") then
      decode_aux.is_a_lr <= not execute_engine.ir(instr_funct7_lsb_c+2); -- LR.W
      decode_aux.is_a_sc <=     execute_engine.ir(instr_funct7_lsb_c+2); -- SC.W
    end if;

    -- BITMANIP instruction --
    if (CPU_EXTENSION_RISCV_B = true) then -- implemented at all?
      -- register-immediate operation --
      if ((execute_engine.ir(instr_funct7_msb_c downto instr_funct7_lsb_c) = "0110000") and (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = "001") and (
           (execute_engine.ir(instr_funct12_lsb_c+4 downto instr_funct12_lsb_c) = "00000") or -- CLZ
           (execute_engine.ir(instr_funct12_lsb_c+4 downto instr_funct12_lsb_c) = "00001") or -- CTZ
           (execute_engine.ir(instr_funct12_lsb_c+4 downto instr_funct12_lsb_c) = "00010") or -- CPOP
           (execute_engine.ir(instr_funct12_lsb_c+4 downto instr_funct12_lsb_c) = "00100") or -- SEXT.B
           (execute_engine.ir(instr_funct12_lsb_c+4 downto instr_funct12_lsb_c) = "00101")    -- SEXT.H
          )) or
         ((execute_engine.ir(instr_funct7_msb_c downto instr_funct7_lsb_c) = "0110000") and (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = "101")) or -- RORI
         ((execute_engine.ir(instr_funct7_msb_c downto instr_funct7_lsb_c) = "0010100") and (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = "101") and
                                                                                            (execute_engine.ir(instr_funct12_lsb_c+4 downto instr_funct12_lsb_c) = "00111")) or -- ORCB
         ((execute_engine.ir(instr_funct7_msb_c downto instr_funct7_lsb_c) = "0100100") and (execute_engine.ir(instr_funct3_msb_c-1 downto instr_funct3_lsb_c) = "01")) or -- BCLRI / BEXTI
         ((execute_engine.ir(instr_funct7_msb_c downto instr_funct7_lsb_c) = "0110100") and (execute_engine.ir(instr_funct3_msb_c-1 downto instr_funct3_lsb_c) = "01")) or -- REV8 / BINVI
         ((execute_engine.ir(instr_funct7_msb_c downto instr_funct7_lsb_c) = "0010100") and (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = "001")) then -- BSETI
        decode_aux.is_b_imm <= '1';
      end if;
      -- register-register operation --
      if ((execute_engine.ir(instr_funct7_msb_c downto instr_funct7_lsb_c) = "0110000") and (execute_engine.ir(instr_funct3_msb_c-1 downto instr_funct3_lsb_c) = "01")) or -- ROR / ROL
         ((execute_engine.ir(instr_funct7_msb_c downto instr_funct7_lsb_c) = "0000101") and (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) /= "000")) or -- MIN[U] / MAX[U] / CMUL[H/R]
         ((execute_engine.ir(instr_funct7_msb_c downto instr_funct7_lsb_c) = "0000100") and (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = "100")) or -- ZEXTH
         ((execute_engine.ir(instr_funct7_msb_c downto instr_funct7_lsb_c) = "0100100") and (execute_engine.ir(instr_funct3_msb_c-1 downto instr_funct3_lsb_c) = "01")) or -- BCLR / BEXT
         ((execute_engine.ir(instr_funct7_msb_c downto instr_funct7_lsb_c) = "0110100") and (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = "001")) or -- BINV
         ((execute_engine.ir(instr_funct7_msb_c downto instr_funct7_lsb_c) = "0010100") and (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = "001")) or -- BSET
         ((execute_engine.ir(instr_funct7_msb_c downto instr_funct7_lsb_c) = "0100000") and (
           (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = "111") or -- ANDN
           (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = "110") or -- ORN
           (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = "100")    -- XORN
          )) or
         ((execute_engine.ir(instr_funct7_msb_c downto instr_funct7_lsb_c) = "0010000") and (
           (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = "010") or -- SH1ADD
           (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = "100") or -- SH2ADD
           (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = "110")    -- SH3ADD
          )
         ) then
        decode_aux.is_b_reg <= '1';
      end if;
    end if;

    -- FLOATING-POINT instructions (Zfinx) --
    if (CPU_EXTENSION_RISCV_Zfinx = true) then -- FPU implemented at all?
      if ((execute_engine.ir(instr_funct7_msb_c downto instr_funct7_lsb_c+3) = "0000")) or -- FADD.S / FSUB.S
         ((execute_engine.ir(instr_funct7_msb_c downto instr_funct7_lsb_c+2) = "00010")) or -- FMUL.S
         ((execute_engine.ir(instr_funct7_msb_c downto instr_funct7_lsb_c+2) = "11100") and (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = "001")) or -- FCLASS.S
         ((execute_engine.ir(instr_funct7_msb_c downto instr_funct7_lsb_c+2) = "00100") and (execute_engine.ir(instr_funct3_msb_c) = '0')) or -- FSGNJ[N/X].S
         ((execute_engine.ir(instr_funct7_msb_c downto instr_funct7_lsb_c+2) = "00101") and (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_msb_c-1) = "00")) or -- FMIN.S / FMAX.S
         ((execute_engine.ir(instr_funct7_msb_c downto instr_funct7_lsb_c+2) = "10100") and (execute_engine.ir(instr_funct3_msb_c) = '0')) or -- FEQ.S / FLT.S / FLE.S
         ((execute_engine.ir(instr_funct7_msb_c downto instr_funct7_lsb_c+2) = "11010") and (execute_engine.ir(instr_funct12_lsb_c+4 downto instr_funct12_lsb_c+1) = "0000")) or -- FCVT.S.W*
         ((execute_engine.ir(instr_funct7_msb_c downto instr_funct7_lsb_c+2) = "11000") and (execute_engine.ir(instr_funct12_lsb_c+4 downto instr_funct12_lsb_c+1) = "0000")) then -- FCVT.W*.S
        if (execute_engine.ir(instr_funct7_lsb_c+1 downto instr_funct7_lsb_c) = float_single_c) then -- single-precision operations only
          decode_aux.is_f_op <= '1';
        end if;
      end if;
    end if;

    -- integer MUL (M/Zmmul) / DIV (M) instruction --
    if (execute_engine.ir(instr_funct7_msb_c downto instr_funct7_lsb_c) = "0000001") then
      if ((CPU_EXTENSION_RISCV_M = true) or (CPU_EXTENSION_RISCV_Zmmul = true)) and (execute_engine.ir(instr_funct3_msb_c) = '0') then
        decode_aux.is_m_mul <= '1';
      end if;
      if (CPU_EXTENSION_RISCV_M = true) and (execute_engine.ir(instr_funct3_msb_c) = '1') then
        decode_aux.is_m_div <= '1';
      end if;
    end if;

    -- CONDITIONAL instruction (Zicond) --
    if (CPU_EXTENSION_RISCV_Zicond = true) and (execute_engine.ir(instr_funct7_msb_c downto instr_funct7_lsb_c) = "0000111") and
       (execute_engine.ir(instr_funct3_msb_c) = '1') and (execute_engine.ir(instr_funct3_lsb_c) = '1') then
      decode_aux.is_zicond <= '1';
    end if;
  end process decode_helper;

  -- register/uimm5 checks --
  decode_aux.rs1_zero <= '1' when (execute_engine.ir(instr_rs1_msb_c downto instr_rs1_lsb_c) = "00000") else '0';
  decode_aux.rd_zero  <= '1' when (execute_engine.ir(instr_rd_msb_c  downto instr_rd_lsb_c ) = "00000") else '0';

  -- simplified opcode --
  decode_aux.opcode <= execute_engine.ir(instr_opcode_msb_c downto instr_opcode_lsb_c+2) & "11";


  -- Execute Engine FSM Comb ----------------------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  execute_engine_fsm_comb: process(execute_engine, debug_ctrl, trap_ctrl, hw_trigger_match, decode_aux, issue_engine, csr, alu_cp_done_i, lsu_wait_i)
  begin
    -- arbiter defaults --
    execute_engine.state_nxt <= execute_engine.state;
    execute_engine.ir_nxt    <= execute_engine.ir;
    execute_engine.is_ci_nxt <= execute_engine.is_ci;
    execute_engine.pc_we     <= '0';
    --
    issue_engine.ack         <= '0';
    --
    fetch_engine.reset       <= '0';
    --
    trap_ctrl.env_enter      <= '0';
    trap_ctrl.env_exit       <= '0';
    trap_ctrl.instr_be       <= '0';
    trap_ctrl.ecall          <= '0';
    trap_ctrl.ebreak         <= '0';
    trap_ctrl.hwtrig         <= '0';
    --
    csr.we_nxt               <= '0';
    csr.re_nxt               <= '0';
    --
    ctrl_nxt                 <= ctrl_bus_zero_c; -- all zero/off by default, default ALU operation = ADD, default RF input = ALU

    -- ALU sign control --
    if (execute_engine.ir(instr_opcode_lsb_c+4) = '1') then -- ALU ops
      ctrl_nxt.alu_unsigned <= execute_engine.ir(instr_funct3_lsb_c+0); -- unsigned ALU operation? (SLTIU, SLTU)
    else -- branches
      ctrl_nxt.alu_unsigned <= execute_engine.ir(instr_funct3_lsb_c+1); -- unsigned branches? (BLTU, BGEU)
    end if;

    -- ALU operand A: is PC? --
    case decode_aux.opcode is
      when opcode_auipc_c | opcode_jal_c | opcode_branch_c =>
        ctrl_nxt.alu_opa_mux <= '1';
      when others =>
        ctrl_nxt.alu_opa_mux <= '0';
    end case;

    -- ALU operand B: is immediate? --
    case decode_aux.opcode is
      when opcode_alui_c | opcode_lui_c | opcode_auipc_c | opcode_load_c | opcode_store_c | opcode_amo_c | opcode_branch_c | opcode_jal_c | opcode_jalr_c =>
        ctrl_nxt.alu_opb_mux <= '1';
      when others =>
        ctrl_nxt.alu_opb_mux <= '0';
    end case;

    -- memory read/write access --
    if (CPU_EXTENSION_RISCV_A = true) and (decode_aux.opcode(2) = opcode_amo_c(2)) then -- lr/sc
      ctrl_nxt.lsu_rw <= execute_engine.ir(instr_funct7_lsb_c+2);
    else -- normal load/store
      ctrl_nxt.lsu_rw <= execute_engine.ir(5);
    end if;

    -- state machine --
    case execute_engine.state is

      when DISPATCH => -- Wait for ISSUE ENGINE to emit valid instruction word
      -- ------------------------------------------------------------
        if (trap_ctrl.env_pending = '1') or (trap_ctrl.exc_fire = '1') then -- pending trap or pending exception (fast)
          execute_engine.state_nxt <= TRAP_ENTER;
        elsif (CPU_EXTENSION_RISCV_Sdtrig = true) and (hw_trigger_match = '1') then -- hardware breakpoint
          execute_engine.pc_we     <= '1'; -- pc <= next_pc
          trap_ctrl.hwtrig         <= '1';
          execute_engine.state_nxt <= TRAP_ENTER;
        elsif (issue_engine.valid(0) = '1') or (issue_engine.valid(1) = '1') then -- new instruction word available
          issue_engine.ack         <= '1';
          trap_ctrl.instr_be       <= issue_engine.data(32); -- access fault during instruction fetch
          execute_engine.is_ci_nxt <= issue_engine.data(33); -- this is a de-compressed instruction
          execute_engine.ir_nxt    <= issue_engine.data(31 downto 0); -- instruction word
          execute_engine.pc_we     <= '1'; -- pc <= next_pc
          execute_engine.state_nxt <= EXECUTE;
        end if;

      when TRAP_ENTER => -- Enter trap environment and jump to trap vector
      -- ------------------------------------------------------------
        if (trap_ctrl.env_pending = '1') then -- wait for sync. exceptions to become pending
          trap_ctrl.env_enter      <= '1';
          execute_engine.state_nxt <= RESTART;
        end if;

      when TRAP_EXIT => -- Return from trap environment and jump to xEPC
      -- ------------------------------------------------------------
        trap_ctrl.env_exit       <= '1';
        execute_engine.state_nxt <= RESTART;

      when RESTART => -- reset and restart instruction fetch at <next_pc>
      -- ------------------------------------------------------------
        fetch_engine.reset       <= '1';
        execute_engine.state_nxt <= BRANCHED;

      when EXECUTE => -- Decode and execute instruction (control will be here for exactly 1 cycle in any case)
      -- [NOTE] register file is read in this stage; due to the sync read, data will be available in the _next_ state
      -- ------------------------------------------------------------
        case decode_aux.opcode is

          -- register/immediate ALU operation --
          when opcode_alu_c | opcode_alui_c =>
            -- ALU core operation --
            case execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) is -- actual ALU operation (re-coding)
              when funct3_subadd_c => -- ADD(I), SUB
                if ((execute_engine.ir(instr_opcode_msb_c-1) = '1') and (execute_engine.ir(instr_funct7_msb_c-1) = '1')) then
                  ctrl_nxt.alu_op <= alu_op_sub_c; -- SUB if not an immediate op and funct7.6 set
                else
                  ctrl_nxt.alu_op <= alu_op_add_c;
                end if;
              when funct3_slt_c | funct3_sltu_c => -- SLT(I), SLTU(I)
                ctrl_nxt.alu_op <= alu_op_slt_c;
              when funct3_xor_c => -- XOR(I)
                ctrl_nxt.alu_op <= alu_op_xor_c;
              when funct3_or_c => -- OR(I)
                ctrl_nxt.alu_op <= alu_op_or_c;
              when others => -- AND(I) or multi-cycle / co-processor operation
                ctrl_nxt.alu_op <= alu_op_and_c;
            end case;

            -- EXT: co-processor MULDIV operation (multi-cycle) --
            if ((CPU_EXTENSION_RISCV_M = true) and (execute_engine.ir(instr_opcode_lsb_c+5) = opcode_alu_c(5)) and
                ((decode_aux.is_m_mul = '1') or (decode_aux.is_m_div = '1'))) or -- MUL/DIV
               ((CPU_EXTENSION_RISCV_Zmmul = true) and (execute_engine.ir(instr_opcode_lsb_c+5) = opcode_alu_c(5)) and
                (decode_aux.is_m_mul = '1')) then -- MUL
              ctrl_nxt.alu_cp_trig(cp_sel_muldiv_c) <= '1'; -- trigger MULDIV CP
              execute_engine.state_nxt              <= ALU_WAIT;
            -- EXT: co-processor BIT-MANIPULATION operation (multi-cycle) --
            elsif (CPU_EXTENSION_RISCV_B = true) and
                  (((execute_engine.ir(instr_opcode_lsb_c+5) = opcode_alu_c(5))  and (decode_aux.is_b_reg = '1')) or -- register operation
                   ((execute_engine.ir(instr_opcode_lsb_c+5) = opcode_alui_c(5)) and (decode_aux.is_b_imm = '1'))) then -- immediate operation
              ctrl_nxt.alu_cp_trig(cp_sel_bitmanip_c) <= '1'; -- trigger BITMANIP CP
              execute_engine.state_nxt                <= ALU_WAIT;
            -- EXT: co-processor CONDITIONAL operations (multi-cycle) --
            elsif (CPU_EXTENSION_RISCV_Zicond = true) and (decode_aux.is_zicond = '1') and
                  (execute_engine.ir(instr_opcode_lsb_c+5) = opcode_alu_c(5)) then
              ctrl_nxt.alu_cp_trig(cp_sel_cond_c) <= '1'; -- trigger COND CP
              execute_engine.state_nxt            <= ALU_WAIT;
            -- BASE: co-processor SHIFT operation (multi-cycle) --
            elsif (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = funct3_sll_c) or
                  (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = funct3_sr_c) then
              ctrl_nxt.alu_cp_trig(cp_sel_shifter_c) <= '1'; -- trigger SHIFTER CP
              execute_engine.state_nxt               <= ALU_WAIT;
            -- BASE: ALU CORE operation (single-cycle) --
            else
              ctrl_nxt.rf_wb_en        <= '1'; -- valid RF write-back
              execute_engine.state_nxt <= DISPATCH;
            end if;

          -- load upper immediate / add upper immediate to PC --
          when opcode_lui_c | opcode_auipc_c =>
            if (execute_engine.ir(instr_opcode_lsb_c+5) = opcode_lui_c(5)) then -- LUI
              ctrl_nxt.alu_op <= alu_op_movb_c; -- pass immediate
            else -- AUIPC
              ctrl_nxt.alu_op <= alu_op_add_c; -- add PC and immediate
            end if;
            ctrl_nxt.rf_wb_en        <= '1'; -- valid RF write-back
            execute_engine.state_nxt <= DISPATCH;

          -- memory access --
          when opcode_load_c | opcode_store_c | opcode_amo_c =>
            execute_engine.state_nxt <= MEM_REQ;

          -- branch / jump and link / with register --
          when opcode_branch_c | opcode_jal_c | opcode_jalr_c =>
            execute_engine.state_nxt <= BRANCH;

          -- memory fence operations --
          when opcode_fence_c =>
            execute_engine.state_nxt <= FENCE;

          -- FPU: floating-point operations --
          when opcode_fop_c =>
            ctrl_nxt.alu_cp_trig(cp_sel_fpu_c) <= '1'; -- trigger FPU co-processor
            execute_engine.state_nxt           <= ALU_WAIT; -- will be aborted via monitor exception if FPU not implemented

          -- CFU: custom RISC-V instructions --
          when opcode_cust0_c | opcode_cust1_c | opcode_cust2_c | opcode_cust3_c =>
            ctrl_nxt.alu_cp_trig(cp_sel_cfu_c) <= '1'; -- trigger CFU co-processor
            execute_engine.state_nxt           <= ALU_WAIT; -- will be aborted via monitor exception if CFU not implemented

          -- environment/CSR operation or ILLEGAL opcode --
          when others =>
            csr.re_nxt               <= '1';
            execute_engine.state_nxt <= SYSTEM;

        end case; -- /EXECUTE

      when ALU_WAIT => -- wait for multi-cycle ALU co-processor operation to finish/trap
      -- ------------------------------------------------------------
        ctrl_nxt.alu_op <= alu_op_cp_c;
        if (alu_cp_done_i = '1') or (trap_ctrl.exc_buf(exc_illegal_c) = '1') then
          ctrl_nxt.rf_wb_en        <= '1'; -- valid RF write-back (won't happen in case of an illegal instruction)
          execute_engine.state_nxt <= DISPATCH;
        end if;

      when FENCE => -- memory fence
      -- ------------------------------------------------------------
        if (trap_ctrl.exc_buf(exc_illegal_c) = '1') then -- abort if illegal instruction
          execute_engine.state_nxt <= DISPATCH;
        else
          ctrl_nxt.lsu_fence       <= '1'; -- NOTE: fence == fence.i
          execute_engine.state_nxt <= RESTART; -- reset instruction fetch + IPB (actually only required for fence.i)
        end if;

      when BRANCH => -- update next_PC on taken branches and jumps
      -- ------------------------------------------------------------
        ctrl_nxt.rf_mux   <= rf_mux_ret_c; -- return address = link PC
        ctrl_nxt.rf_wb_en <= execute_engine.ir(instr_opcode_lsb_c+2); -- save return address if link operation (will not happen if misaligned)
        if (trap_ctrl.exc_buf(exc_illegal_c) = '0') and (execute_engine.branch_taken = '1') then -- valid taken branch
          fetch_engine.reset       <= '1'; -- reset instruction fetch to restart at modified PC
          execute_engine.state_nxt <= BRANCHED; -- shortcut (faster than going to RESTART)
        else
          execute_engine.state_nxt <= DISPATCH;
        end if;

      when BRANCHED => -- delay cycle to wait for reset of pipeline front-end (instruction fetch)
      -- ------------------------------------------------------------
        execute_engine.state_nxt <= DISPATCH;
        -- house keeping: use this state also to (re-)initialize the register file's x0/zero register --
        if (REGFILE_HW_RST = false) then -- x0 does not provide a dedicated hardware reset
          ctrl_nxt.rf_mux     <= rf_mux_csr_c; -- this will return 0 since csr.re_nxt is zero
          ctrl_nxt.rf_zero_we <= '1'; -- force write access to x0
        end if;

      when MEM_REQ => -- trigger memory request
      -- ------------------------------------------------------------
        if (trap_ctrl.exc_buf(exc_illegal_c) = '1') then -- abort if illegal instruction
          execute_engine.state_nxt <= DISPATCH;
        else
          ctrl_nxt.lsu_req         <= '1'; -- memory access request
          execute_engine.state_nxt <= MEM_WAIT;
        end if;

      when MEM_WAIT => -- wait for bus transaction to finish
      -- ------------------------------------------------------------
        ctrl_nxt.rf_mux <= rf_mux_mem_c; -- RF input = memory read data
        if (lsu_wait_i = '0') or -- bus system has completed the transaction
           (trap_ctrl.exc_buf(exc_saccess_c) = '1') or (trap_ctrl.exc_buf(exc_laccess_c) = '1') or -- access exception
           (trap_ctrl.exc_buf(exc_salign_c)  = '1') or (trap_ctrl.exc_buf(exc_lalign_c)  = '1') then -- alignment exception
          if ((CPU_EXTENSION_RISCV_A = true) and (decode_aux.opcode(2) = opcode_amo_c(2))) or -- atomic operation
             (execute_engine.ir(instr_opcode_msb_c-1) = '0') then -- normal load
            ctrl_nxt.rf_wb_en <= '1'; -- allow write-back to register file (won't happen in case of exception)
          end if;
          execute_engine.state_nxt <= DISPATCH;
        end if;

      when SLEEP => -- sleep mode
      -- ------------------------------------------------------------
        if (trap_ctrl.wakeup = '1') then
          execute_engine.state_nxt <= DISPATCH;
        end if;

      when others => -- SYSTEM - system environment operation; no effect if illegal instruction
      -- ------------------------------------------------------------
        execute_engine.state_nxt <= DISPATCH; -- default
        ctrl_nxt.rf_mux          <= rf_mux_csr_c; -- CSR read data
        if (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = funct3_env_c) and -- ENVIRONMENT
           (trap_ctrl.exc_buf(exc_illegal_c) = '0') then -- not an illegal instruction
          case execute_engine.ir(instr_funct12_msb_c downto instr_funct12_lsb_c) is
            when funct12_ecall_c                 => trap_ctrl.ecall          <= '1'; -- ecall
            when funct12_ebreak_c                => trap_ctrl.ebreak         <= '1'; -- ebreak
            when funct12_mret_c | funct12_dret_c => execute_engine.state_nxt <= TRAP_EXIT; -- mret/dret
            when others                          => execute_engine.state_nxt <= SLEEP; -- "funct12_wfi_c" - wfi/sleep
          end case;
        else -- CSR ACCESS - no state change if illegal instruction
          if (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = funct3_csrrw_c) or  -- CSRRW:  always write CSR
             (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = funct3_csrrwi_c) or -- CSRRWI: always write CSR
             (decode_aux.rs1_zero = '0') then -- CSRR(S/C)(I): write CSR if rs1/imm5 is NOT zero
            csr.we_nxt <= '1';
          end if;
          ctrl_nxt.rf_wb_en <= '1'; -- valid RF write-back
        end if;

    end case;
  end process execute_engine_fsm_comb;


  -- CPU Sleep Mode Control -----------------------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  sleep_control: process(rstn_i, clk_aux_i) -- always-on clock domain
  begin
    if (rstn_i = '0') then
      sleep_mode <= '0';
    elsif rising_edge(clk_aux_i) then
      if (execute_engine.state = SLEEP) and -- instruction execution has halted
         (ipb.free /= "11") and -- instruction fetch has halted
         (trap_ctrl.wakeup = '0') then -- no wake-up request
        sleep_mode <= '1';
      else
        sleep_mode <= '0';
      end if;
    end if;
  end process sleep_control;


  -- CPU Control Bus Output -----------------------------------------------------------------
  -- -------------------------------------------------------------------------------------------

  -- register file --
  ctrl_o.rf_wb_en     <= ctrl.rf_wb_en and -- inhibit write-back only for rd-updating exceptions that must not commit
                         (not trap_ctrl.exc_buf(exc_illegal_c)) and
                         (not trap_ctrl.exc_buf(exc_ialign_c))  and (not trap_ctrl.exc_buf(exc_salign_c))  and (not trap_ctrl.exc_buf(exc_lalign_c)) and
                         (not trap_ctrl.exc_buf(exc_iaccess_c)) and (not trap_ctrl.exc_buf(exc_saccess_c)) and (not trap_ctrl.exc_buf(exc_laccess_c));
  ctrl_o.rf_rs1       <= execute_engine.ir(instr_rs1_msb_c downto instr_rs1_lsb_c);
  ctrl_o.rf_rs2       <= execute_engine.ir(instr_rs2_msb_c downto instr_rs2_lsb_c);
  ctrl_o.rf_rs3       <= execute_engine.ir(instr_rs3_msb_c downto instr_rs3_lsb_c);
  ctrl_o.rf_rd        <= execute_engine.ir(instr_rd_msb_c  downto instr_rd_lsb_c);
  ctrl_o.rf_mux       <= ctrl.rf_mux;
  ctrl_o.rf_zero_we   <= ctrl.rf_zero_we;

  -- alu --
  ctrl_o.alu_op       <= ctrl.alu_op;
  ctrl_o.alu_opa_mux  <= ctrl.alu_opa_mux;
  ctrl_o.alu_opb_mux  <= ctrl.alu_opb_mux;
  ctrl_o.alu_unsigned <= ctrl.alu_unsigned;
  ctrl_o.alu_cp_trig  <= ctrl.alu_cp_trig;

  -- data bus interface --
  ctrl_o.lsu_req      <= ctrl.lsu_req;
  ctrl_o.lsu_rw       <= ctrl.lsu_rw;
  ctrl_o.lsu_mo_we    <= '1' when (execute_engine.state = MEM_REQ) else '0'; -- write memory output registers (data & address)
  ctrl_o.lsu_fence    <= ctrl.lsu_fence; -- fence(.i)
  ctrl_o.lsu_priv     <= csr.mstatus_mpp when (csr.mstatus_mprv = '1') else csr.privilege_eff; -- effective privilege level for loads/stores in M-mode

  -- instruction word bit fields --
  ctrl_o.ir_funct3    <= execute_engine.ir(instr_funct3_msb_c  downto instr_funct3_lsb_c);
  ctrl_o.ir_funct12   <= execute_engine.ir(instr_funct12_msb_c downto instr_funct12_lsb_c);
  ctrl_o.ir_opcode    <= execute_engine.ir(instr_opcode_msb_c  downto instr_opcode_lsb_c);

  -- cpu status --
  ctrl_o.cpu_priv     <= csr.privilege_eff;
  ctrl_o.cpu_sleep    <= sleep_mode;
  ctrl_o.cpu_trap     <= trap_ctrl.env_enter;
  ctrl_o.cpu_debug    <= debug_ctrl.running;


-- ****************************************************************************************************************************
-- Illegal Instruction Detection
-- ****************************************************************************************************************************

  -- Instruction Execution Monitor (trap if multi-cycle instruction does not complete) ------
  -- -------------------------------------------------------------------------------------------
  multi_cycle_monitor: process(rstn_i, clk_i)
  begin
    if (rstn_i = '0') then
      monitor.cnt <= (others => '0');
    elsif rising_edge(clk_i) then
      monitor.cnt <= std_ulogic_vector(unsigned(monitor.cnt_add) + 1);
    end if;
  end process multi_cycle_monitor;

  -- timeout counter (allow mapping of entire logic into the LUTs in front of the carry-chain) --
  monitor.cnt_add <= monitor.cnt when (execute_engine.state = ALU_WAIT) else (others => '0');

  -- raise illegal instruction exception if a multi-cycle instruction takes longer than a bound amount of time --
  monitor.exc <= monitor.cnt(monitor.cnt'left);


  -- CSR Access Check: Available at All -----------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  csr_avail_check: process(csr.addr)
  begin
    case csr.addr is

      -- user-defined U-mode CFU CSRs --
      when csr_cfureg0_c | csr_cfureg1_c | csr_cfureg2_c | csr_cfureg3_c =>
        csr_reg_valid <= bool_to_ulogic_f(CPU_EXTENSION_RISCV_Zxcfu); -- available if CFU implemented

      -- floating-point CSRs --
      when csr_fflags_c | csr_frm_c | csr_fcsr_c =>
        csr_reg_valid <= bool_to_ulogic_f(CPU_EXTENSION_RISCV_Zfinx); -- available if FPU implemented

      -- machine trap setup/handling, environment/information registers, etc. --
      when csr_mstatus_c  | csr_mstatush_c      | csr_misa_c      | csr_mie_c     | csr_mtvec_c  |
           csr_mscratch_c | csr_mepc_c          | csr_mcause_c    | csr_mip_c     | csr_mtval_c  |
           csr_mtinst_c   | csr_mcountinhibit_c | csr_mvendorid_c | csr_marchid_c | csr_mimpid_c |
           csr_mhartid_c  | csr_mconfigptr_c    | csr_mxisa_c =>
        csr_reg_valid <= '1'; -- always implemented

      -- machine-controlled user-mode CSRs --
      when csr_mcounteren_c | csr_menvcfg_c | csr_menvcfgh_c =>
        csr_reg_valid <= bool_to_ulogic_f(CPU_EXTENSION_RISCV_U); -- available if U-mode implemented

      -- physical memory protection (PMP) --
      when csr_pmpcfg0_c   | csr_pmpcfg1_c   | csr_pmpcfg2_c   | csr_pmpcfg3_c   | -- configuration
           csr_pmpaddr0_c  | csr_pmpaddr1_c  | csr_pmpaddr2_c  | csr_pmpaddr3_c  |
           csr_pmpaddr4_c  | csr_pmpaddr5_c  | csr_pmpaddr6_c  | csr_pmpaddr7_c  | -- address
           csr_pmpaddr8_c  | csr_pmpaddr9_c  | csr_pmpaddr10_c | csr_pmpaddr11_c |
           csr_pmpaddr12_c | csr_pmpaddr13_c | csr_pmpaddr14_c | csr_pmpaddr15_c =>
        csr_reg_valid <= bool_to_ulogic_f(CPU_EXTENSION_RISCV_Smpmp); -- available if PMP implemented

      -- hardware performance monitors (HPM) --
      when csr_hpmcounter3_c    | csr_hpmcounter4_c    | csr_hpmcounter5_c    | csr_hpmcounter6_c    | csr_hpmcounter7_c    |
           csr_hpmcounter8_c    | csr_hpmcounter9_c    | csr_hpmcounter10_c   | csr_hpmcounter11_c   | csr_hpmcounter12_c   |
           csr_hpmcounter13_c   | csr_hpmcounter14_c   | csr_hpmcounter15_c   | -- user counters LOW
           csr_hpmcounter3h_c   | csr_hpmcounter4h_c   | csr_hpmcounter5h_c   | csr_hpmcounter6h_c   | csr_hpmcounter7h_c   |
           csr_hpmcounter8h_c   | csr_hpmcounter9h_c   | csr_hpmcounter10h_c  | csr_hpmcounter11h_c  | csr_hpmcounter12h_c  |
           csr_hpmcounter13h_c  | csr_hpmcounter14h_c  | csr_hpmcounter15h_c  | -- user counters HIGH
           csr_mhpmcounter3_c   | csr_mhpmcounter4_c   | csr_mhpmcounter5_c   | csr_mhpmcounter6_c   | csr_mhpmcounter7_c   |
           csr_mhpmcounter8_c   | csr_mhpmcounter9_c   | csr_mhpmcounter10_c  | csr_mhpmcounter11_c  | csr_mhpmcounter12_c  |
           csr_mhpmcounter13_c  | csr_mhpmcounter14_c  | csr_mhpmcounter15_c  | -- machine counters LOW
           csr_mhpmcounter3h_c  | csr_mhpmcounter4h_c  | csr_mhpmcounter5h_c  | csr_mhpmcounter6h_c  | csr_mhpmcounter7h_c  |
           csr_mhpmcounter8h_c  | csr_mhpmcounter9h_c  | csr_mhpmcounter10h_c | csr_mhpmcounter11h_c | csr_mhpmcounter12h_c |
           csr_mhpmcounter13h_c | csr_mhpmcounter14h_c | csr_mhpmcounter15h_c | -- machine counters HIGH
           csr_mhpmevent3_c     | csr_mhpmevent4_c     | csr_mhpmevent5_c     | csr_mhpmevent6_c     | csr_mhpmevent7_c     |
           csr_mhpmevent8_c     | csr_mhpmevent9_c     | csr_mhpmevent10_c    | csr_mhpmevent11_c    | csr_mhpmevent12_c    |
           csr_mhpmevent13_c    | csr_mhpmevent14_c    | csr_mhpmevent15_c => -- machine event configuration
        csr_reg_valid <= bool_to_ulogic_f(CPU_EXTENSION_RISCV_Zihpm); -- available if Zihpm implemented

      -- counter and timer CSRs --
      when csr_cycle_c  | csr_mcycle_c  | csr_instret_c  | csr_minstret_c |
           csr_cycleh_c | csr_mcycleh_c | csr_instreth_c | csr_minstreth_c =>
        csr_reg_valid <= bool_to_ulogic_f(CPU_EXTENSION_RISCV_Zicntr); -- available if Zicntr implemented

      -- debug-mode CSRs --
      when csr_dcsr_c | csr_dpc_c | csr_dscratch0_c =>
        csr_reg_valid <= bool_to_ulogic_f(CPU_EXTENSION_RISCV_Sdext); -- available if debug-mode implemented

      -- trigger module CSRs --
      when csr_tselect_c | csr_tdata1_c | csr_tdata2_c | csr_tinfo_c =>
        csr_reg_valid <= bool_to_ulogic_f(CPU_EXTENSION_RISCV_Sdtrig); -- available if trigger module implemented

      -- undefined / not implemented --
      when others =>
        csr_reg_valid <= '0'; -- invalid access

    end case;
  end process csr_avail_check;


  -- CSR Access Check: R/W Capabilities -----------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  csr_rw_check: process(csr.addr, execute_engine.ir, decode_aux.rs1_zero)
  begin
    if (csr.addr(11 downto 10) = "11") and -- CSR is read-only
       ((execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = funct3_csrrw_c)  or -- will always write to CSR
        (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = funct3_csrrwi_c) or -- will always write to CSR
        (decode_aux.rs1_zero = '0')) then -- clear/set instructions: write to CSR only if rs1/imm5 is NOT zero
      csr_rw_valid <= '0'; -- invalid access
    else
      csr_rw_valid <= '1'; -- access granted
    end if;
  end process csr_rw_check;


  -- CSR Access Check: Privilege Level ------------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  csr_priv_check: process(csr, debug_ctrl)
  begin
    if ((csr.addr = csr_dcsr_c) or (csr.addr = csr_dpc_c) or (csr.addr = csr_dscratch0_c)) and -- debug-mode-only CSR?
       (CPU_EXTENSION_RISCV_Sdext = true) and (debug_ctrl.running = '0') then -- debug-mode implemented and not running?
      csr_priv_valid <= '0'; -- invalid access
    elsif (csr.addr(11 downto 8) = csr_cycle_c(11 downto 8)) and -- user-mode counter access
          ((CPU_EXTENSION_RISCV_Zicntr = true) or (CPU_EXTENSION_RISCV_Zihpm = true)) and -- any counters available?
          (CPU_EXTENSION_RISCV_U = true) and (csr.privilege_eff = '0') and (csr.mcounteren = '0') then -- user mode enabled, active and access not allowed?
      csr_priv_valid <= '0'; -- invalid access
    elsif (csr.addr(9 downto 8) /= "00") and (csr.privilege_eff = '0') then -- invalid privilege level
      csr_priv_valid <= '0'; -- invalid access
    else
      csr_priv_valid <= '1'; -- access granted
    end if;
  end process csr_priv_check;


  -- Illegal Instruction Check --------------------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  illegal_check: process(execute_engine, decode_aux, csr, csr_reg_valid, csr_rw_valid, csr_priv_valid, debug_ctrl)
  begin
    illegal_cmd <= '0'; -- default
    case decode_aux.opcode is

      when opcode_lui_c | opcode_auipc_c | opcode_jal_c =>
        illegal_cmd <= '0'; -- all encodings are valid

      when opcode_jalr_c =>
        case execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) is
          when "000"  => illegal_cmd <= '0';
          when others => illegal_cmd <= '1';
        end case;

      when opcode_branch_c =>
        case execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) is
          when funct3_beq_c | funct3_bne_c | funct3_blt_c | funct3_bge_c | funct3_bltu_c | funct3_bgeu_c => illegal_cmd <= '0';
          when others => illegal_cmd <= '1';
        end case;

      when opcode_load_c =>
        case execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) is
          when funct3_lb_c | funct3_lh_c | funct3_lw_c | funct3_lbu_c | funct3_lhu_c => illegal_cmd <= '0';
          when others => illegal_cmd <= '1';
        end case;

      when opcode_store_c =>
        case execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) is
          when funct3_sb_c | funct3_sh_c | funct3_sw_c => illegal_cmd <= '0';
          when others => illegal_cmd <= '1';
        end case;

      when opcode_amo_c =>
        if (CPU_EXTENSION_RISCV_A = true) and ((decode_aux.is_a_lr = '1') or (decode_aux.is_a_sc = '1')) then -- LR.W/SC.W
          illegal_cmd <= '0';
        else
          illegal_cmd <= '1';
        end if;

      when opcode_alu_c =>
        if ((((execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = funct3_subadd_c) or (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = funct3_sr_c)) and
             (execute_engine.ir(instr_funct7_msb_c-2 downto instr_funct7_lsb_c) = "00000") and (execute_engine.ir(instr_funct7_msb_c) = '0')) or
            (((execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = funct3_sll_c) or
              (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = funct3_slt_c) or
              (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = funct3_sltu_c) or
              (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = funct3_xor_c) or
              (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = funct3_or_c) or
              (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = funct3_and_c)) and
              (execute_engine.ir(instr_funct7_msb_c downto instr_funct7_lsb_c) = "0000000"))) or -- valid base ALU instruction?
           (((CPU_EXTENSION_RISCV_M = true) or (CPU_EXTENSION_RISCV_Zmmul = true)) and (decode_aux.is_m_mul = '1')) or -- valid MUL instruction?
           ((CPU_EXTENSION_RISCV_M = true) and (decode_aux.is_m_div = '1')) or -- valid DIV instruction?
           ((CPU_EXTENSION_RISCV_B = true) and (decode_aux.is_b_reg = '1')) or -- valid BITMANIP register instruction?
           ((CPU_EXTENSION_RISCV_Zicond = true) and (decode_aux.is_zicond = '1')) then -- valid CONDITIONAL instruction?
          illegal_cmd <= '0';
        else
          illegal_cmd <= '1';
        end if;

      when opcode_alui_c =>
        if ((execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = funct3_subadd_c) or
            (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = funct3_slt_c) or
            (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = funct3_sltu_c) or
            (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = funct3_xor_c) or
            (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = funct3_or_c) or
            (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = funct3_and_c) or
            ((execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = funct3_sll_c) and
             (execute_engine.ir(instr_funct7_msb_c downto instr_funct7_lsb_c) = "0000000")) or
            ((execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = funct3_sr_c) and
             ((execute_engine.ir(instr_funct7_msb_c-2 downto instr_funct7_lsb_c) = "00000") and (execute_engine.ir(instr_funct7_msb_c) = '0')))) or -- valid base ALUI instruction?
           ((CPU_EXTENSION_RISCV_B = true) and (decode_aux.is_b_imm = '1')) then -- valid BITMANIP immediate instruction?
          illegal_cmd <= '0';
        else
          illegal_cmd <= '1';
        end if;

      when opcode_fence_c =>
        case execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) is
          when funct3_fence_c | funct3_fencei_c => illegal_cmd <= '0'; -- fence[.i]
          when others                           => illegal_cmd <= '1';
        end case;

      when opcode_system_c =>
        if (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = funct3_env_c) then -- system environment
          if (decode_aux.rs1_zero = '1') and (decode_aux.rd_zero = '1') then
            case execute_engine.ir(instr_funct12_msb_c downto instr_funct12_lsb_c) is
              when funct12_ecall_c | funct12_ebreak_c => illegal_cmd <= '0'; -- ecall, ebreak
              when funct12_mret_c                     => illegal_cmd <= (not csr.privilege) or debug_ctrl.running; -- mret allowed in (real/non-debug) M-mode only
              when funct12_dret_c                     => illegal_cmd <= not debug_ctrl.running; -- dret allowed in debug mode only
              when funct12_wfi_c                      => illegal_cmd <= (not csr.privilege) and csr.mstatus_tw; -- wfi allowed in M-mode or if TW is zero
              when others                             => illegal_cmd <= '1';
            end case;
          else
            illegal_cmd <= '1';
          end if;
        elsif (csr_reg_valid = '0') or (csr_rw_valid = '0') or (csr_priv_valid = '0') or -- invalid CSR access?
              (execute_engine.ir(instr_funct3_msb_c downto instr_funct3_lsb_c) = funct3_csril_c) then -- invalid CSR access instruction?
          illegal_cmd <= '1';
        else
          illegal_cmd <= '0';
        end if;

      when opcode_fop_c =>
        illegal_cmd <= (not bool_to_ulogic_f(CPU_EXTENSION_RISCV_Zfinx)) or (not decode_aux.is_f_op);

      when opcode_cust0_c | opcode_cust1_c | opcode_cust2_c | opcode_cust3_c =>
        illegal_cmd <= not bool_to_ulogic_f(CPU_EXTENSION_RISCV_Zxcfu); -- all encodings valid if CFU enable

      when others =>
        illegal_cmd <= '1'; -- undefined/illegal opcode

    end case;
  end process illegal_check;


  -- Illegal Operation Check ----------------------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  trap_ctrl.instr_il <= '1' when ((execute_engine.state = EXECUTE) or (execute_engine.state = ALU_WAIT)) and -- check in execution states only
                                 ((monitor.exc = '1') or -- execution monitor exception (multi-cycle instruction timeout)
                                  (illegal_cmd = '1') or -- illegal instruction?
                                  (execute_engine.ir(instr_opcode_lsb_c+1 downto instr_opcode_lsb_c) /= "11")) else '0'; -- illegal opcode LSBs?


-- ****************************************************************************************************************************
-- Trap Controller
-- ****************************************************************************************************************************

  -- Exception Buffer -----------------------------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  exception_buffer: process(rstn_i, clk_i)
  begin
    if (rstn_i = '0') then
      trap_ctrl.exc_buf <= (others => '0');
    elsif rising_edge(clk_i) then

      -- Exception Buffer -----------------------------------------------------
      -- If several exception sources trigger at once, all the requests will
      -- stay active until the trap environment is started. Only the exception
      -- with highest priority will be used to update the MCAUSE CSR. All
      -- remaining ones will be discarded.
      -- ----------------------------------------------------------------------

      -- misaligned load/store/instruction address --
      trap_ctrl.exc_buf(exc_lalign_c) <= (trap_ctrl.exc_buf(exc_lalign_c) or ma_load_i)          and (not trap_ctrl.env_enter);
      trap_ctrl.exc_buf(exc_salign_c) <= (trap_ctrl.exc_buf(exc_salign_c) or ma_store_i)         and (not trap_ctrl.env_enter);
      trap_ctrl.exc_buf(exc_ialign_c) <= (trap_ctrl.exc_buf(exc_ialign_c) or trap_ctrl.instr_ma) and (not trap_ctrl.env_enter);

      -- load/store/instruction access fault --
      trap_ctrl.exc_buf(exc_laccess_c) <= (trap_ctrl.exc_buf(exc_laccess_c) or be_load_i)          and (not trap_ctrl.env_enter);
      trap_ctrl.exc_buf(exc_saccess_c) <= (trap_ctrl.exc_buf(exc_saccess_c) or be_store_i)         and (not trap_ctrl.env_enter);
      trap_ctrl.exc_buf(exc_iaccess_c) <= (trap_ctrl.exc_buf(exc_iaccess_c) or trap_ctrl.instr_be) and (not trap_ctrl.env_enter);

      -- illegal instruction & environment call --
      trap_ctrl.exc_buf(exc_ecall_c)   <= (trap_ctrl.exc_buf(exc_ecall_c)   or trap_ctrl.ecall)    and (not trap_ctrl.env_enter);
      trap_ctrl.exc_buf(exc_illegal_c) <= (trap_ctrl.exc_buf(exc_illegal_c) or trap_ctrl.instr_il) and (not trap_ctrl.env_enter);

      -- break point --
      if (CPU_EXTENSION_RISCV_Sdext = true) then
        trap_ctrl.exc_buf(exc_ebreak_c) <= (not trap_ctrl.env_enter) and (trap_ctrl.exc_buf(exc_ebreak_c) or
          (trap_ctrl.hwtrig and (not csr.tdata1_action)) or -- trigger module fires and enter-debug is disabled
          (trap_ctrl.ebreak and (    csr.privilege) and (not csr.dcsr_ebreakm) and (not debug_ctrl.running)) or -- enter M-mode handler on ebreak in M-mode
          (trap_ctrl.ebreak and (not csr.privilege) and (not csr.dcsr_ebreaku) and (not debug_ctrl.running)));  -- enter M-mode handler on ebreak in U-mode
      else
        trap_ctrl.exc_buf(exc_ebreak_c) <= (trap_ctrl.exc_buf(exc_ebreak_c) or trap_ctrl.ebreak or (trap_ctrl.hwtrig and (not csr.tdata1_action))) and (not trap_ctrl.env_enter);
      end if;

      -- debug-mode entry --
      if (CPU_EXTENSION_RISCV_Sdext = true) then
        trap_ctrl.exc_buf(exc_db_break_c) <= (trap_ctrl.exc_buf(exc_db_break_c) or debug_ctrl.trig_break) and (not trap_ctrl.env_enter);
        trap_ctrl.exc_buf(exc_db_hw_c)    <= (trap_ctrl.exc_buf(exc_db_hw_c)    or debug_ctrl.trig_hw)    and (not trap_ctrl.env_enter);
      else
        trap_ctrl.exc_buf(exc_db_break_c) <= '0';
        trap_ctrl.exc_buf(exc_db_hw_c)    <= '0';
      end if;
    end if;
  end process exception_buffer;


  -- Interrupt Buffer -----------------------------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  interrupt_buffer: process(rstn_i, clk_aux_i) -- always-on clock domain
  begin
    if (rstn_i = '0') then
      trap_ctrl.irq_pnd <= (others => '0');
      trap_ctrl.irq_buf <= (others => '0');
    elsif rising_edge(clk_aux_i) then

      -- Interrupt-Pending Buffer ---------------------------------------------
      -- Once triggered the fast interrupt requests stay active until
      -- explicitly cleared via the MIP CSR. The RISC-V standard interrupts
      -- have to stay high until cleared by a platform-specific mechanism.
      -- ----------------------------------------------------------------------

      -- RISC-V machine interrupts --
      trap_ctrl.irq_pnd(irq_msi_irq_c) <= msi_i;
      trap_ctrl.irq_pnd(irq_mei_irq_c) <= mei_i;
      trap_ctrl.irq_pnd(irq_mti_irq_c) <= mti_i;

      -- NEORV32-specific fast interrupts --
      for i in 0 to 15 loop
        if (csr.mip_firq_we = '1') then -- write access to MIP(.FIRQ) CSR
          trap_ctrl.irq_pnd(irq_firq_0_c+i) <= firq_i(i) or csr.mip_firq_wdata(i); -- keep buffering incoming FIRQs
        else
          trap_ctrl.irq_pnd(irq_firq_0_c+i) <= firq_i(i) or trap_ctrl.irq_pnd(irq_firq_0_c+i); -- keep pending FIRQs alive
        end if;
      end loop;

      -- debug-mode entry --
      trap_ctrl.irq_pnd(irq_db_halt_c) <= '0'; -- unused
      trap_ctrl.irq_pnd(irq_db_step_c) <= '0'; -- unused

      -- Interrupt (Masking) Buffer -------------------------------------------
      -- Masking of interrupt request lines. Furthermore, this buffer ensures
      -- that an *active* interrupt request line *stays* active (even if
      -- disabled via MIE) if the trap environment is *currently* starting.
      -- ----------------------------------------------------------------------

      -- RISC-V machine interrupts --
      trap_ctrl.irq_buf(irq_msi_irq_c) <= (trap_ctrl.irq_pnd(irq_msi_irq_c) and csr.mie_msi) or (trap_ctrl.env_pending and trap_ctrl.irq_buf(irq_msi_irq_c));
      trap_ctrl.irq_buf(irq_mei_irq_c) <= (trap_ctrl.irq_pnd(irq_mei_irq_c) and csr.mie_mei) or (trap_ctrl.env_pending and trap_ctrl.irq_buf(irq_mei_irq_c));
      trap_ctrl.irq_buf(irq_mti_irq_c) <= (trap_ctrl.irq_pnd(irq_mti_irq_c) and csr.mie_mti) or (trap_ctrl.env_pending and trap_ctrl.irq_buf(irq_mti_irq_c));

      -- NEORV32-specific fast interrupts --
      for i in 0 to 15 loop
        trap_ctrl.irq_buf(irq_firq_0_c+i) <= (trap_ctrl.irq_pnd(irq_firq_0_c+i) and csr.mie_firq(i)) or (trap_ctrl.env_pending and trap_ctrl.irq_buf(irq_firq_0_c+i));
      end loop;

      -- debug-mode entry --
      if (CPU_EXTENSION_RISCV_Sdext = true) then
        trap_ctrl.irq_buf(irq_db_halt_c) <= debug_ctrl.trig_halt or (trap_ctrl.env_pending and trap_ctrl.irq_buf(irq_db_halt_c));
        trap_ctrl.irq_buf(irq_db_step_c) <= debug_ctrl.trig_step or (trap_ctrl.env_pending and trap_ctrl.irq_buf(irq_db_step_c));
      else
        trap_ctrl.irq_buf(irq_db_halt_c) <= '0';
        trap_ctrl.irq_buf(irq_db_step_c) <= '0';
      end if;
    end if;
  end process interrupt_buffer;


  -- Trap Priority Logic --------------------------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  trap_priority: process(rstn_i, clk_i)
  begin
    if (rstn_i = '0') then
      trap_ctrl.cause <= (others => '0');
    elsif rising_edge(clk_i) then
      -- standard RISC-V exceptions --
      if    (trap_ctrl.exc_buf(exc_iaccess_c)  = '1') then trap_ctrl.cause <= trap_iaf_c; -- instruction access fault
      elsif (trap_ctrl.exc_buf(exc_illegal_c)  = '1') then trap_ctrl.cause <= trap_iil_c; -- illegal instruction
      elsif (trap_ctrl.exc_buf(exc_ialign_c)   = '1') then trap_ctrl.cause <= trap_ima_c; -- instruction address misaligned
      elsif (trap_ctrl.exc_buf(exc_ecall_c)    = '1') then trap_ctrl.cause <= trap_env_c(6 downto 2) & csr.privilege & csr.privilege; -- environment call (U/M)
      elsif (trap_ctrl.exc_buf(exc_ebreak_c)   = '1') then trap_ctrl.cause <= trap_brk_c; -- environment breakpoint
      elsif (trap_ctrl.exc_buf(exc_salign_c)   = '1') then trap_ctrl.cause <= trap_sma_c; -- store address misaligned
      elsif (trap_ctrl.exc_buf(exc_lalign_c)   = '1') then trap_ctrl.cause <= trap_lma_c; -- load address misaligned
      elsif (trap_ctrl.exc_buf(exc_saccess_c)  = '1') then trap_ctrl.cause <= trap_saf_c; -- store access fault
      elsif (trap_ctrl.exc_buf(exc_laccess_c)  = '1') then trap_ctrl.cause <= trap_laf_c; -- load access fault
      -- standard RISC-V debug mode exceptions and interrupts --
      elsif (trap_ctrl.irq_buf(irq_db_halt_c)  = '1') then trap_ctrl.cause <= trap_db_halt_c;  -- external halt request (async)
      elsif (trap_ctrl.exc_buf(exc_db_hw_c)    = '1') then trap_ctrl.cause <= trap_db_trig_c;  -- hardware trigger (sync)
      elsif (trap_ctrl.exc_buf(exc_db_break_c) = '1') then trap_ctrl.cause <= trap_db_break_c; -- breakpoint (sync)
      elsif (trap_ctrl.irq_buf(irq_db_step_c)  = '1') then trap_ctrl.cause <= trap_db_step_c;  -- single stepping (async)
      -- NEORV32-specific fast interrupts --
      elsif (trap_ctrl.irq_buf(irq_firq_0_c)   = '1') then trap_ctrl.cause <= trap_firq0_c;  -- fast interrupt channel 0
      elsif (trap_ctrl.irq_buf(irq_firq_1_c)   = '1') then trap_ctrl.cause <= trap_firq1_c;  -- fast interrupt channel 1
      elsif (trap_ctrl.irq_buf(irq_firq_2_c)   = '1') then trap_ctrl.cause <= trap_firq2_c;  -- fast interrupt channel 2
      elsif (trap_ctrl.irq_buf(irq_firq_3_c)   = '1') then trap_ctrl.cause <= trap_firq3_c;  -- fast interrupt channel 3
      elsif (trap_ctrl.irq_buf(irq_firq_4_c)   = '1') then trap_ctrl.cause <= trap_firq4_c;  -- fast interrupt channel 4
      elsif (trap_ctrl.irq_buf(irq_firq_5_c)   = '1') then trap_ctrl.cause <= trap_firq5_c;  -- fast interrupt channel 5
      elsif (trap_ctrl.irq_buf(irq_firq_6_c)   = '1') then trap_ctrl.cause <= trap_firq6_c;  -- fast interrupt channel 6
      elsif (trap_ctrl.irq_buf(irq_firq_7_c)   = '1') then trap_ctrl.cause <= trap_firq7_c;  -- fast interrupt channel 7
      elsif (trap_ctrl.irq_buf(irq_firq_8_c)   = '1') then trap_ctrl.cause <= trap_firq8_c;  -- fast interrupt channel 8
      elsif (trap_ctrl.irq_buf(irq_firq_9_c)   = '1') then trap_ctrl.cause <= trap_firq9_c;  -- fast interrupt channel 9
      elsif (trap_ctrl.irq_buf(irq_firq_10_c)  = '1') then trap_ctrl.cause <= trap_firq10_c; -- fast interrupt channel 10
      elsif (trap_ctrl.irq_buf(irq_firq_11_c)  = '1') then trap_ctrl.cause <= trap_firq11_c; -- fast interrupt channel 11
      elsif (trap_ctrl.irq_buf(irq_firq_12_c)  = '1') then trap_ctrl.cause <= trap_firq12_c; -- fast interrupt channel 12
      elsif (trap_ctrl.irq_buf(irq_firq_13_c)  = '1') then trap_ctrl.cause <= trap_firq13_c; -- fast interrupt channel 13
      elsif (trap_ctrl.irq_buf(irq_firq_14_c)  = '1') then trap_ctrl.cause <= trap_firq14_c; -- fast interrupt channel 14
      elsif (trap_ctrl.irq_buf(irq_firq_15_c)  = '1') then trap_ctrl.cause <= trap_firq15_c; -- fast interrupt channel 15
      -- standard RISC-V interrupts --
      elsif (trap_ctrl.irq_buf(irq_mei_irq_c)  = '1') then trap_ctrl.cause <= trap_mei_c; -- machine external interrupt (MEI)
      elsif (trap_ctrl.irq_buf(irq_msi_irq_c)  = '1') then trap_ctrl.cause <= trap_msi_c; -- machine software interrupt (MSI)
      elsif (trap_ctrl.irq_buf(irq_mti_irq_c)  = '1') then trap_ctrl.cause <= trap_mti_c; -- machine timer interrupt (MTI)
      --
      else trap_ctrl.cause <= trap_mti_c; end if; -- don't care
    end if;
  end process trap_priority;


  -- Trap Controller ------------------------------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  trap_controller: process(rstn_i, clk_i)
  begin
    if (rstn_i = '0') then
      trap_ctrl.env_pending <= '0';
    elsif rising_edge(clk_i) then
      if (trap_ctrl.env_pending = '0') then -- no pending trap environment yet
        -- trigger IRQ only in EXECUTE states to *continue execution* even if there are permanent interrupt requests
        if (trap_ctrl.exc_fire = '1') or ((trap_ctrl.irq_fire = '1') and (execute_engine.state = EXECUTE)) then
          trap_ctrl.env_pending <= '1'; -- now execute engine can start trap handling
        end if;
      elsif (trap_ctrl.env_enter = '1') then -- start of trap environment acknowledged by execute engine
        trap_ctrl.env_pending <= '0';
      end if;
    end if;
  end process trap_controller;

  -- wake-up from / do not enter sleep mode: during debugging or on pending IRQ --
  trap_ctrl.wakeup <= or_reduce_f(trap_ctrl.irq_buf) or debug_ctrl.running or csr.dcsr_step;

  -- any exception? --
  trap_ctrl.exc_fire <= '1' when (or_reduce_f(trap_ctrl.exc_buf) = '1') else '0'; -- sync. exceptions CANNOT be masked

  -- any interrupt? --
  trap_ctrl.irq_fire <= '1' when
    (
     (or_reduce_f(trap_ctrl.irq_buf(irq_firq_15_c downto irq_msi_irq_c)) = '1') and -- pending IRQ
     ((csr.mstatus_mie = '1') or (csr.privilege = priv_mode_u_c)) and -- take IRQ when in M-mode and MIE=1 OR when in U-mode
     (debug_ctrl.running = '0') and (csr.dcsr_step = '0') -- no IRQs when in debug-mode or during debug single-stepping
    ) or
    (trap_ctrl.irq_buf(irq_db_step_c) = '1') or -- debug-mode single-step IRQ
    (trap_ctrl.irq_buf(irq_db_halt_c) = '1') else '0'; -- debug-mode halt IRQ

  -- exception program counter (for updating xPC CSRs) --
  trap_ctrl.epc <= execute_engine.next_pc when (trap_ctrl.cause(trap_ctrl.cause'left) = '1') else execute_engine.pc;


-- ****************************************************************************************************************************
-- Control and Status Registers (CSRs)
-- ****************************************************************************************************************************

  -- CSR Access Address ---------------------------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  csr.addr  <= execute_engine.ir(instr_imm12_msb_c downto instr_imm12_lsb_c);
  -- simplified CSR read address - [WARNING] M-mode (9:8 = 11) and U-mode (9:8 = 00) CSRs only! --
  csr.raddr <= csr.addr(11 downto 10) & csr.addr(8) & csr.addr(8) & csr.addr(7 downto 0);


  -- CSR Write Data -------------------------------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  csr_write_data: process(execute_engine.ir, csr.rdata, rs1_i)
    variable tmp_v : std_ulogic_vector(XLEN-1 downto 0);
  begin
    -- immediate/register operand --
    if (execute_engine.ir(instr_funct3_msb_c) = '1') then
      tmp_v := (others => '0');
      tmp_v(4 downto 0) := execute_engine.ir(19 downto 15); -- uimm5
    else
      tmp_v := rs1_i;
    end if;
    -- tiny ALU to compute CSR write data --
    case execute_engine.ir(instr_funct3_msb_c-1 downto instr_funct3_lsb_c) is
      when "10"   => csr.wdata <= csr.rdata or tmp_v; -- set
      when "11"   => csr.wdata <= csr.rdata and (not tmp_v); -- clear
      when others => csr.wdata <= tmp_v; -- write
    end case;
  end process csr_write_data;


  -- External CSR Interface -----------------------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  xcsr_we_o    <= csr.we;
  xcsr_addr_o  <= csr.addr;
  xcsr_wdata_o <= csr.wdata;
  xcsr_rdata   <= xcsr_rdata_i;


  -- CSR Write Access -----------------------------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  csr_write_access: process(rstn_i, clk_i)
  begin
    if (rstn_i = '0') then
      csr.we             <= '0';
      csr.privilege      <= priv_mode_m_c;
      csr.mstatus_mie    <= '0';
      csr.mstatus_mpie   <= '0';
      csr.mstatus_mpp    <= priv_mode_m_c;
      csr.mstatus_mprv   <= '0';
      csr.mstatus_tw     <= '0';
      csr.mie_msi        <= '0';
      csr.mie_mei        <= '0';
      csr.mie_mti        <= '0';
      csr.mie_firq       <= (others => '0');
      csr.mtvec          <= CPU_BOOT_ADDR(XLEN-1 downto 2) & "00"; -- 32-bit aligned boot address
      csr.mscratch       <= x"19880704";
      csr.mepc           <= CPU_BOOT_ADDR(XLEN-1 downto 2) & "00"; -- 32-bit aligned boot address
      csr.mcause         <= (others => '0');
      csr.mtval          <= (others => '0');
      csr.mtinst         <= (others => '0');
      csr.mcounteren     <= '0';
      csr.mcountinhibit  <= (others => '0');
      csr.mip_firq_wdata <= (others => '0');
      csr.mip_firq_we    <= '0';
      csr.dcsr_ebreakm   <= '0';
      csr.dcsr_ebreaku   <= '0';
      csr.dcsr_step      <= '0';
      csr.dcsr_prv       <= priv_mode_m_c;
      csr.dcsr_cause     <= (others => '0');
      csr.dpc            <= CPU_BOOT_ADDR(XLEN-1 downto 2) & "00"; -- 32-bit aligned boot address
      csr.dscratch0      <= (others => '0');
      csr.tdata1_hit_clr <= '0';
      csr.tdata1_execute <= '0';
      csr.tdata1_action  <= '0';
      csr.tdata1_dmode   <= '0';
      csr.tdata2         <= (others => '0');
    elsif rising_edge(clk_i) then

      -- defaults --
      csr.we             <= csr.we_nxt and (not trap_ctrl.exc_buf(exc_illegal_c)); -- write if not an illegal instruction
      csr.mip_firq_we    <= '0'; -- no write to MIP.FIRQ by default
      csr.tdata1_hit_clr <= '0';

      -- ********************************************************************************
      -- CSR access by application software
      -- ********************************************************************************
      if (csr.we = '1') then
        case csr.addr is

          -- --------------------------------------------------------------------
          -- machine trap setup --
          -- --------------------------------------------------------------------
          when csr_mstatus_c => -- machine status register
            csr.mstatus_mie  <= csr.wdata(03);
            csr.mstatus_mpie <= csr.wdata(07);
            if (CPU_EXTENSION_RISCV_U = true) then
              csr.mstatus_mpp  <= csr.wdata(11) or csr.wdata(12); -- everything /= U will fall back to M
              csr.mstatus_mprv <= csr.wdata(17);
              csr.mstatus_tw   <= csr.wdata(21);
            end if;

          when csr_mie_c => -- machine interrupt enable register
            csr.mie_msi  <= csr.wdata(03);
            csr.mie_mti  <= csr.wdata(07);
            csr.mie_mei  <= csr.wdata(11);
            csr.mie_firq <= csr.wdata(31 downto 16);

          when csr_mtvec_c => -- machine trap-handler base address
            if (csr.wdata(1 downto 0) = "01") then
              csr.mtvec <= csr.wdata(XLEN-1 downto 7) & "00000" & "01"; -- mtvec.MODE=1 (vectored)
            else
              csr.mtvec <= csr.wdata(XLEN-1 downto 2) & "00"; -- mtvec.MODE=0 (direct)
            end if;

          when csr_mcounteren_c => -- machine counter access enable
            if (CPU_EXTENSION_RISCV_U = true) then
              if (CPU_EXTENSION_RISCV_Zicntr = true) and (CPU_EXTENSION_RISCV_Zihpm = true) then
                csr.mcounteren <= or_reduce_f(csr.wdata(15 downto 3)) or csr.wdata(2) or csr.wdata(0); -- hpms, instret, cycle
              elsif (CPU_EXTENSION_RISCV_Zicntr = true) then
                csr.mcounteren <= csr.wdata(2) or csr.wdata(0); -- instret, cycle
              elsif (CPU_EXTENSION_RISCV_Zihpm = true) then
                csr.mcounteren <= or_reduce_f(csr.wdata(15 downto 3)); -- hpms
              end if;
            end if;

          -- --------------------------------------------------------------------
          -- machine trap handling --
          -- --------------------------------------------------------------------
          when csr_mscratch_c => -- machine scratch register
            csr.mscratch <= csr.wdata;

          when csr_mepc_c => -- machine exception program counter
            csr.mepc <= csr.wdata(XLEN-1 downto 1) & '0';
            if (CPU_EXTENSION_RISCV_C = false) then -- RISC-V priv. spec.: MEPC[1] is masked when IALIGN = 32
              csr.mepc(1) <= '0';
            end if;

          when csr_mcause_c => -- machine trap cause
            csr.mcause <= csr.wdata(31) & csr.wdata(4 downto 0); -- type (exception/interrupt) & identifier

          when csr_mip_c => -- machine interrupt pending
            csr.mip_firq_wdata <= csr.wdata(31 downto 16);
            csr.mip_firq_we    <= '1'; -- trigger MIP.FIRQ write

          -- --------------------------------------------------------------------
          -- machine counter setup --
          -- --------------------------------------------------------------------
          when csr_mcountinhibit_c => -- machine counter-inhibit register
            if (CPU_EXTENSION_RISCV_Zicntr = true) then
              csr.mcountinhibit(0) <= csr.wdata(0);
              csr.mcountinhibit(2) <= csr.wdata(2);
            end if;
            if (CPU_EXTENSION_RISCV_Zihpm = true) then
              csr.mcountinhibit(15 downto 3) <= csr.wdata(15 downto 3);
            end if;

          -- --------------------------------------------------------------------
          -- debug mode CSRs --
          -- --------------------------------------------------------------------
          when csr_dcsr_c => -- debug mode control and status register
            if (CPU_EXTENSION_RISCV_Sdext = true) then
              csr.dcsr_ebreakm <= csr.wdata(15);
              csr.dcsr_step    <= csr.wdata(2);
              if (CPU_EXTENSION_RISCV_U = true) then
                csr.dcsr_ebreaku <= csr.wdata(12);
                csr.dcsr_prv     <= csr.wdata(1) or csr.wdata(0); -- everything /= U will fall back to M
              end if;
            end if;

          when csr_dpc_c => -- debug mode program counter
            if (CPU_EXTENSION_RISCV_Sdext = true) then
              csr.dpc <= csr.wdata(XLEN-1 downto 1) & '0';
              if (CPU_EXTENSION_RISCV_C = false) then -- RISC-V priv. spec.: DPC[1] is masked when IALIGN = 32
                csr.dpc(1) <= '0';
              end if;
            end if;

          when csr_dscratch0_c => -- debug mode scratch register 0
            if (CPU_EXTENSION_RISCV_Sdext = true) then
              csr.dscratch0 <= csr.wdata;
            end if;

          -- --------------------------------------------------------------------
          -- trigger module CSRs --
          -- --------------------------------------------------------------------
          when csr_tdata1_c => -- match control
            if (CPU_EXTENSION_RISCV_Sdtrig = true) then
              if (csr.tdata1_dmode = '0') or (debug_ctrl.running = '1') then -- write access from debug-mode only?
                csr.tdata1_execute <= csr.wdata(2);
                csr.tdata1_action  <= csr.wdata(12);
                csr.tdata1_hit_clr <= not csr.wdata(22);
              end if;
              if (debug_ctrl.running = '1') then -- writable from debug-mode only
                csr.tdata1_dmode <= csr.wdata(27);
              end if;
            end if;

          when csr_tdata2_c => -- address compare
            if (CPU_EXTENSION_RISCV_Sdtrig = true) then
              if (csr.tdata1_dmode = '0') or (debug_ctrl.running = '1') then -- write access from debug-mode only?
                csr.tdata2 <= csr.wdata(XLEN-1 downto 1) & '0';
              end if;
            end if;

          -- --------------------------------------------------------------------
          -- not implemented (or implemented externally) --
          -- --------------------------------------------------------------------
          when others => NULL;

        end case;

      -- ********************************************************************************
      -- Hardware CSR access: TRAP ENTER
      -- ********************************************************************************
      elsif (trap_ctrl.env_enter = '1') then

        -- NORMAL trap entry - no CSR update when in debug-mode! --
        if (CPU_EXTENSION_RISCV_Sdext = false) or ((trap_ctrl.cause(5) = '0') and (debug_ctrl.running = '0')) then
          csr.mcause <= trap_ctrl.cause(trap_ctrl.cause'left) & trap_ctrl.cause(4 downto 0); -- trap type & identifier
          csr.mepc   <= trap_ctrl.epc(XLEN-1 downto 1) & '0'; -- trap PC
          -- trap value --
          if (trap_ctrl.cause(6) = '0') and (trap_ctrl.cause(2) = '1') then -- load/store misaligned/access faults [hacky!]
            csr.mtval <= mar_i; -- faulting data access address
          else -- everything else including all interrupts
            csr.mtval <= (others => '0');
          end if;
          -- trap instruction --
          if (trap_ctrl.cause(6) = '0') then -- exception
            csr.mtinst <= execute_engine.ir;
            if (execute_engine.is_ci = '1') and (CPU_EXTENSION_RISCV_C = true) then
              csr.mtinst(1) <= '0'; -- RISC-V priv. spec: clear bit 1 if compressed instruction
            end if;
          else -- interrupt
            csr.mtinst <= (others => '0');
          end if;
          -- update privilege level and interrupt-enable stack --
          csr.privilege    <= priv_mode_m_c; -- execute trap in machine mode
          csr.mstatus_mie  <= '0'; -- disable interrupts
          csr.mstatus_mpie <= csr.mstatus_mie; -- backup previous mie state
          csr.mstatus_mpp  <= csr.privilege; -- backup previous privilege mode
        end if;

        -- DEBUG MODE entry - no CSR update when already in debug-mode! --
        if (CPU_EXTENSION_RISCV_Sdext = true) and (trap_ctrl.cause(5) = '1') and (debug_ctrl.running = '0') then
          -- trap cause --
          csr.dcsr_cause <= trap_ctrl.cause(2 downto 0); -- why did we enter debug mode?
          -- current privilege mode when debug mode was entered --
          csr.dcsr_prv <= csr.privilege;
          -- trap PC --
          csr.dpc <= trap_ctrl.epc(XLEN-1 downto 1) & '0';
        end if;

      -- ********************************************************************************
      -- Hardware CSR access: TRAP EXIT
      -- ********************************************************************************
      elsif (trap_ctrl.env_exit = '1') then

        -- return from debug mode --
        if (CPU_EXTENSION_RISCV_Sdext = true) and (debug_ctrl.running = '1') then
          if (CPU_EXTENSION_RISCV_U = true) then
            csr.privilege <= csr.dcsr_prv;
            if (csr.dcsr_prv /= priv_mode_m_c) then
              csr.mstatus_mprv <= '0'; -- clear if return to priv. mode less than M
            end if;
          end if;

        -- return from normal trap --
        else
          if (CPU_EXTENSION_RISCV_U = true) then
            csr.privilege   <= csr.mstatus_mpp; -- restore previous privilege mode
            csr.mstatus_mpp <= priv_mode_u_c; -- set to least-privileged mode that is supported
            if (csr.mstatus_mpp /= priv_mode_m_c) then
              csr.mstatus_mprv <= '0'; -- clear if return to priv. mode less than M
            end if;
          end if;
          csr.mstatus_mie  <= csr.mstatus_mpie; -- restore machine-mode IRQ enable flag
          csr.mstatus_mpie <= '1';
        end if;

      end if;

      -- ********************************************************************************
      -- Override - hardwire/terminate unimplemented registers/bits
      -- ********************************************************************************

      -- hardwired bits --
      csr.mcountinhibit(1) <= '0'; -- time[h] not implemented

      -- no base counters --
      if (CPU_EXTENSION_RISCV_Zicntr = false) then
        csr.mcountinhibit(2 downto 0) <= (others => '0');
      end if;

      -- no hardware performance monitors --
      if (CPU_EXTENSION_RISCV_Zihpm = false) then
        csr.mcountinhibit(15 downto 3) <= (others => '0');
      end if;

      -- no counters at all --
      if (CPU_EXTENSION_RISCV_Zicntr = false) and (CPU_EXTENSION_RISCV_Zihpm = false) then
        csr.mcounteren <= '0';
      end if;

      -- no user mode --
      if (CPU_EXTENSION_RISCV_U = false) then
        csr.privilege    <= priv_mode_m_c;
        csr.mstatus_mpp  <= priv_mode_m_c;
        csr.mstatus_mprv <= '0';
        csr.mstatus_tw   <= '0';
        csr.dcsr_ebreaku <= '0';
        csr.dcsr_prv     <= '0';
        csr.mcounteren   <= '0';
      end if;

      -- no debug mode --
      if (CPU_EXTENSION_RISCV_Sdext = false) then
        csr.dcsr_ebreakm <= '0';
        csr.dcsr_step    <= '0';
        csr.dcsr_ebreaku <= '0';
        csr.dcsr_prv     <= priv_mode_m_c;
        csr.dcsr_cause   <= (others => '0');
        csr.dpc          <= (others => '0');
        csr.dscratch0    <= (others => '0');
      end if;

      -- no trigger module --
      if (CPU_EXTENSION_RISCV_Sdtrig = false) then
        csr.tdata1_execute <= '0';
        csr.tdata1_action  <= '0';
        csr.tdata1_dmode   <= '0';
        csr.tdata2         <= (others => '0');
      end if;

    end if;
  end process csr_write_access;

  -- effective privilege mode is MACHINE when in debug mode --
  csr.privilege_eff <= priv_mode_m_c when (debug_ctrl.running = '1') else csr.privilege;


  -- CSR Read Access ------------------------------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  csr_read_access: process(csr, trap_ctrl.irq_pnd, hpmevent_rd, cnt_lo_rd, cnt_hi_rd)
  begin
    csr_rdata <= (others => '0'); -- default
    case csr.raddr is

      -- --------------------------------------------------------------------
      -- machine trap setup --
      -- --------------------------------------------------------------------
      when csr_mstatus_c => -- machine status register - low word
        csr_rdata(03) <= csr.mstatus_mie;
        csr_rdata(07) <= csr.mstatus_mpie;
        csr_rdata(12 downto 11) <= (others => csr.mstatus_mpp);
        csr_rdata(17) <= csr.mstatus_mprv;
        csr_rdata(21) <= csr.mstatus_tw and bool_to_ulogic_f(CPU_EXTENSION_RISCV_U);

--    when csr_mstatush_c => csr_rdata <= (others => '0'); -- machine status register - high word - hardwired to zero

      when csr_misa_c => -- ISA and extensions
        csr_rdata(00) <= bool_to_ulogic_f(CPU_EXTENSION_RISCV_A);     -- A CPU extension
        csr_rdata(01) <= bool_to_ulogic_f(CPU_EXTENSION_RISCV_B);     -- B CPU extension
        csr_rdata(02) <= bool_to_ulogic_f(CPU_EXTENSION_RISCV_C);     -- C CPU extension
        csr_rdata(04) <= bool_to_ulogic_f(CPU_EXTENSION_RISCV_E);     -- E CPU extension
        csr_rdata(08) <= bool_to_ulogic_f(not CPU_EXTENSION_RISCV_E); -- I CPU extension (if not E)
        csr_rdata(12) <= bool_to_ulogic_f(CPU_EXTENSION_RISCV_M);     -- M CPU extension
        csr_rdata(20) <= bool_to_ulogic_f(CPU_EXTENSION_RISCV_U);     -- U CPU extension
        csr_rdata(23) <= '1';                                         -- X CPU extension (non-standard extensions / NEORV32-specific)
        csr_rdata(31 downto 30) <= "01"; -- MXL = 32

      when csr_mie_c => -- machine interrupt-enable register
        csr_rdata(03) <= csr.mie_msi;
        csr_rdata(07) <= csr.mie_mti;
        csr_rdata(11) <= csr.mie_mei;
        csr_rdata(31 downto 16) <= csr.mie_firq;

      when csr_mtvec_c => -- machine trap-handler base address
        csr_rdata <= csr.mtvec;

      when csr_mcounteren_c => -- machine counter enable register
        if (CPU_EXTENSION_RISCV_U = true) then
          if (CPU_EXTENSION_RISCV_Zicntr = true) then
            csr_rdata(0) <= csr.mcounteren; -- cycle
            csr_rdata(2) <= csr.mcounteren; -- instret
          end if;
          if (CPU_EXTENSION_RISCV_Zihpm = true) then
            csr_rdata(15 downto 3) <= (others => csr.mcounteren); -- hpmcounter
          end if;
        end if;

      -- --------------------------------------------------------------------
      -- machine configuration --
      -- --------------------------------------------------------------------
--    when csr_menvcfg_c  => csr_rdata <= (others => '0'); -- hardwired to zero
--    when csr_menvcfgh_c => csr_rdata <= (others => '0'); -- hardwired to zero

      -- --------------------------------------------------------------------
      -- machine trap handling --
      -- --------------------------------------------------------------------
      when csr_mscratch_c => -- machine scratch register
        csr_rdata <= csr.mscratch;

      when csr_mepc_c => -- machine exception program counter
        csr_rdata <= csr.mepc(XLEN-1 downto 1) & '0';

      when csr_mcause_c => -- machine trap cause
        csr_rdata(31)         <= csr.mcause(5);
        csr_rdata(4 downto 0) <= csr.mcause(4 downto 0);

      when csr_mtval_c => -- machine trap value
        csr_rdata <= csr.mtval;

      when csr_mip_c => -- machine interrupt pending
        csr_rdata(03)           <= trap_ctrl.irq_pnd(irq_msi_irq_c);
        csr_rdata(07)           <= trap_ctrl.irq_pnd(irq_mti_irq_c);
        csr_rdata(11)           <= trap_ctrl.irq_pnd(irq_mei_irq_c);
        csr_rdata(31 downto 16) <= trap_ctrl.irq_pnd(irq_firq_15_c downto irq_firq_0_c);

      when csr_mtinst_c => -- machine trap instruction
        csr_rdata <= csr.mtinst;

      -- --------------------------------------------------------------------
      -- machine counter setup --
      -- --------------------------------------------------------------------
      when csr_mcountinhibit_c => -- machine counter-inhibit register
        if (CPU_EXTENSION_RISCV_Zicntr = true) then
          csr_rdata(0) <= csr.mcountinhibit(0); -- [m]cycle[h]
          csr_rdata(2) <= csr.mcountinhibit(2); -- [m]instret[h]
        end if;
        if (CPU_EXTENSION_RISCV_Zihpm = true) and (hpm_num_c > 0) then
          for i in 3 to (hpm_num_c+3)-1 loop
            csr_rdata(i) <= csr.mcountinhibit(i); -- [m]hpmcounter*[h]
          end loop;
        end if;

      -- HPM event configuration --
      when csr_mhpmevent3_c  => if (hpm_num_c > 00) then csr_rdata <= hpmevent_rd(03); end if;
      when csr_mhpmevent4_c  => if (hpm_num_c > 01) then csr_rdata <= hpmevent_rd(04); end if;
      when csr_mhpmevent5_c  => if (hpm_num_c > 02) then csr_rdata <= hpmevent_rd(05); end if;
      when csr_mhpmevent6_c  => if (hpm_num_c > 03) then csr_rdata <= hpmevent_rd(06); end if;
      when csr_mhpmevent7_c  => if (hpm_num_c > 04) then csr_rdata <= hpmevent_rd(07); end if;
      when csr_mhpmevent8_c  => if (hpm_num_c > 05) then csr_rdata <= hpmevent_rd(08); end if;
      when csr_mhpmevent9_c  => if (hpm_num_c > 06) then csr_rdata <= hpmevent_rd(09); end if;
      when csr_mhpmevent10_c => if (hpm_num_c > 07) then csr_rdata <= hpmevent_rd(10); end if;
      when csr_mhpmevent11_c => if (hpm_num_c > 08) then csr_rdata <= hpmevent_rd(11); end if;
      when csr_mhpmevent12_c => if (hpm_num_c > 09) then csr_rdata <= hpmevent_rd(12); end if;
      when csr_mhpmevent13_c => if (hpm_num_c > 10) then csr_rdata <= hpmevent_rd(13); end if;
      when csr_mhpmevent14_c => if (hpm_num_c > 11) then csr_rdata <= hpmevent_rd(14); end if;
      when csr_mhpmevent15_c => if (hpm_num_c > 12) then csr_rdata <= hpmevent_rd(15); end if;

      -- --------------------------------------------------------------------
      -- counters and timers --
      -- --------------------------------------------------------------------
      -- low word --
      when csr_mcycle_c        | csr_cycle_c        => if (CPU_EXTENSION_RISCV_Zicntr) then csr_rdata <= cnt_lo_rd(00); end if;
      when csr_minstret_c      | csr_instret_c      => if (CPU_EXTENSION_RISCV_Zicntr) then csr_rdata <= cnt_lo_rd(02); end if;
      when csr_mhpmcounter3_c  | csr_hpmcounter3_c  => if (hpm_num_c > 00) then csr_rdata <= cnt_lo_rd(03); end if;
      when csr_mhpmcounter4_c  | csr_hpmcounter4_c  => if (hpm_num_c > 01) then csr_rdata <= cnt_lo_rd(04); end if;
      when csr_mhpmcounter5_c  | csr_hpmcounter5_c  => if (hpm_num_c > 02) then csr_rdata <= cnt_lo_rd(05); end if;
      when csr_mhpmcounter6_c  | csr_hpmcounter6_c  => if (hpm_num_c > 03) then csr_rdata <= cnt_lo_rd(06); end if;
      when csr_mhpmcounter7_c  | csr_hpmcounter7_c  => if (hpm_num_c > 04) then csr_rdata <= cnt_lo_rd(07); end if;
      when csr_mhpmcounter8_c  | csr_hpmcounter8_c  => if (hpm_num_c > 05) then csr_rdata <= cnt_lo_rd(08); end if;
      when csr_mhpmcounter9_c  | csr_hpmcounter9_c  => if (hpm_num_c > 06) then csr_rdata <= cnt_lo_rd(09); end if;
      when csr_mhpmcounter10_c | csr_hpmcounter10_c => if (hpm_num_c > 07) then csr_rdata <= cnt_lo_rd(10); end if;
      when csr_mhpmcounter11_c | csr_hpmcounter11_c => if (hpm_num_c > 08) then csr_rdata <= cnt_lo_rd(11); end if;
      when csr_mhpmcounter12_c | csr_hpmcounter12_c => if (hpm_num_c > 09) then csr_rdata <= cnt_lo_rd(12); end if;
      when csr_mhpmcounter13_c | csr_hpmcounter13_c => if (hpm_num_c > 10) then csr_rdata <= cnt_lo_rd(13); end if;
      when csr_mhpmcounter14_c | csr_hpmcounter14_c => if (hpm_num_c > 11) then csr_rdata <= cnt_lo_rd(14); end if;
      when csr_mhpmcounter15_c | csr_hpmcounter15_c => if (hpm_num_c > 12) then csr_rdata <= cnt_lo_rd(15); end if;

      -- high word --
      when csr_mcycleh_c        | csr_cycleh_c        => if (CPU_EXTENSION_RISCV_Zicntr) then csr_rdata <= cnt_hi_rd(00); end if;
      when csr_minstreth_c      | csr_instreth_c      => if (CPU_EXTENSION_RISCV_Zicntr) then csr_rdata <= cnt_hi_rd(02); end if;
      when csr_mhpmcounter3h_c  | csr_hpmcounter3h_c  => if (hpm_num_c > 00) then csr_rdata <= cnt_hi_rd(03); end if;
      when csr_mhpmcounter4h_c  | csr_hpmcounter4h_c  => if (hpm_num_c > 01) then csr_rdata <= cnt_hi_rd(04); end if;
      when csr_mhpmcounter5h_c  | csr_hpmcounter5h_c  => if (hpm_num_c > 02) then csr_rdata <= cnt_hi_rd(05); end if;
      when csr_mhpmcounter6h_c  | csr_hpmcounter6h_c  => if (hpm_num_c > 03) then csr_rdata <= cnt_hi_rd(06); end if;
      when csr_mhpmcounter7h_c  | csr_hpmcounter7h_c  => if (hpm_num_c > 04) then csr_rdata <= cnt_hi_rd(07); end if;
      when csr_mhpmcounter8h_c  | csr_hpmcounter8h_c  => if (hpm_num_c > 05) then csr_rdata <= cnt_hi_rd(08); end if;
      when csr_mhpmcounter9h_c  | csr_hpmcounter9h_c  => if (hpm_num_c > 06) then csr_rdata <= cnt_hi_rd(09); end if;
      when csr_mhpmcounter10h_c | csr_hpmcounter10h_c => if (hpm_num_c > 07) then csr_rdata <= cnt_hi_rd(10); end if;
      when csr_mhpmcounter11h_c | csr_hpmcounter11h_c => if (hpm_num_c > 08) then csr_rdata <= cnt_hi_rd(11); end if;
      when csr_mhpmcounter12h_c | csr_hpmcounter12h_c => if (hpm_num_c > 09) then csr_rdata <= cnt_hi_rd(12); end if;
      when csr_mhpmcounter13h_c | csr_hpmcounter13h_c => if (hpm_num_c > 10) then csr_rdata <= cnt_hi_rd(13); end if;
      when csr_mhpmcounter14h_c | csr_hpmcounter14h_c => if (hpm_num_c > 11) then csr_rdata <= cnt_hi_rd(14); end if;
      when csr_mhpmcounter15h_c | csr_hpmcounter15h_c => if (hpm_num_c > 12) then csr_rdata <= cnt_hi_rd(15); end if;

      -- --------------------------------------------------------------------
      -- machine information registers --
      -- --------------------------------------------------------------------
      when csr_mvendorid_c  => csr_rdata <= VENDOR_ID; -- vendor's JEDEC ID
      when csr_marchid_c    => csr_rdata(4 downto 0) <= "10011"; -- architecture ID - official RISC-V open-source arch ID
      when csr_mimpid_c     => csr_rdata <= hw_version_c; -- implementation ID -- NEORV32 hardware version
      when csr_mhartid_c    => csr_rdata <= HART_ID; -- hardware thread ID
--    when csr_mconfigptr_c => csr_rdata <= (others => '0'); -- machine configuration pointer register - hardwired to zero

      -- --------------------------------------------------------------------
      -- debug mode CSRs --
      -- --------------------------------------------------------------------
      when csr_dcsr_c      => if (CPU_EXTENSION_RISCV_Sdext) then csr_rdata <= csr.dcsr_rd;   end if; -- debug mode control and status
      when csr_dpc_c       => if (CPU_EXTENSION_RISCV_Sdext) then csr_rdata <= csr.dpc;       end if; -- debug mode program counter
      when csr_dscratch0_c => if (CPU_EXTENSION_RISCV_Sdext) then csr_rdata <= csr.dscratch0; end if; -- debug mode scratch register 0

      -- --------------------------------------------------------------------
      -- trigger module CSRs --
      -- --------------------------------------------------------------------
--    when csr_tselect_c => if (CPU_EXTENSION_RISCV_Sdtrig) then csr_rdata <= (others => '0'); end if; -- hardwired to zero = only 1 trigger available
      when csr_tdata1_c  => if (CPU_EXTENSION_RISCV_Sdtrig) then csr_rdata <= csr.tdata1_rd;   end if; -- match control
      when csr_tdata2_c  => if (CPU_EXTENSION_RISCV_Sdtrig) then csr_rdata <= csr.tdata2;      end if; -- address-compare
      when csr_tinfo_c => -- trigger information
        if (CPU_EXTENSION_RISCV_Sdtrig) then
          csr_rdata(31 downto 24) <= x"01"; -- Sdtrig ISA spec. version 1.0
          csr_rdata(15 downto 00) <= x"0006"; -- mcontrol6 type trigger only
        end if;

      -- --------------------------------------------------------------------
      -- NEORV32-specific (RISC-V "custom") read-only CSRs --
      -- --------------------------------------------------------------------
      -- machine extended ISA extensions information --
      when csr_mxisa_c =>
        -- extended ISA (sub-)extensions --
        csr_rdata(00) <= '1';                                          -- Zicsr: CSR access (always enabled)
        csr_rdata(01) <= '1';                                          -- Zifencei: instruction stream sync. (always enabled)
        csr_rdata(02) <= bool_to_ulogic_f(CPU_EXTENSION_RISCV_Zmmul);  -- Zmmul: mul/div
        csr_rdata(03) <= bool_to_ulogic_f(CPU_EXTENSION_RISCV_Zxcfu);  -- Zxcfu: custom RISC-V instructions
        csr_rdata(04) <= '0';                                          -- reserved
        csr_rdata(05) <= bool_to_ulogic_f(CPU_EXTENSION_RISCV_Zfinx);  -- Zfinx: FPU using x registers
        csr_rdata(06) <= bool_to_ulogic_f(CPU_EXTENSION_RISCV_Zicond); -- Zicond: integer conditional operations
        csr_rdata(07) <= bool_to_ulogic_f(CPU_EXTENSION_RISCV_Zicntr); -- Zicntr: base counters
        csr_rdata(08) <= bool_to_ulogic_f(CPU_EXTENSION_RISCV_Smpmp);  -- Smpmp: physical memory protection
        csr_rdata(09) <= bool_to_ulogic_f(CPU_EXTENSION_RISCV_Zihpm);  -- Zihpm: hardware performance monitors
        csr_rdata(10) <= bool_to_ulogic_f(CPU_EXTENSION_RISCV_Sdext);  -- Sdext: RISC-V (external) debug mode
        csr_rdata(11) <= bool_to_ulogic_f(CPU_EXTENSION_RISCV_Sdtrig); -- Sdtrig: trigger module
        -- misc --
        csr_rdata(20) <= bool_to_ulogic_f(is_simulation_c);            -- is this a simulation?
        -- tuning options --
        csr_rdata(29) <= bool_to_ulogic_f(REGFILE_HW_RST);             -- full hardware reset of register file
        csr_rdata(30) <= bool_to_ulogic_f(FAST_MUL_EN);                -- DSP-based multiplication (M extensions only)
        csr_rdata(31) <= bool_to_ulogic_f(FAST_SHIFT_EN);              -- parallel logic for shifts (barrel shifters)

      -- --------------------------------------------------------------------
      -- undefined/unavailable (or implemented externally) --
      -- --------------------------------------------------------------------
      when others => NULL; -- read as zero

    end case;
  end process csr_read_access;


  -- CSR read-data gate --
  csr_read_reg: process(rstn_i, clk_i)
  begin
    if (rstn_i = '0') then
      csr.re    <= '0';
      csr.rdata <= (others => '0');
    elsif rising_edge(clk_i) then
      csr.re <= csr.re_nxt;
      if (csr.re = '1') then
        csr.rdata <= csr_rdata or xcsr_rdata;
      else
        csr.rdata <= (others => '0'); -- output zero if no valid CSR read access operation
      end if;
    end if;
  end process csr_read_reg;

  -- CSR read data output --
  csr_rdata_o <= csr.rdata;


-- ****************************************************************************************************************************
-- CPU Counters (Standard Counters and Hardware Performance Monitors)
-- ****************************************************************************************************************************

  -- Counter CSRs ---------------------------------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  -- write enable decoder --
  cnt_we: process(csr)
  begin
    cnt.we_lo <= (others => '0');
    cnt.we_hi <= (others => '0');
    -- [NOTE] no need to check bits 6:4 of the address as they're always zero (checked by illegal CSR logic)
    if (csr.we = '1') and (csr.addr(11 downto 8) = csr_mcycle_c(11 downto 8)) then
      if (csr.addr(7) = '0') then -- low word
        cnt.we_lo(to_integer(unsigned(csr.addr(3 downto 0)))) <= '1';
      else -- high word
        cnt.we_hi(to_integer(unsigned(csr.addr(3 downto 0)))) <= '1';
      end if;
    end if;
  end process cnt_we;


  -- hardware counters --
  cpu_counter_gen:
  for i in 0 to 2+hpm_num_c generate

    -- counter CSRs --
    cnt_reg: process(rstn_i, clk_i)
    begin
      if (rstn_i = '0') then
        cnt.lo(i)  <= (others => '0');
        cnt.ovf(i) <= (others => '0');
        cnt.hi(i)  <= (others => '0');
      elsif rising_edge(clk_i) then
        -- low word --
        if (cnt.we_lo(i) = '1') then
          cnt.lo(i) <= csr.wdata;
        else
          cnt.lo(i) <= cnt.nxt(i)(XLEN-1 downto 0);
        end if;
        cnt.ovf(i)(0) <= cnt.nxt(i)(XLEN);
        -- high word --
        if (cnt.we_hi(i) = '1') then
          cnt.hi(i) <= csr.wdata;
        else
          cnt.hi(i) <= std_ulogic_vector(unsigned(cnt.hi(i)) + unsigned(cnt.ovf(i)));
        end if;
      end if;
    end process cnt_reg;

    -- low-word increment --
    cnt.nxt(i) <= std_ulogic_vector(unsigned('0' & cnt.lo(i)) + 1) when (cnt.inc(i) = '1') else
                  std_ulogic_vector(unsigned('0' & cnt.lo(i)) + 0);

  end generate;


  -- read-back --
  cnt_connect: process(cnt)
  begin
    cnt_lo_rd <= (others => (others => '0'));
    cnt_hi_rd <= (others => (others => '0'));
    -- base counters --
    if (CPU_EXTENSION_RISCV_Zicntr = true) then
      cnt_lo_rd(0) <= cnt.lo(0); -- cycle
      cnt_hi_rd(0) <= cnt.hi(0); -- cycleh
      cnt_lo_rd(2) <= cnt.lo(2); -- instret
      cnt_hi_rd(2) <= cnt.hi(2); -- instreth
    end if;
    -- hpm counters --
    if (CPU_EXTENSION_RISCV_Zihpm = true) and (hpm_num_c > 0) then
      for i in 3 to (hpm_num_c+3)-1 loop
        if (hpm_cnt_lo_width_c > 0) then -- constrain low word size
          cnt_lo_rd(i)(hpm_cnt_lo_width_c-1 downto 0) <= cnt.lo(i)(hpm_cnt_lo_width_c-1 downto 0);
        end if;
        if (hpm_cnt_hi_width_c > 0) then -- constrain high word size
          cnt_hi_rd(i)(hpm_cnt_hi_width_c-1 downto 0) <= cnt.hi(i)(hpm_cnt_hi_width_c-1 downto 0);
        end if;
      end loop;
    end if;
  end process cnt_connect;


  -- Hardware Performance Monitors (HPM) - Counter Event Configuration CSRs -----------------
  -- -------------------------------------------------------------------------------------------
  hpmevent_gen_enable:
  if CPU_EXTENSION_RISCV_Zihpm and (hpm_num_c > 0) generate

    -- write enable decoder --
    hpmevent_write: process(csr)
    begin
      hpmevent_we <= (others => '0');
      -- [NOTE] no need to check bit 4 of the address as it's always zero (checked by illegal CSR logic)
      if (csr.addr(11 downto 5) = csr_mcountinhibit_c(11 downto 5)) and (csr.we = '1') then
        hpmevent_we(to_integer(unsigned(csr.addr(3 downto 0)))) <= '1';
      end if;
    end process hpmevent_write;

    -- event registers --
    hpmevent_reg_gen:
    for i in 3 to (hpm_num_c+3)-1 generate
      hpmevent_reg: process(rstn_i, clk_i)
      begin
        if (rstn_i = '0') then
          hpmevent_cfg(i) <= (others => '0');
        elsif rising_edge(clk_i) then
          if (hpmevent_we(i) = '1') then
            hpmevent_cfg(i) <= csr.wdata(hpmcnt_event_size_c-1 downto 0);
          end if;
          hpmevent_cfg(i)(hpmcnt_event_tm_c) <= '0'; -- time, unused/reserved
        end if;
      end process hpmevent_reg;
      -- read-back --
      hpmevent_rd(i)(XLEN-1 downto hpmcnt_event_size_c) <= (others => '0');
      hpmevent_rd(i)(hpmcnt_event_size_c-1 downto 0)    <= hpmevent_cfg(i);
    end generate;

    -- terminate unused entries --
    hpmevent_terminate_gen:
    for i in hpm_num_c+3 to 15 generate
      hpmevent_cfg(i) <= (others => '0');
      hpmevent_rd(i)  <= (others => '0');
    end generate;

  end generate;


  -- no HPMs implemented --
  hpmevent_gen_disable:
  if (not CPU_EXTENSION_RISCV_Zihpm) or (hpm_num_c = 0) generate
    hpmevent_cfg <= (others => (others => '0'));
    hpmevent_rd  <= (others => (others => '0'));
  end generate;


  -- Counter Increment Control (Trigger Events) ---------------------------------------------
  -- -------------------------------------------------------------------------------------------
  counter_event: process(rstn_i, clk_i)
  begin -- increment if an enabled event fires; do not increment if CPU is in debug mode or if counter is inhibited
    if (rstn_i = '0') then
      cnt.inc <= (others => '0');
    elsif rising_edge(clk_i) then
      cnt.inc <= (others => '0'); -- default
      -- base counters --
      if (CPU_EXTENSION_RISCV_Zicntr = true) then
        cnt.inc(0) <= cnt_event(hpmcnt_event_cy_c) and (not csr.mcountinhibit(0)) and (not debug_ctrl.running);
        cnt.inc(2) <= cnt_event(hpmcnt_event_ir_c) and (not csr.mcountinhibit(2)) and (not debug_ctrl.running);
      end if;
      -- hpm counters --
      if (CPU_EXTENSION_RISCV_Zihpm = true) and (hpm_num_c > 0) then
        for i in 3 to (hpm_num_c+3)-1 loop
          cnt.inc(i) <= or_reduce_f(cnt_event and hpmevent_cfg(i)) and (not csr.mcountinhibit(i)) and (not debug_ctrl.running);
        end loop;
      end if;
    end if;
  end process counter_event;

  -- RISC-V-specific base counter events (for HPM and base counters) --
  cnt_event(hpmcnt_event_cy_c) <= '1' when (sleep_mode = '0')               else '0'; -- cycle: active cycle
  cnt_event(hpmcnt_event_tm_c) <=                                                '0'; -- time: unused/reserved
  cnt_event(hpmcnt_event_ir_c) <= '1' when (execute_engine.state = EXECUTE) else '0'; -- instret: retired (==executed) instruction

  -- NEORV32-specific counter events (for HPM counters only) --
  cnt_event(hpmcnt_event_compr_c)    <= '1' when (execute_engine.state = EXECUTE)  and (execute_engine.is_ci = '1')  else '0'; -- executed compressed instruction
  cnt_event(hpmcnt_event_wait_dis_c) <= '1' when (execute_engine.state = DISPATCH) and (issue_engine.valid   = "00") else '0'; -- instruction dispatch wait cycle
  cnt_event(hpmcnt_event_wait_alu_c) <= '1' when (execute_engine.state = ALU_WAIT)                                   else '0'; -- multi-cycle ALU co-processor wait cycle

  cnt_event(hpmcnt_event_branch_c)   <= '1' when (execute_engine.state = BRANCH)   else '0'; -- executed branch instruction
  cnt_event(hpmcnt_event_branched_c) <= '1' when (execute_engine.state = BRANCHED) else '0'; -- control flow transfer

  cnt_event(hpmcnt_event_load_c)     <= '1' when (ctrl.lsu_req = '1') and (ctrl.lsu_rw = '0')               else '0'; -- executed load operation
  cnt_event(hpmcnt_event_store_c)    <= '1' when (ctrl.lsu_req = '1') and (ctrl.lsu_rw = '1')               else '0'; -- executed store operation
  cnt_event(hpmcnt_event_wait_lsu_c) <= '1' when (ctrl.lsu_req = '0') and (execute_engine.state = MEM_WAIT) else '0'; -- load/store unit memory wait cycle

  cnt_event(hpmcnt_event_trap_c)     <= '1' when (trap_ctrl.env_enter = '1') else '0'; -- entered trap


-- ****************************************************************************************************************************
-- CPU Debug Mode (Part of the On-Chip Debugger)
-- ****************************************************************************************************************************

  -- Debug Control --------------------------------------------------------------------------
  -- -------------------------------------------------------------------------------------------
  debug_mode_enable:
  if CPU_EXTENSION_RISCV_Sdext generate

    debug_control: process(rstn_i, clk_i)
    begin
      if (rstn_i = '0') then
        debug_ctrl.running <= '0';
      elsif rising_edge(clk_i) then
        if (debug_ctrl.running = '0') then -- debug mode OFFLINE
          if (trap_ctrl.env_enter = '1') and (trap_ctrl.cause(5) = '1') then -- waiting for entry event
            debug_ctrl.running <= '1';
          end if;
        else -- debug mode ONLINE
          if (trap_ctrl.env_exit = '1') then -- waiting for exit event
            debug_ctrl.running <= '0';
          end if;
        end if;
      end if;
    end process debug_control;

    -- debug mode entry triggers --
    debug_ctrl.trig_hw    <= trap_ctrl.hwtrig and (not debug_ctrl.running) and csr.tdata1_action and csr.tdata1_dmode; -- enter debug mode by HW trigger module request (only valid if dmode = 1)
    debug_ctrl.trig_break <= trap_ctrl.ebreak and (debug_ctrl.running or   -- re-enter debug mode
                             ((    csr.privilege) and csr.dcsr_ebreakm) or -- enabled goto-debug-mode in machine mode on "ebreak"
                             ((not csr.privilege) and csr.dcsr_ebreaku));  -- enabled goto-debug-mode in user mode on "ebreak"
    debug_ctrl.trig_halt  <= db_halt_req_i and (not debug_ctrl.running); -- external halt request (if not halted already)
    debug_ctrl.trig_step  <= csr.dcsr_step and (not debug_ctrl.running); -- single-step mode (trigger when NOT CURRENTLY in debug mode)

  end generate;

  -- Sdext ISA extension not enabled --
  debug_mode_disable:
  if not CPU_EXTENSION_RISCV_Sdext generate
    debug_ctrl.running    <= '0';
    debug_ctrl.trig_hw    <= '0';
    debug_ctrl.trig_break <= '0';
    debug_ctrl.trig_halt  <= '0';
    debug_ctrl.trig_step  <= '0';
  end generate;


  -- Debug Control and Status Register (dcsr) - Read-Back -----------------------------------
  -- -------------------------------------------------------------------------------------------
  csr.dcsr_rd(31 downto 28) <= "0100"; -- xdebugver: external debug support compatible to spec. version 1.0
  csr.dcsr_rd(27 downto 16) <= (others => '0'); -- reserved
  csr.dcsr_rd(15)           <= csr.dcsr_ebreakm; -- ebreakm: what happens on ebreak in m-mode? (normal trap OR debug-enter)
  csr.dcsr_rd(14)           <= '0'; -- reserved
  csr.dcsr_rd(13)           <= '0'; -- ebreaks: supervisor mode not implemented
  csr.dcsr_rd(12)           <= csr.dcsr_ebreaku when (CPU_EXTENSION_RISCV_U = true) else '0'; -- ebreaku: what happens on ebreak in u-mode? (normal trap OR debug-enter)
  csr.dcsr_rd(11)           <= '0'; -- stepie: interrupts are disabled during single-stepping
  csr.dcsr_rd(10)           <= '1'; -- stopcount: standard counters and HPMs are stopped when in debug mode
  csr.dcsr_rd(09)           <= '0'; -- stoptime: timers increment as usual
  csr.dcsr_rd(08 downto 06) <= csr.dcsr_cause; -- debug mode entry cause
  csr.dcsr_rd(05)           <= '0'; -- reserved
  csr.dcsr_rd(04)           <= '1'; -- mprven: mstatus.mprv is also evaluated in debug mode
  csr.dcsr_rd(03)           <= '0'; -- nmip: no pending non-maskable interrupt
  csr.dcsr_rd(02)           <= csr.dcsr_step; -- step: single-step mode
  csr.dcsr_rd(01 downto 00) <= (others => csr.dcsr_prv); -- prv: privilege mode when debug mode was entered


-- ****************************************************************************************************************************
-- Hardware Trigger Module (Part of the On-Chip Debugger)
-- ****************************************************************************************************************************
  trigger_module_enable:
  if CPU_EXTENSION_RISCV_Sdtrig generate

    -- trigger on instruction address match (trigger right BEFORE execution) --
    hw_trigger_match <= '1' when (csr.tdata1_execute = '1') and -- trigger enabled to match on instruction address
                                 (hw_trigger_fired = '0') and -- trigger has not fired yet
                                 (csr.tdata2(XLEN-1 downto 1) = execute_engine.next_pc(XLEN-1 downto 1)) -- address match
                                 else '0';

    -- status flag - set when trigger has fired --
    hw_trigger_exception: process(rstn_i, clk_i)
    begin
      if (rstn_i = '0') then
        hw_trigger_fired <= '0';
      elsif rising_edge(clk_i) then
        if (hw_trigger_match = '1') and (trap_ctrl.exc_buf(exc_ebreak_c) = '1') then -- trigger has fired and breakpoint exception is pending
          hw_trigger_fired <= '1';
        elsif (csr.tdata1_hit_clr = '1') then
          hw_trigger_fired <= '0';
        end if;
      end if;
    end process hw_trigger_exception;

  end generate;

  -- Sdtrig ISA extension not enabled --
  trigger_module_disable:
  if not CPU_EXTENSION_RISCV_Sdtrig generate
    hw_trigger_match <= '0';
    hw_trigger_fired <= '0';
  end generate;


  -- Match Control CSR (mcontrol6 @ tdata1) - Read-Back -------------------------------------
  -- -------------------------------------------------------------------------------------------
  csr.tdata1_rd(31 downto 28) <= x"6"; -- type: address match trigger (mcontrol6)
  csr.tdata1_rd(27)           <= csr.tdata1_dmode; -- dmode: set to ignore machine-mode write access to tdata* CSRs
  csr.tdata1_rd(26)           <= '0'; -- uncertain: trigger satisfies the configured conditions
  csr.tdata1_rd(25)           <= '0'; -- hit1: hardwired to zero
  csr.tdata1_rd(24)           <= '0'; -- vs: VS-mode not implemented
  csr.tdata1_rd(23)           <= '0'; -- vu: VU-mode not implemented
  csr.tdata1_rd(22)           <= hw_trigger_fired; -- hit0: set when trigger has fired
  csr.tdata1_rd(21)           <= '0'; -- select: only address matching is supported
  csr.tdata1_rd(20 downto 19) <= "00"; -- reserved
  csr.tdata1_rd(18 downto 16) <= "000"; -- size: match accesses of any size
  csr.tdata1_rd(15 downto 12) <= "000" & csr.tdata1_action; -- action = 1: enter debug mode on trigger, action = 0: ebreak exception on trigger
  csr.tdata1_rd(11)           <= '0'; -- chain: chaining not supported - there is only one trigger
  csr.tdata1_rd(10 downto 07) <= "0000"; -- match: equal-match only
  csr.tdata1_rd(6)            <= '1'; -- m: trigger always enabled when in machine-mode
  csr.tdata1_rd(5)            <= '0'; -- uncertainen: hardwired to zero
  csr.tdata1_rd(4)            <= '0'; -- s: supervisor-mode not supported
  csr.tdata1_rd(3)            <= bool_to_ulogic_f(CPU_EXTENSION_RISCV_U); -- u: trigger always enabled when in user-mode (if implemented)
  csr.tdata1_rd(2)            <= csr.tdata1_execute; -- execute: enable trigger on instruction match
  csr.tdata1_rd(1)            <= '0'; -- store: store address or data matching not supported
  csr.tdata1_rd(0)            <= '0'; -- load: load address or data matching not supported


end neorv32_cpu_control_rtl;
