diff --git a/coreblocks/arch/csr_address.py b/coreblocks/arch/csr_address.py index 84e8fb064..44409a6ba 100644 --- a/coreblocks/arch/csr_address.py +++ b/coreblocks/arch/csr_address.py @@ -1,5 +1,10 @@ from amaranth.lib.enum import IntEnum, unique +__all__ = [ + "CSRAddress", + "MstatusFieldOffsets", +] + @unique class CSRAddress(IntEnum, shape=12): @@ -430,4 +435,34 @@ class CSRAddress(IntEnum, shape=12): DSCRATCH1 = 0x7B3 # Debug scratch register 1. # Internal Coreblocks CSRs - COREBLOCKS_TEST_CSR = 0x7FF # used only for testbench verification + # used only for testbench verification + + # CSR for custom communication with testbenches + COREBLOCKS_TEST_CSR = 0x7FF + # CSR providing writable current privilege mode (U-mode accesible) + COREBLOCKS_TEST_PRIV_MODE = 0x8FF + + +@unique +class MstatusFieldOffsets(IntEnum): + SIE = 1 # Supervisor Interrupt Enable + MIE = 3 # Machine Interrupt Enable + SPIE = 5 # Supervisor Previous Interrupt Enable + UBE = 6 # User Endianess Control + MPIE = 7 # Machine Previous Interrupt Enable + SPP = 8 # Supervisor Previous Pirvilege + VS = 9 # Vector Context Status + MPP = 11 # Machine Previous Pirvilege + FS = 13 # Float Context Status + XS = 15 # Additional Extension State Context Status + MPRV = 17 # Modify Pirvilege + SUM = 18 # Supervisor User Memory Access + MXR = 19 # Make Executable Readable + TVM = 20 # Trap Virtual Memory + TW = 21 # Timeout Wait + TSR = 22 # Trap SRET + UXL = 32 # User XLEN + SXL = 34 # Supervisor XLEN + SBE = 36 # Supervisor Endianess Control + MBE = 37 # Machine Endianess Contorol + SD = -1 # Context Status Dirty bit. Placed on last bit of mstatus diff --git a/coreblocks/arch/isa_consts.py b/coreblocks/arch/isa_consts.py index b673bf339..17641f829 100644 --- a/coreblocks/arch/isa_consts.py +++ b/coreblocks/arch/isa_consts.py @@ -12,6 +12,7 @@ "Registers", "PrivilegeLevel", "InterruptCauseNumber", + "XlenEncoding", ] @@ -172,3 +173,10 @@ class InterruptCauseNumber(IntEnum): MTI = 7 # machine timer interrupt SEI = 9 # supervisor external interrupt MEI = 11 # machine external interrupt + + +@unique +class XlenEncoding(IntEnum, shape=2): + W32 = 1 + W64 = 2 + W128 = 3 diff --git a/coreblocks/backend/retirement.py b/coreblocks/backend/retirement.py index 477b735c3..d68205c05 100644 --- a/coreblocks/backend/retirement.py +++ b/coreblocks/backend/retirement.py @@ -8,7 +8,7 @@ from coreblocks.params.genparams import GenParams from coreblocks.arch import ExceptionCause -from coreblocks.interface.keys import CoreStateKey, GenericCSRRegistersKey, InstructionPrecommitKey +from coreblocks.interface.keys import CoreStateKey, CSRInstancesKey, InstructionPrecommitKey from coreblocks.priv.csr.csr_instances import CSRAddress, DoubleCounterCSR @@ -67,7 +67,7 @@ def elaborate(self, platform): m.submodules += [self.perf_instr_ret, self.perf_trap_latency] - m_csr = self.dependency_manager.get_dependency(GenericCSRRegistersKey()).m_mode + m_csr = self.dependency_manager.get_dependency(CSRInstancesKey()).m_mode m.submodules.instret_csr = self.instret_csr side_fx = Signal(init=1) diff --git a/coreblocks/core.py b/coreblocks/core.py index f909235e6..6aa9b5b8a 100644 --- a/coreblocks/core.py +++ b/coreblocks/core.py @@ -11,7 +11,7 @@ from coreblocks.interface.layouts import * from coreblocks.interface.keys import ( FetchResumeKey, - GenericCSRRegistersKey, + CSRInstancesKey, CommonBusDataKey, ) from coreblocks.params.genparams import GenParams @@ -89,7 +89,7 @@ def __init__(self, *, gen_params: GenParams): ) self.csr_generic = GenericCSRRegisters(self.gen_params) - self.connections.add_dependency(GenericCSRRegistersKey(), self.csr_generic) + self.connections.add_dependency(CSRInstancesKey(), self.csr_generic) self.interrupt_controller = InternalInterruptController(self.gen_params) diff --git a/coreblocks/func_blocks/csr/csr.py b/coreblocks/func_blocks/csr/csr.py index eb2664e24..a82074256 100644 --- a/coreblocks/func_blocks/csr/csr.py +++ b/coreblocks/func_blocks/csr/csr.py @@ -15,6 +15,7 @@ from coreblocks.interface.keys import ( CSRListKey, FetchResumeKey, + CSRInstancesKey, InstructionPrecommitKey, ExceptionReportKey, AsyncInterruptInsertSignalKey, @@ -128,9 +129,6 @@ def elaborate(self, platform): | ((instr.exec_fn.funct3 == Funct3.CSRRCI) & (instr.s1_val != 0)) ) - # Temporary, until privileged spec is implemented - priv_level = Signal(PrivilegeLevel, init=PrivilegeLevel.MACHINE) - exe_side_fx = Signal() # Methods used within this Tranaction are CSRRegister internal _fu_(read|write) handlers which are always ready @@ -142,7 +140,10 @@ def elaborate(self, platform): with m.Case(csr_number): priv_valid = Signal() - m.d.comb += priv_valid.eq(priv_level_required <= priv_level) + current_priv_mode = ( + self.dependency_manager.get_dependency(CSRInstancesKey()).m_mode.priv_mode.read(m).data + ) + m.d.comb += priv_valid.eq(priv_level_required <= current_priv_mode) with m.If(priv_valid): read_val = Signal(self.gen_params.isa.xlen) diff --git a/coreblocks/func_blocks/fu/exception.py b/coreblocks/func_blocks/fu/exception.py index 842f52e42..e4b67e6a8 100644 --- a/coreblocks/func_blocks/fu/exception.py +++ b/coreblocks/func_blocks/fu/exception.py @@ -1,5 +1,6 @@ from typing import Sequence from amaranth import * +from coreblocks.arch.isa_consts import PrivilegeLevel from transactron.utils.dependencies import DependencyContext from transactron import * @@ -9,7 +10,7 @@ from coreblocks.arch import OpType, Funct3, ExceptionCause from coreblocks.interface.layouts import FuncUnitLayouts from transactron.utils import OneHotSwitch -from coreblocks.interface.keys import ExceptionReportKey +from coreblocks.interface.keys import ExceptionReportKey, CSRInstancesKey from coreblocks.func_blocks.fu.common.fu_decoder import DecoderManager from enum import IntFlag, auto @@ -50,8 +51,8 @@ def __init__(self, gen_params: GenParams, unit_fn=ExceptionUnitFn()): self.issue = Method(i=layouts.issue) self.accept = Method(o=layouts.accept) - dm = DependencyContext.get() - self.report = dm.get_dependency(ExceptionReportKey()) + self.dm = DependencyContext.get() + self.report = self.dm.get_dependency(ExceptionReportKey()) def elaborate(self, platform): m = TModule() @@ -69,12 +70,17 @@ def _(arg): cause = Signal(ExceptionCause) + priv_level = self.dm.get_dependency(CSRInstancesKey()).m_mode.priv_mode.read(m).data + with OneHotSwitch(m, decoder.decode_fn) as OneHotCase: with OneHotCase(ExceptionUnitFn.Fn.EBREAK): m.d.comb += cause.eq(ExceptionCause.BREAKPOINT) with OneHotCase(ExceptionUnitFn.Fn.ECALL): - # TODO: Switch privilege level when implemented - m.d.comb += cause.eq(ExceptionCause.ENVIRONMENT_CALL_FROM_M) + with m.Switch(priv_level): + with m.Case(PrivilegeLevel.MACHINE): + m.d.comb += cause.eq(ExceptionCause.ENVIRONMENT_CALL_FROM_M) + with m.Case(PrivilegeLevel.USER): + m.d.comb += cause.eq(ExceptionCause.ENVIRONMENT_CALL_FROM_U) with OneHotCase(ExceptionUnitFn.Fn.INSTR_ACCESS_FAULT): m.d.comb += cause.eq(ExceptionCause.INSTRUCTION_ACCESS_FAULT) with OneHotCase(ExceptionUnitFn.Fn.ILLEGAL_INSTRUCTION): diff --git a/coreblocks/func_blocks/fu/priv.py b/coreblocks/func_blocks/fu/priv.py index 6a6e40bf3..5c90bbc5f 100644 --- a/coreblocks/func_blocks/fu/priv.py +++ b/coreblocks/func_blocks/fu/priv.py @@ -2,6 +2,7 @@ from enum import IntFlag, auto, unique from typing import Sequence +from coreblocks.arch.isa_consts import PrivilegeLevel from transactron import * @@ -18,7 +19,7 @@ MretKey, AsyncInterruptInsertSignalKey, ExceptionReportKey, - GenericCSRRegistersKey, + CSRInstancesKey, InstructionPrecommitKey, FetchResumeKey, FlushICacheKey, @@ -71,6 +72,7 @@ def elaborate(self, platform): instr_valid = Signal() finished = Signal() + illegal_instruction = Signal() instr_rob = Signal(self.gp.rob_entries_bits) instr_pc = Signal(self.gp.isa.xlen) @@ -79,7 +81,8 @@ def elaborate(self, platform): mret = self.dm.get_dependency(MretKey()) async_interrupt_active = self.dm.get_dependency(AsyncInterruptInsertSignalKey()) exception_report = self.dm.get_dependency(ExceptionReportKey()) - csr = self.dm.get_dependency(GenericCSRRegistersKey()) + csr = self.dm.get_dependency(CSRInstancesKey()) + priv_mode = csr.m_mode.priv_mode flush_icache = self.dm.get_dependency(FlushICacheKey()) m.submodules.fetch_resume_fifo = self.fetch_resume_fifo @@ -101,14 +104,26 @@ def _(arg): m.d.sync += finished.eq(1) self.perf_instr.incr(m, instr_fn, cond=info.side_fx) + priv_data = priv_mode.read(m).data + + illegal_mret = (instr_fn == PrivilegedFn.Fn.MRET) & (priv_data != PrivilegeLevel.MACHINE) + # future todo: WFI should be illegal in U-Mode only if S-Mode is supported + illegal_wfi = ( + (instr_fn == PrivilegedFn.Fn.WFI) + & (priv_data == PrivilegeLevel.USER) + & csr.m_mode.mstatus_tw.read(m).data + ) + with condition(m, nonblocking=True) as branch: - with branch(info.side_fx & (instr_fn == PrivilegedFn.Fn.MRET)): + with branch(info.side_fx & (instr_fn == PrivilegedFn.Fn.MRET) & ~illegal_mret): mret(m) with branch(info.side_fx & (instr_fn == PrivilegedFn.Fn.FENCEI)): flush_icache(m) - with branch(info.side_fx & (instr_fn == PrivilegedFn.Fn.WFI)): + with branch(info.side_fx & (instr_fn == PrivilegedFn.Fn.WFI) & ~illegal_wfi): m.d.sync += finished.eq(async_interrupt_active) + m.d.sync += illegal_instruction.eq(illegal_wfi | illegal_mret) + @def_method(m, self.accept, ready=instr_valid & finished) def _(): m.d.sync += instr_valid.eq(0) @@ -125,13 +140,19 @@ def _(): with OneHotCase(PrivilegedFn.Fn.WFI): m.d.av_comb += ret_pc.eq(instr_pc + 4) + with m.If(illegal_instruction): + m.d.av_comb += ret_pc.eq(instr_pc) + exception = Signal() - with m.If(async_interrupt_active): + 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) + 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." # mret() method is called from precommit() that was executed at least one cycle earlier (because # of finished condition). If calling mret() caused interrupt to be active, it is already represented - # by updated async_interrupt_active singal. + # 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) diff --git a/coreblocks/interface/keys.py b/coreblocks/interface/keys.py index 1c42a25ab..4e91adbc6 100644 --- a/coreblocks/interface/keys.py +++ b/coreblocks/interface/keys.py @@ -18,7 +18,7 @@ "PredictedJumpTargetKey", "FetchResumeKey", "ExceptionReportKey", - "GenericCSRRegistersKey", + "CSRInstancesKey", "AsyncInterruptInsertSignalKey", "MretKey", "CoreStateKey", @@ -58,7 +58,7 @@ class ExceptionReportKey(SimpleKey[Method]): @dataclass(frozen=True) -class GenericCSRRegistersKey(SimpleKey["GenericCSRRegisters"]): +class CSRInstancesKey(SimpleKey["GenericCSRRegisters"]): pass diff --git a/coreblocks/params/configurations.py b/coreblocks/params/configurations.py index 135868fef..3de46e422 100644 --- a/coreblocks/params/configurations.py +++ b/coreblocks/params/configurations.py @@ -83,6 +83,8 @@ class _CoreConfigurationDataClass: Bit mask specifing if interrupt should be edge or level triggered. If nth bit is set to 1, interrupt with id 16+n will be considered as edge triggered and clearable via `mip`. In other case bit `mip` is read-only and directly connected to input signal (implementation must provide clearing method) + user_mode: bool + Enable User Mode. allow_partial_extensions: bool Allow partial support of extensions. extra_verification: bool @@ -128,6 +130,8 @@ def __post_init__(self): interrupt_custom_count: int = 0 interrupt_custom_edge_trig_mask: int = 0 + user_mode: bool = True + allow_partial_extensions: bool = False extra_verification: bool = True diff --git a/coreblocks/params/genparams.py b/coreblocks/params/genparams.py index 1961a6c47..0cfa36142 100644 --- a/coreblocks/params/genparams.py +++ b/coreblocks/params/genparams.py @@ -82,6 +82,8 @@ def __init__(self, cfg: CoreConfiguration): self.interrupt_custom_count = cfg.interrupt_custom_count self.interrupt_custom_edge_trig_mask = cfg.interrupt_custom_edge_trig_mask + self.user_mode = cfg.user_mode + self._toolchain_isa_str = gen_isa_string(extensions, cfg.xlen, skip_internal=True) self._generate_test_hardware = cfg._generate_test_hardware diff --git a/coreblocks/priv/csr/aliased.py b/coreblocks/priv/csr/aliased.py index 21100b400..13c45b348 100644 --- a/coreblocks/priv/csr/aliased.py +++ b/coreblocks/priv/csr/aliased.py @@ -1,6 +1,7 @@ from amaranth import * from typing import Optional +from enum import Enum from coreblocks.interface.layouts import CSRRegisterLayouts from coreblocks.params.genparams import GenParams @@ -22,7 +23,10 @@ def __init__(self, csr_number: Optional[int], gen_params: GenParams, width: Opti self.gen_params = gen_params self.csr_number = csr_number self.width = width if width is not None else gen_params.isa.xlen - self.fields = [] + + self.fields: list[tuple[int, CSRRegister]] = [] + self.ro_values: list[tuple[int, int, int | Enum]] = [] + csr_layouts = gen_params.get(CSRRegisterLayouts) self._fu_read = Method(o=csr_layouts._fu_read) @@ -44,6 +48,11 @@ def add_field(self, bit_position: int, csr: CSRRegister): self.fields.append((bit_position, csr)) # TODO: verify bounds + def add_read_only_field(self, bit_position: int, bit_width: int, value: int | Enum): + assert not self.elaborated + self.ro_values.append((bit_position, bit_width, value)) + # TODO: verify bounds + def elaborate(self, platform): m = TModule() self.elaborated = True @@ -57,6 +66,10 @@ def _(data: Value): def _() -> Value: for start, csr in self.fields: m.d.av_comb += self.value[start : start + csr.width].eq(csr._fu_read(m)["data"]) + + for start, width, value in self.ro_values: + m.d.av_comb += self.value[start : start + width].eq(value) + return self.value return m diff --git a/coreblocks/priv/csr/csr_instances.py b/coreblocks/priv/csr/csr_instances.py index 3fa8439e7..ec254739a 100644 --- a/coreblocks/priv/csr/csr_instances.py +++ b/coreblocks/priv/csr/csr_instances.py @@ -2,7 +2,9 @@ from typing import Optional from coreblocks.arch import CSRAddress +from coreblocks.arch.csr_address import MstatusFieldOffsets from coreblocks.arch.isa import Extension +from coreblocks.arch.isa_consts import PrivilegeLevel, XlenEncoding from coreblocks.params.genparams import GenParams from coreblocks.priv.csr.csr_register import CSRRegister from coreblocks.priv.csr.aliased import AliasedCSR @@ -68,6 +70,8 @@ def __init__(self, gen_params: GenParams): self.mconfigptr = CSRRegister(CSRAddress.MCONFIGPTR, gen_params, init=0) self.mstatus = AliasedCSR(CSRAddress.MSTATUS, gen_params) + if gen_params.isa.xlen == 32: + self.mstatush = AliasedCSR(CSRAddress.MSTATUSH, gen_params) self.mcause = CSRRegister(CSRAddress.MCAUSE, gen_params) @@ -78,6 +82,18 @@ def __init__(self, gen_params: GenParams): mepc_ro_bits = 0b1 if Extension.C in gen_params.isa.extensions else 0b11 # pc alignment (SPEC) self.mepc = CSRRegister(CSRAddress.MEPC, gen_params, ro_bits=mepc_ro_bits) + self.priv_mode = CSRRegister( + None, + gen_params, + width=PrivilegeLevel.as_shape().width, + init=PrivilegeLevel.MACHINE, + ) + if gen_params._generate_test_hardware: + self.priv_mode_public = AliasedCSR(CSRAddress.COREBLOCKS_TEST_PRIV_MODE, gen_params) + self.priv_mode_public.add_field(0, self.priv_mode) + + self.mstatus_fields_implementation(gen_params, self.mstatus, self.mstatush) + def elaborate(self, platform): m = Module() @@ -87,6 +103,80 @@ def elaborate(self, platform): return m + def mstatus_fields_implementation(self, gen_params: GenParams, mstatus: AliasedCSR, mstatush: AliasedCSR): + def filter_legal_priv_mode(m: TModule, v: Value): + legal = Signal(1) + with m.Switch(v): + with m.Case(PrivilegeLevel.MACHINE): + m.d.av_comb += legal.eq(1) + with m.Case(PrivilegeLevel.USER): + m.d.av_comb += legal.eq(gen_params.user_mode) + with m.Default(): + m.d.av_comb += legal.eq(0) + + return (legal, v) + + # MIE bit - global interrupt enable + self.mstatus_mie = CSRRegister(None, gen_params, width=1) + mstatus.add_field(MstatusFieldOffsets.MIE, self.mstatus_mie) + # MPIE bit - previous MIE + self.mstatus_mpie = CSRRegister(None, gen_params, width=1) + mstatus.add_field(MstatusFieldOffsets.MPIE, self.mstatus_mpie) + # MPP field - previous priv mode + self.mstatus_mpp = CSRRegister( + None, + gen_params, + width=PrivilegeLevel.as_shape().width, + fu_write_filtermap=filter_legal_priv_mode, + init=PrivilegeLevel.MACHINE, + ) + mstatus.add_field(MstatusFieldOffsets.MPP, self.mstatus_mpp) + + # Fixed MXLEN/SXLEN/UXLEN = isa.xlen + if gen_params.isa.xlen == 64: + # Registers only exist in RV64 + mstatus.add_read_only_field( + MstatusFieldOffsets.UXL, XlenEncoding.as_shape().width, XlenEncoding.W64 if gen_params.user_mode else 0 + ) + mstatus.add_read_only_field(MstatusFieldOffsets.SXL, XlenEncoding.as_shape().width, 0) + + # Little-endianess + mstatus.add_read_only_field(MstatusFieldOffsets.UBE, 1, 0) + if gen_params.isa.xlen == 32: + mstatush.add_read_only_field(MstatusFieldOffsets.SBE - mstatus.width, 1, 0) + mstatush.add_read_only_field(MstatusFieldOffsets.MBE - mstatus.width, 1, 0) + elif gen_params.isa.xlen == 64: + mstatus.add_read_only_field(MstatusFieldOffsets.SBE, 1, 0) + mstatus.add_read_only_field(MstatusFieldOffsets.MBE, 1, 0) + + self.mstatus_mprv = CSRRegister(None, gen_params, width=1, ro_bits=0 if gen_params.user_mode else 1) + mstatus.add_field(MstatusFieldOffsets.MPRV, self.mstatus_mprv) + + # Supervisor mode not supported - read only 0 supervisor bits + mstatus.add_read_only_field(MstatusFieldOffsets.SUM, 1, 0) + mstatus.add_read_only_field(MstatusFieldOffsets.MXR, 1, 0) + mstatus.add_read_only_field(MstatusFieldOffsets.TVM, 1, 0) + mstatus.add_read_only_field(MstatusFieldOffsets.TSR, 1, 0) + mstatus.add_read_only_field(MstatusFieldOffsets.SPP, 1, 0) + mstatus.add_read_only_field(MstatusFieldOffsets.SPIE, 1, 0) + mstatus.add_read_only_field(MstatusFieldOffsets.SIE, 1, 0) + + self.mstatus_tw = CSRRegister(None, gen_params, width=1, ro_bits=0 if gen_params.user_mode else 1) + mstatus.add_field(MstatusFieldOffsets.TW, self.mstatus_tw) + + # Extension Context Status bits + # future todo: implement actual state modification tracking of F and V registers and CSRs + # State = 3 is DIRTY. Implementation is allowed to always set dirty for VS and FS, regardless of CSR updates + mstatus.add_read_only_field(MstatusFieldOffsets.VS, 2, 3 if Extension.V in gen_params.isa.extensions else 0) + mstatus.add_read_only_field(MstatusFieldOffsets.FS, 2, 3 if Extension.F in gen_params.isa.extensions else 0) + mstatus.add_read_only_field(MstatusFieldOffsets.XS, 2, 0) + # SD field - set to one when one of the states is dirty + mstatus.add_read_only_field( + MstatusFieldOffsets.SD % mstatus.width, # SD is last bit of `mstatus` (depends on xlen) + 1, + Extension.V in gen_params.isa.extensions or Extension.F in gen_params.isa.extensions, + ) + class GenericCSRRegisters(Elaboratable): def __init__(self, gen_params: GenParams): diff --git a/coreblocks/priv/traps/interrupt_controller.py b/coreblocks/priv/traps/interrupt_controller.py index 8fa2db8db..8d3cb9ba2 100644 --- a/coreblocks/priv/traps/interrupt_controller.py +++ b/coreblocks/priv/traps/interrupt_controller.py @@ -5,7 +5,7 @@ from coreblocks.interface.layouts import InternalInterruptControllerLayouts from coreblocks.priv.csr.csr_register import CSRRegister from coreblocks.params.genparams import GenParams -from coreblocks.interface.keys import AsyncInterruptInsertSignalKey, GenericCSRRegistersKey, MretKey +from coreblocks.interface.keys import AsyncInterruptInsertSignalKey, CSRInstancesKey, MretKey from transactron.core import Method, TModule, def_method from transactron.core.transaction import Transaction @@ -55,23 +55,16 @@ def __init__(self, gen_params: GenParams): ) self.gen_params = gen_params - dm = DependencyContext.get() + self.dm = DependencyContext.get() self.edge_reported_mask = self.gen_params.interrupt_custom_edge_trig_mask << ISA_RESERVED_INTERRUPTS if gen_params.interrupt_custom_count > gen_params.isa.xlen - ISA_RESERVED_INTERRUPTS: raise RuntimeError("Too many custom interrupts") - # MIE bit - global interrupt enable - part of mstatus CSR - self.mstatus_mie = CSRRegister(None, gen_params, width=1) - # MPIE bit - previous MIE - part of mstatus - self.mstatus_mpie = CSRRegister(None, gen_params, width=1) - # MPP bit - previous priv mode - part of mstatus - self.mstatus_mpp = CSRRegister(None, gen_params, width=2, ro_bits=0b11, init=PrivilegeLevel.MACHINE) - # TODO: filter xPP for only legal modes (when not read-only) - mstatus = dm.get_dependency(GenericCSRRegistersKey()).m_mode.mstatus - mstatus.add_field(3, self.mstatus_mie) - mstatus.add_field(7, self.mstatus_mpie) - mstatus.add_field(11, self.mstatus_mpp) + self.m_mode_csr = m_mode_csr = self.dm.get_dependency(CSRInstancesKey()).m_mode + self.mstatus_mie = m_mode_csr.mstatus_mie + self.mstatus_mpie = m_mode_csr.mstatus_mpie + self.mstatus_mpp = m_mode_csr.mstatus_mpp mie_writeable = ( # (1 << InterruptCauseNumber.MSI) TODO: CLINT @@ -83,12 +76,12 @@ def __init__(self, gen_params: GenParams): self.mip = CSRRegister(CSRAddress.MIP, gen_params, fu_write_priority=False, ro_bits=~self.edge_reported_mask) self.interrupt_insert = Signal() - dm.add_dependency(AsyncInterruptInsertSignalKey(), self.interrupt_insert) + self.dm.add_dependency(AsyncInterruptInsertSignalKey(), self.interrupt_insert) self.interrupt_cause = Method(o=gen_params.get(InternalInterruptControllerLayouts).interrupt_cause) self.mret = Method() - dm.add_dependency(MretKey(), self.mret) + self.dm.add_dependency(MretKey(), self.mret) self.entry = Method() @@ -97,14 +90,16 @@ def elaborate(self, platform): interrupt_cause = Signal(self.gen_params.isa.xlen) - m.submodules += [self.mstatus_mie, self.mstatus_mpie, self.mstatus_mpp, self.mie, self.mip] + m.submodules += [self.mie, self.mip] + + priv_mode = self.dm.get_dependency(CSRInstancesKey()).m_mode.priv_mode interrupt_enable = Signal() mie = Signal(self.gen_params.isa.xlen) mip = Signal(self.gen_params.isa.xlen) with Transaction().body(m) as assign_trans: m.d.comb += [ - interrupt_enable.eq(self.mstatus_mie.read(m).data), + interrupt_enable.eq(self.mstatus_mie.read(m).data | (priv_mode.read(m).data == PrivilegeLevel.USER)), mie.eq(self.mie.read(m).data), mip.eq(self.mip.read(m).data), ] @@ -152,10 +147,16 @@ def _(): with m.If(self.entry.run): self.mstatus_mie.write(m, {"data": 0}) self.mstatus_mpie.write(m, self.mstatus_mie.read(m).data) + self.mstatus_mpp.write(m, priv_mode.read(m).data) + priv_mode.write(m, PrivilegeLevel.MACHINE) with m.Elif(self.mret.run): self.mstatus_mie.write(m, self.mstatus_mpie.read(m).data) self.mstatus_mpie.write(m, {"data": 1}) - # TODO: Set mpp when other privilege modes are implemented + self.mstatus_mpp.write(m, PrivilegeLevel.USER if self.gen_params.user_mode else PrivilegeLevel.MACHINE) + mpp = self.mstatus_mpp.read(m).data + priv_mode.write(m, mpp) + with m.If(mpp != PrivilegeLevel.MACHINE): + self.m_mode_csr.mstatus_mprv.write(m, 0) interrupt_priority = [ InterruptCauseNumber.MEI, diff --git a/test/asm/user_mode.asm b/test/asm/user_mode.asm new file mode 100644 index 000000000..8b14993d3 --- /dev/null +++ b/test/asm/user_mode.asm @@ -0,0 +1,110 @@ +# User mode test +# runs `user_code`* in user mode that ends with control transfer via excpetion +# or interrupt to trap_handler. +# trap_handler verifies trap cause and priv mode change + +_start: + li x4, 0 + + la x1, trap_handler + csrw mtvec, x1 + + li x1, 0b11 << 11 + csrc mstatus, x1 # Set transfer to User mode to mpp + + li x1, 0b10 << 11 + csrs mstatus, x1 # Invalid mpp mode, check if write is ignored + + la x1, user_code + csrw mepc, x1 + mret # go to user_code in user mode + +user_code: + # case0 - test ecall from user mode + csrr x1, 0x8FF # custom testbech CSR - check current priv mode + bnez x1, fail # user mode = 0 + + ecall + + j fail + +user_code2: + # case1 - standard interrupt entry from user mode + # case2 - wfi should be illegal in user mode when mstatus.TW is set + wfi + j fail + +user_code3: + # case3 - test write to CSR not accesible from user mode + csrr x1, mstatus + j fail + +fail: + j fail + +pass: + j pass + +trap_handler: + csrr x1, mstatus + li x2, 0b11 << 11 + and x1, x1, x2 # mpp + bnez x1, fail + + csrr x1, 0x8FF # custom - current priv mode + li x2, 0b11 # machine mode + bne x1, x2, fail + + addi x4, x4, 1 + +case0: + addi x3, x4, -1 + bgtz x3, case1 + + csrr x1, mcause + li x2, 8 # ECALL_FROM_U + bne x1, x2, fail + + li x1, 1<<17 + csrs mie, x1 # enable fixed level interrupt + + # MIE = 0, but interrupts are active in U-MODE (when enabled in mie) + + la x1, user_code2 + csrw mepc, x1 + mret + +case1: + addi x3, x3, -1 + bgtz x3, case2 + + csrr x1, mcause + li x2, 0x80000011 # interrupt 17 + bne x1, x2, fail + + li x1, 1<<21 + csrs mstatus, x1 # enable TW + + la x1, user_code2 + csrw mepc, x1 + mret + +case2: + addi x3, x3, -1 + bgtz x3, case3 + + csrr x1, mcause + li x2, 2 # ILLEGAL_INSTRUCTION + bne x1, x2, fail + + la x1, user_code3 + csrw mepc, x1 + mret + +case3: + csrr x1, mcause + li x2, 2 # ILLEGAL_INSTRUCTION + bne x1, x2, fail + + addi x4, x4, 1 + j pass diff --git a/test/backend/test_retirement.py b/test/backend/test_retirement.py index 4dbfa57ab..bc50290c9 100644 --- a/test/backend/test_retirement.py +++ b/test/backend/test_retirement.py @@ -53,7 +53,7 @@ def elaborate(self, platform): m.submodules.mock_exception_clear = self.mock_exception_clear = TestbenchIO(Adapter()) m.submodules.generic_csr = self.generic_csr = GenericCSRRegisters(self.gen_params) - DependencyContext.get().add_dependency(GenericCSRRegistersKey(), self.generic_csr) + DependencyContext.get().add_dependency(CSRInstancesKey(), self.generic_csr) m.submodules.mock_fetch_continue = self.mock_fetch_continue = TestbenchIO(Adapter(i=fetch_layouts.resume)) m.submodules.mock_instr_decrement = self.mock_instr_decrement = TestbenchIO( diff --git a/test/func_blocks/csr/test_csr.py b/test/func_blocks/csr/test_csr.py index 82851b6d6..001c031e3 100644 --- a/test/func_blocks/csr/test_csr.py +++ b/test/func_blocks/csr/test_csr.py @@ -5,11 +5,18 @@ from transactron.core.tmodule import TModule from coreblocks.func_blocks.csr.csr import CSRUnit from coreblocks.priv.csr.csr_register import CSRRegister +from coreblocks.priv.csr.csr_instances import GenericCSRRegisters from coreblocks.params import GenParams from coreblocks.arch import Funct3, ExceptionCause, OpType from coreblocks.params.configurations import test_core_config from coreblocks.interface.layouts import ExceptionRegisterLayouts, RetirementLayouts -from coreblocks.interface.keys import AsyncInterruptInsertSignalKey, ExceptionReportKey, InstructionPrecommitKey +from coreblocks.interface.keys import ( + AsyncInterruptInsertSignalKey, + ExceptionReportKey, + InstructionPrecommitKey, + CSRInstancesKey, +) +from coreblocks.arch.isa_consts import PrivilegeLevel from transactron.utils.dependencies import DependencyContext from transactron.testing import * @@ -38,8 +45,11 @@ def elaborate(self, platform): m.submodules.exception_report = self.exception_report = TestbenchIO( Adapter(i=self.gen_params.get(ExceptionRegisterLayouts).report) ) + m.submodules.csr_instances = self.csr_instances = GenericCSRRegisters(self.gen_params) + m.submodules.priv_io = self.priv_io = TestbenchIO(AdapterTrans(self.csr_instances.m_mode.priv_mode.write)) DependencyContext.get().add_dependency(ExceptionReportKey(), self.exception_report.adapter.iface) DependencyContext.get().add_dependency(AsyncInterruptInsertSignalKey(), Signal()) + DependencyContext.get().add_dependency(CSRInstancesKey(), self.csr_instances) m.submodules.fetch_resume = self.fetch_resume = TestbenchIO(AdapterTrans(self.dut.fetch_resume)) @@ -55,7 +65,8 @@ def make_csr(number: int): make_csr(i) if not self.only_legal: - make_csr(0xC00) # read-only csr + make_csr(0xCC0) # read-only csr + make_csr(0x7FE) # machine mode only return m @@ -156,15 +167,20 @@ def test_randomized(self): sim.add_process(self.process_test) exception_csr_numbers = [ - 0xC00, # read_only + 0xCC0, # read_only 0xFFF, # nonexistent - # 0x100 TODO: check priv level when implemented + 0x7FE, # missing priv ] def process_exception_test(self): yield from self.dut.fetch_resume.enable() yield from self.dut.exception_report.enable() for csr in self.exception_csr_numbers: + if csr == 0x7FE: + yield from self.dut.priv_io.call(data=PrivilegeLevel.USER) + else: + yield from self.dut.priv_io.call(data=PrivilegeLevel.MACHINE) + yield from self.random_wait_geom() yield from self.dut.select.call() diff --git a/test/func_blocks/fu/functional_common.py b/test/func_blocks/fu/functional_common.py index 40e90816c..9426cad70 100644 --- a/test/func_blocks/fu/functional_common.py +++ b/test/func_blocks/fu/functional_common.py @@ -10,10 +10,11 @@ from coreblocks.params import GenParams from coreblocks.params.configurations import test_core_config +from coreblocks.priv.csr.csr_instances import GenericCSRRegisters from transactron.utils.dependencies import DependencyContext from coreblocks.params.fu_params import FunctionalComponentParams from coreblocks.arch import Funct3, Funct7 -from coreblocks.interface.keys import AsyncInterruptInsertSignalKey, ExceptionReportKey +from coreblocks.interface.keys import AsyncInterruptInsertSignalKey, ExceptionReportKey, CSRInstancesKey from coreblocks.interface.layouts import ExceptionRegisterLayouts from coreblocks.arch.optypes import OpType from transactron.lib import Adapter @@ -99,12 +100,14 @@ def setup(self, fixture_initialize_testing_env): self.gen_params = GenParams(test_core_config) self.report_mock = TestbenchIO(Adapter(i=self.gen_params.get(ExceptionRegisterLayouts).report)) + self.csrs = GenericCSRRegisters(self.gen_params) DependencyContext.get().add_dependency(ExceptionReportKey(), self.report_mock.adapter.iface) DependencyContext.get().add_dependency(AsyncInterruptInsertSignalKey(), Signal()) + DependencyContext.get().add_dependency(CSRInstancesKey(), self.csrs) self.m = SimpleTestCircuit(self.func_unit.get_module(self.gen_params)) - self.circ = ModuleConnector(dut=self.m, report_mock=self.report_mock) + self.circ = ModuleConnector(dut=self.m, report_mock=self.report_mock, csrs=self.csrs) random.seed(self.seed) self.requests = deque[RecordIntDict]() diff --git a/test/test_core.py b/test/test_core.py index 883cc4d42..eae0bcc85 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -6,6 +6,7 @@ from transactron.testing import TestCaseWithSimulator +from coreblocks.arch.isa_consts import PrivilegeLevel from coreblocks.core import Core from coreblocks.params import GenParams from coreblocks.params.instr import * @@ -285,3 +286,62 @@ def test_interrupted_prog(self): with self.run_simulation(self.m) as sim: sim.add_process(self.run_with_interrupt_process) sim.add_process(self.clear_level_interrupt_procsess) + + +@parameterized_class( + ("source_file", "cycle_count", "expected_regvals"), + [ + ("user_mode.asm", 1000, {4: 5}), + ], +) +class TestCoreInterruptOnPrivMode(TestCoreAsmSourceBase): + source_file: str + cycle_count: int + expected_regvals: dict[int, int] + + def setup_method(self): + self.configuration = full_core_config.replace( + _generate_test_hardware=True, interrupt_custom_count=2, interrupt_custom_edge_trig_mask=0b01 + ) + self.gen_params = GenParams(self.configuration) + random.seed(161453) + + def run_with_interrupt_process(self): + cycles = 0 + + # wait for interrupt enable + while (yield self.m.core.interrupt_controller.mie.value) == 0 and cycles < self.cycle_count: + cycles += 1 + yield Tick() + + while cycles < self.cycle_count: + yield from self.random_wait(5) + yield self.m.interrupt_level.eq(1) + yield Tick() + + # wait for the interrupt to get registered + while ( + yield self.m.core.csr_generic.m_mode.priv_mode.value + ) != PrivilegeLevel.MACHINE and cycles < self.cycle_count: + cycles += 1 + yield Tick() + + yield self.m.interrupt_level.eq(0) + yield Tick() + + # wait until ISR returns + while ( + yield self.m.core.csr_generic.m_mode.priv_mode.value + ) == PrivilegeLevel.MACHINE and cycles < self.cycle_count: + cycles += 1 + yield Tick() + + for reg_id, val in self.expected_regvals.items(): + assert (yield from self.get_arch_reg_val(reg_id)) == val + + def test_interrupted_prog(self): + bin_src = self.prepare_source(self.source_file) + 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_process(self.run_with_interrupt_process)