/* 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_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[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 = 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; 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 & inst_valid_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 & (~debug_mode_q)) | exception_req; assign int_or_exception_cause = exception_req ? exception_cause : interrupt_cause; assign int_or_exception_offset = exception_req ? exception_offset : interrupt_offset; 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)) | (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; trigger_match_d = trigger_match_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 + int_or_exception_offset; return_addr_d = inst_addr_i; state_d = S_W_MSTATUS; 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 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; 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; end end endmodule