exception.py
1 from amaranth import * 2 from transactron.utils.dependencies import DependencyContext 3 from coreblocks.params.genparams import GenParams 4 5 from coreblocks.arch import ExceptionCause 6 from coreblocks.interface.layouts import ExceptionRegisterLayouts 7 from coreblocks.interface.keys import ExceptionReportKey 8 from transactron.core import TModule, def_method, Method 9 from transactron.lib.connectors import ConnectTrans 10 from transactron.lib.fifo import BasicFifo 11 12 13 # NOTE: This function is not used in ExceptionCauseRegister, but may be useful in computing priorities before reporting 14 def should_update_priority(m: TModule, current_cause: Value, new_cause: Value) -> Value: 15 # Comparing all priorities would be expensive, this function only checks conditions that could happen in hardware 16 _update = Signal() 17 18 # All breakpoint kinds have the highest priority in conflicting cases 19 with m.If(new_cause == ExceptionCause.BREAKPOINT): 20 m.d.comb += _update.eq(1) 21 22 with m.If( 23 ((new_cause == ExceptionCause.INSTRUCTION_PAGE_FAULT) | (new_cause == ExceptionCause.INSTRUCTION_ACCESS_FAULT)) 24 & (current_cause != ExceptionCause.BREAKPOINT) 25 ): 26 m.d.comb += _update.eq(1) 27 28 with m.If( 29 (new_cause == ExceptionCause.LOAD_ADDRESS_MISALIGNED) 30 & ((current_cause == ExceptionCause.LOAD_ACCESS_FAULT) | (current_cause == ExceptionCause.LOAD_PAGE_FAULT)) 31 ): 32 m.d.comb += _update.eq(1) 33 34 with m.If( 35 (new_cause == ExceptionCause.STORE_ADDRESS_MISALIGNED) 36 & ((current_cause == ExceptionCause.STORE_ACCESS_FAULT) | (current_cause == ExceptionCause.STORE_PAGE_FAULT)) 37 ): 38 m.d.comb += _update.eq(1) 39 40 return _update 41 42 43 class ExceptionInformationRegister(Elaboratable): 44 """ExceptionInformationRegister 45 46 Stores parameters of earliest (in instruction order) exception, to save resources in the `ReorderBuffer`. 47 All FUs that report exceptions should `report` the details to `ExceptionCauseRegister` and set `exception` bit in 48 result data. Exception order is computed in this module. Only one exception can be reported for single instruction, 49 exception priorities should be computed locally before calling report. 50 If `exception` bit is set in the ROB, `Retirement` stage fetches exception details from this module. 51 """ 52 53 def __init__(self, gen_params: GenParams, rob_get_indices: Method, fetch_stall_exception: Method): 54 self.gen_params = gen_params 55 56 self.cause = Signal(ExceptionCause) 57 self.rob_id = Signal(gen_params.rob_entries_bits) 58 self.pc = Signal(gen_params.isa.xlen) 59 self.mtval = Signal(gen_params.isa.xlen) 60 self.valid = Signal() 61 62 self.layouts = gen_params.get(ExceptionRegisterLayouts) 63 64 self.report = Method(i=self.layouts.report) 65 66 self.clears: list[Method] = [] 67 68 # Break long combinational paths from single-cycle FUs 69 def call_report(): 70 report_fifo = BasicFifo(self.layouts.report, 2) 71 report_connector = ConnectTrans.create(report_fifo.read, self.report) 72 self.clears.append(report_fifo.clear) 73 added = False 74 75 def call(m: TModule, **kwargs): 76 nonlocal added 77 if not added: 78 m.submodules += [report_fifo, report_connector] 79 added = True 80 return report_fifo.write(m, **kwargs) 81 82 return call 83 84 dm = DependencyContext.get() 85 dm.add_dependency(ExceptionReportKey(), call_report) 86 87 self.get = Method(o=self.layouts.get) 88 89 self.clear = Method() 90 91 self.rob_get_indices = rob_get_indices 92 self.fetch_stall_exception = fetch_stall_exception 93 94 def elaborate(self, platform): 95 m = TModule() 96 97 @def_method(m, self.report) 98 def _(cause, rob_id, pc, mtval): 99 should_write = Signal() 100 101 with m.If(self.valid & (self.rob_id == rob_id)): 102 # entry for the same rob_id cannot be overwritten, because its update couldn't be validated 103 # in Retirement. 104 m.d.comb += should_write.eq(0) 105 with m.Elif(self.valid): 106 rob_start_idx = self.rob_get_indices(m).start 107 m.d.comb += should_write.eq( 108 (rob_id - rob_start_idx).as_unsigned() < (self.rob_id - rob_start_idx).as_unsigned() 109 ) 110 with m.Else(): 111 m.d.comb += should_write.eq(1) 112 113 with m.If(should_write): 114 m.d.sync += self.rob_id.eq(rob_id) 115 m.d.sync += self.cause.eq(cause) 116 m.d.sync += self.pc.eq(pc) 117 m.d.sync += self.mtval.eq(mtval) 118 119 m.d.sync += self.valid.eq(1) 120 121 # In case of any reported exception, core will need to be flushed. Fetch can be stalled immediately 122 self.fetch_stall_exception(m) 123 124 @def_method(m, self.get, nonexclusive=True) 125 def _(): 126 return {"rob_id": self.rob_id, "cause": self.cause, "pc": self.pc, "mtval": self.mtval, "valid": self.valid} 127 128 @def_method(m, self.clear) 129 def _(): 130 m.d.sync += self.valid.eq(0) 131 for clear in self.clears: 132 clear(m) 133 del self.clears # exception will be raised if new fifos are created later 134 135 return m