Fix break and single-step on a load/store access fault dropping the exception. Better fix for halt request on definitely-excepting stage M instruction giving unreachable next-instruction DPC.
This commit is contained in:
		
							parent
							
								
									65bfca5fdf
								
							
						
					
					
						commit
						e9fccffca0
					
				|  | @ -257,6 +257,7 @@ wire                 x_alu_cmp; | |||
| wire [W_DATA-1:0]    m_trap_addr; | ||||
| wire                 m_trap_is_irq; | ||||
| wire                 m_trap_enter_vld; | ||||
| wire                 m_trap_enter_soon; | ||||
| wire                 m_trap_enter_rdy = f_jump_rdy; | ||||
| 
 | ||||
| reg  [W_REGADDR-1:0] xm_rs1; | ||||
|  | @ -275,7 +276,8 @@ wire x_jump_req; | |||
| 
 | ||||
| // IRQs squeeze in between the instructions in X and M, so in this case X | ||||
| // stalls but M can continue. -> X always stalls on M trap, M *may* stall. | ||||
| wire x_stall_on_trap = m_trap_enter_vld && !m_trap_enter_rdy; | ||||
| wire x_stall_on_trap = m_trap_enter_vld && !m_trap_enter_rdy || | ||||
| 	m_trap_enter_soon && !m_trap_enter_vld; | ||||
| 
 | ||||
| assign x_stall = | ||||
| 	m_stall || | ||||
|  | @ -368,7 +370,7 @@ always @ (*) begin | |||
| 		MEMOP_SH:  bus_hsize_d = HSIZE_HWORD; | ||||
| 		default:   bus_hsize_d = HSIZE_BYTE; | ||||
| 	endcase | ||||
| 	bus_aph_req_d = x_memop_vld && !(x_stall_raw || x_unaligned_addr || m_trap_enter_vld); | ||||
| 	bus_aph_req_d = x_memop_vld && !(x_stall_raw || x_unaligned_addr || m_trap_enter_soon); | ||||
| end | ||||
| 
 | ||||
| // Multiply/divide | ||||
|  | @ -391,7 +393,7 @@ if (EXTENSION_M) begin: has_muldiv | |||
| 		else | ||||
| 			x_muldiv_posted <= (x_muldiv_posted || (x_muldiv_op_vld && x_muldiv_op_rdy)) && x_stall; | ||||
| 
 | ||||
| 	wire x_muldiv_kill = m_trap_enter_vld; | ||||
| 	wire x_muldiv_kill = m_trap_enter_soon; | ||||
| 
 | ||||
| 	wire x_use_fast_mul = MUL_FAST && d_aluop == ALUOP_MULDIV && d_mulop == M_OP_MUL; | ||||
| 
 | ||||
|  | @ -473,6 +475,8 @@ wire [W_DATA-1:0] x_csr_wdata = d_csr_w_imm ? | |||
| wire [W_DATA-1:0] x_csr_rdata; | ||||
| wire              x_csr_illegal_access; | ||||
| 
 | ||||
| // "Previous" refers to next-most-recent instruction to be in D/X, i.e. the | ||||
| // most recent instruction to reach stage M (which may or may not still be in M). | ||||
| reg prev_instr_was_32_bit; | ||||
| 
 | ||||
| always @ (posedge clk or negedge rst_n) begin | ||||
|  | @ -522,19 +526,21 @@ hazard3_csr #( | |||
| 	// Can generate access faults (hence traps), but do not actually perform access. | ||||
| 	.addr                       (d_imm[11:0]), // todo could just connect this to the instruction bits | ||||
| 	.wdata                      (x_csr_wdata), | ||||
| 	.wen_soon                   (d_csr_wen && !m_trap_enter_vld), | ||||
| 	.wen                        (d_csr_wen && !m_trap_enter_vld && !x_stall), | ||||
| 	.wen_soon                   (d_csr_wen && !m_trap_enter_soon), | ||||
| 	.wen                        (d_csr_wen && !m_trap_enter_soon && !x_stall), | ||||
| 	.wtype                      (d_csr_wtype), | ||||
| 	.rdata                      (x_csr_rdata), | ||||
| 	.ren_soon                   (d_csr_ren && !m_trap_enter_vld), | ||||
| 	.ren                        (d_csr_ren && !m_trap_enter_vld && !x_stall), | ||||
| 	.ren_soon                   (d_csr_ren && !m_trap_enter_soon), | ||||
| 	.ren                        (d_csr_ren && !m_trap_enter_soon && !x_stall), | ||||
| 	.illegal                    (x_csr_illegal_access), | ||||
| 
 | ||||
| 	// Trap signalling | ||||
| 	.trap_addr                  (m_trap_addr), | ||||
| 	.trap_is_irq                (m_trap_is_irq), | ||||
| 	.trap_enter_soon            (m_trap_enter_soon), | ||||
| 	.trap_enter_vld             (m_trap_enter_vld), | ||||
| 	.trap_enter_rdy             (m_trap_enter_rdy), | ||||
| 	.loadstore_dphase_pending   (!xm_memop[3]), | ||||
| 	.mepc_in                    (m_exception_return_addr), | ||||
| 
 | ||||
| 	// IRQ and exception requests | ||||
|  | @ -566,7 +572,7 @@ always @ (posedge clk or negedge rst_n) begin | |||
| 			// If the transfer is unaligned, make sure it is completely NOP'd on the bus | ||||
| 			xm_memop <= d_memop | {x_unaligned_addr, 3'h0}; | ||||
| 			xm_except <= x_except; | ||||
| 			if (x_stall || m_trap_enter_vld) begin | ||||
| 			if (x_stall || m_trap_enter_soon) begin | ||||
| 				// Insert bubble | ||||
| 				xm_rd <= {W_REGADDR{1'b0}}; | ||||
| 				xm_memop <= MEMOP_NONE; | ||||
|  |  | |||
|  | @ -81,6 +81,15 @@ module hazard3_csr #( | |||
| 	output wire                trap_is_irq, | ||||
| 	output wire                trap_enter_vld, | ||||
| 	input  wire                trap_enter_rdy, | ||||
| 	// True when we are about to trap into debug mode, but are waiting for an | ||||
| 	// excepting or potentially-excepting instruction to clear M first. The | ||||
| 	// instruction in X is suppressed, X PC does not increment but still | ||||
| 	// tracks exception addresses. | ||||
| 	output wire                trap_enter_soon, | ||||
| 	// We need to know about load/stores in data phase because their exception | ||||
| 	// status is still unknown, so we fence off on them before entering debug | ||||
| 	// mode. | ||||
| 	input  wire                loadstore_dphase_pending, | ||||
| 	input  wire [XLEN-1:0]     mepc_in, | ||||
| 
 | ||||
| 	// Exceptions must *not* be a function of bus stall. | ||||
|  | @ -917,30 +926,45 @@ end | |||
| // instructions in stage 2 and stage 3) or in an exception-like manner | ||||
| // (replace the instruction in stage 3). | ||||
| // | ||||
| // A tricky case is halt request: this normally performs an IRQ-like entry, | ||||
| // because the instruction in stage 3 can not in general be discarded, as it | ||||
| // may already have had system side effects: for example a load/store on an | ||||
| // IO region. | ||||
| // Halt request and step-break are IRQ-like. We need to be careful when a halt | ||||
| // request lines up with an instruction in M which either has generated an | ||||
| // exception (e.g. an ecall) or may yet generate an exception (a load). In | ||||
| // this case the correct thing to do is to: | ||||
| // | ||||
| // However an asynchronous halt request when the instruction in stage 3 is | ||||
| // itself generating an exception is an exception-like halt entry. Otherwise, | ||||
| // we set DPC to the instruction *after* (in X) the excepting one, which is | ||||
| // never actually reached, due to the exception. | ||||
| // - Squash whatever instruction may be in X, and inhibit X PC increment | ||||
| // - Wait until after the exception entry is taken (or the load/store | ||||
| //   completes successfully) | ||||
| // - Immediately trigger an IRQ-like debug entry. | ||||
| // | ||||
| // This ensures the debugger sees mcause/mepc set correctly, with dpc pointing | ||||
| // to the handler entry point, if the instruction excepts. | ||||
| 
 | ||||
| wire exception_req_any; | ||||
| wire halt_delayed_by_exception = exception_req_any || loadstore_dphase_pending; | ||||
| 
 | ||||
| // This would also include triggers, if/when those are implemented: | ||||
| wire want_halt_except = DEBUG_SUPPORT && !debug_mode && ( | ||||
| 	dcsr_ebreakm && except == EXCEPT_EBREAK || | ||||
| 	// This clause takes priority over the IRQ-like dbg_req_halt clause below: | ||||
| 	dbg_req_halt && exception_req_any | ||||
| 	dcsr_ebreakm && except == EXCEPT_EBREAK | ||||
| ); | ||||
| 
 | ||||
| // Note all exception-like causes (trigger, ebreak) are higher priority than IRQ-like | ||||
| wire want_halt_irq = DEBUG_SUPPORT && !debug_mode && !want_halt_except && ( | ||||
| 	dbg_req_halt || (dbg_req_halt_on_reset && have_just_reset) || step_halt_req | ||||
| // Note exception-like causes (trigger, ebreak) are higher priority than | ||||
| // IRQ-like. | ||||
| // | ||||
| // We must mask halt_req with delay_irq_entry (true on cycle 2 and beyond of | ||||
| // load/store address phase) because at that point we can't suppress the bus | ||||
| // access.. | ||||
| wire want_halt_irq_if_no_exception = DEBUG_SUPPORT && !debug_mode && !want_halt_except && ( | ||||
| 	(dbg_req_halt && !delay_irq_entry) || | ||||
| 	(dbg_req_halt_on_reset && have_just_reset) || | ||||
| 	step_halt_req | ||||
| ); | ||||
| 
 | ||||
| // Exception (or potential exception due to load/store) in M delays halt | ||||
| // entry. The halt intention still blocks X, so we can't get blocked forever | ||||
| // by a string of load/stores. This is just here to get sequencing between an | ||||
| // exception (or potential exception) in M, and debug mode entry. | ||||
| wire want_halt_irq = want_halt_irq_if_no_exception && !halt_delayed_by_exception; | ||||
| 
 | ||||
| assign dcause_next = | ||||
| 	// Trigger would be highest priority if implemented | ||||
| 	except == EXCEPT_EBREAK                                    ? 3'h1 : // ebreak (priority 3) | ||||
|  | @ -1045,6 +1069,8 @@ assign trap_enter_vld = | |||
| 	DEBUG_SUPPORT && ( | ||||
| 		(!delay_irq_entry && want_halt_irq) || want_halt_except || pending_dbg_resume); | ||||
| 
 | ||||
| assign trap_enter_soon = trap_enter_vld || (DEBUG_SUPPORT && want_halt_irq_if_no_exception); | ||||
| 
 | ||||
| assign mcause_irq_next = !exception_req_any; | ||||
| assign mcause_code_next = exception_req_any ? {2'h0, except} : mcause_irq_num; | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue