309 lines
12 KiB
Systemverilog
309 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 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_executed_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 illegal_inst_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 int_req_i,
|
|
input wire[7:0] int_id_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 INT_OFFSET = 32;
|
|
|
|
localparam S_IDLE = 5'b00001;
|
|
localparam S_W_MEPC = 5'b00010;
|
|
localparam S_W_DCSR = 5'b00100;
|
|
localparam S_ASSERT = 5'b01000;
|
|
localparam S_W_MSTATUS = 5'b10000;
|
|
|
|
reg debug_mode_d, debug_mode_q;
|
|
reg[4:0] state_d, state_q;
|
|
reg[31:0] assert_addr_d, assert_addr_q;
|
|
reg[31:0] return_addr_d, return_addr_q;
|
|
reg trigger_match_d, trigger_match_q;
|
|
reg csr_we;
|
|
reg[31:0] csr_waddr;
|
|
reg[31:0] csr_wdata;
|
|
|
|
reg[7:0] int_id_d, int_id_q;
|
|
reg in_irq_context_d, in_irq_context_q;
|
|
wire global_int_en;
|
|
wire interrupt_req_valid;
|
|
|
|
assign global_int_en = mstatus_i[3];
|
|
|
|
assign interrupt_req_valid = inst_valid_i &
|
|
int_req_i &
|
|
((int_id_i != int_id_q) | (~in_irq_context_q));
|
|
|
|
reg exception_req;
|
|
reg[31:0] exception_cause;
|
|
|
|
always @ (*) begin
|
|
if (illegal_inst_i) begin
|
|
exception_req = 1'b1;
|
|
exception_cause = 32'h0;
|
|
end else if (inst_ecall_i & inst_valid_i) begin
|
|
exception_req = 1'b1;
|
|
exception_cause = 32'h2;
|
|
end else begin
|
|
exception_req = 1'b0;
|
|
exception_cause = 32'h0;
|
|
end
|
|
end
|
|
|
|
wire int_or_exception_req;
|
|
wire[31:0] int_or_exception_cause;
|
|
|
|
assign int_or_exception_req = (interrupt_req_valid & global_int_en & (~debug_mode_q)) | exception_req;
|
|
assign int_or_exception_cause = exception_req ? exception_cause : (32'h8 + {24'h0, int_id_i});
|
|
|
|
wire trigger_matching;
|
|
|
|
gen_ticks_sync #(
|
|
.DP(5),
|
|
.DW(1)
|
|
) gen_trigger_sync (
|
|
.rst_n(rst_n),
|
|
.clk(clk),
|
|
.din(trigger_match_q),
|
|
.dout(trigger_matching)
|
|
);
|
|
|
|
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 & (~trigger_matching)) begin
|
|
enter_debug_cause_trigger = 1'b1;
|
|
dcsr_cause_d = `DCSR_CAUSE_TRIGGER;
|
|
end else if (inst_ebreak_i & inst_valid_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] & inst_valid_i & inst_executed_i) 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)) |
|
|
int_or_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;
|
|
trigger_match_d = trigger_match_q;
|
|
int_id_d = int_id_q;
|
|
in_irq_context_d = in_irq_context_q;
|
|
|
|
case (state_q)
|
|
S_IDLE: begin
|
|
if (int_or_exception_req & (!debug_mode_q)) begin
|
|
csr_we = 1'b1;
|
|
csr_waddr = {20'h0, `CSR_MCAUSE};
|
|
csr_wdata = int_or_exception_cause;
|
|
assert_addr_d = mtvec_i;
|
|
return_addr_d = inst_addr_i;
|
|
state_d = S_W_MSTATUS;
|
|
int_id_d = int_id_i;
|
|
in_irq_context_d = 1'b1;
|
|
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
|
|
if (enter_debug_cause_trigger) begin
|
|
trigger_match_d = 1'b1;
|
|
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
|
|
in_irq_context_d = 1'b0;
|
|
assert_addr_d = mepc_i;
|
|
csr_we = 1'b1;
|
|
csr_waddr = {20'h0, `CSR_MSTATUS};
|
|
// 开全局中断
|
|
csr_wdata = {mstatus_i[31:4], 1'b1, mstatus_i[2:0]};
|
|
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;
|
|
trigger_match_d = 1'b0;
|
|
end
|
|
end
|
|
|
|
S_W_MSTATUS: begin
|
|
csr_we = 1'b1;
|
|
csr_waddr = {20'h0, `CSR_MSTATUS};
|
|
// 关全局中断
|
|
csr_wdata = {mstatus_i[31:4], 1'b0, mstatus_i[2:0]};
|
|
state_d = S_W_MEPC;
|
|
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;
|
|
trigger_match_q <= 1'b0;
|
|
int_id_q <= 8'h0;
|
|
in_irq_context_q <= 1'b0;
|
|
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;
|
|
trigger_match_q <= trigger_match_d;
|
|
int_id_q <= int_id_d;
|
|
in_irq_context_q <= in_irq_context_d;
|
|
end
|
|
end
|
|
|
|
endmodule
|