Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Asynchronous interrupts #532

Merged
merged 14 commits into from
Dec 16, 2023
28 changes: 19 additions & 9 deletions coreblocks/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from coreblocks.params.dependencies import DependencyManager
from coreblocks.stages.func_blocks_unifier import FuncBlocksUnifier
from coreblocks.structs_common.instr_counter import CoreInstructionCounter
from coreblocks.structs_common.interrupt_controller import InterruptController
from transactron.core import Transaction, TModule
from transactron.lib import FIFO, ConnectTrans
from coreblocks.params.layouts import *
Expand Down Expand Up @@ -37,14 +38,21 @@ def __init__(self, *, gen_params: GenParams, wb_instr_bus: WishboneBus, wb_data_
self.wb_master_instr = WishboneMaster(self.gen_params.wb_params)
self.wb_master_data = WishboneMaster(self.gen_params.wb_params)

# make fifo_fetch visible outside the core for injecting instructions
self.core_counter = CoreInstructionCounter(self.gen_params)

# make fetch_continue visible outside the core for injecting instructions
self.fifo_fetch = FIFO(self.gen_params.get(FetchLayouts).raw_instr, 2)

drop_args_transform = (self.gen_params.get(FetchLayouts).raw_instr, lambda _a, _b: {})
self.core_counter_increment_discard_map = MethodMap(
self.core_counter.increment, i_transform=drop_args_transform
)
self.fetch_continue = MethodProduct([self.fifo_fetch.write, self.core_counter_increment_discard_map.method])

self.free_rf_fifo = BasicFifo(
self.gen_params.get(SchedulerLayouts).free_rf_layout, 2**self.gen_params.phys_regs_bits
)

self.core_counter = CoreInstructionCounter(self.gen_params)

cache_layouts = self.gen_params.get(ICacheLayouts)
if gen_params.icache_params.enable:
self.icache_refiller = SimpleWBCacheRefiller(
Expand Down Expand Up @@ -78,6 +86,8 @@ def __init__(self, *, gen_params: GenParams, wb_instr_bus: WishboneBus, wb_data_
rf_write=self.RF.write,
)

self.interrupt_controller = InterruptController(self.gen_params)

self.csr_generic = GenericCSRRegisters(self.gen_params)
connections.add_dependency(GenericCSRRegistersKey(), self.csr_generic)

Expand All @@ -100,17 +110,14 @@ def elaborate(self, platform):
m.submodules.icache_refiller = self.icache_refiller
m.submodules.icache = self.icache

drop_args_transform = (self.gen_params.get(FetchLayouts).raw_instr, lambda _a, _b: {})
core_counter_increment_discard_map = MethodMap(self.core_counter.increment, i_transform=drop_args_transform)
fetch_continue = MethodProduct([self.fifo_fetch.write, core_counter_increment_discard_map.use(m)])

if Extension.C in self.gen_params.isa.extensions:
m.submodules.fetch = self.fetch = UnalignedFetch(self.gen_params, self.icache, fetch_continue.use(m))
m.submodules.fetch = self.fetch = UnalignedFetch(self.gen_params, self.icache, self.fetch_continue.use(m))
Kristopher38 marked this conversation as resolved.
Show resolved Hide resolved
else:
m.submodules.fetch = self.fetch = Fetch(self.gen_params, self.icache, fetch_continue.use(m))
m.submodules.fetch = self.fetch = Fetch(self.gen_params, self.icache, self.fetch_continue.use(m))

m.submodules.fifo_fetch = self.fifo_fetch
m.submodules.core_counter = self.core_counter
m.submodules.args_discard_map = self.core_counter_increment_discard_map

m.submodules.fifo_decode = fifo_decode = FIFO(self.gen_params.get(DecodeLayouts).decoded_instr, 2)
m.submodules.decode = Decode(
Expand Down Expand Up @@ -150,8 +157,11 @@ def elaborate(self, platform):
fetch_continue=self.fetch.verify_branch,
fetch_stall=self.fetch.stall_exception,
instr_decrement=self.core_counter.decrement,
trap_entry=self.interrupt_controller.entry,
)

m.submodules.interrupt_controller = self.interrupt_controller

m.submodules.csr_generic = self.csr_generic

# push all registers to FreeRF at reset. r0 should be skipped, stop when counter overflows to 0
Expand Down
3 changes: 3 additions & 0 deletions coreblocks/frontend/fetch.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from amaranth import *
from transactron.core import Priority
from transactron.lib import BasicFifo, Semaphore
from coreblocks.frontend.icache import ICacheInterface
from coreblocks.frontend.rvc import InstrDecompress, is_instr_compressed
Expand Down Expand Up @@ -31,6 +32,7 @@ def __init__(self, gen_params: GenParams, icache: ICacheInterface, cont: Method)

self.verify_branch = Method(i=self.gp.get(FetchLayouts).branch_verify)
self.stall_exception = Method()
self.stall_exception.add_conflict(self.verify_branch, Priority.LEFT)

# PC of the last fetched instruction. For now only used in tests.
self.pc = Signal(self.gp.isa.xlen)
Expand Down Expand Up @@ -130,6 +132,7 @@ def __init__(self, gen_params: GenParams, icache: ICacheInterface, cont: Method)

self.verify_branch = Method(i=self.gp.get(FetchLayouts).branch_verify)
self.stall_exception = Method()
self.stall_exception.add_conflict(self.verify_branch, Priority.LEFT)

# PC of the last fetched instruction. For now only used in tests.
self.pc = Signal(self.gp.isa.xlen)
Expand Down
46 changes: 36 additions & 10 deletions coreblocks/fu/jumpbranch.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
from enum import IntFlag, auto

from typing import Sequence
from coreblocks.params.layouts import ExceptionRegisterLayouts

from transactron import *
from transactron.core import def_method
from transactron.lib import *
from transactron.utils import assign

from coreblocks.params import *
from coreblocks.params.keys import AsyncInterruptInsertSignalKey
from transactron.utils import OneHotSwitch
from coreblocks.utils.protocols import FuncUnit

Expand Down Expand Up @@ -156,23 +159,46 @@ def _(arg):

m.d.top_comb += jb.in_rvc.eq(arg.exec_fn.funct7)

# Spec: "[...] if the target address is not four-byte aligned. This exception is reported on the branch
# or jump instruction, not on the target instruction. No instruction-address-misaligned exception is
# generated for a conditional branch that is not taken."
is_auipc = decoder.decode_fn == JumpBranchFn.Fn.AUIPC
jump_result = Mux(jb.taken, jb.jmp_addr, jb.reg_res)

exception = Signal()
exception_report = self.dm.get_dependency(ExceptionReportKey())

jmp_addr_misaligned = (jb.jmp_addr & (0b1 if Extension.C in self.gen.isa.extensions else 0b11)) != 0
with m.If((decoder.decode_fn != JumpBranchFn.Fn.AUIPC) & jb.taken & jmp_addr_misaligned):

async_interrupt_active = self.gen.get(DependencyManager).get_dependency(AsyncInterruptInsertSignalKey())

exception_entry = Record(self.gen.get(ExceptionRegisterLayouts).report)

with m.If(~is_auipc & jb.taken & jmp_addr_misaligned):
# Spec: "[...] if the target address is not four-byte aligned. This exception is reported on the branch
# or jump instruction, not on the target instruction. No instruction-address-misaligned exception is
# generated for a conditional branch that is not taken."
m.d.comb += exception.eq(1)
report = self.dm.get_dependency(ExceptionReportKey())
report(m, rob_id=arg.rob_id, cause=ExceptionCause.INSTRUCTION_ADDRESS_MISALIGNED, pc=arg.pc)
m.d.comb += assign(
exception_entry,
{"rob_id": arg.rob_id, "cause": ExceptionCause.INSTRUCTION_ADDRESS_MISALIGNED, "pc": arg.pc},
)
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.
# Exceptions have priority, because the instruction that reports async interrupt is commited
# and exception would be lost.
m.d.comb += exception.eq(1)
m.d.comb += assign(
exception_entry,
{"rob_id": arg.rob_id, "cause": ExceptionCause._COREBLOCKS_ASYNC_INTERRUPT, "pc": jump_result},
)

with m.If(exception):
exception_report(m, exception_entry)

fifo_res.write(m, rob_id=arg.rob_id, result=jb.reg_res, rp_dst=arg.rp_dst, exception=exception)

# skip writing next branch target for auipc
with m.If(decoder.decode_fn != JumpBranchFn.Fn.AUIPC):
fifo_branch.write(
m, from_pc=jb.in_pc, next_pc=Mux(jb.taken, jb.jmp_addr, jb.reg_res), resume_from_exception=0
)
with m.If(~is_auipc):
fifo_branch.write(m, from_pc=jb.in_pc, next_pc=jump_result, resume_from_exception=0)

return m

Expand Down
112 changes: 112 additions & 0 deletions coreblocks/fu/priv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
from amaranth import *

from enum import IntFlag, auto
from typing import Sequence


from transactron import *
from transactron.lib import BasicFifo

from coreblocks.params import *
from coreblocks.params.keys import MretKey
from coreblocks.utils.protocols import FuncUnit

from coreblocks.fu.fu_decoder import DecoderManager


class PrivilegedFn(DecoderManager):
@unique
class Fn(IntFlag):
MRET = auto()

@classmethod
def get_instructions(cls) -> Sequence[tuple]:
return [(cls.Fn.MRET, OpType.MRET)]


class PrivilegedFuncUnit(Elaboratable):
def __init__(self, gp: GenParams):
self.gp = gp

self.layouts = layouts = gp.get(FuncUnitLayouts)
self.dm = gp.get(DependencyManager)

self.issue = Method(i=layouts.issue)
self.accept = Method(o=layouts.accept)
self.precommit = Method(i=gp.get(RetirementLayouts).precommit)

self.branch_resolved_fifo = BasicFifo(self.gp.get(FetchLayouts).branch_verify, 2)

def elaborate(self, platform):
m = TModule()

instr_valid = Signal()
finished = Signal()

instr_rob = Signal(self.gp.rob_entries_bits)
instr_pc = Signal(self.gp.isa.xlen)

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())

m.submodules.branch_resolved_fifo = self.branch_resolved_fifo

@def_method(m, self.issue, ready=~instr_valid)
def _(arg):
m.d.sync += [
instr_valid.eq(1),
instr_rob.eq(arg.rob_id),
instr_pc.eq(arg.pc),
]

@def_method(m, self.precommit)
def _(rob_id, side_fx):
with m.If(instr_valid & (rob_id == instr_rob)):
m.d.sync += finished.eq(1)
with m.If(side_fx):
mret(m)

@def_method(m, self.accept, ready=instr_valid & finished)
def _():
m.d.sync += instr_valid.eq(0)
m.d.sync += finished.eq(0)

ret_pc = csr.m_mode.mepc.read(m).data

exception = Signal()
with m.If(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.
# 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)
with m.Else():
# Unstall the fetch to return address (MRET is SYSTEM opcode)
self.branch_resolved_fifo.write(m, next_pc=ret_pc, from_pc=0, resume_from_exception=0)

return {
"rob_id": instr_rob,
"exception": exception,
"rp_dst": 0,
"result": 0,
}

return m


class PrivilegedUnitComponent(FunctionalComponentParams):
def get_module(self, gp: GenParams) -> FuncUnit:
unit = PrivilegedFuncUnit(gp)
connections = gp.get(DependencyManager)
connections.add_dependency(InstructionPrecommitKey(), unit.precommit)
connections.add_dependency(BranchResolvedKey(), unit.branch_resolved_fifo.read)
return unit

def get_optypes(self) -> set[OpType]:
return PrivilegedFn().get_op_types()
8 changes: 7 additions & 1 deletion coreblocks/params/configurations.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,19 @@
from coreblocks.fu.zbc import ZbcComponent
from coreblocks.fu.zbs import ZbsComponent
from coreblocks.fu.exception import ExceptionUnitComponent
from coreblocks.fu.priv import PrivilegedUnitComponent
from coreblocks.lsu.dummyLsu import LSUBlockComponent
from coreblocks.structs_common.csr import CSRBlockComponent

__all__ = ["CoreConfiguration", "basic_core_config", "tiny_core_config", "full_core_config", "test_core_config"]

basic_configuration: tuple[BlockComponentParams, ...] = (
RSBlockComponent([ALUComponent(), ShiftUnitComponent(), JumpComponent(), ExceptionUnitComponent()], rs_entries=4),
RSBlockComponent(
[ALUComponent(), ShiftUnitComponent(), JumpComponent(), ExceptionUnitComponent(), PrivilegedUnitComponent()],
rs_entries=4,
),
LSUBlockComponent(),
CSRBlockComponent(),
)


Expand Down Expand Up @@ -116,6 +121,7 @@ def replace(self, **kwargs):
ZbsComponent(),
JumpComponent(),
ExceptionUnitComponent(),
PrivilegedUnitComponent(),
],
rs_entries=4,
),
Expand Down
3 changes: 2 additions & 1 deletion coreblocks/params/isa.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ class FenceFm(IntEnum, shape=4):


@unique
class ExceptionCause(IntEnum, shape=4):
class ExceptionCause(IntEnum, shape=5):
INSTRUCTION_ADDRESS_MISALIGNED = 0
INSTRUCTION_ACCESS_FAULT = 1
ILLEGAL_INSTRUCTION = 2
Expand All @@ -155,6 +155,7 @@ class ExceptionCause(IntEnum, shape=4):
INSTRUCTION_PAGE_FAULT = 12
LOAD_PAGE_FAULT = 13
STORE_PAGE_FAULT = 15
_COREBLOCKS_ASYNC_INTERRUPT = 16


@unique
Expand Down
13 changes: 13 additions & 0 deletions coreblocks/params/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from transactron import Method
from transactron.lib import MethodTryProduct, Collector
from coreblocks.peripherals.wishbone import WishboneMaster
from amaranth import Signal

if TYPE_CHECKING:
from coreblocks.structs_common.csr_generic import GenericCSRRegisters # noqa: F401
Expand All @@ -15,6 +16,8 @@
"BranchResolvedKey",
"ExceptionReportKey",
"GenericCSRRegistersKey",
"AsyncInterruptInsertSignalKey",
"MretKey",
]


Expand All @@ -41,3 +44,13 @@ class ExceptionReportKey(SimpleKey[Method]):
@dataclass(frozen=True)
class GenericCSRRegistersKey(SimpleKey["GenericCSRRegisters"]):
pass


@dataclass(frozen=True)
class AsyncInterruptInsertSignalKey(SimpleKey[Signal]):
pass


@dataclass(frozen=True)
class MretKey(SimpleKey[Method]):
pass
1 change: 1 addition & 0 deletions coreblocks/params/layouts.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"DecodeLayouts",
"FuncUnitLayouts",
"RSInterfaceLayouts",
"RetirementLayouts",
"RSLayouts",
"RFLayouts",
"UnsignedMulUnitLayouts",
Expand Down
2 changes: 1 addition & 1 deletion coreblocks/params/optypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ class OpType(IntEnum):
],
Extension.XINTMACHINEMODE: [
OpType.MRET,
OpType.WFI,
# OpType.WFI, - uncomment when WFI implemented, to not break fully supported extensions check
],
Extension.XINTSUPERVISOR: [
OpType.SRET,
Expand Down
Loading