// Copyright 202[x] Silicon Labs, Inc. // // This file, and derivatives thereof are licensed under the // Solderpad License, Version 2.0 (the "License"); // Use of this file means you agree to the terms and conditions // of the license and are in full compliance with the License. // You may obtain a copy of the License at // // https://solderpad.org/licenses/SHL-2.0/ // // Unless required by applicable law or agreed to in writing, software // and hardware implementations thereof // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESSED OR IMPLIED. // See the License for the specific language governing permissions and // limitations under the License. //////////////////////////////////////////////////////////////////////////////// // Engineer: Øystein Knauserud - oystein.knauserud@silabs.com // // // // Additional contributions by: // // // // Design Name: cv32e40s_controller_fsm // // Project Name: CV32E40S // // Language: SystemVerilog // // // // Description: FSM of the pipeline controller // // // //////////////////////////////////////////////////////////////////////////////// module cv32e40s_controller_fsm import cv32e40s_pkg::*; #( parameter bit DEBUG = 1, parameter bit CLIC = 0, parameter int unsigned CLIC_ID_WIDTH = 5, parameter rv32_e RV32 = RV32I ) ( // Clocks and reset input logic clk, // Gated clock input logic rst_n, input logic fetch_enable_i, // Start executing // From bypass logic input ctrl_byp_t ctrl_byp_i, // From IF stage input logic [31:0] pc_if_i, input logic last_op_if_i, // IF stage is handling the last operation of a sequence. input logic abort_op_if_i, // IF stage contains an operation that will be aborted (bus error or MPU exception) // From ID stage input if_id_pipe_t if_id_pipe_i, input logic alu_jmp_id_i, // Jump in ID input logic sys_mret_id_i, // mret in ID input logic alu_en_id_i, // alu_en qualifier for jumps input logic sys_en_id_i, // sys_en qualifier for mret input logic first_op_id_i, // ID stage is handling the first operation of a sequence input logic last_op_id_i, // ID stage is handling the last operation of a sequence input logic abort_op_id_i, // ID stage contains an (to be) aborted instruction or sequence // From EX stage input id_ex_pipe_t id_ex_pipe_i, input logic branch_decision_ex_i, // branch decision signal from EX ALU input logic last_op_ex_i, // EX stage contains the last operation of an instruction // From WB stage input ex_wb_pipe_t ex_wb_pipe_i, input lsu_err_wb_t lsu_err_wb_i, // LSU caused bus_error or integrity error in WB stage, gated with data_rvalid_i inside load_store_unit input logic last_op_wb_i, // WB stage contains the last operation of an instruction input logic abort_op_wb_i, // WB stage contains an (to be) aborted instruction or sequence input mpu_status_e mpu_status_wb_i, // MPU status (WB timing) input logic [31:0] wpt_match_wb_i, // LSU watchpoint trigger (WB) // From LSU (WB) input logic data_stall_wb_i, // WB stalled by LSU input logic lsu_valid_wb_i, // LSU instruction in WB is valid input logic lsu_busy_i, // LSU is busy with outstanding transfers input logic lsu_interruptible_i, // LSU can be interrupted // Interrupt Controller Signals input logic irq_wu_ctrl_i, // Irq wakeup control input logic irq_req_ctrl_i, // Irq request input logic [9:0] irq_id_ctrl_i, // Irq id input logic irq_clic_shv_i, // CLIC mode selective hardware vectoring input logic [7:0] irq_clic_level_i, // CLIC mode current interrupt level input logic [1:0] irq_clic_priv_i, // CLIC mode current interrupt privilege input privlvl_t priv_lvl_i, // Current running priviledge level // Wakeup signal for WFE (from toplevel input) input logic wu_wfe_i, // From cs_registers input logic [1:0] mtvec_mode_i, input dcsr_t dcsr_i, input mcause_t mcause_i, input xsecure_ctrl_t xsecure_ctrl_i, input mintstatus_t mintstatus_i, // Trigger module input logic etrigger_wb_i, // Trigger module detected match in WB (etrigger) // Toplevel input input logic debug_req_i, // External debug request // All controller FSM outputs output ctrl_fsm_t ctrl_fsm_o, // CSR write strobes input logic csr_wr_in_wb_flush_i, // Stage valid/ready signals input logic if_valid_i, // IF stage has valid (non-bubble) data for next stage input logic id_ready_i, // ID stage is ready for new data input logic id_valid_i, // ID stage has valid (non-bubble) data for next stage input logic ex_ready_i, // EX stage is ready for new data input logic ex_valid_i, // EX stage has valid (non-bubble) data for next stage input logic wb_ready_i, // WB stage is ready for new data, input logic wb_valid_i, // WB stage ha valid (non-bubble) data // Fencei flush handshake output logic fencei_flush_req_o, input logic fencei_flush_ack_i, // Data OBI interface monitor cv32e40s_if_c_obi.monitor m_c_obi_data_if ); // FSM state encoding ctrl_state_e ctrl_fsm_cs, ctrl_fsm_ns; // Debug state debug_state_e debug_fsm_cs, debug_fsm_ns; // Sticky version of lsu_err_wb_i logic nmi_pending_q; logic nmi_is_store_q; // 1 for store, 0 for load logic nmi_is_integrity_q; // 1 for integrity, 0 for bus error // Debug mode logic debug_mode_n; logic debug_mode_q; // Signals used for halting IF after first instruction // during single step logic single_step_halt_if_n; logic single_step_halt_if_q; // Halting IF after issuing one insn in single step mode // ID signals logic sys_mret_id; // MRET in ID logic jmp_id; // JAL, JALR in ID logic jump_in_id; logic jump_taken_id; logic clic_ptr_in_id; // CLIC pointer in ID logic mret_ptr_in_id; // mret pointer in ID // EX signals logic branch_in_ex; logic branch_taken_ex; logic branch_taken_n; logic branch_taken_q; logic jump_taken_n; logic jump_taken_q; logic clic_ptr_in_ex; // WB signals logic exception_in_wb; logic exception_alert_minor_wb; logic exception_alert_major_wb; logic [10:0] exception_cause_wb; logic wfi_in_wb; logic wfe_in_wb; logic fencei_in_wb; logic fence_in_wb; logic mret_in_wb; logic mret_ptr_in_wb; // CLIC pointer caused by mret is in WB logic dret_in_wb; logic ebreak_in_wb; logic trigger_match_in_wb; // mcontrol2/6 trigger in WB logic etrigger_in_wb; // exception trigger in WB logic clic_ptr_in_wb; // CLIC pointer caused by directly acking an SHV is in WB (no mret) logic pending_nmi; logic pending_nmi_early; logic pending_async_debug; logic pending_sync_debug; logic pending_single_step; logic pending_interrupt; // Flags for allowing interrupt and debug logic exception_allowed; logic interrupt_allowed; logic nmi_allowed; logic async_debug_allowed; logic sync_debug_allowed; logic single_step_allowed; // Flag for blocking interrupts due to debug conditions logic debug_interruptible; // Flag indicating there is a 'live' CLIC pointer in the pipeline // Used to block debug and interrupts until pointer fetch is done and the final ISR instruction PC is available. logic clic_ptr_in_pipeline; // Internal irq_ack for use when a (clic) pointer reaches ID stage and // we have single stepping enabled. logic non_shv_irq_ack; // Flops for debug cause logic [2:0] debug_cause_n; logic [2:0] debug_cause_q; // Cause of synchronous debug entry logic [2:0] sync_debug_cause; // Flop for remembering causes of wakeup logic woke_to_debug_q; logic woke_to_interrupt_q; logic [10:0] exc_cause; // id of taken interrupt. Max width, unused bits are tied off. logic fence_req_set; logic fence_req_clr; logic fence_req_q; logic fencei_req_and_ack_q; logic fencei_ongoing; // Pipeline PC mux control pipe_pc_mux_e pipe_pc_mux_ctrl; // Flag for signalling that a new instruction arrived in WB. // Used for performance counters. High for one cycle, unless WB is halted // (for fence.i for example), then it will remain high until un-halted. logic wb_counter_event; // Gated version of wb_counter_event // Do not count if halted or killed logic wb_counter_event_gated; // Flop for acking flush requests due to CSR writes logic csr_flush_ack_n; logic csr_flush_ack_q; // Flag for checking if multi op instructions are in an interruptible state logic sequence_in_progress_wb; logic sequence_interruptible; // Flag for checking if ID stage can be halted // Used to not halt sequences in the middle, potentially causing deadlocks logic sequence_in_progress_id; logic id_stage_haltable; // Flag that is high during the cycle after an LSU instruction finishes in WB logic interrupt_blanking_q; // Flop for tracking when a pointer fetch is in progress logic clic_ptr_in_progress_id; logic clic_ptr_in_progress_id_set; logic clic_ptr_in_progress_id_clear; assign sequence_interruptible = !sequence_in_progress_wb; assign id_stage_haltable = !(sequence_in_progress_id || clic_ptr_in_progress_id); // Once the fencei handshake is initiated, it must complete and the instruction must retire. // The instruction retires when fencei_req_and_ack_q = 1 assign fencei_ongoing = fencei_flush_req_o || fencei_req_and_ack_q; // Mux selector for vectored IRQ PC // Used for both basic mode and CLIC when shv == 0. assign ctrl_fsm_o.mtvec_pc_mux = ((mtvec_mode_i == 2'b0) || ((mtvec_mode_i == 2'b11) && !irq_clic_shv_i)) ? 5'h0 : exc_cause[4:0]; // CLIC mode vectored PC mux is always the same as exc_cause. assign ctrl_fsm_o.mtvt_pc_mux = exc_cause[9:0]; // Set which mtvec index to jump to when an NMI is taken // index 0 for non-vectored CLINT mode and CLIC mode, 0xF for vectored CLINT mode assign ctrl_fsm_o.nmi_mtvec_index = (mtvec_mode_i == 2'b01) ? 5'hF : 5'h0; //////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////// // - Blocking dummy instructions during single stepping and in debug mode. // a_no_back_to_back_dummy in if_stage_sva checks that dummies can't come back-to-back with no other instructions in between. assign ctrl_fsm_o.allow_dummy_instr = !dcsr_i.step && // Valid in IF because it can only be written in debug mode !debug_mode_q; // Valid in IF because pipeline is killed when entering and exiting debug // ID stage // A jump is taken in ID for jump instructions, and also for mret instructions // Checking validity of jump/mret instruction with if_id_pipe_i.instr_valid and the respective alu_en/sys_en. // Using the ID stage local instr_valid would bring halt_id and kill_id into the equation // causing a path from data_rvalid to instr_addr_o/instr_req_o/instr_memtype_o via pc_set. assign sys_mret_id = sys_en_id_i && sys_mret_id_i && if_id_pipe_i.instr_valid; assign jmp_id = alu_en_id_i && alu_jmp_id_i && if_id_pipe_i.instr_valid; // Detect that a jump is in the ID stage. // This will also be true for table jumps, as they are encoded as JAL instructions. // An extra table jump flag is used in the logic for taken jumps to disinguish between // regular jumps and table jumps. // Table jumps do an implicit read of the JVT CSR, so csr_stall must be accounted for. assign jump_in_id = (jmp_id && !if_id_pipe_i.instr_meta.tbljmp && !ctrl_byp_i.jalr_stall) || (jmp_id && if_id_pipe_i.instr_meta.tbljmp && !ctrl_byp_i.csr_stall_id ) || (sys_mret_id && !ctrl_byp_i.csr_stall_id); // Blocking on branch_taken_q, as a jump has already been taken assign jump_taken_id = jump_in_id && !jump_taken_q; // Signalling jump or mret in ID stage to the pc_check module. // Mret during debug mode will be flagged as illegal and sys_mret_id will be 0. assign ctrl_fsm_o.jump_in_id_raw = jmp_id || sys_mret_id; // Detect clic pointers in ID assign clic_ptr_in_id = if_id_pipe_i.instr_valid && if_id_pipe_i.instr_meta.clic_ptr; // Detect mret pointers in ID assign mret_ptr_in_id = if_id_pipe_i.instr_valid && if_id_pipe_i.instr_meta.mret_ptr; // Note: RVFI does not use jump_taken_id (which is not in itself an issue). An assertion in id_stage_sva checks that the jump target remains stable. // EX stage // Branch taken for valid branch instructions in EX with valid decision assign branch_in_ex = id_ex_pipe_i.alu_bch && id_ex_pipe_i.alu_en && id_ex_pipe_i.instr_valid; assign ctrl_fsm_o.branch_in_ex_raw = id_ex_pipe_i.alu_bch && id_ex_pipe_i.alu_en; // Blocking on branch_taken_q, as a branch ha already been taken assign branch_taken_ex = branch_in_ex && branch_decision_ex_i && !branch_taken_q; // Exception should trigger minor alert if the following evaluates to 1 assign exception_alert_minor_wb = ((ex_wb_pipe_i.instr.mpu_status != MPU_OK) || ex_wb_pipe_i.instr.bus_resp.err || ex_wb_pipe_i.illegal_insn || (mpu_status_wb_i != MPU_OK)) && ex_wb_pipe_i.instr_valid; // Major alert will be set if we take an exception for an instruction side integrity error assign exception_alert_major_wb = (ex_wb_pipe_i.instr.bus_resp.integrity_err && ex_wb_pipe_i.instr_valid); // Exception in WB if the following evaluates to 1 // Not checking for ex_wb_pipe_i.last_op to enable exceptions to be taken as soon as possible for // split load/stores or Zc sequences. // // For ebreak instructions, the following scenarios are possible. Only one scenario per privilege level could cause an exception: // priv_lvl | ebreakm | ebreaku | debug_mode | action //----------|---------|---------|------------|----------------------------------------- // M | 0 | x | 0 | Exception // M | 0 | x | 1 | Debug entry (restart from dm_halt_addr_i) // M | 1 | x | 0 | Debug entry // M | 1 | x | 1 | Debug entry (restart from dm_halt_addr_i) //----------|---------|---------|------------|----------------------------------------- // U | x | 0 | 0 | Exception // U | x | 0 | 1 | Debug entry (restart from dm_halt_addr_i) // U | x | 1 | 0 | Debug entry // U | x | 1 | 1 | Debug entry (restart from dm_halt_addr_i) // assign exception_in_wb = ((ex_wb_pipe_i.instr.mpu_status != MPU_OK) || ex_wb_pipe_i.instr.bus_resp.integrity_err || ex_wb_pipe_i.instr.bus_resp.err || ex_wb_pipe_i.illegal_insn || (ex_wb_pipe_i.sys_en && ex_wb_pipe_i.sys_ecall_insn) || (ex_wb_pipe_i.sys_en && ex_wb_pipe_i.sys_ebrk_insn && (ex_wb_pipe_i.priv_lvl == PRIV_LVL_M) && !dcsr_i.ebreakm && !debug_mode_q) || (ex_wb_pipe_i.sys_en && ex_wb_pipe_i.sys_ebrk_insn && (ex_wb_pipe_i.priv_lvl == PRIV_LVL_U) && !dcsr_i.ebreaku && !debug_mode_q) || (mpu_status_wb_i != MPU_OK)) && ex_wb_pipe_i.instr_valid; assign ctrl_fsm_o.exception_in_wb = exception_in_wb; // Set exception cause // For CLIC: Pointer fetches with PMA/PMP errors will get the exception code converted to LOAD_FAULT // Bus errors will be converted to NMI as for regular loads. assign exception_cause_wb = (ex_wb_pipe_i.instr.mpu_status != MPU_OK) ? EXC_CAUSE_INSTR_FAULT : ex_wb_pipe_i.instr.bus_resp.integrity_err ? EXC_CAUSE_INSTR_INTEGRITY_FAULT : ex_wb_pipe_i.instr.bus_resp.err ? EXC_CAUSE_INSTR_BUS_FAULT : ex_wb_pipe_i.illegal_insn ? EXC_CAUSE_ILLEGAL_INSN : (ex_wb_pipe_i.sys_en && ex_wb_pipe_i.sys_ecall_insn) ? (priv_lvl_i==PRIV_LVL_M ? EXC_CAUSE_ECALL_MMODE : EXC_CAUSE_ECALL_UMODE ) : (ex_wb_pipe_i.sys_en && ex_wb_pipe_i.sys_ebrk_insn && (ex_wb_pipe_i.priv_lvl == PRIV_LVL_M) && !dcsr_i.ebreakm && !debug_mode_q) ? EXC_CAUSE_BREAKPOINT : (ex_wb_pipe_i.sys_en && ex_wb_pipe_i.sys_ebrk_insn && (ex_wb_pipe_i.priv_lvl == PRIV_LVL_U) && !dcsr_i.ebreaku && !debug_mode_q) ? EXC_CAUSE_BREAKPOINT : (mpu_status_wb_i == MPU_WR_FAULT) ? EXC_CAUSE_STORE_FAULT : EXC_CAUSE_LOAD_FAULT; assign ctrl_fsm_o.exception_cause_wb = exception_cause_wb; // For now we are always allowed to take exceptions once they arrive in WB. // For a misaligned load/store with MPU error on the first half, the second half // will arrive in EX when the first half (with error) arrives in WB. The exception will // be taken and the bus transaction of the second half will be suppressed by the ctrl_fsm_o.kill_ex signal. // The only higher priority events are NMI, debug and interrupts, and none of them are allowed if there is // a load/store in WB. assign exception_allowed = 1'b1; // wfi in wb assign wfi_in_wb = ex_wb_pipe_i.sys_en && ex_wb_pipe_i.sys_wfi_insn && ex_wb_pipe_i.instr_valid; // wfe in wb assign wfe_in_wb = ex_wb_pipe_i.sys_en && ex_wb_pipe_i.sys_wfe_insn && ex_wb_pipe_i.instr_valid; // fencei in wb assign fencei_in_wb = ex_wb_pipe_i.sys_en && ex_wb_pipe_i.sys_fencei_insn && ex_wb_pipe_i.instr_valid; // fence in wb assign fence_in_wb = ex_wb_pipe_i.sys_en && ex_wb_pipe_i.sys_fence_insn && ex_wb_pipe_i.instr_valid; // mret in wb - only valid when ex_wb_pipe_i.first_op == 1. // Regular mret instruction will have first_op == last_op == 1. // Mret with mcause.minhv==1 will restart a CLIC pointer fetch. The mret instruction will have first_op==1 && last_op==0 // while the pointer will finish the sequence with first_op==0 and last_op==1. The mret related CSR updates will happen // once the mret instruction reaches WB. The pointer fetch may generate an exception, causing further CSR updates // once the attempted pointer fetch reaches WB. // // When PC hardening is enabled, the mret instruction is split in two (last_sec_op == 0 followed by last_sec_op == 1) // The associated CSR updates will happen when the final part (last_sec_op == 1) is in the WB stage. assign mret_in_wb = ex_wb_pipe_i.sys_en && ex_wb_pipe_i.sys_mret_insn && ex_wb_pipe_i.instr_valid && ex_wb_pipe_i.last_sec_op; // CLIC pointer (caused by mret) in WB. assign mret_ptr_in_wb = ex_wb_pipe_i.instr_meta.mret_ptr && ex_wb_pipe_i.instr_valid; // dret in wb assign dret_in_wb = ex_wb_pipe_i.sys_en && ex_wb_pipe_i.sys_dret_insn && ex_wb_pipe_i.instr_valid; // ebreak in wb assign ebreak_in_wb = ex_wb_pipe_i.sys_en && ex_wb_pipe_i.sys_ebrk_insn && ex_wb_pipe_i.instr_valid; // Trigger match in wb // Trigger_match during debug mode is masked in the trigger logic inside cs_registers.sv assign trigger_match_in_wb = ((|ex_wb_pipe_i.trigger_match) || (|wpt_match_wb_i)) && ex_wb_pipe_i.instr_valid; // Only set the etrigger_in_wb flag when wb_valid is true (WB is not halted or killed). // If a higher priority event than taking an exception (NMI, external debug or interrupts) are present, wb_valid_i will be // suppressed by either halt_wb followed by kill_wb (debug), or kill_wb (NMI/interrupt). assign etrigger_in_wb = etrigger_wb_i && wb_valid_i; // Regular CLIC pointer in EX (not caused by mret) assign clic_ptr_in_ex = id_ex_pipe_i.instr_meta.clic_ptr && id_ex_pipe_i.instr_valid; // Regular CLIC pointer in WB (not caused by mret) assign clic_ptr_in_wb = ex_wb_pipe_i.instr_meta.clic_ptr && ex_wb_pipe_i.instr_valid; // Pending NMI // Using flopped version to avoid paths from data_err_i/data_rvalid_i to instr_* outputs assign pending_nmi = nmi_pending_q; // Early version of the pending_nmi signal, using the unflopped lsu_err_wb_i.bus_err and lsu_err_wb_i.integrity_err // This signal is used for halting the ID stage in the same cycle as the bus error arrives. // This ensures that any instruction in the ID stage that may depend on the result of the faulted load // will not propagate to the EX stage. For cycles after lsu_err_wb_i[0] is // high, ID stage will be halted due to pending_nmi and !nmi_allowed. assign pending_nmi_early = lsu_err_wb_i.bus_err || lsu_err_wb_i.integrity_err; // dcsr.nmip will always see a pending nmi if nmi_pending_q is set. // This CSR bit shall not be gated by debug mode or step without stepie assign ctrl_fsm_o.pending_nmi = nmi_pending_q; // Debug // Single step will need to finish insn in WB, including LSU // LSU will now set valid_1_o only for second part of misaligned instructions. // We can always allow single step when checking for wb_valid_i in 'pending_single_step' // - no other instructions should be in the pipeline. assign single_step_allowed = 1'b1; /* "If control is transferred to a trap handler while executing the instruction, then Debug Mode is re-entered immediately after the PC is changed to the trap handler, and the appropriate tval and cause registers are updated. In this case none of the trap handler is executed, and if the cause was a pending interrupt no instructions might be executed at all." Hence, a pending_single_step is asserted if we take an interrupt when we should be stepping. For any interruptible instructions (non-LSU), at any stage, we would kill the instruction and jump to debug mode without executing any instructions. Interrupt handler's first instruction will be in dpc. For LSU instructions that may not be killed (if they reach WB or stay in EX for >1 cycles), we are not allowed to take interrupts, and we will re-enter debug mode after finishing the LSU. Interrupt will then be taken when we enter the next step. */ assign non_shv_irq_ack = ctrl_fsm_o.irq_ack && !irq_clic_shv_i; // single step becomes pending when the last operation of an instruction is done in WB (including CLIC pointers), or we ack a non-shv interrupt (including NMI). // If a CLIC SHV interrupt is taken during single step, a pointer that reaches WB will trigger the debug entry. // - For un-faulted pointer fetches, the second fetch of the CLIC vectoring took place in ID, and the final SHV handler target address will be available from IF. // - A faulted pointer fetch does not perform the second fetch. Instead the exception handler fetch will occur before entering debug due to stepping. // // Unlike [a]synchrounous debug entries, single step does not halt the pipeline. // This causes the reason for debug entry to 'disappear' as seen from the DEBUG_TAKEN state one cycle later. // The signal pending_single_step should never be used outside of the FUNCTIONAL state. assign pending_single_step = (!debug_mode_q && dcsr_i.step && ((wb_valid_i && (last_op_wb_i || abort_op_wb_i)) || non_shv_irq_ack || (pending_nmi && nmi_allowed))); // Detect if there is a live CLIC pointer in the pipeline // This should block debug and interrupts generate if (CLIC) begin : gen_clic_pointer_flag // A CLIC pointer may be in the pipeline from the moment we start fetching (clic_ptr_in_progress_id == 1) // or while a pointer is in the EX or WB stages. assign clic_ptr_in_pipeline = clic_ptr_in_ex || clic_ptr_in_wb || clic_ptr_in_progress_id; end else begin : gen_basic_pointer_flag assign clic_ptr_in_pipeline = 1'b0; end endgenerate // External debug will kill insn in WB, do not allow if LSU is not interruptible, a fence.i handshake is taking place // or if an offloaded instruction is in WB. // LSU will not be interruptible if the outstanding counter != 0, or // a trans_valid has been clocked without ex_valid && wb_ready handshake. // When a fencei is present in WB and the LSU has completed all tranfers, the fencei handshake will be initiated. This must complete and the fencei instruction must retire before allowing external debug. // Any multi operation instruction (table jumps, push/pop and double moves) may not be interrupted once the first operation has completed its operation in WB. // - This is guarded with using the sequence_interruptible, which tracks sequence progress through the WB stage. // When a CLIC pointer is in the pipeline stages EX or WB, we must block debug. // - Debug would otherwise kill the pointer and use the address of the pointer for dpc. A following dret would then return to the mtvt table, losing program progress. // // Debug entry because of haltreq is disallowed when the LSU is busy and therefore // haltreq can only cause debug entry on the instruction following a load or store that // keep the LSU busy. If such load or store however is being single stepped or has an // associated breakpoint or watchpoint, then debug will be entered because of that // lower priority reason even though haltreq is asserted. This is okay because if instruction // timing is considered haltreq should be considered only asserted on the following // instruction (i.e. the asynchronous haltreq signal is considered asserted too late to // impact the current instruction in the pipeline). // If the core woke up from sleep due to interrupts, the wakeup reason will be honored // by not allowing async debug the cycle after wakeup. assign async_debug_allowed = lsu_interruptible_i && !fencei_ongoing && !clic_ptr_in_pipeline && sequence_interruptible && !woke_to_interrupt_q && !csr_flush_ack_q && !(ctrl_fsm_cs == SLEEP); // synchronous debug entry have far fewer restrictions than asynchronous entries. In principle synchronous debug entry should have the same // 'allowed' signal as exceptions - that is it should always be possible. assign sync_debug_allowed = !(ctrl_fsm_cs == SLEEP); // Debug pending for any other synchronous reason than single step // Note that the WB stage may be killed for interrupts and NMIs, thus invalidating the instruction causing the sync debug entry. // Exception triggers do not set pending_sync_debug, as they need to take the single step path through the FSM. assign pending_sync_debug = (trigger_match_in_wb) || (ebreak_in_wb && dcsr_i.ebreakm && (ex_wb_pipe_i.priv_lvl == PRIV_LVL_M) && !debug_mode_q) || // Ebreak with dcsr.ebreakm==1 during machine mode (ebreak_in_wb && dcsr_i.ebreaku && (ex_wb_pipe_i.priv_lvl == PRIV_LVL_U) && !debug_mode_q) || // Ebreak with dcsr.ebreaku==1 during user mode (ebreak_in_wb && debug_mode_q); // Ebreak during debug_mode restarts execution from dm_halt_addr, as a regular debug entry without CSR updates. // Debug pending for external debug request, only if not already in debug mode // Ideally the !debug_mode_q below should be factored into async_debug_allowed, but // that can currently cause a deadlock if debug_req_i gets asserted while in debug mode, as // a pending but not allowed async debug will cause the ID stage to halt forever while trying // to get to an interruptible state. // When the core wakes up from sleep due to debug_req_i, woke_to_debug_q will be set for exactly one cycle // to allow the core to enter debug one cycle after waking up, even though debug_req_i may have been deasserted. assign pending_async_debug = (debug_req_i || woke_to_debug_q) && !debug_mode_q; // Determine cause of debug. Set for all causes of debug entry. // In case of ebreak during debug mode, the entry code in DEBUG_TAKEN will // make sure not to update any CSRs. // The flopped version of this is checked during DEBUG_TAKEN state (one cycle delay) // For this core, the three top priorities are covered by pending_async_debug. // 1: resethaltreq (0x5) // 2: halt group (0x6) // 3: haltreq (0x3) // 4: trigger match (0x2) // 5: ebreak (0x1) // 6: single step (0x4) // The synchronous causes are determined here, while the priority between haltreq, sync_debug_cause and single step is determined within the FSM. assign sync_debug_cause = (trigger_match_in_wb) ? DBG_CAUSE_TRIGGER : // Etrigger will enter DEBUG_TAKEN as a single step (no halting), but kill pipeline as non-stepping entries. (ebreak_in_wb && dcsr_i.ebreakm && (ex_wb_pipe_i.priv_lvl == PRIV_LVL_M) && !debug_mode_q) ? DBG_CAUSE_EBREAK : // Ebreak during machine mode (ebreak_in_wb && dcsr_i.ebreaku && (ex_wb_pipe_i.priv_lvl == PRIV_LVL_U) && !debug_mode_q) ? DBG_CAUSE_EBREAK : // Ebreak during user mode (ebreak_in_wb && debug_mode_q) ? DBG_CAUSE_EBREAK : // Ebreak during debug mode DBG_CAUSE_NONE; // Debug cause to CSR from flopped version (valid during DEBUG_TAKEN) assign ctrl_fsm_o.debug_cause = debug_cause_q; // interrupt pending comes directly from the interrupt controller assign pending_interrupt = irq_req_ctrl_i; // Allow interrupts to be taken only if there is no data request in WB, // and no trans_valid has been clocked from EX to environment. // Not allowing interrupts when the core cannot take interrupts due to debug conditions. // Offloaded instructions in WB also block, as they cannot be killed after commit_kill=0 (EX stage) // LSU instructions which were suppressed due to previous exceptions or trigger match // will be interruptable as they were converted to NOP in ID stage. // When a fencei is present in WB and the LSU has completed all tranfers, the fencei handshake will be initiated. This must complete and the fencei instruction must retire before allowing interrupts. // Any multi operation instruction (table jumps, push/pop and double moves) may not be interrupted once the first operation has completed its operation in WB. // - This is guarded with using the sequence_interruptible, which tracks sequence progress through the WB stage. // When a CLIC pointer is in the pipeline stages EX or WB, we must block interrupts. // - Interrupt would otherwise kill the pointer and use the address of the pointer for mepc. A following mret would then return to the mtvt table, losing program progress. assign interrupt_allowed = lsu_interruptible_i && debug_interruptible && !fencei_ongoing && !clic_ptr_in_pipeline && sequence_interruptible && !interrupt_blanking_q && !csr_flush_ack_q && !(ctrl_fsm_cs == SLEEP); // Allowing NMI's follow the same rule as regular interrupts, except we don't need to regard blanking of NMIs after a load/store. // If the core woke up from sleep due to either debug or regular interrupts, the wakeup reason is honored by not allowing NMIs in the cycle after // waking up to such an event. assign nmi_allowed = lsu_interruptible_i && debug_interruptible && !fencei_ongoing && !clic_ptr_in_pipeline && sequence_interruptible && !(woke_to_debug_q || woke_to_interrupt_q) && !csr_flush_ack_q && !(ctrl_fsm_cs == SLEEP); // Do not allow interrupts if in debug mode, or single stepping without dcsr.stepie set. assign debug_interruptible = !(debug_mode_q || (dcsr_i.step && !dcsr_i.stepie)); // Do not count if we have an exception in WB, ebreak in WB, trigger match in WB (we do not execute the instruction at trigger address), // or WB stage is killed or halted. // When WB is halted, we do not know (yet) if the instruction will retire or get killed. // Halted WB due to debug will result in WB getting killed // Halted WB due to fence.i will result in fence.i retire after handshake is done and we count when WB is un-halted // ctrl_fsm_o.halt_limited_wb will only be set during SLEEP, and only affect the WB stage (not cs_registers) // In terms of counter events, no event should be counted while either of the WB related halts are asserted. assign wb_counter_event_gated = wb_counter_event && !exception_in_wb && !ebreak_in_wb && !trigger_match_in_wb && !ex_wb_pipe_i.instr_meta.dummy && // Don't count dummy instuctions !ctrl_fsm_o.kill_wb && !ctrl_fsm_o.halt_wb && !ctrl_fsm_o.halt_limited_wb; // Performance counter events assign ctrl_fsm_o.mhpmevent.minstret = wb_counter_event_gated; // Mux used to select PC from the different pipeline stages always_comb begin ctrl_fsm_o.pipe_pc = ex_wb_pipe_i.pc; unique case (pipe_pc_mux_ctrl) PC_WB: ctrl_fsm_o.pipe_pc = ex_wb_pipe_i.pc; PC_EX: ctrl_fsm_o.pipe_pc = id_ex_pipe_i.pc; PC_ID: ctrl_fsm_o.pipe_pc = if_id_pipe_i.pc; PC_IF: ctrl_fsm_o.pipe_pc = pc_if_i; default:; endcase end ////////////// // FSM comb // ////////////// always_comb begin // Default values ctrl_fsm_ns = ctrl_fsm_cs; ctrl_fsm_o.ctrl_busy = 1'b1; ctrl_fsm_o.instr_req = 1'b1; ctrl_fsm_o.pc_mux = PC_BOOT; ctrl_fsm_o.pc_set = 1'b0; ctrl_fsm_o.irq_ack = 1'b0; ctrl_fsm_o.irq_id = '0; ctrl_fsm_o.irq_priv = '0; ctrl_fsm_o.irq_shv = '0; ctrl_fsm_o.irq_level = 8'h00; ctrl_fsm_o.dbg_ack = 1'b0; // IF stage is halted if an instruction has been issued during single step // to avoid more than one instructions passing down the pipe. ctrl_fsm_o.halt_if = single_step_halt_if_q; // ID stage is halted when hazards are present (i.e. stalls for which the instruction currently in ID is not ready to be issued yet). // // In addition the ID stage may be halted to enable interrupts or debug to be taken. // - If an interrupt (including NMI) is present it is not guaranteed that the pipeline // can take the interrupt immediately. A LSU instruction could for instance be outstanding, causing the interrupt not to be taken. // The controller uses (pending_interrupt && interrupt_allowed) to check for these conditions. // When an interrupt (or NMI) is pending, the ID stage is halted to guarantee that an interruptible bubble eventually will // occur in the WB stage. // -- The ID stage is only halted iff debug_interruptible==1, indicating that we are not in debug mode or single step mode with interrupts disabled. // --- If halting for interrupts while in debug mode (debug_interruptible == 0), the core would deadlock waiting for interrupt_allowed, but the core // would never exit debug mode because the dret instruction could be blocked by the halted ID stage. // -- The ID stage is only halted iff also the ID stage is haltable (meaning ID stage is not currently in the middle of a sequence or waiting for a CLIC pointer) // // - The ID stage is halted when any async or sync debug is pending for the same reasons as for interrupts. // // - If not checking for id_stage_haltable for interrupts and debug, the core could end up in a situation where it tries to create a bubble // by halting ID, but the condition disallowing interrupt or debug will not disappear until the sequence currently handled by the ID stage // is done. This would create an unrecoverable deadlock. ctrl_fsm_o.halt_id = (ctrl_byp_i.jalr_stall || ctrl_byp_i.load_stall || ctrl_byp_i.csr_stall_id || ctrl_byp_i.sleep_stall || ctrl_byp_i.mnxti_id_stall) || ((pending_interrupt || pending_nmi || pending_nmi_early) && debug_interruptible && id_stage_haltable) || ((pending_async_debug || pending_sync_debug) && id_stage_haltable); // Halting EX if minstret_stall occurs. Otherwise we would read the wrong minstret value // Also halting EX if an offloaded instruction in WB may cause an exception, such that a following offloaded // instruction can correctly receive commit_kill. // Halting EX when an instruction in WB may cause an interrupt to become pending. ctrl_fsm_o.halt_ex = ctrl_byp_i.minstret_stall || ctrl_byp_i.irq_enable_stall || ctrl_byp_i.mnxti_ex_stall || ctrl_byp_i.csr_stall_ex; ctrl_fsm_o.halt_wb = 1'b0; ctrl_fsm_o.halt_limited_wb = 1'b0; // By default no stages are killed ctrl_fsm_o.kill_if = 1'b0; ctrl_fsm_o.kill_id = 1'b0; ctrl_fsm_o.kill_ex = 1'b0; ctrl_fsm_o.kill_wb = 1'b0; ctrl_fsm_o.csr_restore_mret = 1'b0; ctrl_fsm_o.csr_restore_dret = 1'b0; ctrl_fsm_o.csr_save_cause = 1'b0; ctrl_fsm_o.csr_cause = 32'h0; pipe_pc_mux_ctrl = PC_WB; exc_cause = 11'b0; debug_cause_n = DBG_CAUSE_NONE; debug_mode_n = debug_mode_q; ctrl_fsm_o.debug_csr_save = 1'b0; ctrl_fsm_o.debug_trigger_hit = '0; // Mask of which triggers did hit. ctrl_fsm_o.debug_trigger_hit_update = 1'b0; // Signal that hit bits of mcontrol6 shall be written. // Single step halting of IF single_step_halt_if_n = single_step_halt_if_q; // Ensure jumps and branches are taken only once branch_taken_n = branch_taken_q; jump_taken_n = jump_taken_q; fence_req_set = 1'b0; fence_req_clr = 1'b1; ctrl_fsm_o.pc_set_clicv = 1'b0; ctrl_fsm_o.pc_set_tbljmp = 1'b0; csr_flush_ack_n = 1'b0; ctrl_fsm_o.exception_alert_minor = 1'b0; ctrl_fsm_o.exception_alert_major = 1'b0; ctrl_fsm_o.mret_jump_id = 1'b0; ctrl_fsm_o.jump_in_id = jump_in_id; ctrl_fsm_o.jump_taken_id = jump_taken_id; ctrl_fsm_o.branch_in_ex = branch_in_ex; ctrl_fsm_o.branch_taken_ex = branch_taken_ex; clic_ptr_in_progress_id_set = 1'b0; clic_ptr_in_progress_id_clear = 1'b0; unique case (ctrl_fsm_cs) RESET: begin ctrl_fsm_o.instr_req = 1'b0; if (fetch_enable_i) begin if (debug_req_i) begin // Not raising instr_req to prevent fetching until we are in debug mode ctrl_fsm_o.instr_req = 1'b0; ctrl_fsm_o.pc_mux = PC_BOOT; ctrl_fsm_o.pc_set = 1'b1; // pc_set is required for propagating boot address to dpc (from IF stage) ctrl_fsm_ns = DEBUG_TAKEN; debug_cause_n = DBG_CAUSE_HALTREQ; end else begin ctrl_fsm_o.instr_req = 1'b1; ctrl_fsm_o.pc_mux = PC_BOOT; ctrl_fsm_o.pc_set = 1'b1; ctrl_fsm_ns = FUNCTIONAL; end end end FUNCTIONAL: begin // NMI if (pending_nmi && nmi_allowed) begin ctrl_fsm_o.kill_if = 1'b1; ctrl_fsm_o.kill_id = 1'b1; ctrl_fsm_o.kill_ex = 1'b1; ctrl_fsm_o.kill_wb = 1'b1; ctrl_fsm_o.pc_set = 1'b1; ctrl_fsm_o.pc_mux = PC_TRAP_NMI; ctrl_fsm_o.csr_save_cause = 1'b1; ctrl_fsm_o.csr_cause.irq = 1'b1; ctrl_fsm_o.csr_cause.exception_code = nmi_is_integrity_q ? (nmi_is_store_q ? INT_CAUSE_LSU_STORE_INTEGRITY_FAULT : INT_CAUSE_LSU_LOAD_INTEGRITY_FAULT) : (nmi_is_store_q ? INT_CAUSE_LSU_STORE_FAULT : INT_CAUSE_LSU_LOAD_FAULT); // Set alert major if NMI is due to integrity ctrl_fsm_o.exception_alert_major = nmi_is_integrity_q; // Set minor alert if NMI is due to bus errors ctrl_fsm_o.exception_alert_minor = !nmi_is_integrity_q; // Clear mcause.minhv when taking an NMI (only set when taking exceptions on CLIC/mret pointers) ctrl_fsm_o.csr_cause.minhv = 1'b0; if (CLIC) begin // Keep current interrupt level when taking NMIs ctrl_fsm_o.irq_level = mintstatus_i.mil; end // Save pc from oldest valid instruction if (ex_wb_pipe_i.instr_valid) begin pipe_pc_mux_ctrl = PC_WB; end else if (id_ex_pipe_i.instr_valid) begin pipe_pc_mux_ctrl = PC_EX; end else if (if_id_pipe_i.instr_valid) begin pipe_pc_mux_ctrl = PC_ID; end else begin // IF PC will always be valid as it points to the next // instruction to be issued from IF to ID. pipe_pc_mux_ctrl = PC_IF; end // External debug entry (async) end else if (pending_async_debug && async_debug_allowed) begin // Halt the whole pipeline // Halting makes sure instructions stay in the pipeline stage without propagating to the next. // This is needed by the debug entry code in the DEBUG_STAKEN state to pick the correct PC for storing in dpc. ctrl_fsm_o.halt_if = 1'b1; ctrl_fsm_o.halt_id = 1'b1; ctrl_fsm_o.halt_ex = 1'b1; ctrl_fsm_o.halt_wb = 1'b1; ctrl_fsm_ns = DEBUG_TAKEN; debug_cause_n = DBG_CAUSE_HALTREQ; // IRQ end else if (pending_interrupt && interrupt_allowed) begin ctrl_fsm_o.kill_if = 1'b1; ctrl_fsm_o.kill_id = 1'b1; ctrl_fsm_o.kill_ex = 1'b1; ctrl_fsm_o.kill_wb = 1'b1; ctrl_fsm_o.pc_set = 1'b1; exc_cause = {1'b0, irq_id_ctrl_i}; ctrl_fsm_o.irq_ack = 1'b1; ctrl_fsm_o.irq_id = irq_id_ctrl_i; ctrl_fsm_o.csr_save_cause = 1'b1; ctrl_fsm_o.csr_cause.irq = 1'b1; // Clear mcause.minhv when taking an interrupt (only set when taking exceptions on CLIC/mret pointers) ctrl_fsm_o.csr_cause.minhv = 1'b0; if (CLIC) begin ctrl_fsm_o.csr_cause.exception_code = {1'b0, irq_id_ctrl_i}; ctrl_fsm_o.irq_level = irq_clic_level_i; ctrl_fsm_o.irq_priv = irq_clic_priv_i; ctrl_fsm_o.irq_shv = irq_clic_shv_i; if (irq_clic_shv_i) begin ctrl_fsm_o.pc_mux = PC_TRAP_CLICV; clic_ptr_in_progress_id_set = 1'b1; ctrl_fsm_o.pc_set_clicv = 1'b1; end else begin ctrl_fsm_o.pc_mux = PC_TRAP_IRQ; end end else begin ctrl_fsm_o.pc_mux = PC_TRAP_IRQ; ctrl_fsm_o.csr_cause.exception_code = {1'b0, irq_id_ctrl_i}; end // Save pc from oldest valid instruction if (ex_wb_pipe_i.instr_valid) begin pipe_pc_mux_ctrl = PC_WB; end else if (id_ex_pipe_i.instr_valid) begin pipe_pc_mux_ctrl = PC_EX; end else if (if_id_pipe_i.instr_valid) begin pipe_pc_mux_ctrl = PC_ID; end else begin // IF PC will always be valid as it points to the next // instruction to be issued from IF to ID. pipe_pc_mux_ctrl = PC_IF; end // Synchronous debug entry (ebreak, trigger match) end else if (pending_sync_debug && sync_debug_allowed) begin // Halt the whole pipeline // Halting makes sure instructions stay in the pipeline stage without propagating to the next. // This is needed by the debug entry code in the DEBUG_STAKEN state to pick the correct PC for storing in dpc. ctrl_fsm_o.halt_if = 1'b1; ctrl_fsm_o.halt_id = 1'b1; ctrl_fsm_o.halt_ex = 1'b1; ctrl_fsm_o.halt_wb = 1'b1; ctrl_fsm_ns = DEBUG_TAKEN; debug_cause_n = sync_debug_cause; end else begin if (exception_in_wb && exception_allowed) begin // Kill all stages ctrl_fsm_o.kill_if = 1'b1; ctrl_fsm_o.kill_id = 1'b1; ctrl_fsm_o.kill_ex = 1'b1; // All write enables are suppressed, no need to kill WB. // RVFI also needs wb_valid to be able to signal an exception on rvfi_valid/rvfi_trap. ctrl_fsm_o.kill_wb = 1'b0; // Set pc to exception handler ctrl_fsm_o.pc_set = 1'b1; ctrl_fsm_o.pc_mux = debug_mode_q ? PC_TRAP_DBE : PC_TRAP_EXC; // Save CSR from WB pipe_pc_mux_ctrl = PC_WB; ctrl_fsm_o.csr_save_cause = !debug_mode_q; // Do not update CSRs if in debug mode ctrl_fsm_o.csr_cause.exception_code = exception_cause_wb; // Set mcause.minhv if exception is for a CLIC or mret pointer. Otherwise clear it ctrl_fsm_o.csr_cause.minhv = clic_ptr_in_wb || mret_ptr_in_wb; // Trigger Minor Alert ctrl_fsm_o.exception_alert_minor = exception_alert_minor_wb; // Trigger Major Alert ctrl_fsm_o.exception_alert_major = exception_alert_major_wb; // Special insn end else if (wfi_in_wb || wfe_in_wb) begin // Halt the entire pipeline // WFI/WFE will stay in WB until we exit sleep mode ctrl_fsm_o.halt_wb = 1'b1; ctrl_fsm_o.instr_req = 1'b0; ctrl_fsm_ns = SLEEP; end else if (fencei_in_wb || fence_in_wb) begin // fence.i behavior: // // - Can be killed due to interrupts and debug at any time before fencei_flush_req_o is asserted (so even after initial cycle in WB). // - Initially halt entire pipeline, making sure that possibly following loads/stores do not initiate transactions. // - Wait until the LSU is ready (i.e. write buffer must also be empty and possible NMIs will have been raised). // - Once the LSU is ready take the NMI if present or otherwise continue fence.i handling by initiating the fencei_flush_req_o handshake. // - Once the fencei_flush_req_o handshake is complete flush the entire pipeline (branch to next instruction) and retire the fence.i. // fence behavior: // // - Can be killed due to interrupts and debug at any time (so even after initial cycle in WB). // - Initially halt entire pipeline, making sure that possibly following loads/stores do not initiate transactions. // - Wait until the LSU is ready (i.e. write buffer must also be empty and possible NMIs will have been raised). // - Once the LSU is ready take the NMI if present or otherwise continue fence handling. // - Flush the entire pipeline (branch to next instruction) and retire the fence. // Halt the pipeline ctrl_fsm_o.halt_if = 1'b1; ctrl_fsm_o.halt_id = 1'b1; ctrl_fsm_o.halt_ex = 1'b1; ctrl_fsm_o.halt_wb = 1'b1; // Set fence_req_q when the LSU is no longer busy fence_req_set = !lsu_busy_i; fence_req_clr = 1'b0; if (fencei_in_wb ? fencei_req_and_ack_q : fence_req_q) begin // Unhalt wb, kill if,id,ex ctrl_fsm_o.kill_if = 1'b1; ctrl_fsm_o.kill_id = 1'b1; ctrl_fsm_o.kill_ex = 1'b1; ctrl_fsm_o.halt_wb = 1'b0; // Jump to PC from oldest valid instruction, excluding WB stage if (id_ex_pipe_i.instr_valid) begin pipe_pc_mux_ctrl = PC_EX; end else if (if_id_pipe_i.instr_valid) begin pipe_pc_mux_ctrl = PC_ID; end else begin pipe_pc_mux_ctrl = PC_IF; end ctrl_fsm_o.pc_set = 1'b1; ctrl_fsm_o.pc_mux = PC_WB_PLUS4; // Clear fence_req_q fence_req_set = 1'b0; fence_req_clr = 1'b1; end end else if (dret_in_wb) begin // dret takes jump from WB stage // Kill previous stages and jump to pc in dpc ctrl_fsm_o.kill_if = 1'b1; ctrl_fsm_o.kill_id = 1'b1; ctrl_fsm_o.kill_ex = 1'b1; ctrl_fsm_o.pc_mux = PC_DRET; ctrl_fsm_o.pc_set = 1'b1; ctrl_fsm_o.csr_restore_dret = 1'b1; single_step_halt_if_n = 1'b0; debug_mode_n = 1'b0; end else if (csr_wr_in_wb_flush_i) begin // CSR write in WB requires pipeline flush, halt all stages except WB // EX could contain a load/store, need to avoid its address phase going onto the bus ctrl_fsm_o.halt_if = 1'b1; ctrl_fsm_o.halt_id = 1'b1; ctrl_fsm_o.halt_ex = 1'b1; // Set flop input to get ack in the next cycle when the write is done. csr_flush_ack_n = 1'b1; end else if (csr_flush_ack_q) begin // Flush pipeline because of CSR update in the previous cycle ctrl_fsm_o.kill_if = 1'b1; ctrl_fsm_o.kill_id = 1'b1; ctrl_fsm_o.kill_ex = 1'b1; // Jump to PC from oldest valid instruction, excluding WB stage if (id_ex_pipe_i.instr_valid) begin pipe_pc_mux_ctrl = PC_EX; end else if (if_id_pipe_i.instr_valid) begin pipe_pc_mux_ctrl = PC_ID; end else begin pipe_pc_mux_ctrl = PC_IF; end ctrl_fsm_o.pc_set = 1'b1; ctrl_fsm_o.pc_mux = PC_WB_PLUS4; end else if (branch_taken_ex) begin ctrl_fsm_o.kill_if = 1'b1; // For SECURE, branches will be both in ID and EX when the branch is taken, avoid killing ID. ctrl_fsm_o.kill_id = (SECURE && xsecure_ctrl_i.cpuctrl.pc_hardening) ? 1'b0 : 1'b1; ctrl_fsm_o.pc_mux = PC_BRANCH; ctrl_fsm_o.pc_set = 1'b1; // Set flag to avoid further branches to the same target // if we are stalled branch_taken_n = 1'b1; end else if (jump_taken_id) begin // Jumps in ID (JAL, JALR, mret) // kill_if ctrl_fsm_o.kill_if = 1'b1; if (sys_mret_id) begin // If the mcause.minhv bit is set when an mret is in ID, the mret should restart the CLIC pointer fetch using mepc as // a pointer to the pointer instead of jumping to the address in mepc. // This is done below by signalling pc_set_clicv along with pc_mux=PC_MRET. This will // treat the mepc as an address to a CLIC pointer. The minhv flag will only be set when an exception is taken on a // CLIC or mret pointer, and cleared when a trap for any other cause except debug is taken. It is also writeable by SW. // ID stage is halted while it contains an mret and at the same time there are CSR writes (including CLIC pointers) in EX or WB, hence it is safe to use mcause here. if (mcause_i.minhv) begin // mcause.minhv set, exception occured during last pointer fetch (or SW wrote it) // Do another pointer fetch from the address stored in mepc. ctrl_fsm_o.pc_set = 1'b1; ctrl_fsm_o.pc_set_clicv = 1'b1; // Treat mepc as a pointer fetch ctrl_fsm_o.pc_mux = PC_MRET; // Set flag to avoid further jumps to the same target // if we are stalled jump_taken_n = 1'b1; // Clear flag for halting IF in case of single stepping // For the single step to complete, both the mret and the pointer must reach WB. // If no unhalt is done here, the pointer will never reach WB. single_step_halt_if_n = 1'b0; end else begin // xcause.xinhv not set for the previous privilege level, do regular mret ctrl_fsm_o.pc_mux = PC_MRET; ctrl_fsm_o.pc_set = 1'b1; // Set flag to avoid further jumps to the same target // if we are stalled jump_taken_n = 1'b1; end // Make sure cs_registers updates privilege level for IF stage ctrl_fsm_o.mret_jump_id = !debug_mode_q; end else begin // For table jumps we have two different jumps // - First part does a pointer fetch from (jvt + (index<<2)) // - Second part jumps to the fetched pointer // Regular jumps use the regular jump to the target calculated in the ID stage. ctrl_fsm_o.pc_mux = if_id_pipe_i.instr_meta.tbljmp && !if_id_pipe_i.last_op ? PC_TBLJUMP : if_id_pipe_i.instr_meta.tbljmp && if_id_pipe_i.last_op ? PC_POINTER : PC_JUMP; ctrl_fsm_o.pc_set = 1'b1; ctrl_fsm_o.pc_set_tbljmp = if_id_pipe_i.instr_meta.tbljmp && !if_id_pipe_i.last_op; // Set flag to avoid further jumps to the same target // if we are stalled jump_taken_n = 1'b1; end end else if (clic_ptr_in_id || mret_ptr_in_id) begin if (!(if_id_pipe_i.instr.bus_resp.err || if_id_pipe_i.instr.bus_resp.integrity_err || (if_id_pipe_i.instr.mpu_status != MPU_OK))) begin if (!branch_taken_q) begin ctrl_fsm_o.pc_set = 1'b1; ctrl_fsm_o.pc_mux = PC_POINTER; ctrl_fsm_o.kill_if = 1'b1; branch_taken_n = 1'b1; end end end // The following if statements are in parallel with the main decision tree. // If any of these would be part of the decision tree, they could mask jumps and branches // from being taken. // CLIC pointer in ID clears pointer fetch flag if (clic_ptr_in_id && id_valid_i && ex_ready_i) begin clic_ptr_in_progress_id_clear = 1'b1; end // Regular mret in WB restores CSR regs if (mret_in_wb && !ctrl_fsm_o.kill_wb && !ctrl_fsm_o.halt_wb) begin ctrl_fsm_o.csr_restore_mret = !debug_mode_q; end end // !debug or interrupts // Single step debug entry or etrigger debug entry // Need to be after (in parallell with) exception/interrupt handling // to ensure mepc and if_pc are set correctly for use in dpc, // and to ensure only one instruction can retire during single step // Triggers other than exception trigger do not cause any state change before debug entry. // Exception triggers do all the side effects off taking an exception (mcause, mepc etc) but without // executing the first handler instruction before debug entry. If an exception trigger factored into // 'pending_sync_debug' instead og the single step logic, then these side effects of taking the exception would not occur, since exceptions // are prioritized below all debug entries in the FSM. if (pending_single_step || etrigger_in_wb) begin if (single_step_allowed) begin ctrl_fsm_ns = DEBUG_TAKEN; // etrigger has higher priority than step. // Any other higher priority cause of debug would not be pending is this context // Async and sync debug entries will halt WB, causing !wb_valid which in turn pulls pending_single_step and etrigger_in_wb low. // Any taken interrupt or NMI kills WB, also pulling wb_valid low. Pending_single_step will still be high, but any other debug entry // reason as seen from WB will be deasserted. if (etrigger_in_wb) begin debug_cause_n = DBG_CAUSE_TRIGGER; end else begin debug_cause_n = DBG_CAUSE_STEP; end end end end SLEEP: begin // There should be a bubble in EX in this state (checked by assertion) // We are avoiding that a load/store starts its bus transaction ctrl_fsm_o.ctrl_busy = 1'b0; ctrl_fsm_o.instr_req = 1'b0; // Put backpressure on pipeline to avoid retiring WFI until we wake up. // Using limited version of halt_wb to avoid timing paths through cs_registers and bypass onto the data OBI bus. // Assertions exist to check that no CSR instruction can be in WB at this time. ctrl_fsm_o.halt_limited_wb = 1'b1; // Wake up from SLEEP if (ctrl_fsm_o.wake_from_sleep) begin ctrl_fsm_ns = FUNCTIONAL; ctrl_fsm_o.ctrl_busy = 1'b1; // Keep IF/ID halted while waking up (EX contains a bubble) // Any jump/table jump/mret which is in ID in this cycle must also remain in ID // the next cycle for their side effects to be taken during the FUNCTIONAL state in case the interrupt is not actually taken. ctrl_fsm_o.halt_id = 1'b1; // Unhalt WB to allow WFI to retire when we exit SLEEP mode // Using limited version of halt_wb to avoid timing paths through cs_registers and bypass onto the data OBI bus. ctrl_fsm_o.halt_limited_wb = 1'b0; end end DEBUG_TAKEN: begin // Indicate that debug is taken ctrl_fsm_o.dbg_ack = 1'b1; // Clear flags for halting IF during single step single_step_halt_if_n = 1'b0; // Set pc ctrl_fsm_o.pc_set = 1'b1; ctrl_fsm_o.pc_mux = PC_TRAP_DBD; // Save CSRs ctrl_fsm_o.csr_save_cause = !(ebreak_in_wb && debug_mode_q); // No CSR update for ebreak in debug mode ctrl_fsm_o.debug_csr_save = 1'b1; // If a trigger was hit, signal that mcontrol6 hit1 and hit0 bits should be written. // Only triggers configured as mcontrol6 will perform the actual write within debug_triggers. if (debug_cause_q == DBG_CAUSE_TRIGGER) begin ctrl_fsm_o.debug_trigger_hit_update = 1'b1; ctrl_fsm_o.debug_trigger_hit = ex_wb_pipe_i.trigger_match | wpt_match_wb_i; end // debug_cause_q set when decision was made to enter debug if (debug_cause_q != DBG_CAUSE_STEP) begin // Kill pipeline ctrl_fsm_o.kill_if = 1'b1; ctrl_fsm_o.kill_id = 1'b1; ctrl_fsm_o.kill_ex = 1'b1; // Ebreak that causes debug entry should not be killed, otherwise RVFI will skip it // Trigger match should also be signalled as not killed (all write enables are suppressed in ID), otherwise RVFI will not properly signal a trigger match. // Exception trigger match should have nothing in WB, excepted instruction finished the previous cycle and set mepc and mcause due to the exception. // Ebreak during debug_mode restarts from dm_halt_addr, without CSR updates. Not killing ebreak due to the same RVFI/ISS reasons. // Neither ebreak nor trigger match have any state updates in WB. For trigger match, all write enables are suppressed in the ID stage. // Thus this change is not visible to core state, only for RVFI use. ctrl_fsm_o.kill_wb = !((debug_cause_q == DBG_CAUSE_EBREAK) || (debug_cause_q == DBG_CAUSE_TRIGGER)); // Save pc from oldest valid instruction if (ex_wb_pipe_i.instr_valid) begin pipe_pc_mux_ctrl = PC_WB; end else if (id_ex_pipe_i.instr_valid) begin pipe_pc_mux_ctrl = PC_EX; end else if (if_id_pipe_i.instr_valid) begin pipe_pc_mux_ctrl = PC_ID; end else begin pipe_pc_mux_ctrl = PC_IF; end end else begin // Single step // Only kill IF. WB should be allowed to complete // ID and EX are empty as IF is blocked after one issue in single step mode ctrl_fsm_o.kill_if = 1'b1; ctrl_fsm_o.kill_id = 1'b0; ctrl_fsm_o.kill_ex = 1'b0; ctrl_fsm_o.kill_wb = 1'b0; // Should use pc from IF (next insn, as IF is halted after first issue) pipe_pc_mux_ctrl = PC_IF; end // Enter debug mode next cycle debug_mode_n = 1'b1; ctrl_fsm_ns = FUNCTIONAL; end default: begin // should never happen ctrl_fsm_o.instr_req = 1'b0; ctrl_fsm_ns = RESET; end endcase // Detect first insn issue in single step after dret // Any Zc sequence must fully complete (last_op_if_i) before halting the IF stage. // Used to block further issuing if (!ctrl_fsm_o.debug_mode && dcsr_i.step && !single_step_halt_if_q && (if_valid_i && id_ready_i && (last_op_if_i || abort_op_if_i))) begin single_step_halt_if_n = 1'b1; end // The branch_taken flag is used to prevent multiple 'pc_set' for the same branch instruction // in case it stays multiple cycles in EX. // The flag is cleared when a new instruction enters EX and EX contains the last operation of an instruction (branch is done). // New instruction from ID to EX is detected by checking id_ex_pipe.last_sec_op==1 when the ID/EX handshake is performed. // NB! It should not be needed to include the branch_taken_q flag, but the assertion checking for no // back-to-back branches cannot converge without it. // // The timing of the wanted behaviour is shown in the table below, we need to clear the flag when the target instruction // enters the EX stage. // | IF | ID | EX | WB | // | | B 2/2 | B 1/2 | LD/ST | <- Branch is taken, pc_set=1, wb_ready=0 // | target | B 2/2 | B 1/2 | LD/ST | <- branch_taken_q = 1 // | target+1 | target | B 2/2 | B 1/2 | <- branch_taken_q = 1 // | target+2 | target+1 | target | B 2/2 | <- branch_taken_q = 0 // | target+2 | target+1 | target | B 2/2 | <- branch_taken_q = 0 if ((id_valid_i && ex_ready_i && id_ex_pipe_i.last_op && branch_taken_q) || ctrl_fsm_o.kill_ex) begin branch_taken_n = 1'b0; end // Clear jump_taken flag when a new instruction enters the ID stage, or ID is killed. // The flag has ID stage timing, and thus when a new instruction enters ID // the flag must be cleared. IF stage has no 'last_sec_op' output, as the instructions // are only split into sub operations in the ID stage. // Jump_taken_q flag not strictly needed, but added to get same semantics as for branches. if ((if_valid_i && id_ready_i && jump_taken_q) || ctrl_fsm_o.kill_id) begin jump_taken_n = 1'b0; end end // Wakeup from sleep assign ctrl_fsm_o.wake_from_sleep = pending_nmi || irq_wu_ctrl_i || pending_async_debug || (wfe_in_wb && wu_wfe_i); // Only WFE wakes up for wfe_wu_i assign ctrl_fsm_o.debug_no_sleep = debug_mode_q || dcsr_i.step; //////////////////// // Flops // //////////////////// // FSM state and debug_mode always_ff @(posedge clk , negedge rst_n) begin if (rst_n == 1'b0) begin ctrl_fsm_cs <= RESET; debug_mode_q <= 1'b0; debug_cause_q <= DBG_CAUSE_NONE; end else begin ctrl_fsm_cs <= ctrl_fsm_ns; debug_mode_q <= debug_mode_n; debug_cause_q <= debug_cause_n; end end // debug_mode_if is a control input for the if stage. // For both debug mode entry end exit, the IF, ID and EX stages are killed. While the IF stage is killed it starts // fetching the next instruction (sets the obi request high), requiring a valid debug mode signal for this fetch. // The debug_mode_if signal is valid for all IF fetches. assign ctrl_fsm_o.debug_mode_if = debug_mode_n; assign ctrl_fsm_o.debug_mode = debug_mode_q; // Sticky version of lsu_err_wb_i always_ff @(posedge clk, negedge rst_n) begin if (rst_n == 1'b0) begin nmi_pending_q <= 1'b0; nmi_is_store_q <= 1'b0; nmi_is_integrity_q <= 1'b0; end else begin // Bus error [0] or integrity error [1] if ((lsu_err_wb_i.bus_err || lsu_err_wb_i.integrity_err) && !nmi_pending_q) begin // Set whenever an error occurs in WB for the LSU, unless we already have an NMI pending. // Later errors could overwrite the bit for load/store type, and with mtval the address would be overwritten. nmi_pending_q <= 1'b1; nmi_is_integrity_q <= lsu_err_wb_i.integrity_err; nmi_is_store_q <= lsu_err_wb_i.store; // Clear when the controller takes the NMI end else if (ctrl_fsm_o.pc_set && (ctrl_fsm_o.pc_mux == PC_TRAP_NMI)) begin nmi_pending_q <= 1'b0; end end end // Flop used to gate if_valid after one instruction issued // in single step mode always_ff @(posedge clk, negedge rst_n) begin if (rst_n == 1'b0) begin single_step_halt_if_q <= 1'b0; branch_taken_q <= 1'b0; jump_taken_q <= 1'b0; csr_flush_ack_q <= 1'b0; end else begin single_step_halt_if_q <= single_step_halt_if_n; branch_taken_q <= branch_taken_n; jump_taken_q <= jump_taken_n; csr_flush_ack_q <= csr_flush_ack_n; end end // Flop used to track LSU instructions in WB. High in the cycle after an LSU instruction // leaves WB. // Used for disregarding interrupts one cycle after a load/store to make sure the // interrupt controller propagates the inputs through its flops. always_ff @(posedge clk, negedge rst_n) begin if (rst_n == 1'b0) begin interrupt_blanking_q <= 1'b0; end else begin interrupt_blanking_q <= ex_wb_pipe_i.instr_valid && ex_wb_pipe_i.lsu_en; end end // Flops for fencei handshake request always_ff @(posedge clk, negedge rst_n) begin if (rst_n == 1'b0) begin fence_req_q <= 1'b0; fencei_req_and_ack_q <= 1'b0; end else begin // Flop fencei_flush_ack_i to break timing paths // fencei_flush_ack_i must be qualified with fencei_flush_req_o fencei_req_and_ack_q <= fencei_flush_req_o && fencei_flush_ack_i; // Set and clear fence_req_q based on FSM output. Also clear when fence.i handshake is completed if (fence_req_clr || (fencei_flush_req_o && fencei_flush_ack_i)) begin fence_req_q <= 1'b0; end else if (fence_req_set) begin fence_req_q <= 1'b1; end end end // Set flush request if we have a fence.i assign fencei_flush_req_o = fencei_in_wb ? fence_req_q : 1'b0; // minstret event always_ff @(posedge clk, negedge rst_n) begin if (rst_n == 1'b0) begin wb_counter_event <= 1'b0; end else begin // When the last part of an instruction reaches WB we may increment counters, // unless WB stage is halted. WB stage is halted due to either halt_wb or halt_limited wb (SLEEP with WFI/WFE in WB only). // A halted instruction in WB may or may not be killed later, // thus we cannot count it until we know for sure if it will retire. // i.e halt_wb due to debug will result in killed WB, while for fence.i it will retire. // Note that this event bit is further gated before sent to the actual counters in case // other conditions prevent counting. // CLIC: Exluding pointer fetches as they are not instructions if (ex_valid_i && wb_ready_i && last_op_ex_i && !id_ex_pipe_i.instr_meta.clic_ptr) begin wb_counter_event <= 1'b1; end else begin // Keep event flag high while WB is halted, as we don't know if it will retire yet if (!ctrl_fsm_o.halt_wb && !ctrl_fsm_o.halt_limited_wb) begin wb_counter_event <= 1'b0; end end end end // Instruction sequences may not be interrupted/killed if the first operation has already been retired. // Flop set when first_op without either last_op or abort_op is done in WB, and cleared when last_op is done. always_ff @(posedge clk, negedge rst_n) begin if (rst_n == 1'b0) begin sequence_in_progress_wb <= 1'b0; end else begin if (!sequence_in_progress_wb) begin if (wb_valid_i && ex_wb_pipe_i.first_op && !(last_op_wb_i || abort_op_wb_i)) begin // wb_valid implies ex_wb_pipe.instr_valid sequence_in_progress_wb <= 1'b1; end end else begin // sequence_in_progress_wb is set, clear when last_op retires or sequence is aborted due to exceptions. if (wb_valid_i && (last_op_wb_i || abort_op_wb_i)) begin sequence_in_progress_wb <= 1'b0; end // No need to reset this flag on kill_wb. // When flag is high, there should be no reason to kill WB as they are blocked by the flag itself. // This is checked by an assertion, no killing while !sequence_interruptible // If debug entry is caused by a watchpoint address trigger, then abort_op_wb_i will be 1 and a debug entry is initiated. // This must also cause the sequence_in_progress_wb to be reset as the sequence is effectively terminated, although the instruction itself is not killed or completed // in a normal manner. As the WB stage is halted for debug entry on a watchcpoint trigger, wb_valid_i is zero. if (ex_wb_pipe_i.instr_valid && (|wpt_match_wb_i) && abort_op_wb_i) begin sequence_in_progress_wb <= 1'b0; end end end end // Logic to track first_op and last_op through the ID stage to detect unhaltable sequences // Flop set when first_op is done in ID, and cleared when last_op is done, or ID is killed // If the ID stage first_op has a known exception or trigger match the flop will not be set // as the controller will either take an exception or enter debug mode without finishing the sequence. always_ff @(posedge clk, negedge rst_n) begin if (rst_n == 1'b0) begin sequence_in_progress_id <= 1'b0; end else begin if (!sequence_in_progress_id) begin if (id_valid_i && ex_ready_i && first_op_id_i && !(last_op_id_i || abort_op_id_i)) begin // id_valid implies if_id_pipe.instr.valid sequence_in_progress_id <= 1'b1; end end else begin // sequence_in_progress_id is set, clear when last_op retires or ID stage is killed if (id_valid_i && ex_ready_i && (last_op_id_i || abort_op_id_i)) begin sequence_in_progress_id <= 1'b0; end end // Reset flag on a kill_id. May happen before sequence_in_progress_id gets set, or if a sequence gets an exception in the middle if (ctrl_fsm_o.kill_id) begin sequence_in_progress_id <= 1'b0; end end end // Flag for tracking CLIC pointer fetches. Only done when acking an SHV interrupt, not when an mret restarts a pointer fetch. always_ff @(posedge clk, negedge rst_n) begin if (rst_n == 1'b0) begin clic_ptr_in_progress_id <= 1'b0; end else begin if (!clic_ptr_in_progress_id) begin if (clic_ptr_in_progress_id_set) begin clic_ptr_in_progress_id <= 1'b1; end end else begin // clic_ptr_in_progress_id is set, clear when controller assert the clear-bit or ID stage is killed if (clic_ptr_in_progress_id_clear) begin clic_ptr_in_progress_id <= 1'b0; end end // When clic_ptr_in_progress_id is high, the ID stage can never be killed and thus no reset on kill_id is needed. // This is checked by an assertion. Taking an SHV interrupt kills the pipeline ensuring no exceptions may happen, and // the flag itself keeps debug or interrupts from killing the pipeline. end end // Flags for remembering if the core woke up due to a debug request or interrupt. always_ff @(posedge clk, negedge rst_n) begin if (rst_n == 1'b0) begin woke_to_debug_q <= 1'b0; woke_to_interrupt_q <= 1'b0; end else begin // Woke up to debug if no nmi was pending woke_to_debug_q <= (ctrl_fsm_cs == SLEEP) && debug_req_i && !(pending_nmi); // Woke up to interrupts if no NMI or debug was pending woke_to_interrupt_q <= (ctrl_fsm_cs == SLEEP) && irq_wu_ctrl_i && !(pending_nmi || debug_req_i); end end ///////////////////// // Debug state FSM // ///////////////////// always_ff @(posedge clk , negedge rst_n) begin if (rst_n == 1'b0) begin debug_fsm_cs <= HAVERESET; end else begin debug_fsm_cs <= debug_fsm_ns; end end always_comb begin debug_fsm_ns = debug_fsm_cs; case (debug_fsm_cs) HAVERESET: begin if (debug_mode_n || (ctrl_fsm_ns == FUNCTIONAL)) begin if (debug_mode_n) begin debug_fsm_ns = HALTED; end else begin debug_fsm_ns = RUNNING; end end end RUNNING: begin if (debug_mode_n) begin debug_fsm_ns = HALTED; end end HALTED: begin if (!debug_mode_n) begin debug_fsm_ns = RUNNING; end end default: begin debug_fsm_ns = HAVERESET; end endcase end assign ctrl_fsm_o.debug_havereset = debug_fsm_cs[HAVERESET_INDEX]; assign ctrl_fsm_o.debug_running = debug_fsm_cs[RUNNING_INDEX]; assign ctrl_fsm_o.debug_halted = debug_fsm_cs[HALTED_INDEX]; endmodule