/ coreblocks / priv / traps / exception.py
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