ibex/rtl/ibex_controller.sv
Pirmin Vogel e1a7dcf37f ID stage: rework CSR-related pipeline flushes
This commit clarifies why CSR-related pipeline flushes are needed
(e.g. when enabling interrupts), only introduces them when doing the
critical modifications (write/set bits in `mstatus` and `mie` CSRs
for enabling interrupts, reads and clears are uncritical for these
CSRs), and makes sure the controller is actually able to start
handling interrupts while doing a CSR-related pipeline flush.

This resolves lowrisc/ibex#6.
2019-08-16 13:28:52 +01:00

583 lines
21 KiB
Systemverilog
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright lowRISC contributors.
// Copyright 2018 ETH Zurich and University of Bologna.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
////////////////////////////////////////////////////////////////////////////////
// Engineer: Matthias Baer - baermatt@student.ethz.ch //
// //
// Additional contributions by: //
// Igor Loi - igor.loi@unibo.it //
// Andreas Traber - atraber@student.ethz.ch //
// Sven Stucki - svstucki@student.ethz.ch //
// Davide Schiavone - pschiavo@iis.ee.ethz.ch //
// //
// Design Name: Main controller //
// Project Name: ibex //
// Language: SystemVerilog //
// //
// Description: Main controller of the processor //
// //
////////////////////////////////////////////////////////////////////////////////
/**
* Main controller of the processor
*/
module ibex_controller (
input logic clk_i,
input logic rst_ni,
input logic fetch_enable_i, // start decoding
output logic ctrl_busy_o, // core is busy processing instrs
output logic first_fetch_o, // core is at the FIRST FETCH stage
// decoder related signals
input logic illegal_insn_i, // decoder has an invalid instr
input logic ecall_insn_i, // decoder has ECALL instr
input logic mret_insn_i, // decoder has MRET instr
input logic dret_insn_i, // decoder has DRET instr
input logic wfi_insn_i, // decoder has WFI instr
input logic ebrk_insn_i, // decoder has EBREAK instr
input logic csr_pipe_flush_i, // do CSR-related pipeline flush
// from IF-ID pipeline stage
input logic instr_valid_i, // instr from IF-ID reg is valid
input logic [31:0] instr_i, // instr from IF-ID reg, for mtval
input logic [15:0] instr_compressed_i, // instr from IF-ID reg, for mtval
input logic instr_is_compressed_i, // instr from IF-ID reg is compressed
input logic instr_fetch_err_i, // instr from IF-ID reg has error
input logic [31:0] pc_id_i, // instr from IF-ID reg address
// to IF-ID pipeline stage
output logic instr_valid_clear_o, // kill instr in IF-ID reg
output logic id_in_ready_o, // ID stage is ready for new instr
// to prefetcher
output logic instr_req_o, // start fetching instructions
output logic pc_set_o, // jump to address set by pc_mux
output ibex_pkg::pc_sel_e pc_mux_o, // IF stage fetch address selector
// (boot, normal, exception...)
output ibex_pkg::exc_pc_sel_e exc_pc_mux_o, // IF stage selector for exception PC
output ibex_pkg::exc_cause_e exc_cause_o, // for IF stage, CSRs
// LSU
input logic [31:0] lsu_addr_last_i, // for mtval
input logic load_err_i,
input logic store_err_i,
// jump/branch signals
input logic branch_set_i, // branch taken set signal
input logic jump_set_i, // jump taken set signal
// interrupt signals
input logic csr_mstatus_mie_i, // M-mode interrupt enable bit
input logic csr_msip_i, // software interrupt pending
input logic csr_mtip_i, // timer interrupt pending
input logic csr_meip_i, // external interrupt pending
input logic [14:0] csr_mfip_i, // fast interrupt pending
input logic irq_pending_i, // interrupt request pending
input logic irq_nm_i, // non-maskeable interrupt
// debug signals
input logic debug_req_i,
output ibex_pkg::dbg_cause_e debug_cause_o,
output logic debug_csr_save_o,
input logic debug_single_step_i,
input logic debug_ebreakm_i,
output logic csr_save_if_o,
output logic csr_save_id_o,
output logic csr_restore_mret_id_o,
output logic csr_save_cause_o,
output logic [31:0] csr_mtval_o,
// stall signals
input logic stall_lsu_i,
input logic stall_multdiv_i,
input logic stall_jump_i,
input logic stall_branch_i,
// performance monitors
output logic perf_jump_o, // we are executing a jump
// instruction (j, jr, jal, jalr)
output logic perf_tbranch_o // we are executing a taken branch
// instruction
);
import ibex_pkg::*;
// FSM state encoding
typedef enum logic [3:0] {
RESET, BOOT_SET, WAIT_SLEEP, SLEEP, FIRST_FETCH, DECODE, FLUSH,
IRQ_TAKEN, DBG_TAKEN_IF, DBG_TAKEN_ID
} ctrl_fsm_e;
ctrl_fsm_e ctrl_fsm_cs, ctrl_fsm_ns;
logic nmi_mode_q, nmi_mode_d;
logic debug_mode_q, debug_mode_d;
logic load_err_q, load_err_d;
logic store_err_q, store_err_d;
logic stall;
logic halt_if;
logic halt_id;
logic exc_req;
logic exc_req_lsu;
logic special_req;
logic enter_debug_mode;
logic handle_irq;
logic [4:0] mfip_id;
logic unused_csr_mtip;
`ifndef SYNTHESIS
// synopsys translate_off
// make sure we are called later so that we do not generate messages for
// glitches
always_ff @(negedge clk_i) begin
// print warning in case of decoding errors
if ((ctrl_fsm_cs == DECODE) && instr_valid_i && illegal_insn_i) begin
$display("%t: Illegal instruction (core %0d) at PC 0x%h: 0x%h", $time, ibex_core.core_id_i,
ibex_id_stage.pc_id_i, ibex_id_stage.instr_rdata_i);
end
end
// synopsys translate_on
`endif
////////////////
// Exceptions //
////////////////
assign load_err_d = load_err_i;
assign store_err_d = store_err_i;
// exception requests
assign exc_req = ecall_insn_i | ebrk_insn_i | illegal_insn_i | instr_fetch_err_i;
// LSU exception requests
assign exc_req_lsu = store_err_i | load_err_i;
// special requests: special instructions, pipeline flushes, exceptions...
assign special_req = mret_insn_i | dret_insn_i | wfi_insn_i | csr_pipe_flush_i |
exc_req | exc_req_lsu;
////////////////
// Interrupts //
////////////////
assign enter_debug_mode = debug_req_i & ~debug_mode_q;
// interrupts including NMI are ignored while in debug mode [Debug Spec v0.13.2, p.39]
assign handle_irq = ~debug_mode_q &
((irq_nm_i & ~nmi_mode_q) | (irq_pending_i & csr_mstatus_mie_i));
// generate ID of fast interrupts, highest priority to highest ID
always_comb begin : gen_mfip_id
if (csr_mfip_i[14]) mfip_id = 5'd14;
else if (csr_mfip_i[13]) mfip_id = 5'd13;
else if (csr_mfip_i[12]) mfip_id = 5'd12;
else if (csr_mfip_i[11]) mfip_id = 5'd11;
else if (csr_mfip_i[10]) mfip_id = 5'd10;
else if (csr_mfip_i[ 9]) mfip_id = 5'd9;
else if (csr_mfip_i[ 8]) mfip_id = 5'd8;
else if (csr_mfip_i[ 7]) mfip_id = 5'd7;
else if (csr_mfip_i[ 6]) mfip_id = 5'd6;
else if (csr_mfip_i[ 5]) mfip_id = 5'd5;
else if (csr_mfip_i[ 5]) mfip_id = 5'd5;
else if (csr_mfip_i[ 4]) mfip_id = 5'd4;
else if (csr_mfip_i[ 3]) mfip_id = 5'd3;
else if (csr_mfip_i[ 2]) mfip_id = 5'd2;
else if (csr_mfip_i[ 1]) mfip_id = 5'd1;
else mfip_id = 5'd0;
end
assign unused_csr_mtip = csr_mtip_i;
/////////////////////
// Core controller //
/////////////////////
always_comb begin
// Default values
instr_req_o = 1'b1;
csr_save_if_o = 1'b0;
csr_save_id_o = 1'b0;
csr_restore_mret_id_o = 1'b0;
csr_save_cause_o = 1'b0;
csr_mtval_o = '0;
pc_mux_o = PC_BOOT;
pc_set_o = 1'b0;
exc_pc_mux_o = EXC_PC_IRQ;
exc_cause_o = EXC_CAUSE_INSN_ADDR_MISA; // = 6'h00
ctrl_fsm_ns = ctrl_fsm_cs;
ctrl_busy_o = 1'b1;
first_fetch_o = 1'b0;
halt_if = 1'b0;
halt_id = 1'b0;
debug_csr_save_o = 1'b0;
debug_cause_o = DBG_CAUSE_EBREAK;
debug_mode_d = debug_mode_q;
nmi_mode_d = 1'b0;
perf_tbranch_o = 1'b0;
perf_jump_o = 1'b0;
unique case (ctrl_fsm_cs)
RESET: begin
// just wait for fetch_enable
instr_req_o = 1'b0;
pc_mux_o = PC_BOOT;
pc_set_o = 1'b1;
if (fetch_enable_i) begin
ctrl_fsm_ns = BOOT_SET;
end
end
BOOT_SET: begin
// copy boot address to instr fetch address
instr_req_o = 1'b1;
pc_mux_o = PC_BOOT;
pc_set_o = 1'b1;
ctrl_fsm_ns = FIRST_FETCH;
end
WAIT_SLEEP: begin
ctrl_busy_o = 1'b0;
instr_req_o = 1'b0;
halt_if = 1'b1;
halt_id = 1'b1;
ctrl_fsm_ns = SLEEP;
end
SLEEP: begin
// instruction in IF stage is already valid
// we begin execution when an interrupt has arrived
ctrl_busy_o = 1'b0;
instr_req_o = 1'b0;
halt_if = 1'b1;
halt_id = 1'b1;
// normal execution flow
// in debug mode or single step mode we leave immediately (wfi=nop)
if (irq_nm_i || irq_pending_i || debug_req_i || debug_mode_q || debug_single_step_i) begin
ctrl_fsm_ns = FIRST_FETCH;
end
end
FIRST_FETCH: begin
first_fetch_o = 1'b1;
// Stall because of IF miss
if (id_in_ready_o) begin
ctrl_fsm_ns = DECODE;
end
// handle interrupts
if (handle_irq) begin
// This assumes that the pipeline is always flushed before
// going to sleep.
ctrl_fsm_ns = IRQ_TAKEN;
halt_if = 1'b1;
halt_id = 1'b1;
end
// enter debug mode
if (enter_debug_mode) begin
ctrl_fsm_ns = DBG_TAKEN_IF;
halt_if = 1'b1;
halt_id = 1'b1;
end
end
DECODE: begin
// normal operating mode of the ID stage, in case of debug and interrupt requests,
// priorities are as follows (lower number == higher priority)
// 1. currently running (multicycle) instructions and exceptions caused by these
// 2. debug requests
// 3. interrupt requests
if (instr_valid_i) begin
// set PC in IF stage to branch or jump target
if (branch_set_i || jump_set_i) begin
pc_mux_o = PC_JUMP;
pc_set_o = 1'b1;
perf_tbranch_o = branch_set_i;
perf_jump_o = jump_set_i;
// get ready for special instructions, exceptions, pipeline flushes
end else if (special_req) begin
ctrl_fsm_ns = FLUSH;
halt_if = 1'b1;
halt_id = 1'b1;
end
// stall IF stage to not starve debug and interrupt requests, these just
// need to wait until after the current (multicycle) instruction
if ((enter_debug_mode || handle_irq) && stall) begin
halt_if = 1'b1;
end
// single stepping:
// execute a single instruction and then enter debug mode, in case of exceptions,
// set registers but do not jump into handler [Debug Spec v0.13.2, p.44]
if (debug_single_step_i && !debug_mode_q) begin
halt_if = 1'b1;
if (!special_req && !stall) begin
ctrl_fsm_ns = DBG_TAKEN_IF;
end
end
end // instr_valid_i
if (!stall && !special_req) begin
if (enter_debug_mode) begin
// enter debug mode
ctrl_fsm_ns = DBG_TAKEN_IF;
halt_if = 1'b1;
halt_id = 1'b1;
end else if (handle_irq) begin
// handle interrupt (not in debug mode)
ctrl_fsm_ns = IRQ_TAKEN;
halt_if = 1'b1;
halt_id = 1'b1;
end
end
end // DECODE
IRQ_TAKEN: begin
if (handle_irq) begin
pc_mux_o = PC_EXC;
pc_set_o = 1'b1;
exc_pc_mux_o = EXC_PC_IRQ;
csr_save_if_o = 1'b1;
csr_save_cause_o = 1'b1;
// interrupt priorities according to Privileged Spec v1.11 p.31
if (irq_nm_i && !nmi_mode_q) begin
exc_cause_o = EXC_CAUSE_IRQ_NM;
nmi_mode_d = 1'b1; // enter NMI mode
end else if (csr_mfip_i != 15'b0) begin
exc_cause_o = exc_cause_e'({1'b1, mfip_id});
end else if (csr_meip_i) begin
exc_cause_o = EXC_CAUSE_IRQ_EXTERNAL_M;
end else if (csr_msip_i) begin
exc_cause_o = EXC_CAUSE_IRQ_SOFTWARE_M;
end else begin // csr_mtip_i
exc_cause_o = EXC_CAUSE_IRQ_TIMER_M;
end
end
ctrl_fsm_ns = DECODE;
end
DBG_TAKEN_IF: begin
// enter debug mode and save PC in IF to dpc
// jump to debug exception handler in debug memory
if (debug_single_step_i || debug_req_i) begin
pc_mux_o = PC_EXC;
pc_set_o = 1'b1;
exc_pc_mux_o = EXC_PC_DBD;
csr_save_if_o = 1'b1;
debug_csr_save_o = 1'b1;
csr_save_cause_o = 1'b1;
if (debug_single_step_i) begin
debug_cause_o = DBG_CAUSE_STEP;
end else begin
debug_cause_o = DBG_CAUSE_HALTREQ;
end
// enter debug mode
debug_mode_d = 1'b1;
end
ctrl_fsm_ns = DECODE;
end
DBG_TAKEN_ID: begin
// enter debug mode and save PC in ID to dpc, used when encountering
// 1. EBREAK during debug mode
// 2. EBREAK with forced entry into debug mode (ebreakm or ebreaku set).
// regular ebreak's go through FLUSH.
//
// for 1. do not update dcsr and dpc, for 2. do so [Debug Spec v0.13.2, p.39]
// jump to debug exception handler in debug memory
if (ebrk_insn_i) begin
pc_mux_o = PC_EXC;
pc_set_o = 1'b1;
exc_pc_mux_o = EXC_PC_DBD;
// update dcsr and dpc
if (debug_ebreakm_i && !debug_mode_q) begin // ebreak with forced entry
// dpc (set to the address of the EBREAK, i.e. set to PC in ID stage)
csr_save_cause_o = 1'b1;
csr_save_id_o = 1'b1;
// dcsr
debug_csr_save_o = 1'b1;
debug_cause_o = DBG_CAUSE_EBREAK;
end
// enter debug mode
debug_mode_d = 1'b1;
end
ctrl_fsm_ns = DECODE;
end
FLUSH: begin
// flush the pipeline
halt_if = 1'b1;
halt_id = 1'b1;
ctrl_fsm_ns = DECODE;
// exceptions: set exception PC, save PC and exception cause
// exc_req_lsu is high for one clock cycle only (in DECODE)
if (exc_req || store_err_q || load_err_q) begin
pc_set_o = 1'b1;
pc_mux_o = PC_EXC;
exc_pc_mux_o = debug_mode_q ? EXC_PC_DBG_EXC : EXC_PC_EXC;
csr_save_id_o = 1'b1;
csr_save_cause_o = 1'b1;
// set exception registers, priorities according to Table 3.7 of Privileged Spec v1.11
if (instr_fetch_err_i) begin
exc_cause_o = EXC_CAUSE_INSTR_ACCESS_FAULT;
csr_mtval_o = pc_id_i;
end else if (illegal_insn_i) begin
exc_cause_o = EXC_CAUSE_ILLEGAL_INSN;
csr_mtval_o = instr_is_compressed_i ? {16'b0, instr_compressed_i} : instr_i;
end else if (ecall_insn_i) begin
exc_cause_o = EXC_CAUSE_ECALL_MMODE;
end else if (ebrk_insn_i) begin
if (debug_mode_q) begin
/*
* EBREAK in debug mode re-enters debug mode
*
* "The only exception is EBREAK. When that is executed in Debug
* Mode, it halts the hart again but without updating dpc or
* dcsr." [Debug Spec v0.13.2, p.39]
*/
pc_set_o = 1'b0;
csr_save_id_o = 1'b0;
csr_save_cause_o = 1'b0;
ctrl_fsm_ns = DBG_TAKEN_ID;
end else if (debug_ebreakm_i) begin
/*
* dcsr.ebreakm == 1:
* "EBREAK instructions in M-mode enter Debug Mode."
* [Debug Spec v0.13.2, p.42]
*/
pc_set_o = 1'b0;
csr_save_id_o = 1'b0;
csr_save_cause_o = 1'b0;
ctrl_fsm_ns = DBG_TAKEN_ID;
end else begin
/*
* "The EBREAK instruction is used by debuggers to cause control
* to be transferred back to a debugging environment. It
* generates a breakpoint exception and performs no other
* operation. [...] ECALL and EBREAK cause the receiving
* privilege modes epc register to be set to the address of the
* ECALL or EBREAK instruction itself, not the address of the
* following instruction." [Privileged Spec v1.11, p.40]
*/
exc_cause_o = EXC_CAUSE_BREAKPOINT;
end
end else if (store_err_q) begin
exc_cause_o = EXC_CAUSE_STORE_ACCESS_FAULT;
csr_mtval_o = lsu_addr_last_i;
end else if (load_err_q) begin
exc_cause_o = EXC_CAUSE_LOAD_ACCESS_FAULT;
csr_mtval_o = lsu_addr_last_i;
end
end else begin
// special instructions and pipeline flushes
if (mret_insn_i) begin
pc_mux_o = PC_ERET;
pc_set_o = 1'b1;
csr_restore_mret_id_o = 1'b1;
if (nmi_mode_q) begin
nmi_mode_d = 1'b0; // exit NMI mode
end
end else if (dret_insn_i) begin
pc_mux_o = PC_DRET;
pc_set_o = 1'b1;
debug_mode_d = 1'b0;
end else if (wfi_insn_i) begin
ctrl_fsm_ns = WAIT_SLEEP;
end else if (csr_pipe_flush_i && handle_irq) begin
// start handling IRQs when doing CSR-related pipeline flushes
ctrl_fsm_ns = IRQ_TAKEN;
end
end // exc_req
// single stepping
// set exception registers, but do not jump into handler [Debug Spec v0.13.2, p.44]
if (debug_single_step_i && !debug_mode_q) begin
pc_set_o = 1'b0;
ctrl_fsm_ns = DBG_TAKEN_IF;
end
end // FLUSH
default: begin
instr_req_o = 1'b0;
ctrl_fsm_ns = ctrl_fsm_e'(1'bX);
end
endcase
end
///////////////////
// Stall control //
///////////////////
// if high, current instr needs at least one more cycle to finish after the current cycle
// if low, current instr finishes in current cycle
// multicycle instructions have this set except during the last cycle
assign stall = stall_lsu_i | stall_multdiv_i | stall_jump_i | stall_branch_i;
// signal to IF stage that ID stage is ready for next instr
assign id_in_ready_o = ~stall & ~halt_if;
// kill instr in IF-ID pipeline reg that are done, or if a
// multicycle instr causes an exception for example
assign instr_valid_clear_o = ~stall | halt_id;
// update registers
always_ff @(posedge clk_i or negedge rst_ni) begin : update_regs
if (!rst_ni) begin
ctrl_fsm_cs <= RESET;
nmi_mode_q <= 1'b0;
debug_mode_q <= 1'b0;
load_err_q <= 1'b0;
store_err_q <= 1'b0;
end else begin
ctrl_fsm_cs <= ctrl_fsm_ns;
nmi_mode_q <= nmi_mode_d;
debug_mode_q <= debug_mode_d;
load_err_q <= load_err_d;
store_err_q <= store_err_d;
end
end
endmodule