From 26f5a90ebcc3a4b4d2d44eb733e3d9d353a24f65 Mon Sep 17 00:00:00 2001 From: Piotr Wegrzyn Date: Fri, 14 Jun 2024 06:32:34 +0000 Subject: [PATCH 01/15] mtval support --- coreblocks/backend/retirement.py | 1 + coreblocks/core.py | 10 +++++----- coreblocks/frontend/decoder/decode_stage.py | 4 +++- coreblocks/frontend/decoder/rvc.py | 4 +++- coreblocks/frontend/fetch/fetch.py | 9 +++++++++ coreblocks/func_blocks/csr/csr.py | 3 ++- coreblocks/func_blocks/fu/exception.py | 20 +++++++++++++------ coreblocks/func_blocks/fu/jumpbranch.py | 13 +++++++++--- coreblocks/func_blocks/fu/lsu/dummyLsu.py | 13 +++++++----- .../func_blocks/fu/lsu/lsu_requester.py | 4 ++-- coreblocks/func_blocks/fu/priv.py | 4 +++- coreblocks/interface/layouts.py | 14 ++++++++----- coreblocks/priv/csr/csr_instances.py | 2 ++ coreblocks/priv/traps/exception.py | 8 +++++--- test/priv/traps/test_exception.py | 6 +++--- transactron/utils/transactron_helpers.py | 6 ++++++ 16 files changed, 85 insertions(+), 36 deletions(-) diff --git a/coreblocks/backend/retirement.py b/coreblocks/backend/retirement.py index b03cb20ee..fe2541e6d 100644 --- a/coreblocks/backend/retirement.py +++ b/coreblocks/backend/retirement.py @@ -162,6 +162,7 @@ def flush_instr(rob_entry): # Register RISC-V architectural trap in CSRs m_csr.mcause.write(m, cause_entry) m_csr.mepc.write(m, cause_register.pc) + m_csr.mtval.write(m, cause_register.mtval) self.trap_entry(m) # Fetch is already stalled by ExceptionCauseRegister diff --git a/coreblocks/core.py b/coreblocks/core.py index 3a14b6932..aa0b3c9b8 100644 --- a/coreblocks/core.py +++ b/coreblocks/core.py @@ -20,7 +20,7 @@ from coreblocks.core_structs.rf import RegisterFile from coreblocks.priv.csr.csr_instances import GenericCSRRegisters from coreblocks.frontend.frontend import CoreFrontend -from coreblocks.priv.traps.exception import ExceptionCauseRegister +from coreblocks.priv.traps.exception import ExceptionInformationRegister from coreblocks.scheduler.scheduler import Scheduler from coreblocks.backend.annoucement import ResultAnnouncement from coreblocks.backend.retirement import Retirement @@ -69,7 +69,7 @@ def __init__(self, *, gen_params: GenParams): self.connections.add_dependency(CommonBusDataKey(), self.bus_master_data_adapter) - self.exception_cause_register = ExceptionCauseRegister( + self.exception_information_register = ExceptionInformationRegister( self.gen_params, rob_get_indices=self.ROB.get_indices, fetch_stall_exception=self.frontend.stall, @@ -134,7 +134,7 @@ def elaborate(self, platform): gen_params=self.gen_params, ) - m.submodules.exception_cause_register = self.exception_cause_register + m.submodules.exception_information_register = self.exception_information_register fetch_resume_fb, fetch_resume_unifiers = self.connections.get_dependency(FetchResumeKey()) m.submodules.fetch_resume_unifiers = ModuleConnector(**fetch_resume_unifiers) @@ -151,8 +151,8 @@ def elaborate(self, platform): r_rat_peek=rrat.peek, free_rf_put=free_rf_fifo.write, rf_free=rf.free, - exception_cause_get=self.exception_cause_register.get, - exception_cause_clear=self.exception_cause_register.clear, + exception_cause_get=self.exception_information_register.get, + exception_cause_clear=self.exception_information_register.clear, frat_rename=frat.rename, fetch_continue=self.frontend.resume_from_exception, instr_decrement=core_counter.decrement, diff --git a/coreblocks/frontend/decoder/decode_stage.py b/coreblocks/frontend/decoder/decode_stage.py index 7064aa691..ec90fada4 100644 --- a/coreblocks/frontend/decoder/decode_stage.py +++ b/coreblocks/frontend/decoder/decode_stage.py @@ -95,7 +95,9 @@ def elaborate(self, platform): "rl_s1": Mux(instr_decoder.rs1_v & (~exception_override), instr_decoder.rs1, 0), "rl_s2": Mux(instr_decoder.rs2_v & (~exception_override), instr_decoder.rs2, 0), }, - "imm": instr_decoder.imm, + "imm": Mux( + ~exception_override, instr_decoder.imm, Mux(raw.access_fault, raw.access_fault, raw.instr) + ), "csr": instr_decoder.csr, "pc": raw.pc, }, diff --git a/coreblocks/frontend/decoder/rvc.py b/coreblocks/frontend/decoder/rvc.py index 00e244daa..beac67865 100644 --- a/coreblocks/frontend/decoder/rvc.py +++ b/coreblocks/frontend/decoder/rvc.py @@ -291,6 +291,8 @@ def elaborate(self, platform): res = self.instr_mux(quadrant, quadrants) - m.d.comb += self.instr_out.eq(Mux(res[1], res[0], IllegalInstr())) + # In case of illegal instruction, ouput `instr_in` to be able to save it into `mtval` CSR. + # Decoder would still recognize it as illegal because of quadrant != 0b11 + m.d.comb += self.instr_out.eq(Mux(res[1], res[0], self.instr_in)) return m diff --git a/coreblocks/frontend/fetch/fetch.py b/coreblocks/frontend/fetch/fetch.py index a53b506c8..729dfc369 100644 --- a/coreblocks/frontend/fetch/fetch.py +++ b/coreblocks/frontend/fetch/fetch.py @@ -331,11 +331,16 @@ def flush(): if Extension.C in self.gen_params.isa.extensions: with m.If(s1_data.instr_block_cross): m.d.av_comb += raw_instrs[0].pc.eq(params.pc_from_fb(fetch_block_addr, 0) - 2) + with m.If(s1_data.access_fault): + m.d.av_comb += raw_instrs[0].access_fault.eq( + 0b10 + ) # Mark that access fault happened only at second half with condition(m) as branch: with branch(flushing_counter == 0): with m.If(access_fault | unsafe_stall): # TODO: Raise different code for page fault when supported + # could be passed in 3rd bit of access_fault flush() m.d.sync += stalled_unsafe.eq(1) with m.Elif(redirect): @@ -523,6 +528,7 @@ def elaborate(self, platform): @def_method(m, self.predecode) def _(instr): + quadrant = instr[0:2] opcode = instr[2:7] funct3 = instr[12:15] rd = instr[7:12] @@ -557,6 +563,9 @@ def _(instr): with m.Default(): m.d.av_comb += ret.cfi_type.eq(CfiType.INVALID) + with m.If(quadrant != 0b11): + m.d.av_comb += ret.cfi_type.eq(CfiType.INVALID) + m.d.av_comb += ret.unsafe.eq( (opcode == Opcode.SYSTEM) | ((opcode == Opcode.MISC_MEM) & (funct3 == Funct3.FENCEI)) ) diff --git a/coreblocks/func_blocks/csr/csr.py b/coreblocks/func_blocks/csr/csr.py index 40cc1dd26..505908fed 100644 --- a/coreblocks/func_blocks/csr/csr.py +++ b/coreblocks/func_blocks/csr/csr.py @@ -208,7 +208,7 @@ def _(): interrupt = self.dependency_manager.get_dependency(AsyncInterruptInsertSignalKey()) with m.If(exception): - report(m, rob_id=instr.rob_id, cause=ExceptionCause.ILLEGAL_INSTRUCTION, pc=instr.pc) + report(m, rob_id=instr.rob_id, cause=ExceptionCause.ILLEGAL_INSTRUCTION, pc=instr.pc, mtval=0) with m.Elif(interrupt): # SPEC: "These conditions for an interrupt trap to occur [..] must also be evaluated immediately # following [..] an explicit write to a CSR on which these interrupt trap conditions expressly depend." @@ -220,6 +220,7 @@ def _(): rob_id=instr.rob_id, cause=ExceptionCause._COREBLOCKS_ASYNC_INTERRUPT, pc=instr.pc + self.gen_params.isa.ilen_bytes, + mtval=0, ) m.d.sync += exception.eq(0) diff --git a/coreblocks/func_blocks/fu/exception.py b/coreblocks/func_blocks/fu/exception.py index 842f52e42..806437366 100644 --- a/coreblocks/func_blocks/fu/exception.py +++ b/coreblocks/func_blocks/fu/exception.py @@ -68,21 +68,29 @@ def _(arg): m.d.comb += decoder.exec_fn.eq(arg.exec_fn) cause = Signal(ExceptionCause) + mtval = Signal(self.gen_params.isa.xlen) with OneHotSwitch(m, decoder.decode_fn) as OneHotCase: with OneHotCase(ExceptionUnitFn.Fn.EBREAK): - m.d.comb += cause.eq(ExceptionCause.BREAKPOINT) + m.d.av_comb += cause.eq(ExceptionCause.BREAKPOINT) + m.d.av_comb += mtval.eq(arg.pc) with OneHotCase(ExceptionUnitFn.Fn.ECALL): # TODO: Switch privilege level when implemented - m.d.comb += cause.eq(ExceptionCause.ENVIRONMENT_CALL_FROM_M) + m.d.av_comb += cause.eq(ExceptionCause.ENVIRONMENT_CALL_FROM_M) with OneHotCase(ExceptionUnitFn.Fn.INSTR_ACCESS_FAULT): - m.d.comb += cause.eq(ExceptionCause.INSTRUCTION_ACCESS_FAULT) + m.d.av_comb += cause.eq(ExceptionCause.INSTRUCTION_ACCESS_FAULT) + # With C extension access fault can be only on the second half of instruction, and mepc != mtval. + # This information is passed in imm field + m.d.av_comb += mtval.eq(arg.pc + (arg.imm[1] << 1)) with OneHotCase(ExceptionUnitFn.Fn.ILLEGAL_INSTRUCTION): - m.d.comb += cause.eq(ExceptionCause.ILLEGAL_INSTRUCTION) + m.d.av_comb += cause.eq(ExceptionCause.ILLEGAL_INSTRUCTION) + m.d.av_comb += mtval.eq(arg.imm) # passed instruction bytes with OneHotCase(ExceptionUnitFn.Fn.BREAKPOINT): - m.d.comb += cause.eq(ExceptionCause.BREAKPOINT) + m.d.av_comb += cause.eq(ExceptionCause.BREAKPOINT) + m.d.av_comb += mtval.eq(arg.pc) with OneHotCase(ExceptionUnitFn.Fn.INSTR_PAGE_FAULT): - m.d.comb += cause.eq(ExceptionCause.INSTRUCTION_PAGE_FAULT) + m.d.av_comb += cause.eq(ExceptionCause.INSTRUCTION_PAGE_FAULT) + m.d.av_comb += mtval.eq(arg.pc + (arg.imm[1] << 1)) self.report(m, rob_id=arg.rob_id, cause=cause, pc=arg.pc) diff --git a/coreblocks/func_blocks/fu/jumpbranch.py b/coreblocks/func_blocks/fu/jumpbranch.py index 2d164d05a..6cc31f66f 100644 --- a/coreblocks/func_blocks/fu/jumpbranch.py +++ b/coreblocks/func_blocks/fu/jumpbranch.py @@ -214,8 +214,13 @@ def _(): # generated for a conditional branch that is not taken." m.d.comb += exception.eq(1) exception_report( - m, rob_id=instr.rob_id, cause=ExceptionCause.INSTRUCTION_ADDRESS_MISALIGNED, pc=instr.pc + m, + rob_id=instr.rob_id, + cause=ExceptionCause.INSTRUCTION_ADDRESS_MISALIGNED, + pc=instr.pc, + mtval=instr.jmp_addr, ) + with m.Elif(async_interrupt_active & ~is_auipc): # Jump instructions are entry points for async interrupts. # This way we can store known pc via report to global exception register and avoid it in ROB. @@ -223,13 +228,15 @@ def _(): # and exception would be lost. m.d.comb += exception.eq(1) exception_report( - m, rob_id=instr.rob_id, cause=ExceptionCause._COREBLOCKS_ASYNC_INTERRUPT, pc=jump_result + m, rob_id=instr.rob_id, cause=ExceptionCause._COREBLOCKS_ASYNC_INTERRUPT, pc=jump_result, mtval=0 ) with m.Elif(misprediction): # Async interrupts can have priority, because `jump_result` is handled in the same way. # No extra misprediction penalty will be introducted at interrupt return to `jump_result` address. m.d.comb += exception.eq(1) - exception_report(m, rob_id=instr.rob_id, cause=ExceptionCause._COREBLOCKS_MISPREDICTION, pc=jump_result) + exception_report( + m, rob_id=instr.rob_id, cause=ExceptionCause._COREBLOCKS_MISPREDICTION, pc=jump_result, mtval=0 + ) with m.If(~is_auipc): self.fifo_branch_resolved.write(m, from_pc=instr.pc, next_pc=jump_result, misprediction=misprediction) diff --git a/coreblocks/func_blocks/fu/lsu/dummyLsu.py b/coreblocks/func_blocks/fu/lsu/dummyLsu.py index e807cc636..6ab05b45b 100644 --- a/coreblocks/func_blocks/fu/lsu/dummyLsu.py +++ b/coreblocks/func_blocks/fu/lsu/dummyLsu.py @@ -4,6 +4,7 @@ from transactron import Method, def_method, Transaction, TModule from transactron.lib.connectors import FIFO, Forwarder from transactron.utils import DependencyContext +from transactron.utils.transactron_helpers import extend_layout from transactron.lib.simultaneous import condition from transactron.lib.logging import HardwareLogger @@ -64,7 +65,9 @@ def elaborate(self, platform): m.submodules.requester = requester = LSURequester(self.gen_params, self.bus) m.submodules.requests = requests = Forwarder(self.fu_layouts.issue) - m.submodules.results_noop = results_noop = FIFO(self.lsu_layouts.accept, 2) + m.submodules.results_noop = results_noop = FIFO( + extend_layout(self.lsu_layouts.accept, ("addr", self.gen_params.isa.xlen)), 2 + ) m.submodules.issued = issued = FIFO(self.fu_layouts.issue, 2) m.submodules.issued_noop = issued_noop = FIFO(self.fu_layouts.issue, 2) @@ -77,7 +80,7 @@ def _(arg): with m.If(~is_fence): requests.write(m, arg) with m.Else(): - results_noop.write(m, data=0, exception=0, cause=0) + results_noop.write(m, data=0, exception=0, cause=0, addr=0) issued_noop.write(m, arg) # Issues load/store requests when the instruction is known, is a LOAD/STORE, and just before commit. @@ -106,14 +109,14 @@ def _(arg): with m.If(res["exception"]): issued_noop.write(m, arg) - results_noop.write(m, data=0, exception=res["exception"], cause=res["cause"]) + results_noop.write(m, data=0, exception=res["exception"], cause=res["cause"], addr=res["addr"]) with m.Else(): issued.write(m, arg) # Handles flushed instructions as a no-op. with Transaction().body(m, request=flush): arg = requests.read(m) - results_noop.write(m, data=0, exception=0, cause=0) + results_noop.write(m, data=0, exception=0, cause=0, addr=0) issued_noop.write(m, arg) @def_method(m, self.accept) @@ -129,7 +132,7 @@ def _(): m.d.comb += arg.eq(issued_noop.read(m)) with m.If(res["exception"]): - self.report(m, rob_id=arg["rob_id"], cause=res["cause"], pc=arg["pc"]) + self.report(m, rob_id=arg["rob_id"], cause=res["cause"], pc=arg["pc"], mtval=arg["addr"]) self.log.debug(m, 1, "accept rob_id={} result=0x{:08x} exception={}", arg.rob_id, res.data, res.exception) diff --git a/coreblocks/func_blocks/fu/lsu/lsu_requester.py b/coreblocks/func_blocks/fu/lsu/lsu_requester.py index 9176294ee..eaa604001 100644 --- a/coreblocks/func_blocks/fu/lsu/lsu_requester.py +++ b/coreblocks/func_blocks/fu/lsu/lsu_requester.py @@ -143,7 +143,7 @@ def _(addr: Value, data: Value, funct3: Value, store: Value): Mux(store, ExceptionCause.STORE_ADDRESS_MISALIGNED, ExceptionCause.LOAD_ADDRESS_MISALIGNED) ) - return {"exception": exception, "cause": cause} + return {"exception": exception, "cause": cause, "addr": addr} @def_method(m, self.accept) def _(): @@ -172,6 +172,6 @@ def _(): Mux(request_args.store, ExceptionCause.STORE_ACCESS_FAULT, ExceptionCause.LOAD_ACCESS_FAULT) ) - return {"data": data, "exception": exception, "cause": cause} + return {"data": data, "exception": exception, "cause": cause, "addr": request_args.addr} return m diff --git a/coreblocks/func_blocks/fu/priv.py b/coreblocks/func_blocks/fu/priv.py index 6a6e40bf3..9f0f09393 100644 --- a/coreblocks/func_blocks/fu/priv.py +++ b/coreblocks/func_blocks/fu/priv.py @@ -135,7 +135,9 @@ def _(): # Interrupt is reported on this xRET instruction with return address set to instruction that we # would normally return to (mepc value is preserved) m.d.comb += exception.eq(1) - exception_report(m, cause=ExceptionCause._COREBLOCKS_ASYNC_INTERRUPT, pc=ret_pc, rob_id=instr_rob) + exception_report( + m, cause=ExceptionCause._COREBLOCKS_ASYNC_INTERRUPT, pc=ret_pc, rob_id=instr_rob, mtval=0 + ) with m.Else(): log.info(m, True, "Unstalling fetch from the priv unit new_pc=0x{:x}", ret_pc) # Unstall the fetch diff --git a/coreblocks/interface/layouts.py b/coreblocks/interface/layouts.py index 31f42a2e2..dd8bc70e7 100644 --- a/coreblocks/interface/layouts.py +++ b/coreblocks/interface/layouts.py @@ -1,10 +1,10 @@ from typing import Optional from amaranth import signed -from amaranth.lib.data import StructLayout, ArrayLayout +from amaranth.lib.data import ArrayLayout from coreblocks.params import GenParams from coreblocks.arch import * from transactron.utils import LayoutList, LayoutListField, layout_subset -from transactron.utils.transactron_helpers import from_method_layout, make_layout +from transactron.utils.transactron_helpers import from_method_layout, make_layout, extend_layout __all__ = [ "CommonLayoutFields", @@ -444,7 +444,7 @@ class FetchLayouts: def __init__(self, gen_params: GenParams): fields = gen_params.get(CommonLayoutFields) - self.access_fault: LayoutListField = ("access_fault", 1) + self.access_fault: LayoutListField = ("access_fault", 2) """Instruction fetch failed.""" self.raw_instr = make_layout( @@ -634,20 +634,24 @@ def __init__(self, gen_params: GenParams): class ExceptionRegisterLayouts: - """Layouts used in the exception register.""" + """Layouts used in the exception information register.""" def __init__(self, gen_params: GenParams): fields = gen_params.get(CommonLayoutFields) + self.mtval: LayoutListField = ("mtval", gen_params.isa.xlen) + """ Value to set for mtval CSR register """ + self.valid: LayoutListField = ("valid", 1) self.report = make_layout( fields.cause, fields.rob_id, fields.pc, + self.mtval, ) - self.get = StructLayout(self.report.members | make_layout(self.valid).members) + self.get = extend_layout(self.report, self.valid) class InternalInterruptControllerLayouts: diff --git a/coreblocks/priv/csr/csr_instances.py b/coreblocks/priv/csr/csr_instances.py index 1bf3c3a09..0caedeb50 100644 --- a/coreblocks/priv/csr/csr_instances.py +++ b/coreblocks/priv/csr/csr_instances.py @@ -77,6 +77,8 @@ def __init__(self, gen_params: GenParams): # TODO: set low bits R/O based on gp align self.mepc = CSRRegister(CSRAddress.MEPC, gen_params) + self.mtval = CSRRegister(CSRAddress.MTVAL, gen_params) + def elaborate(self, platform): m = Module() diff --git a/coreblocks/priv/traps/exception.py b/coreblocks/priv/traps/exception.py index d2f34f43e..ad9379a89 100644 --- a/coreblocks/priv/traps/exception.py +++ b/coreblocks/priv/traps/exception.py @@ -40,8 +40,8 @@ def should_update_prioriy(m: TModule, current_cause: Value, new_cause: Value) -> return _update -class ExceptionCauseRegister(Elaboratable): - """ExceptionCauseRegister +class ExceptionInformationRegister(Elaboratable): + """ExceptionInformationRegister Stores parameters of earliest (in instruction order) exception, to save resources in the `ReorderBuffer`. All FUs that report exceptions should `report` the details to `ExceptionCauseRegister` and set `exception` bit in @@ -56,6 +56,7 @@ def __init__(self, gen_params: GenParams, rob_get_indices: Method, fetch_stall_e self.cause = Signal(ExceptionCause) self.rob_id = Signal(gen_params.rob_entries_bits) self.pc = Signal(gen_params.isa.xlen) + self.mtval = Signal(gen_params.isa.xlen) self.valid = Signal() self.layouts = gen_params.get(ExceptionRegisterLayouts) @@ -82,7 +83,7 @@ def elaborate(self, platform): m.submodules.report_connector = ConnectTrans(self.fu_report_fifo.read, report) @def_method(m, report) - def _(cause, rob_id, pc): + def _(cause, rob_id, pc, mtval): should_write = Signal() with m.If(self.valid & (self.rob_id == rob_id)): @@ -101,6 +102,7 @@ def _(cause, rob_id, pc): m.d.sync += self.rob_id.eq(rob_id) m.d.sync += self.cause.eq(cause) m.d.sync += self.pc.eq(pc) + m.d.sync += self.mtval.eq(mtval) m.d.sync += self.valid.eq(1) diff --git a/test/priv/traps/test_exception.py b/test/priv/traps/test_exception.py index d6d2a91bc..1607d5e87 100644 --- a/test/priv/traps/test_exception.py +++ b/test/priv/traps/test_exception.py @@ -1,7 +1,7 @@ from amaranth import * from coreblocks.interface.layouts import ROBLayouts -from coreblocks.priv.traps.exception import ExceptionCauseRegister +from coreblocks.priv.traps.exception import ExceptionInformationRegister from coreblocks.params import GenParams from coreblocks.arch import ExceptionCause from coreblocks.params.configurations import test_core_config @@ -13,7 +13,7 @@ import random -class TestExceptionCauseRegister(TestCaseWithSimulator): +class TestExceptionInformationRegister(TestCaseWithSimulator): rob_max = 7 def should_update(self, new_arg, old_arg, rob_start) -> bool: @@ -36,7 +36,7 @@ def test_randomized(self): self.rob_idx_mock = TestbenchIO(Adapter(o=self.gen_params.get(ROBLayouts).get_indices)) self.fetch_stall_mock = TestbenchIO(Adapter()) self.dut = SimpleTestCircuit( - ExceptionCauseRegister( + ExceptionInformationRegister( self.gen_params, self.rob_idx_mock.adapter.iface, self.fetch_stall_mock.adapter.iface ) ) diff --git a/transactron/utils/transactron_helpers.py b/transactron/utils/transactron_helpers.py index 0bb55f311..59dd88bf3 100644 --- a/transactron/utils/transactron_helpers.py +++ b/transactron/utils/transactron_helpers.py @@ -18,6 +18,8 @@ "mock_def_helper", "get_src_loc", "from_method_layout", + "make_layout", + "extend_layout", ] T = TypeVar("T") @@ -137,6 +139,10 @@ def make_layout(*fields: LayoutListField) -> StructLayout: return from_method_layout(fields) +def extend_layout(layout: StructLayout, *fields: LayoutListField) -> StructLayout: + return StructLayout(layout.members | from_method_layout(fields).members) + + def from_method_layout(layout: MethodLayout) -> StructLayout: if isinstance(layout, StructLayout): return layout From 9f13cb1c650efb442a86bbc71076d45fcc09d055 Mon Sep 17 00:00:00 2001 From: Piotr Wegrzyn Date: Thu, 1 Aug 2024 16:26:24 +0200 Subject: [PATCH 02/15] Fix signal assignments --- coreblocks/frontend/decoder/rvc.py | 2 +- coreblocks/func_blocks/fu/exception.py | 2 +- coreblocks/func_blocks/fu/lsu/dummyLsu.py | 9 +++------ coreblocks/func_blocks/fu/lsu/lsu_requester.py | 2 +- coreblocks/interface/layouts.py | 2 +- coreblocks/priv/traps/exception.py | 2 +- 6 files changed, 8 insertions(+), 11 deletions(-) diff --git a/coreblocks/frontend/decoder/rvc.py b/coreblocks/frontend/decoder/rvc.py index beac67865..c852b39e6 100644 --- a/coreblocks/frontend/decoder/rvc.py +++ b/coreblocks/frontend/decoder/rvc.py @@ -291,7 +291,7 @@ def elaborate(self, platform): res = self.instr_mux(quadrant, quadrants) - # In case of illegal instruction, ouput `instr_in` to be able to save it into `mtval` CSR. + # In case of illegal instruction, output `instr_in` to be able to save it into `mtval` CSR. # Decoder would still recognize it as illegal because of quadrant != 0b11 m.d.comb += self.instr_out.eq(Mux(res[1], res[0], self.instr_in)) diff --git a/coreblocks/func_blocks/fu/exception.py b/coreblocks/func_blocks/fu/exception.py index 806437366..954ee83fb 100644 --- a/coreblocks/func_blocks/fu/exception.py +++ b/coreblocks/func_blocks/fu/exception.py @@ -92,7 +92,7 @@ def _(arg): m.d.av_comb += cause.eq(ExceptionCause.INSTRUCTION_PAGE_FAULT) m.d.av_comb += mtval.eq(arg.pc + (arg.imm[1] << 1)) - self.report(m, rob_id=arg.rob_id, cause=cause, pc=arg.pc) + self.report(m, rob_id=arg.rob_id, cause=cause, pc=arg.pc, mtval=mtval) fifo.write(m, result=0, exception=1, rob_id=arg.rob_id, rp_dst=arg.rp_dst) diff --git a/coreblocks/func_blocks/fu/lsu/dummyLsu.py b/coreblocks/func_blocks/fu/lsu/dummyLsu.py index 6ab05b45b..cef1daa4e 100644 --- a/coreblocks/func_blocks/fu/lsu/dummyLsu.py +++ b/coreblocks/func_blocks/fu/lsu/dummyLsu.py @@ -4,7 +4,6 @@ from transactron import Method, def_method, Transaction, TModule from transactron.lib.connectors import FIFO, Forwarder from transactron.utils import DependencyContext -from transactron.utils.transactron_helpers import extend_layout from transactron.lib.simultaneous import condition from transactron.lib.logging import HardwareLogger @@ -65,9 +64,7 @@ def elaborate(self, platform): m.submodules.requester = requester = LSURequester(self.gen_params, self.bus) m.submodules.requests = requests = Forwarder(self.fu_layouts.issue) - m.submodules.results_noop = results_noop = FIFO( - extend_layout(self.lsu_layouts.accept, ("addr", self.gen_params.isa.xlen)), 2 - ) + m.submodules.results_noop = results_noop = FIFO(self.lsu_layouts.accept, 2) m.submodules.issued = issued = FIFO(self.fu_layouts.issue, 2) m.submodules.issued_noop = issued_noop = FIFO(self.fu_layouts.issue, 2) @@ -109,7 +106,7 @@ def _(arg): with m.If(res["exception"]): issued_noop.write(m, arg) - results_noop.write(m, data=0, exception=res["exception"], cause=res["cause"], addr=res["addr"]) + results_noop.write(m, data=0, exception=res["exception"], cause=res["cause"], addr=addr) with m.Else(): issued.write(m, arg) @@ -132,7 +129,7 @@ def _(): m.d.comb += arg.eq(issued_noop.read(m)) with m.If(res["exception"]): - self.report(m, rob_id=arg["rob_id"], cause=res["cause"], pc=arg["pc"], mtval=arg["addr"]) + self.report(m, rob_id=arg["rob_id"], cause=res["cause"], pc=arg["pc"], mtval=res["addr"]) self.log.debug(m, 1, "accept rob_id={} result=0x{:08x} exception={}", arg.rob_id, res.data, res.exception) diff --git a/coreblocks/func_blocks/fu/lsu/lsu_requester.py b/coreblocks/func_blocks/fu/lsu/lsu_requester.py index eaa604001..c8abd6017 100644 --- a/coreblocks/func_blocks/fu/lsu/lsu_requester.py +++ b/coreblocks/func_blocks/fu/lsu/lsu_requester.py @@ -143,7 +143,7 @@ def _(addr: Value, data: Value, funct3: Value, store: Value): Mux(store, ExceptionCause.STORE_ADDRESS_MISALIGNED, ExceptionCause.LOAD_ADDRESS_MISALIGNED) ) - return {"exception": exception, "cause": cause, "addr": addr} + return {"exception": exception, "cause": cause} @def_method(m, self.accept) def _(): diff --git a/coreblocks/interface/layouts.py b/coreblocks/interface/layouts.py index dd8bc70e7..5ab3744d8 100644 --- a/coreblocks/interface/layouts.py +++ b/coreblocks/interface/layouts.py @@ -581,7 +581,7 @@ def __init__(self, gen_params: GenParams): self.issue_out = make_layout(fields.exception, fields.cause) - self.accept = make_layout(fields.data, fields.exception, fields.cause) + self.accept = make_layout(fields.data, fields.exception, fields.cause, fields.addr) class CSRRegisterLayouts: diff --git a/coreblocks/priv/traps/exception.py b/coreblocks/priv/traps/exception.py index ad9379a89..9898626e1 100644 --- a/coreblocks/priv/traps/exception.py +++ b/coreblocks/priv/traps/exception.py @@ -111,7 +111,7 @@ def _(cause, rob_id, pc, mtval): @def_method(m, self.get) def _(): - return {"rob_id": self.rob_id, "cause": self.cause, "pc": self.pc, "valid": self.valid} + return {"rob_id": self.rob_id, "cause": self.cause, "pc": self.pc, "mtval": self.mtval, "valid": self.valid} @def_method(m, self.clear) def _(): From 788d3248d2251c7329022b92959b3f8481674239 Mon Sep 17 00:00:00 2001 From: Piotr Wegrzyn Date: Thu, 8 Aug 2024 13:53:41 +0000 Subject: [PATCH 03/15] Support loading initialized data in asm tests --- test/asm/exception_mem.asm | 2 ++ test/asm/fibonacci_mem.asm | 3 ++ test/asm/init_regs.s | 7 +++++ test/asm/interrupt.asm | 11 +++++-- test/asm/link.ld | 3 +- test/asm/wfi_int.asm | 7 ++++- test/test_core.py | 60 +++++++++++++++++++++++--------------- 7 files changed, 65 insertions(+), 28 deletions(-) diff --git a/test/asm/exception_mem.asm b/test/asm/exception_mem.asm index c3556e795..4964f135d 100644 --- a/test/asm/exception_mem.asm +++ b/test/asm/exception_mem.asm @@ -9,3 +9,5 @@ sw x2, 4(x0) sw x1, 4(x0) /* TODO: actually check the side fx */ li x2, 9 +.section .bss +.skip 0x8 diff --git a/test/asm/fibonacci_mem.asm b/test/asm/fibonacci_mem.asm index 9986aca76..0db8b2130 100644 --- a/test/asm/fibonacci_mem.asm +++ b/test/asm/fibonacci_mem.asm @@ -24,3 +24,6 @@ loop: bne x3, x4, loop infloop: j infloop + +.section .bss +.skip 0xC diff --git a/test/asm/init_regs.s b/test/asm/init_regs.s index 5c27a365b..48c767bd8 100644 --- a/test/asm/init_regs.s +++ b/test/asm/init_regs.s @@ -1,3 +1,4 @@ +.macro INIT_REGS_LOAD # load the initial states of registers # the value of a register `n` is assumed to be stored under address `0x100 + n * 4`. lw x1, 0x104(x0) @@ -31,3 +32,9 @@ lw x29,0x174(x0) lw x30,0x178(x0) lw x31,0x17c(x0) +.endm + +.macro INIT_REGS_ALLOCATION +.org 0x100 +.skip 0x80 +.endm diff --git a/test/asm/interrupt.asm b/test/asm/interrupt.asm index 02388127f..ccf079245 100644 --- a/test/asm/interrupt.asm +++ b/test/asm/interrupt.asm @@ -1,7 +1,9 @@ - _start: - .include "init_regs.s" +.include "init_regs.s" -# fibonacci spiced with interrupt handler (also with fibonacci) +_start: + INIT_REGS_LOAD + + # fibonacci spiced with interrupt handler (also with fibonacci) li x1, 0x200 csrw mtvec, x1 li x27, 0 # handler count @@ -98,3 +100,6 @@ fail: .org 0x200 j int_handler li x31, 0xae # should never happen + +.bss + INIT_REGS_ALLOCATION diff --git a/test/asm/link.ld b/test/asm/link.ld index 9ceab42eb..b8c136191 100644 --- a/test/asm/link.ld +++ b/test/asm/link.ld @@ -5,7 +5,8 @@ start = 0; SECTIONS { .text : { *(.text) } - . = 0x100000000; /* start from 2**32 - trick to emulate Harvard architecture (.bss addresses will start from 0) */ + . = 0x100000000; /* start from 2**32 - trick to emulate Harvard architecture (memory addresses will start from 0) */ + .data : { *(.data) } .bss : { *(.bss) } _end = .; } diff --git a/test/asm/wfi_int.asm b/test/asm/wfi_int.asm index 3f50000c4..838e0b5f8 100644 --- a/test/asm/wfi_int.asm +++ b/test/asm/wfi_int.asm @@ -1,5 +1,7 @@ +.include "init_regs.s" + _start: - .include "init_regs.s" + INIT_REGS_LOAD li x1, 0x100 # set handler vector csrw mtvec, x1 @@ -26,3 +28,6 @@ skip: .org 0x100 j handler + +.data + INIT_REGS_ALLOCATION diff --git a/test/test_core.py b/test/test_core.py index d9cf4655d..4e8497eb4 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -12,7 +12,6 @@ from coreblocks.params.configurations import CoreConfiguration, basic_core_config, full_core_config from coreblocks.peripherals.wishbone import WishboneMemorySlave -from typing import Optional import random import subprocess import tempfile @@ -20,13 +19,10 @@ class CoreTestElaboratable(Elaboratable): - def __init__(self, gen_params: GenParams, instr_mem: list[int] = [0], data_mem: Optional[list[int]] = None): + def __init__(self, gen_params: GenParams, instr_mem: list[int] = [0], data_mem: list[int] = []): self.gen_params = gen_params self.instr_mem = instr_mem - if data_mem is None: - self.data_mem = [0] * (2**10) - else: - self.data_mem = data_mem + self.data_mem = data_mem def elaborate(self, platform): m = Module() @@ -71,12 +67,10 @@ def get_arch_reg_val(self, reg_id): class TestCoreAsmSourceBase(TestCoreBase): base_dir: str = "test/asm/" - def prepare_source(self, filename): - bin_src = [] + def prepare_source(self, filename, *, c_extension=False): with ( tempfile.NamedTemporaryFile() as asm_tmp, tempfile.NamedTemporaryFile() as ld_tmp, - tempfile.NamedTemporaryFile() as bin_tmp, ): subprocess.check_call( [ @@ -84,7 +78,7 @@ def prepare_source(self, filename): "-mabi=ilp32", # Specified manually, because toolchains from most distributions don't support new extensioins # and this test should be accessible locally. - "-march=rv32im_zicsr", + f"-march=rv32im{'c' if c_extension else ''}_zicsr", "-I", self.base_dir, "-o", @@ -104,16 +98,36 @@ def prepare_source(self, filename): ld_tmp.name, ] ) - subprocess.check_call( - ["riscv64-unknown-elf-objcopy", "-O", "binary", "-j", ".text", ld_tmp.name, bin_tmp.name] - ) - code = bin_tmp.read() - for word_idx in range(0, len(code), 4): - word = code[word_idx : word_idx + 4] - bin_instr = int.from_bytes(word, "little") - bin_src.append(bin_instr) - return bin_src + def load_section(section: str): + with tempfile.NamedTemporaryFile() as bin_tmp: + bin = [] + + subprocess.check_call( + [ + "riscv64-unknown-elf-objcopy", + "-O", + "binary", + "-j", + section, + "--set-section-flags", + f"{section}=alloc,load,contents", + ld_tmp.name, + bin_tmp.name, + ] + ) + + data = bin_tmp.read() + for word_idx in range(0, len(data), 4): + word = data[word_idx : word_idx + 4] + bin.append(int.from_bytes(word, "little")) + + return bin + + return { + "text": load_section(".text"), + "data": load_section(".data") + load_section(".bss"), + } @parameterized_class( @@ -146,7 +160,8 @@ def test_asm_source(self): self.gen_params = GenParams(self.configuration) bin_src = self.prepare_source(self.source_file) - self.m = CoreTestElaboratable(self.gen_params, instr_mem=bin_src) + self.m = CoreTestElaboratable(self.gen_params, instr_mem=bin_src["text"], data_mem=bin_src["data"]) + with self.run_simulation(self.m) as sim: sim.add_sync_process(self.run_and_check) @@ -269,10 +284,9 @@ def do_interrupt(): def test_interrupted_prog(self): bin_src = self.prepare_source(self.source_file) - data_mem = [0] * (2**10) for reg_id, val in self.start_regvals.items(): - data_mem[self.reg_init_mem_offset // 4 + reg_id] = val - self.m = CoreTestElaboratable(self.gen_params, instr_mem=bin_src, data_mem=data_mem) + bin_src["data"][self.reg_init_mem_offset // 4 + reg_id] = val + self.m = CoreTestElaboratable(self.gen_params, instr_mem=bin_src["text"], data_mem=bin_src["data"]) with self.run_simulation(self.m) as sim: sim.add_sync_process(self.run_with_interrupt_process) sim.add_sync_process(self.clear_level_interrupt_procsess) From d5eb89a8146acaef29060cc55f60f89b4ad687b6 Mon Sep 17 00:00:00 2001 From: Piotr Wegrzyn Date: Thu, 8 Aug 2024 14:09:20 +0000 Subject: [PATCH 04/15] Fix decoding logic --- coreblocks/frontend/decoder/decode_stage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coreblocks/frontend/decoder/decode_stage.py b/coreblocks/frontend/decoder/decode_stage.py index ec90fada4..65dedfd22 100644 --- a/coreblocks/frontend/decoder/decode_stage.py +++ b/coreblocks/frontend/decoder/decode_stage.py @@ -65,7 +65,7 @@ def elaborate(self, platform): ] exception_override = Signal() - m.d.comb += exception_override.eq(instr_decoder.illegal | raw.access_fault) + m.d.comb += exception_override.eq(instr_decoder.illegal | raw.access_fault.any()) exception_funct = Signal(Funct3) with m.If(raw.access_fault): m.d.comb += exception_funct.eq(Funct3._EINSTRACCESSFAULT) From 89b8fd46d4ec27a8b0ba53bc419554d8477557d0 Mon Sep 17 00:00:00 2001 From: Piotr Wegrzyn Date: Thu, 8 Aug 2024 14:10:15 +0000 Subject: [PATCH 05/15] Add mtval re-encoding on illegal CSR instructions --- coreblocks/frontend/decoder/instr_decoder.py | 8 ++++++++ coreblocks/func_blocks/csr/csr.py | 18 +++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/coreblocks/frontend/decoder/instr_decoder.py b/coreblocks/frontend/decoder/instr_decoder.py index 69574fa70..c6cd56875 100644 --- a/coreblocks/frontend/decoder/instr_decoder.py +++ b/coreblocks/frontend/decoder/instr_decoder.py @@ -317,6 +317,14 @@ def elaborate(self, platform): self.rs1_v.eq(0), ] + # HACK: pass logical registers is unused high bits of CSR instruction for `mtval` reconstruction + with m.If((self.optype == OpType.CSR_REG) | (self.optype == OpType.CSR_IMM)): + m.d.comb += self.imm[32 - self.gen_params.isa.reg_cnt_log : 32].eq(self.rd) + m.d.comb += self.imm[32 - self.gen_params.isa.reg_cnt_log * 2 : 32 - self.gen_params.isa.reg_cnt_log].eq( + self.rs1 + ) + assert 32 - self.gen_params.isa.reg_cnt_log * 2 >= 5 + # Instruction simplification # lui rd, imm -> addi rd, x0, (imm << 12) diff --git a/coreblocks/func_blocks/csr/csr.py b/coreblocks/func_blocks/csr/csr.py index 505908fed..3c008d031 100644 --- a/coreblocks/func_blocks/csr/csr.py +++ b/coreblocks/func_blocks/csr/csr.py @@ -8,6 +8,7 @@ from transactron.utils.dependencies import DependencyContext from coreblocks.arch import OpType, Funct3, ExceptionCause, PrivilegeLevel +from coreblocks.arch.isa_consts import Opcode from coreblocks.params import GenParams from coreblocks.params.fu_params import BlockComponentParams from coreblocks.func_blocks.interface.func_protocols import FuncBlock @@ -188,7 +189,7 @@ def _(rs_entry_id, rs_data): m.d.sync += assign(instr, rs_data) with m.If(rs_data.exec_fn.op_type == OpType.CSR_IMM): # Pass immediate as first operand - m.d.sync += instr.s1_val.eq(rs_data.imm) + m.d.sync += instr.s1_val.eq(rs_data.imm[0:5]) m.d.sync += instr.valid.eq(1) @@ -209,6 +210,21 @@ def _(): with m.If(exception): report(m, rob_id=instr.rob_id, cause=ExceptionCause.ILLEGAL_INSTRUCTION, pc=instr.pc, mtval=0) + mtval = Signal(self.gen_params.isa.xlen) + # re-encode the CSR instruction to speed-up missing CSR emulation (optional, otherwise tval _must_ be 0) + m.d.av_comb += mtval[0:2].eq(0b11) + m.d.av_comb += mtval[2:7].eq(Opcode.SYSTEM) + m.d.av_comb += mtval[7:12].eq(instr.imm[32 - self.gen_params.isa.reg_cnt_log : 32]) # rl_rd + m.d.av_comb += mtval[12:15].eq(instr.exec_fn.funct3) + m.d.av_comb += mtval[15:20].eq( + Mux( + instr.exec_fn.op_type == OpType.CSR_IMM, + instr.imm[0:5], + instr.imm[32 - self.gen_params.isa.reg_cnt_log * 2 : 32 - self.gen_params.isa.reg_cnt_log], + ) + ) # rl_s1 or imm + m.d.av_comb += mtval[20:32].eq(instr.csr) + report(m, rob_id=instr.rob_id, cause=ExceptionCause.ILLEGAL_INSTRUCTION, pc=instr.pc, mtval=mtval) with m.Elif(interrupt): # SPEC: "These conditions for an interrupt trap to occur [..] must also be evaluated immediately # following [..] an explicit write to a CSR on which these interrupt trap conditions expressly depend." From 9d2012e6ef4b37c7927f575bf8555a2a60e64f21 Mon Sep 17 00:00:00 2001 From: Piotr Wegrzyn Date: Thu, 8 Aug 2024 14:11:35 +0000 Subject: [PATCH 06/15] Add asm tests --- test/asm/mtval.asm | 99 ++++++++++++++++++++++++++++++++++++++++++++++ test/test_core.py | 6 +++ 2 files changed, 105 insertions(+) create mode 100644 test/asm/mtval.asm diff --git a/test/asm/mtval.asm b/test/asm/mtval.asm new file mode 100644 index 000000000..cd7a46d51 --- /dev/null +++ b/test/asm/mtval.asm @@ -0,0 +1,99 @@ + la x1, handler + csrw mtvec, x1 + li x8, 0 + li x7, 0x80000000 +c0: + lw x1, 0x230(x7) +c1: + ebreak +c2: + j i_out_of_range +c3: + j i_partial_out_of_range +c4: +.word 0x43 +c5: +.word 0x8000 +c6: + csrr x1, 0x123 +c7: + csrwi 0x123, 8 +c8: + sw x1, 0x231(x7) +c9: + ecall + +pass: + j pass + + +# TODO: check if mepc can misalign + +handler: + la x1, excpected_mtval + add x1, x1, x8 + lw x2, (x1) + csrr x1, mtval + bne x1, x2, fail + + la x1, excpected_mcause + add x1, x1, x8 + lw x2, (x1) + csrr x1, mcause + bne x1, x2, fail + + la x1, next_instr + add x1, x1, x8 + lw x2, (x1) + csrw mepc, x2 + + addi x8, x8, 4 + + mret + +fail: + j fail + +.org 0x0FFE +# it is legal - C is enabled in core, but can't be enabled in toolchain to keep 4-byte nops +i_partial_out_of_range: +nop +i_out_of_range: +nop + +.data +excpected_mtval: +.word 0x80000230 +.word c1 +.word i_out_of_range +.word i_partial_out_of_range + 2 +.word 0x43 +.word 0x8000 +.word 0x123020f3 +.word 0x12345073 +.word 0x80000231 +.word 0 +excpected_mcause: +.word 5 +.word 3 +.word 1 +.word 1 +.word 2 +.word 2 +.word 2 +.word 2 +.word 6 +.word 11 +# testing misaligned instr branch is not possible with C enabled +next_instr: +.word c1 +.word c2 +.word c3 +.word c4 +.word c5 +.word c6 +.word c7 +.word c8 +.word c9 +.word pass + diff --git a/test/test_core.py b/test/test_core.py index 4e8497eb4..0136b1da5 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -141,9 +141,11 @@ def load_section(section: str): ("exception_mem", "exception_mem.asm", 200, {1: 1, 2: 2}, basic_core_config), ("exception_handler", "exception_handler.asm", 2000, {2: 987, 11: 0xAAAA, 15: 16}, full_core_config), ("wfi_no_int", "wfi_no_int.asm", 200, {1: 1}, full_core_config), + ("mtval", "mtval.asm", 2000, {8: 5 * 8}, full_core_config), ], ) class TestCoreBasicAsm(TestCoreAsmSourceBase): + name: str source_file: str cycle_count: int expected_regvals: dict[int, int] @@ -160,6 +162,10 @@ def test_asm_source(self): self.gen_params = GenParams(self.configuration) bin_src = self.prepare_source(self.source_file) + + if self.name == "mtval": + bin_src["text"] = bin_src["text"][: 0x1000 // 4] # force instruction memory size clip in `mtval` test + self.m = CoreTestElaboratable(self.gen_params, instr_mem=bin_src["text"], data_mem=bin_src["data"]) with self.run_simulation(self.m) as sim: From b692d15be5e7aa9c6c67faa60dae192fdcab5c67 Mon Sep 17 00:00:00 2001 From: Piotr Wegrzyn Date: Thu, 8 Aug 2024 14:43:31 +0000 Subject: [PATCH 07/15] Fix after merge --- coreblocks/func_blocks/csr/csr.py | 1 - 1 file changed, 1 deletion(-) diff --git a/coreblocks/func_blocks/csr/csr.py b/coreblocks/func_blocks/csr/csr.py index 3c008d031..1e38c8334 100644 --- a/coreblocks/func_blocks/csr/csr.py +++ b/coreblocks/func_blocks/csr/csr.py @@ -209,7 +209,6 @@ def _(): interrupt = self.dependency_manager.get_dependency(AsyncInterruptInsertSignalKey()) with m.If(exception): - report(m, rob_id=instr.rob_id, cause=ExceptionCause.ILLEGAL_INSTRUCTION, pc=instr.pc, mtval=0) mtval = Signal(self.gen_params.isa.xlen) # re-encode the CSR instruction to speed-up missing CSR emulation (optional, otherwise tval _must_ be 0) m.d.av_comb += mtval[0:2].eq(0b11) From c873f24778ec724fbdf2bb1451c19a7eefa2844e Mon Sep 17 00:00:00 2001 From: Piotr Wegrzyn Date: Thu, 8 Aug 2024 14:43:53 +0000 Subject: [PATCH 08/15] Fix LSU test --- test/func_blocks/lsu/test_dummylsu.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/func_blocks/lsu/test_dummylsu.py b/test/func_blocks/lsu/test_dummylsu.py index afeab7913..a4a3252d8 100644 --- a/test/func_blocks/lsu/test_dummylsu.py +++ b/test/func_blocks/lsu/test_dummylsu.py @@ -168,6 +168,7 @@ def generate_instr(self, max_reg_val, max_imm_val): ExceptionCause.LOAD_ADDRESS_MISALIGNED if misaligned else ExceptionCause.LOAD_ACCESS_FAULT ), "pc": 0, + "mtval": addr, } ) From bd0f9b2fa706648d582e8cee762fcff67327c7ab Mon Sep 17 00:00:00 2001 From: Piotr Wegrzyn Date: Thu, 8 Aug 2024 14:44:14 +0000 Subject: [PATCH 09/15] Set mepc=0 explicitly at ecall --- coreblocks/func_blocks/fu/exception.py | 1 + 1 file changed, 1 insertion(+) diff --git a/coreblocks/func_blocks/fu/exception.py b/coreblocks/func_blocks/fu/exception.py index 954ee83fb..d95b192f7 100644 --- a/coreblocks/func_blocks/fu/exception.py +++ b/coreblocks/func_blocks/fu/exception.py @@ -77,6 +77,7 @@ def _(arg): with OneHotCase(ExceptionUnitFn.Fn.ECALL): # TODO: Switch privilege level when implemented m.d.av_comb += cause.eq(ExceptionCause.ENVIRONMENT_CALL_FROM_M) + m.d.av_comb += mtval.eq(0) # by SPEC with OneHotCase(ExceptionUnitFn.Fn.INSTR_ACCESS_FAULT): m.d.av_comb += cause.eq(ExceptionCause.INSTRUCTION_ACCESS_FAULT) # With C extension access fault can be only on the second half of instruction, and mepc != mtval. From e2554ddcd2ee3f8d979e4d93cca35d7faded45a3 Mon Sep 17 00:00:00 2001 From: Piotr Wegrzyn Date: Mon, 12 Aug 2024 09:36:46 +0000 Subject: [PATCH 10/15] Complete unit tests --- test/asm/mtval.asm | 2 -- test/frontend/test_instr_decoder.py | 6 +++++- test/frontend/test_rvc.py | 7 +++++++ test/func_blocks/csr/test_csr.py | 5 +++-- test/func_blocks/fu/functional_common.py | 10 +++++++++- test/func_blocks/fu/test_exception_unit.py | 7 ++++++- test/func_blocks/fu/test_jb_unit.py | 4 +++- test/priv/traps/test_exception.py | 3 ++- test/test_core.py | 3 --- 9 files changed, 35 insertions(+), 12 deletions(-) diff --git a/test/asm/mtval.asm b/test/asm/mtval.asm index cd7a46d51..12eac2d91 100644 --- a/test/asm/mtval.asm +++ b/test/asm/mtval.asm @@ -27,8 +27,6 @@ pass: j pass -# TODO: check if mepc can misalign - handler: la x1, excpected_mtval add x1, x1, x8 diff --git a/test/frontend/test_instr_decoder.py b/test/frontend/test_instr_decoder.py index 2abd84c9d..1830f1063 100644 --- a/test/frontend/test_instr_decoder.py +++ b/test/frontend/test_instr_decoder.py @@ -217,7 +217,11 @@ def process(): assert (yield self.decoder.rs2_v) == (test.rs2 is not None) if test.imm is not None: - assert (yield self.decoder.imm.as_signed()) == test.imm + if test.csr is not None: + # in CSR instruction additional fields are passed in unused bits of imm field + assert (yield self.decoder.imm.as_signed() & ((2**5) - 1)) == test.imm + else: + assert (yield self.decoder.imm.as_signed()) == test.imm if test.succ is not None: assert (yield self.decoder.succ) == test.succ diff --git a/test/frontend/test_rvc.py b/test/frontend/test_rvc.py index ad86a1f3d..03706eaa0 100644 --- a/test/frontend/test_rvc.py +++ b/test/frontend/test_rvc.py @@ -284,12 +284,19 @@ def test(self): self.m = InstrDecompress(self.gen_params) def process(): + illegal = Signal(32) + yield illegal.eq(IllegalInstr()) + for instr_in, instr_out in self.test_cases: yield self.m.instr_in.eq(instr_in) expected = Signal(32) yield expected.eq(instr_out) yield Settle() + if (yield expected) == (yield illegal): + yield expected.eq(instr_in) # for exception handling + yield Settle() + assert (yield self.m.instr_out) == (yield expected) yield diff --git a/test/func_blocks/csr/test_csr.py b/test/func_blocks/csr/test_csr.py index ec0f04008..5efcb1ee9 100644 --- a/test/func_blocks/csr/test_csr.py +++ b/test/func_blocks/csr/test_csr.py @@ -92,7 +92,7 @@ def generate_instruction(self): rd = random.randint(0, 15) rs1 = 0 if imm_op else random.randint(0, 15) - imm = random.randint(0, 2**self.gen_params.isa.xlen - 1) + imm = random.randint(0, 2**5 - 1) rs1_val = random.randint(0, 2**self.gen_params.isa.xlen - 1) if rs1 else 0 operand_val = imm if imm_op else rs1_val csr = random.choice(list(self.dut.csr.keys())) @@ -191,7 +191,8 @@ def process_exception_test(self): assert res["exception"] == 1 report = yield from self.dut.exception_report.call_result() - assert report is not None + assert isinstance(report, dict) + report.pop("mtval") # mtval tested in mtval.asm test assert {"rob_id": rob_id, "cause": ExceptionCause.ILLEGAL_INSTRUCTION, "pc": 0} == report def test_exception(self): diff --git a/test/func_blocks/fu/functional_common.py b/test/func_blocks/fu/functional_common.py index 2310e6e0f..8bfd38796 100644 --- a/test/func_blocks/fu/functional_common.py +++ b/test/func_blocks/fu/functional_common.py @@ -140,10 +140,18 @@ def setup(self, fixture_initialize_testing_env): cause = None if "exception" in results: cause = results["exception"] - self.exceptions.append({"rob_id": rob_id, "cause": cause, "pc": results.setdefault("exception_pc", pc)}) + self.exceptions.append( + { + "rob_id": rob_id, + "cause": cause, + "pc": results.setdefault("exception_pc", pc), + "mtval": results.setdefault("mtval", 0), + } + ) results.pop("exception") results.pop("exception_pc") + results.pop("mtval") self.responses.append({"rob_id": rob_id, "rp_dst": rp_dst, "exception": int(cause is not None)} | results) diff --git a/test/func_blocks/fu/test_exception_unit.py b/test/func_blocks/fu/test_exception_unit.py index 3f6793c4b..ad57bb896 100644 --- a/test/func_blocks/fu/test_exception_unit.py +++ b/test/func_blocks/fu/test_exception_unit.py @@ -22,20 +22,25 @@ class TestExceptionUnit(FunctionalUnitTestCase[ExceptionUnitFn.Fn]): @staticmethod def compute_result(i1: int, i2: int, i_imm: int, pc: int, fn: ExceptionUnitFn.Fn, xlen: int) -> dict[str, int]: cause = None + mtval = 0 match fn: case ExceptionUnitFn.Fn.EBREAK | ExceptionUnitFn.Fn.BREAKPOINT: cause = ExceptionCause.BREAKPOINT + mtval = pc case ExceptionUnitFn.Fn.ECALL: cause = ExceptionCause.ENVIRONMENT_CALL_FROM_M case ExceptionUnitFn.Fn.INSTR_ACCESS_FAULT: cause = ExceptionCause.INSTRUCTION_ACCESS_FAULT + mtval = pc case ExceptionUnitFn.Fn.INSTR_PAGE_FAULT: cause = ExceptionCause.INSTRUCTION_PAGE_FAULT + mtval = pc case ExceptionUnitFn.Fn.ILLEGAL_INSTRUCTION: cause = ExceptionCause.ILLEGAL_INSTRUCTION + mtval = i_imm # in case of illegal instruction, raw instr bits are passed in imm field - return {"result": 0} | {"exception": cause} if cause is not None else {} + return {"result": 0} | {"exception": cause, "mtval": mtval} if cause is not None else {} def test_fu(self): self.run_standard_fu_test() diff --git a/test/func_blocks/fu/test_jb_unit.py b/test/func_blocks/fu/test_jb_unit.py index baa2759af..84ab5603d 100644 --- a/test/func_blocks/fu/test_jb_unit.py +++ b/test/func_blocks/fu/test_jb_unit.py @@ -120,14 +120,16 @@ def compute_result(i1: int, i2: int, i_imm: int, pc: int, fn: JumpBranchFn.Fn, x exception = None exception_pc = pc + mtval = 0 if next_pc & 0b11 != 0: exception = ExceptionCause.INSTRUCTION_ADDRESS_MISALIGNED + mtval = next_pc elif misprediction: exception = ExceptionCause._COREBLOCKS_MISPREDICTION exception_pc = next_pc return {"result": res, "from_pc": pc, "next_pc": next_pc, "misprediction": misprediction} | ( - {"exception": exception, "exception_pc": exception_pc} if exception is not None else {} + {"exception": exception, "exception_pc": exception_pc, "mtval": mtval} if exception is not None else {} ) diff --git a/test/priv/traps/test_exception.py b/test/priv/traps/test_exception.py index 1607d5e87..4c307abc9 100644 --- a/test/priv/traps/test_exception.py +++ b/test/priv/traps/test_exception.py @@ -57,7 +57,8 @@ def process_test(): while saved_entry and report_rob == saved_entry["rob_id"]: report_rob = random.randint(0, self.rob_max) report_pc = random.randrange(2**self.gen_params.isa.xlen) - report_arg = {"cause": cause, "rob_id": report_rob, "pc": report_pc} + report_mtval = random.randrange(2**self.gen_params.isa.xlen) + report_arg = {"cause": cause, "rob_id": report_rob, "pc": report_pc, "mtval": report_mtval} expected = report_arg if self.should_update(report_arg, saved_entry, self.rob_id) else saved_entry yield from self.dut.report.call(report_arg) diff --git a/test/test_core.py b/test/test_core.py index f0429b298..2bb8d7260 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -157,13 +157,10 @@ def test_asm_source(self): self.gen_params = GenParams(self.configuration) bin_src = self.prepare_source(self.source_file) -<<<<<<< HEAD if self.name == "mtval": bin_src["text"] = bin_src["text"][: 0x1000 // 4] # force instruction memory size clip in `mtval` test -======= ->>>>>>> origin self.m = CoreTestElaboratable(self.gen_params, instr_mem=bin_src["text"], data_mem=bin_src["data"]) with self.run_simulation(self.m) as sim: From c603db91840c0bc471f4313db0a76a27fcbc57f0 Mon Sep 17 00:00:00 2001 From: Piotr Wegrzyn Date: Thu, 19 Sep 2024 10:06:07 +0200 Subject: [PATCH 11/15] fix typos --- coreblocks/frontend/decoder/instr_decoder.py | 2 +- coreblocks/func_blocks/csr/csr.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/coreblocks/frontend/decoder/instr_decoder.py b/coreblocks/frontend/decoder/instr_decoder.py index c6cd56875..35b83ccbe 100644 --- a/coreblocks/frontend/decoder/instr_decoder.py +++ b/coreblocks/frontend/decoder/instr_decoder.py @@ -317,7 +317,7 @@ def elaborate(self, platform): self.rs1_v.eq(0), ] - # HACK: pass logical registers is unused high bits of CSR instruction for `mtval` reconstruction + # HACK: pass logical registers in unused high bits of CSR instruction for `mtval` reconstruction with m.If((self.optype == OpType.CSR_REG) | (self.optype == OpType.CSR_IMM)): m.d.comb += self.imm[32 - self.gen_params.isa.reg_cnt_log : 32].eq(self.rd) m.d.comb += self.imm[32 - self.gen_params.isa.reg_cnt_log * 2 : 32 - self.gen_params.isa.reg_cnt_log].eq( diff --git a/coreblocks/func_blocks/csr/csr.py b/coreblocks/func_blocks/csr/csr.py index 1e38c8334..829e8ff37 100644 --- a/coreblocks/func_blocks/csr/csr.py +++ b/coreblocks/func_blocks/csr/csr.py @@ -210,7 +210,7 @@ def _(): with m.If(exception): mtval = Signal(self.gen_params.isa.xlen) - # re-encode the CSR instruction to speed-up missing CSR emulation (optional, otherwise tval _must_ be 0) + # re-encode the CSR instruction to speed-up missing CSR emulation (optional, otherwise mtval must be 0) m.d.av_comb += mtval[0:2].eq(0b11) m.d.av_comb += mtval[2:7].eq(Opcode.SYSTEM) m.d.av_comb += mtval[7:12].eq(instr.imm[32 - self.gen_params.isa.reg_cnt_log : 32]) # rl_rd From 9c18dfa3f27f142df8929c300eaff1fa842d99cf Mon Sep 17 00:00:00 2001 From: Piotr Wegrzyn Date: Mon, 14 Oct 2024 15:31:40 +0200 Subject: [PATCH 12/15] Fix after merge --- coreblocks/func_blocks/fu/exception.py | 4 ++-- coreblocks/func_blocks/fu/priv.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/coreblocks/func_blocks/fu/exception.py b/coreblocks/func_blocks/fu/exception.py index 0efd52273..7c9777d77 100644 --- a/coreblocks/func_blocks/fu/exception.py +++ b/coreblocks/func_blocks/fu/exception.py @@ -80,9 +80,9 @@ def _(arg): with OneHotCase(ExceptionUnitFn.Fn.ECALL): with m.Switch(priv_level): with m.Case(PrivilegeLevel.MACHINE): - m.d.comb += cause.eq(ExceptionCause.ENVIRONMENT_CALL_FROM_M) + m.d.av_comb += cause.eq(ExceptionCause.ENVIRONMENT_CALL_FROM_M) with m.Case(PrivilegeLevel.USER): - m.d.comb += cause.eq(ExceptionCause.ENVIRONMENT_CALL_FROM_U) + m.d.av_comb += cause.eq(ExceptionCause.ENVIRONMENT_CALL_FROM_U) m.d.av_comb += mtval.eq(0) # by SPEC with OneHotCase(ExceptionUnitFn.Fn.INSTR_ACCESS_FAULT): m.d.av_comb += cause.eq(ExceptionCause.INSTRUCTION_ACCESS_FAULT) diff --git a/coreblocks/func_blocks/fu/priv.py b/coreblocks/func_blocks/fu/priv.py index 857c834d4..2f08cced4 100644 --- a/coreblocks/func_blocks/fu/priv.py +++ b/coreblocks/func_blocks/fu/priv.py @@ -146,7 +146,7 @@ def _(): exception = Signal() with m.If(illegal_instruction): m.d.comb += exception.eq(1) - exception_report(m, cause=ExceptionCause.ILLEGAL_INSTRUCTION, pc=ret_pc, rob_id=instr_rob) + exception_report(m, cause=ExceptionCause.ILLEGAL_INSTRUCTION, pc=ret_pc, rob_id=instr_rob, mtval=0) with m.Elif(async_interrupt_active): # SPEC: "These conditions for an interrupt trap to occur [..] must also be evaluated immediately # following the execution of an xRET instruction." From f1df7aa6fe8f3ad3e3dcbc68e4fad514ab82a003 Mon Sep 17 00:00:00 2001 From: Piotr Wegrzyn Date: Mon, 14 Oct 2024 15:42:34 +0200 Subject: [PATCH 13/15] Add encoding for illegal WFI/MRET --- coreblocks/func_blocks/fu/priv.py | 37 +++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/coreblocks/func_blocks/fu/priv.py b/coreblocks/func_blocks/fu/priv.py index 2f08cced4..268c50d14 100644 --- a/coreblocks/func_blocks/fu/priv.py +++ b/coreblocks/func_blocks/fu/priv.py @@ -2,7 +2,7 @@ from enum import IntFlag, auto, unique from typing import Sequence -from coreblocks.arch.isa_consts import PrivilegeLevel +from coreblocks.arch.isa_consts import Funct12, Funct3, Opcode, PrivilegeLevel from transactron import * @@ -45,17 +45,17 @@ def get_instructions(cls) -> Sequence[tuple]: class PrivilegedFuncUnit(Elaboratable): - def __init__(self, gp: GenParams): - self.gp = gp + def __init__(self, gen_params: GenParams): + self.gen_params = gen_params self.priv_fn = PrivilegedFn() - self.layouts = layouts = gp.get(FuncUnitLayouts) + self.layouts = layouts = gen_params.get(FuncUnitLayouts) self.dm = DependencyContext.get() self.issue = Method(i=layouts.issue) self.accept = Method(o=layouts.accept) - self.fetch_resume_fifo = BasicFifo(self.gp.get(FetchLayouts).resume, 2) + self.fetch_resume_fifo = BasicFifo(self.gen_params.get(FetchLayouts).resume, 2) self.perf_instr = TaggedCounter( "backend.fu.priv.instr", @@ -68,14 +68,14 @@ def elaborate(self, platform): m.submodules += [self.perf_instr] - m.submodules.decoder = decoder = self.priv_fn.get_decoder(self.gp) + m.submodules.decoder = decoder = self.priv_fn.get_decoder(self.gen_params) instr_valid = Signal() finished = Signal() illegal_instruction = Signal() - instr_rob = Signal(self.gp.rob_entries_bits) - instr_pc = Signal(self.gp.isa.xlen) + instr_rob = Signal(self.gen_params.rob_entries_bits) + instr_pc = Signal(self.gen_params.isa.xlen) instr_fn = self.priv_fn.get_function() mret = self.dm.get_dependency(MretKey()) @@ -129,7 +129,7 @@ def _(): m.d.sync += instr_valid.eq(0) m.d.sync += finished.eq(0) - ret_pc = Signal(self.gp.isa.xlen) + ret_pc = Signal(self.gen_params.isa.xlen) with OneHotSwitch(m, instr_fn) as OneHotCase: with OneHotCase(PrivilegedFn.Fn.MRET): @@ -145,8 +145,21 @@ def _(): exception = Signal() with m.If(illegal_instruction): - m.d.comb += exception.eq(1) - exception_report(m, cause=ExceptionCause.ILLEGAL_INSTRUCTION, pc=ret_pc, rob_id=instr_rob, mtval=0) + m.d.av_comb += exception.eq(1) + + # Replace with const zero if turns out not worth to re-encode instruction + instr = Signal(self.gen_params.isa.xlen) + m.d.av_comb += instr[0:2].eq(0b11) + m.d.av_comb += instr[2:7].eq(Opcode.SYSTEM) + m.d.av_comb += instr[7:12].eq(0) + m.d.av_comb += instr[12:15].eq(Funct3.PRIV) + m.d.av_comb += instr[15:20].eq(0) + m.d.av_comb += instr[20:32].eq(Mux(instr_fn == PrivilegedFn.Fn.MRET, Funct12.WFI, Funct12.MRET)) + log.error( + m, (instr_fn != PrivilegedFn.Fn.MRET) & (instr_fn != PrivilegedFn.Fn.WFI), "missing Funct12 case" + ) + + exception_report(m, cause=ExceptionCause.ILLEGAL_INSTRUCTION, pc=ret_pc, rob_id=instr_rob, mtval=instr) with m.Elif(async_interrupt_active): # SPEC: "These conditions for an interrupt trap to occur [..] must also be evaluated immediately # following the execution of an xRET instruction." @@ -155,7 +168,7 @@ def _(): # by updated async_interrupt_active signal. # Interrupt is reported on this xRET instruction with return address set to instruction that we # would normally return to (mepc value is preserved) - m.d.comb += exception.eq(1) + m.d.av_comb += exception.eq(1) exception_report( m, cause=ExceptionCause._COREBLOCKS_ASYNC_INTERRUPT, pc=ret_pc, rob_id=instr_rob, mtval=0 ) From 813402141b03acccd3254d1a272425fddee4f061 Mon Sep 17 00:00:00 2001 From: Piotr Wegrzyn Date: Mon, 14 Oct 2024 16:32:02 +0200 Subject: [PATCH 14/15] Document test --- test/asm/mtval.asm | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/test/asm/mtval.asm b/test/asm/mtval.asm index 12eac2d91..d56fffe0b 100644 --- a/test/asm/mtval.asm +++ b/test/asm/mtval.asm @@ -1,33 +1,38 @@ +# test `mtval` and `mcause` CSR values for various excpetions +# C extension is required in the core, but must be disabled in the toolchain + la x1, handler csrw mtvec, x1 li x8, 0 + li x7, 0x80000000 -c0: +c0: # load from illegal address. mtval=addr mcause=LOAD_ACCESS_FAULT lw x1, 0x230(x7) -c1: +c1: # mtval=pc mcause=BREAKPOINT ebreak -c2: +c2: # instruction address out of memory mtval=i_out_of_range mcause=INSTRUCTION_ACCESS_FAULT j i_out_of_range -c3: +c3: # jump to 2-byte aligned, 4-byte long instruction, of which first two bytes are available + # and other half is outside of memory range. mtval=i_partial_out_of_range+2 mcause=INSTRUCTION_ACCESS_FAULT j i_partial_out_of_range -c4: +c4: # illegal 4-byte instruction ([0:2] = 0b11) mtval=raw instruction mcause=ILLEGAL_INSTRUCTION .word 0x43 -c5: +c5: # illegal compressed type ([0:2] != 0b11) instruction mtval=raw instruction mcause=ILLEGAL_INSTRUCTION .word 0x8000 -c6: +c6: # access to missing csr mtval=raw instruction mcause=ILLEGAL_INSTRUCTION csrr x1, 0x123 -c7: +c7: # access to missing csr mtval=raw instruction mcause=ILLEGAL_INSTRUCTION csrwi 0x123, 8 -c8: +c8: # store to misaligned address mtvak=addr mcause=STORE_ADDRESS_MISALIGNED sw x1, 0x231(x7) -c9: +c9: # mtval=0 mcause=ENVIRONMENT_CALL_FROM_M ecall pass: j pass -handler: +handler: # test each case. test case number = in x8>>2 la x1, excpected_mtval add x1, x1, x8 lw x2, (x1) @@ -52,14 +57,15 @@ handler: fail: j fail -.org 0x0FFE # it is legal - C is enabled in core, but can't be enabled in toolchain to keep 4-byte nops +.org 0x0FFE i_partial_out_of_range: nop i_out_of_range: nop .data + excpected_mtval: .word 0x80000230 .word c1 @@ -71,6 +77,7 @@ excpected_mtval: .word 0x12345073 .word 0x80000231 .word 0 + excpected_mcause: .word 5 .word 3 @@ -82,7 +89,8 @@ excpected_mcause: .word 2 .word 6 .word 11 -# testing misaligned instr branch is not possible with C enabled +# testing misaligned instr branch is not possible with C enabled :( + next_instr: .word c1 .word c2 From 7777168b7242a1842b27d5c3f34c6d7fef63ca8d Mon Sep 17 00:00:00 2001 From: Piotr Wegrzyn Date: Mon, 14 Oct 2024 17:07:28 +0200 Subject: [PATCH 15/15] Enum as access_fault --- coreblocks/frontend/decoder/decode_stage.py | 10 ++++++++-- coreblocks/frontend/fetch/fetch.py | 10 +++++++--- coreblocks/func_blocks/fu/exception.py | 6 ++++-- coreblocks/interface/layouts.py | 14 ++++++++++++-- 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/coreblocks/frontend/decoder/decode_stage.py b/coreblocks/frontend/decoder/decode_stage.py index 65dedfd22..4d09e8de0 100644 --- a/coreblocks/frontend/decoder/decode_stage.py +++ b/coreblocks/frontend/decoder/decode_stage.py @@ -67,7 +67,7 @@ def elaborate(self, platform): exception_override = Signal() m.d.comb += exception_override.eq(instr_decoder.illegal | raw.access_fault.any()) exception_funct = Signal(Funct3) - with m.If(raw.access_fault): + with m.If(raw.access_fault.any()): m.d.comb += exception_funct.eq(Funct3._EINSTRACCESSFAULT) with m.Elif(instr_decoder.illegal): self.perf_illegal_instr.incr(m) @@ -96,7 +96,13 @@ def elaborate(self, platform): "rl_s2": Mux(instr_decoder.rs2_v & (~exception_override), instr_decoder.rs2, 0), }, "imm": Mux( - ~exception_override, instr_decoder.imm, Mux(raw.access_fault, raw.access_fault, raw.instr) + ~exception_override, + instr_decoder.imm, + Mux( + raw.access_fault.any(), + raw.access_fault, # pass access fault details to FU + raw.instr, # illegal instruction - pass raw instruction bits for `mtval` + ), ), "csr": instr_decoder.csr, "pc": raw.pc, diff --git a/coreblocks/frontend/fetch/fetch.py b/coreblocks/frontend/fetch/fetch.py index 042fbe003..efe1b39d0 100644 --- a/coreblocks/frontend/fetch/fetch.py +++ b/coreblocks/frontend/fetch/fetch.py @@ -323,18 +323,22 @@ def flush(): m.d.av_comb += [ raw_instrs[i].instr.eq(instrs[i]), raw_instrs[i].pc.eq(params.pc_from_fb(fetch_block_addr, i)), - raw_instrs[i].access_fault.eq(access_fault), raw_instrs[i].rvc.eq(s1_data.rvc[i]), raw_instrs[i].predicted_taken.eq(redirect & (predcheck_res.fb_instr_idx == i)), + raw_instrs[i].access_fault.eq( + Mux(s1_data.access_fault, FetchLayouts.AccessFaultFlag.ACCESS_FAULT, 0) + ), ] if Extension.C in self.gen_params.isa.extensions: with m.If(s1_data.instr_block_cross): m.d.av_comb += raw_instrs[0].pc.eq(params.pc_from_fb(fetch_block_addr, 0) - 2) with m.If(s1_data.access_fault): + # Mark that access fault happened only at second (current) half. + # If fault happened on the first half `instr_block_cross` would be false m.d.av_comb += raw_instrs[0].access_fault.eq( - 0b10 - ) # Mark that access fault happened only at second half + FetchLayouts.AccessFaultFlag.ACCESS_FAULT_ON_SECOND_HALF + ) with condition(m) as branch: with branch(flushing_counter == 0): diff --git a/coreblocks/func_blocks/fu/exception.py b/coreblocks/func_blocks/fu/exception.py index 7c9777d77..4d5ba2ff7 100644 --- a/coreblocks/func_blocks/fu/exception.py +++ b/coreblocks/func_blocks/fu/exception.py @@ -8,7 +8,7 @@ from coreblocks.params import GenParams, FunctionalComponentParams from coreblocks.arch import OpType, Funct3, ExceptionCause -from coreblocks.interface.layouts import FuncUnitLayouts +from coreblocks.interface.layouts import FetchLayouts, FuncUnitLayouts from transactron.utils import OneHotSwitch from coreblocks.interface.keys import ExceptionReportKey, CSRInstancesKey @@ -88,7 +88,9 @@ def _(arg): m.d.av_comb += cause.eq(ExceptionCause.INSTRUCTION_ACCESS_FAULT) # With C extension access fault can be only on the second half of instruction, and mepc != mtval. # This information is passed in imm field - m.d.av_comb += mtval.eq(arg.pc + (arg.imm[1] << 1)) + m.d.av_comb += mtval.eq( + arg.pc + ((arg.imm & FetchLayouts.AccessFaultFlag.ACCESS_FAULT_ON_SECOND_HALF).any() << 1) + ) with OneHotCase(ExceptionUnitFn.Fn.ILLEGAL_INSTRUCTION): m.d.av_comb += cause.eq(ExceptionCause.ILLEGAL_INSTRUCTION) m.d.av_comb += mtval.eq(arg.imm) # passed instruction bytes diff --git a/coreblocks/interface/layouts.py b/coreblocks/interface/layouts.py index 5ab3744d8..a19ecca45 100644 --- a/coreblocks/interface/layouts.py +++ b/coreblocks/interface/layouts.py @@ -1,6 +1,7 @@ from typing import Optional from amaranth import signed from amaranth.lib.data import ArrayLayout +from amaranth.lib.enum import IntFlag, auto from coreblocks.params import GenParams from coreblocks.arch import * from transactron.utils import LayoutList, LayoutListField, layout_subset @@ -441,11 +442,20 @@ def __init__(self, gen_params: GenParams): class FetchLayouts: """Layouts used in the fetcher.""" + class AccessFaultFlag(IntFlag): + # standard access fault when accessing instruction + # from beginning (exception pc = instruction pc) (fault on full instruction or first half) + ACCESS_FAULT = auto() + # with C extension (2-byte alignment enabled) fault condition + # could only affect second half of 4-byte instruction. + # Bit set if this is the case + ACCESS_FAULT_ON_SECOND_HALF = auto() + def __init__(self, gen_params: GenParams): fields = gen_params.get(CommonLayoutFields) - self.access_fault: LayoutListField = ("access_fault", 2) - """Instruction fetch failed.""" + self.access_fault: LayoutListField = ("access_fault", FetchLayouts.AccessFaultFlag) + """Instruction fetch errors. See `FetchLayouts.AccessFaultFlag` fields documentation""" self.raw_instr = make_layout( fields.instr,