diff --git a/coreblocks/arch/isa_consts.py b/coreblocks/arch/isa_consts.py index 17641f829..abb95cca2 100644 --- a/coreblocks/arch/isa_consts.py +++ b/coreblocks/arch/isa_consts.py @@ -165,6 +165,12 @@ class PrivilegeLevel(IntEnum, shape=2): MACHINE = 0b11 +@unique +class TrapVectorMode(IntEnum, shape=2): + DIRECT = 0b00 + VECTORED = 0b01 + + @unique class InterruptCauseNumber(IntEnum): SSI = 1 # supervisor software interrupt diff --git a/coreblocks/backend/retirement.py b/coreblocks/backend/retirement.py index 38c45040c..20f704276 100644 --- a/coreblocks/backend/retirement.py +++ b/coreblocks/backend/retirement.py @@ -10,6 +10,7 @@ from coreblocks.arch import ExceptionCause from coreblocks.interface.keys import CoreStateKey, CSRInstancesKey, InstructionPrecommitKey from coreblocks.priv.csr.csr_instances import CSRAddress, DoubleCounterCSR +from coreblocks.arch.isa_consts import TrapVectorMode class Retirement(Elaboratable): @@ -213,8 +214,17 @@ def flush_instr(rob_entry): self.perf_trap_latency.stop(m) handler_pc = Signal(self.gen_params.isa.xlen) - # mtvec without mode is [mxlen-1:2], mode is two last bits. Only direct mode is supported - m.d.av_comb += handler_pc.eq(m_csr.mtvec.read(m).data & ~(0b11)) + mtvec_offset = Signal(self.gen_params.isa.xlen) + mtvec_base = m_csr.mtvec_base.read(m).data + mtvec_mode = m_csr.mtvec_mode.read(m).data + mcause = m_csr.mcause.read(m).data + + # When mode is Vectored, interrupts set pc to base + 4 * cause_number + with m.If(mcause[-1] & (mtvec_mode == TrapVectorMode.VECTORED)): + m.d.av_comb += mtvec_offset.eq(mcause << 2) + + # (mtvec_base stores base[MXLEN-1:2]) + m.d.av_comb += handler_pc.eq((mtvec_base << 2) + mtvec_offset) resume_pc = Mux(continue_pc_override, continue_pc, handler_pc) m.d.sync += continue_pc_override.eq(0) diff --git a/coreblocks/priv/csr/csr_instances.py b/coreblocks/priv/csr/csr_instances.py index adc598b33..33b80f04d 100644 --- a/coreblocks/priv/csr/csr_instances.py +++ b/coreblocks/priv/csr/csr_instances.py @@ -4,7 +4,7 @@ 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.arch.isa_consts import PrivilegeLevel, XlenEncoding, TrapVectorMode from coreblocks.params.genparams import GenParams from coreblocks.priv.csr.csr_register import CSRRegister from coreblocks.priv.csr.aliased import AliasedCSR @@ -75,9 +75,7 @@ def __init__(self, gen_params: GenParams): self.mcause = CSRRegister(CSRAddress.MCAUSE, gen_params) - # SPEC: The mtvec register must always be implemented, but can contain a read-only value. - # set `MODE` as fixed to 0 - Direct mode "All exceptions set pc to BASE" - self.mtvec = CSRRegister(CSRAddress.MTVEC, gen_params, ro_bits=0b11) + self.mtvec = AliasedCSR(CSRAddress.MTVEC, gen_params) 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) @@ -99,6 +97,7 @@ def __init__(self, gen_params: GenParams): self.priv_mode_public.add_field(0, self.priv_mode) self._mstatus_fields_implementation(gen_params, self.mstatus, self.mstatush) + self._mtvec_fields_implementation(gen_params, self.mtvec) def elaborate(self, platform): m = Module() @@ -109,6 +108,19 @@ def elaborate(self, platform): return m + def _mtvec_fields_implementation(self, gen_params: GenParams, mtvec: AliasedCSR): + def filter_legal_mode(m: TModule, v: Value): + legal = Signal(1) + m.d.av_comb += legal.eq((v == TrapVectorMode.DIRECT) | (v == TrapVectorMode.VECTORED)) + return (legal, v) + + self.mtvec_base = CSRRegister(None, gen_params, width=gen_params.isa.xlen - 2) + mtvec.add_field(TrapVectorMode.as_shape().width, self.mtvec_base) + self.mtvec_mode = CSRRegister( + None, gen_params, width=TrapVectorMode.as_shape().width, fu_write_filtermap=filter_legal_mode + ) + mtvec.add_field(0, self.mtvec_mode) + def _mstatus_fields_implementation(self, gen_params: GenParams, mstatus: AliasedCSR, mstatush: AliasedCSR): def filter_legal_priv_mode(m: TModule, v: Value): legal = Signal(1) diff --git a/test/asm/interrupt_vectored.asm b/test/asm/interrupt_vectored.asm new file mode 100644 index 000000000..19c05042f --- /dev/null +++ b/test/asm/interrupt_vectored.asm @@ -0,0 +1,136 @@ +.include "init_regs.s" + +_start: + INIT_REGS_LOAD + + # fibonacci spiced with interrupt handler (also with fibonacci) + li x1, 0x201 + csrw mtvec, x1 + li x1, 0x203 + csrw mtvec, x1 + csrr x16, mtvec # since mtvec is WARL, should stay 0x201 + ecall # synchronous exception jumps to 0x200 + 0x0 + +interrupts: + li x27, 0 # handler count + li x30, 0 # interrupt count + li x31, 0xde # branch guard + + csrsi mstatus, 0x8 # machine interrupt enable + csrr x29, mstatus + li x1, 0x30000 + csrw mie, x1 # enable custom interrupt 0 and 1 + li x1, 0 + li x2, 1 + li x5, 4 + li x6, 7 + li x7, 0 + li x12, 4 + li x13, 7 + li x14, 0 +loop: + add x3, x2, x1 + mv x1, x2 + mv x2, x3 + bne x2, x4, loop +infloop: + j infloop + +int0_handler: + # save main loop register state + mv x9, x1 + mv x10, x2 + mv x11, x3 + + # check cause + li x2, 0x80000010 # cause for 01,11 + csrr x3, mcause + bne x2, x3, fail + + # fibonacci step + beq x7, x8, skip + add x7, x6, x5 + mv x5, x6 + mv x6, x7 + +skip: + # generate new mie mask + andi x2, x30, 0x3 + bnez x2, fill_skip + li x2, 0x3 + fill_skip: + slli x2, x2, 16 + csrw mie, x2 + + # clear interrupts + csrr x1, mip + srli x1, x1, 16 + andi x2, x1, 0x1 + beqz x2, skip_clear_edge + addi x30, x30, 1 + li x2, 0x10000 + csrc mip, x2 # clear edge reported interrupt + skip_clear_edge: + andi x2, x1, 0x2 + beqz x2, skip_clear_level + addi x30, x30, 1 + csrwi 0x7ff, 1 # clear level reported interrupt via custom csr + skip_clear_level: + addi x27, x27, 1 + + # restore main loop register state + mv x1, x9 + mv x2, x10 + mv x3, x11 + mret + +int1_handler: + # save main loop register state + mv x9, x1 + mv x10, x2 + mv x11, x3 + + # check cause + li x2, 0x80000011 # cause for 10 + csrr x3, mcause + bne x2, x3, fail + + # fibonacci step + beq x14, x15, skip + add x14, x13, x12 + mv x12, x13 + mv x13, x14 + j skip + +ecall_handler: + li x17, 0x111 + la x1, interrupts + csrw mepc, x1 + mret + +fail: + csrwi 0x7ff, 2 + j fail + +.org 0x200 + j ecall_handler + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + j fail + j int0_handler + j int1_handler + li x31, 0xae # should never happen + +INIT_REGS_ALLOCATION diff --git a/test/test_core.py b/test/test_core.py index 867ed3e15..237360f53 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -194,6 +194,15 @@ def test_asm_source(self): ("interrupt.asm", 600, {4: 89, 8: 843}, {2: 89, 7: 843, 31: 0xDE}, 30, 50, False), # interrupts are only inserted on branches, we always have some forward progression. 15 for trigger variantion. ("interrupt.asm", 80, {4: 21, 8: 9349}, {2: 21, 7: 9349, 31: 0xDE}, 0, 15, False), + ( + "interrupt_vectored.asm", + 200, + {4: 21, 8: 9349, 15: 24476}, + {2: 21, 7: 9349, 14: 24476, 31: 0xDE, 16: 0x201, 17: 0x111}, + 0, + 15, + False, + ), ("wfi_int.asm", 80, {2: 10}, {2: 10, 3: 10}, 5, 15, True), ], )