tinyriscv/rtl/core/exception.sv

328 lines
12 KiB
Systemverilog

/*
Copyright 2021 Blue Liang, liangkangnan@163.com
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
`include "defines.sv"
`define CAUSE_IRQ_EXTERNAL_M {1'b1, 31'd11}
`define CAUSE_IRQ_SOFTWARE_M {1'b1, 31'd3}
`define CAUSE_IRQ_TIMER_M {1'b1, 31'd7}
`define CAUSE_EXCEP_ECALL_M {1'b0, 31'd11}
`define CAUSE_EXCEP_EBREAK_M {1'b0, 31'd3}
`define MIE_MTIE_BIT 7
`define MIE_MEIE_BIT 11
`define MIE_MSIE_BIT 3
`define DCSR_CAUSE_NONE 3'h0
`define DCSR_CAUSE_STEP 3'h4
`define DCSR_CAUSE_DBGREQ 3'h3
`define DCSR_CAUSE_EBREAK 3'h1
`define DCSR_CAUSE_HALT 3'h5
`define DCSR_CAUSE_TRIGGER 3'h2
module exception (
input wire clk,
input wire rst_n,
input wire inst_valid_i,
input wire inst_ecall_i, // ecall指令
input wire inst_ebreak_i, // ebreak指令
input wire inst_mret_i, // mret指令
input wire inst_dret_i, // dret指令
input wire[31:0] inst_addr_i, // 指令地址
input wire[31:0] mtvec_i, // mtvec寄存器
input wire[31:0] mepc_i, // mepc寄存器
input wire[31:0] mstatus_i, // mstatus寄存器
input wire[31:0] mie_i, // mie寄存器
input wire[31:0] dpc_i, // dpc寄存器
input wire[31:0] dcsr_i, // dcsr寄存器
input wire irq_software_i,
input wire irq_timer_i,
input wire irq_external_i,
input wire[14:0] irq_fast_i,
input wire trigger_match_i,
input wire[31:0] debug_halt_addr_i,
input wire debug_req_i,
output wire csr_we_o, // 写CSR寄存器标志
output wire[31:0] csr_waddr_o, // 写CSR寄存器地址
output wire[31:0] csr_wdata_o, // 写CSR寄存器数据
output wire stall_flag_o, // 流水线暂停标志
output wire[31:0] int_addr_o, // 中断入口地址
output wire int_assert_o // 中断标志
);
localparam ILLEGAL_INSTR_OFFSET = 0;
localparam INSTR_ADDR_MISA_OFFSET = 4;
localparam ECALL_OFFSET = 8;
localparam EBREAK_OFFSET = 12;
localparam LOAD_MISA_OFFSET = 16;
localparam STORE_MISA_OFFSET = 20;
localparam RESERVED1_EXCEPTION_OFFSET = 24;
localparam RESERVED2_EXCEPTION_OFFSET = 28;
localparam EXTERNAL_INT_OFFSET = 32;
localparam SOFTWARE_INT_OFFSET = 36;
localparam TIMER_INT_OFFSET = 40;
localparam FAST_INT_OFFSET = 44;
localparam S_IDLE = 4'b0001;
localparam S_W_MEPC = 4'b0010;
localparam S_W_DCSR = 4'b0100;
localparam S_ASSERT = 4'b1000;
reg debug_mode_d, debug_mode_q;
reg[3:0] state_d, state_q;
reg[31:0] assert_addr_d, assert_addr_q;
reg[31:0] return_addr_d, return_addr_q;
reg csr_we;
reg[31:0] csr_waddr;
reg[31:0] csr_wdata;
wire global_int_en;
assign global_int_en = mstatus_i[3];
reg[3:0] fast_irq_id;
wire fast_irq_req;
always @ (*) begin
if (irq_fast_i[ 0]) fast_irq_id = 4'd0;
else if (irq_fast_i[ 1]) fast_irq_id = 4'd1;
else if (irq_fast_i[ 2]) fast_irq_id = 4'd2;
else if (irq_fast_i[ 3]) fast_irq_id = 4'd3;
else if (irq_fast_i[ 4]) fast_irq_id = 4'd4;
else if (irq_fast_i[ 5]) fast_irq_id = 4'd5;
else if (irq_fast_i[ 6]) fast_irq_id = 4'd6;
else if (irq_fast_i[ 7]) fast_irq_id = 4'd7;
else if (irq_fast_i[ 8]) fast_irq_id = 4'd8;
else if (irq_fast_i[ 9]) fast_irq_id = 4'd9;
else if (irq_fast_i[10]) fast_irq_id = 4'd10;
else if (irq_fast_i[11]) fast_irq_id = 4'd11;
else if (irq_fast_i[12]) fast_irq_id = 4'd12;
else if (irq_fast_i[13]) fast_irq_id = 4'd13;
else fast_irq_id = 4'd14;
end
assign fast_irq_req = |irq_fast_i;
reg interrupt_req_tmp;
reg[31:0] interrupt_cause;
reg[31:0] interrupt_offset;
wire interrupt_req = inst_valid_i & interrupt_req_tmp;
always @ (*) begin
if (fast_irq_req) begin
interrupt_req_tmp = 1'b1;
interrupt_cause = {1'b1, {26{1'b0}}, 1'b1, fast_irq_id};
interrupt_offset = {fast_irq_id, 2'b0} + FAST_INT_OFFSET;
end else if (irq_external_i & mie_i[`MIE_MEIE_BIT]) begin
interrupt_req_tmp = 1'b1;
interrupt_cause = `CAUSE_IRQ_EXTERNAL_M;
interrupt_offset = EXTERNAL_INT_OFFSET;
end else if (irq_software_i & mie_i[`MIE_MSIE_BIT]) begin
interrupt_req_tmp = 1'b1;
interrupt_cause = `CAUSE_IRQ_SOFTWARE_M;
interrupt_offset = SOFTWARE_INT_OFFSET;
end else if (irq_timer_i & mie_i[`MIE_MTIE_BIT]) begin
interrupt_req_tmp = 1'b1;
interrupt_cause = `CAUSE_IRQ_TIMER_M;
interrupt_offset = TIMER_INT_OFFSET;
end else begin
interrupt_req_tmp = 1'b0;
interrupt_cause = 32'h0;
interrupt_offset = 32'h0;
end
end
reg exception_req;
reg[31:0] exception_cause;
reg[31:0] exception_offset;
always @ (*) begin
if (inst_ecall_i) begin
exception_req = 1'b1;
exception_cause = `CAUSE_EXCEP_ECALL_M;
exception_offset = ECALL_OFFSET;
end else begin
exception_req = 1'b0;
exception_cause = 32'h0;
exception_offset = 32'h0;
end
end
wire int_or_exception_req;
wire[31:0] int_or_exception_cause;
wire[31:0] int_or_exception_offset;
assign int_or_exception_req = (interrupt_req & global_int_en) | exception_req;
assign int_or_exception_cause = exception_req ? exception_cause : interrupt_cause;
assign int_or_exception_offset = exception_req ? exception_offset : interrupt_offset;
reg enter_debug_cause_debugger_req;
reg enter_debug_cause_single_step;
reg enter_debug_cause_ebreak;
reg enter_debug_cause_reset_halt;
reg enter_debug_cause_trigger;
reg[2:0] dcsr_cause_d, dcsr_cause_q;
always @ (*) begin
enter_debug_cause_debugger_req = 1'b0;
enter_debug_cause_single_step = 1'b0;
enter_debug_cause_ebreak = 1'b0;
enter_debug_cause_reset_halt = 1'b0;
enter_debug_cause_trigger = 1'b0;
dcsr_cause_d = `DCSR_CAUSE_NONE;
if (trigger_match_i & inst_valid_i) begin
enter_debug_cause_trigger = 1'b1;
dcsr_cause_d = `DCSR_CAUSE_TRIGGER;
end else if (inst_ebreak_i) begin
enter_debug_cause_ebreak = 1'b1;
dcsr_cause_d = `DCSR_CAUSE_EBREAK;
end else if ((inst_addr_i == `CPU_RESET_ADDR) & inst_valid_i & debug_req_i) begin
enter_debug_cause_reset_halt = 1'b1;
dcsr_cause_d = `DCSR_CAUSE_HALT;
end else if ((~debug_mode_q) & debug_req_i & inst_valid_i) begin
enter_debug_cause_debugger_req = 1'b1;
dcsr_cause_d = `DCSR_CAUSE_DBGREQ;
end else if ((~debug_mode_q) & dcsr_i[2] & (state_q == S_IDLE)) begin
enter_debug_cause_single_step = 1'b1;
dcsr_cause_d = `DCSR_CAUSE_STEP;
end
end
wire debug_mode_req = enter_debug_cause_debugger_req |
enter_debug_cause_single_step |
enter_debug_cause_reset_halt |
enter_debug_cause_trigger |
enter_debug_cause_ebreak;
assign stall_flag_o = ((state_q != S_IDLE) & (state_q != S_ASSERT)) |
(interrupt_req & global_int_en) | exception_req |
debug_mode_req |
inst_mret_i |
inst_dret_i;
always @ (*) begin
state_d = state_q;
assert_addr_d = assert_addr_q;
debug_mode_d = debug_mode_q;
return_addr_d = return_addr_q;
csr_we = 1'b0;
csr_waddr = 32'h0;
csr_wdata = 32'h0;
case (state_q)
S_IDLE: begin
if (int_or_exception_req) begin
csr_we = 1'b1;
csr_waddr = {20'h0, `CSR_MCAUSE};
csr_wdata = int_or_exception_cause;
assert_addr_d = mtvec_i + int_or_exception_offset;
return_addr_d = inst_addr_i;
state_d = S_W_MEPC;
end else if (debug_mode_req) begin
debug_mode_d = 1'b1;
if (enter_debug_cause_debugger_req |
enter_debug_cause_single_step |
enter_debug_cause_trigger |
enter_debug_cause_reset_halt) begin
csr_we = 1'b1;
csr_waddr = {20'h0, `CSR_DPC};
csr_wdata = enter_debug_cause_reset_halt ? (`CPU_RESET_ADDR) : inst_addr_i;
// when run openocd compliance test, use it.
// openocd compliance test bug: It report test fail when the reset address is 0x0:
// "NDMRESET should move DPC to reset value."
//csr_wdata = enter_debug_cause_reset_halt ? (`CPU_RESET_ADDR + 4'h4) : inst_addr_i;
end
assert_addr_d = debug_halt_addr_i;
// ebreak do not change dpc and dcsr value
if (enter_debug_cause_ebreak) begin
state_d = S_ASSERT;
end else begin
state_d = S_W_DCSR;
end
end else if (inst_mret_i) begin
assert_addr_d = mepc_i;
state_d = S_ASSERT;
end else if (inst_dret_i) begin
assert_addr_d = dpc_i;
state_d = S_ASSERT;
debug_mode_d = 1'b0;
end
end
S_W_MEPC: begin
csr_we = 1'b1;
csr_waddr = {20'h0, `CSR_MEPC};
csr_wdata = return_addr_q;
state_d = S_ASSERT;
end
S_W_DCSR: begin
csr_we = 1'b1;
csr_waddr = {20'h0, `CSR_DCSR};
csr_wdata = {dcsr_i[31:9], dcsr_cause_q, dcsr_i[5:0]};
state_d = S_ASSERT;
end
S_ASSERT: begin
csr_we = 1'b0;
state_d = S_IDLE;
end
default:;
endcase
end
assign csr_we_o = csr_we;
assign csr_waddr_o = csr_waddr;
assign csr_wdata_o = csr_wdata;
assign int_assert_o = (state_q == S_ASSERT);
assign int_addr_o = assert_addr_q;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
state_q <= S_IDLE;
assert_addr_q <= 32'h0;
debug_mode_q <= 1'b0;
return_addr_q <= 32'h0;
dcsr_cause_q <= `DCSR_CAUSE_NONE;
end else begin
state_q <= state_d;
assert_addr_q <= assert_addr_d;
debug_mode_q <= debug_mode_d;
return_addr_q <= return_addr_d;
dcsr_cause_q <= dcsr_cause_d;
end
end
endmodule