Skip to content

Add support for 'fence.i' instruction #649

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

Merged
merged 5 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ _coreblocks_regression.counter
# cocotb build
/test/regression/cocotb/build
/test/regression/cocotb/results.xml
/test/regression/cocotb/cocotb-config.cache

# riscv-tests
test/external/riscv-tests/test-*
Expand Down
3 changes: 3 additions & 0 deletions coreblocks/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
GenericCSRRegistersKey,
InstructionPrecommitKey,
CommonBusDataKey,
FlushICacheKey,
)
from coreblocks.params.genparams import GenParams
from coreblocks.params.isa_params import Extension
Expand Down Expand Up @@ -87,6 +88,8 @@ def __init__(self, *, gen_params: GenParams, wb_instr_bus: WishboneInterface, wb
self.connections = gen_params.get(DependencyManager)
self.connections.add_dependency(CommonBusDataKey(), self.bus_master_data_adapter)

self.connections.add_dependency(FlushICacheKey(), self.icache.flush)

if Extension.C in self.gen_params.isa.extensions:
self.fetch = UnalignedFetch(self.gen_params, self.icache, self.fetch_continue.method)
else:
Expand Down
6 changes: 4 additions & 2 deletions coreblocks/frontend/fetch/fetch.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,9 @@ def stall(exception=False):
res = self.icache.accept_res(m)

opcode = res.fetch_block[2:7]
funct3 = res.fetch_block[12:15]
# whether we have to wait for the retirement of this instruction before we make futher speculation
unsafe_instr = opcode == Opcode.SYSTEM
unsafe_instr = (opcode == Opcode.SYSTEM) | ((opcode == Opcode.MISC_MEM) & (funct3 == Funct3.FENCEI))

with m.If(spin == target.spin):
instr = Signal(self.gen_params.isa.ilen)
Expand Down Expand Up @@ -200,8 +201,9 @@ def elaborate(self, platform) -> TModule:
m.d.top_comb += instr.eq(Mux(is_rvc, decompress.instr_out, full_instr))

opcode = instr[2:7]
funct3 = instr[12:15]
# whether we have to wait for the retirement of this instruction before we make futher speculation
unsafe_instr = opcode == Opcode.SYSTEM
unsafe_instr = (opcode == Opcode.SYSTEM) | ((opcode == Opcode.MISC_MEM) & (funct3 == Funct3.FENCEI))

# Check if we are ready to dispatch an instruction in the current cycle.
# This can happen in three situations:
Expand Down
58 changes: 49 additions & 9 deletions coreblocks/func_blocks/fu/priv.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@


from transactron import *
from transactron.lib import BasicFifo
from transactron.utils import DependencyManager
from transactron.lib import BasicFifo, logging
from transactron.lib.metrics import TaggedCounter
from transactron.lib.simultaneous import condition
from transactron.utils import DependencyManager, OneHotSwitch

from coreblocks.params import *
from coreblocks.params import GenParams, FunctionalComponentParams
Expand All @@ -19,25 +21,31 @@
GenericCSRRegistersKey,
InstructionPrecommitKey,
FetchResumeKey,
FlushICacheKey,
)
from coreblocks.func_blocks.interface.func_protocols import FuncUnit

from coreblocks.func_blocks.fu.common.fu_decoder import DecoderManager


log = logging.HardwareLogger("backend.fu.priv")


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

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


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

self.layouts = layouts = gp.get(FuncUnitLayouts)
self.dm = gp.get(DependencyManager)
Expand All @@ -48,46 +56,77 @@ def __init__(self, gp: GenParams):

self.fetch_resume_fifo = BasicFifo(self.gp.get(FetchLayouts).resume, 2)

self.perf_instr = TaggedCounter(
"backend.fu.priv.instr",
"Number of instructions precommited with side effects by the priviledge unit",
tags=PrivilegedFn.Fn,
)

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

m.submodules += [self.perf_instr]

m.submodules.decoder = decoder = self.priv_fn.get_decoder(self.gp)

instr_valid = Signal()
finished = Signal()

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

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())
flush_icache = self.dm.get_dependency(FlushICacheKey())

m.submodules.fetch_resume_fifo = self.fetch_resume_fifo

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

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

with condition(m) as branch:
with branch(~side_fx):
pass
with branch(instr_fn == PrivilegedFn.Fn.MRET):
mret(m)
with branch(instr_fn == PrivilegedFn.Fn.FENCEI):
flush_icache(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
ret_pc = Signal(self.gp.isa.xlen)
handle_interrupts_now = Signal()

with OneHotSwitch(m, instr_fn) as OneHotCase:
with OneHotCase(PrivilegedFn.Fn.MRET):
m.d.av_comb += ret_pc.eq(csr.m_mode.mepc.read(m).data)
m.d.av_comb += handle_interrupts_now.eq(1)
with OneHotCase(PrivilegedFn.Fn.FENCEI):
# FENCE.I can't be compressed, so the next instruction is always pc+4
m.d.av_comb += ret_pc.eq(instr_pc + 4)
m.d.av_comb += handle_interrupts_now.eq(0)

exception = Signal()
with m.If(async_interrupt_active):
with m.If(async_interrupt_active & handle_interrupts_now):
# 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
Expand All @@ -98,7 +137,8 @@ def _():
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)
log.info(m, True, "Unstalling fetch from the priv unit new_pc=0x{:x}", ret_pc)
# Unstall the fetch
self.fetch_resume_fifo.write(m, pc=ret_pc, resume_from_exception=0)

return {
Expand Down
5 changes: 5 additions & 0 deletions coreblocks/interface/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,8 @@ class MretKey(SimpleKey[Method]):
@dataclass(frozen=True)
class CoreStateKey(SimpleKey[Method]):
pass


@dataclass(frozen=True)
class FlushICacheKey(SimpleKey[Method]):
pass
11 changes: 7 additions & 4 deletions test/params/test_configurations.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ class ISAStrTest:

TEST_CASES = [
ISAStrTest(
basic_core_config, "rv32izicsr_xintmachinemode", "rv32izicsr_xintmachinemode", "rv32izicsr_xintmachinemode"
basic_core_config,
"rv32izicsr_zifencei_xintmachinemode",
"rv32izicsr_zifencei_xintmachinemode",
"rv32izicsr_zifencei_xintmachinemode",
),
ISAStrTest(
full_core_config,
"rv32imcbzicsr_xintmachinemode",
"rv32imcbzicsr_xintmachinemode",
"rv32imcbzicsr_xintmachinemode",
"rv32imcbzicsr_zifencei_xintmachinemode",
"rv32imcbzicsr_zifencei_xintmachinemode",
"rv32imcbzicsr_zifencei_xintmachinemode",
),
ISAStrTest(tiny_core_config, "rv32e", "rv32", "rv32e"),
ISAStrTest(test_core_config, "rv32", "rv32", "rv32i"),
Expand Down
2 changes: 1 addition & 1 deletion test/regression/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def load_regression_tests() -> list[str]:
print("Couldn't build regression tests")
all_tests = set(get_all_test_names())

exclude = {"rv32ui-ma_data", "rv32ui-fence_i"}
exclude = {"rv32ui-ma_data"}

return sorted(list(all_tests - exclude))

Expand Down
18 changes: 13 additions & 5 deletions test/regression/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,9 @@ def write(self, req: WriteRequest) -> WriteReply:
return WriteReply(status=ReplyStatus.ERROR)


def load_segment(segment: Segment, *, disable_write_protection: bool = False) -> RandomAccessMemory:
def load_segment(
segment: Segment, *, disable_write_protection: bool = False, force_executable: bool = False
) -> RandomAccessMemory:
paddr = segment.header["p_paddr"]
memsz = segment.header["p_memsz"]
flags_raw = segment.header["p_flags"]
Expand All @@ -158,11 +160,11 @@ def load_segment(segment: Segment, *, disable_write_protection: bool = False) ->
flags |= SegmentFlags.READ
if flags_raw & P_FLAGS.PF_W or disable_write_protection:
flags |= SegmentFlags.WRITE
if flags_raw & P_FLAGS.PF_X:
if flags_raw & P_FLAGS.PF_X or force_executable:
flags |= SegmentFlags.EXECUTABLE

config = CoreConfiguration()
if flags_raw & P_FLAGS.PF_X:
if flags & SegmentFlags.EXECUTABLE:
# align instruction section to full icache lines
align_bits = config.icache_line_bytes_log
# workaround for fetching/stalling issue
Expand All @@ -182,14 +184,20 @@ def load_segment(segment: Segment, *, disable_write_protection: bool = False) ->
return RandomAccessMemory(range(seg_start, seg_end), flags, data)


def load_segments_from_elf(file_path: str, *, disable_write_protection: bool = False) -> list[RandomAccessMemory]:
def load_segments_from_elf(
file_path: str, *, disable_write_protection: bool = False, force_executable: bool = False
) -> list[RandomAccessMemory]:
segments: list[RandomAccessMemory] = []

with open(file_path, "rb") as f:
elffile = ELFFile(f)
for segment in elffile.iter_segments():
if segment.header["p_type"] != "PT_LOAD":
continue
segments.append(load_segment(segment, disable_write_protection=disable_write_protection))
segments.append(
load_segment(
segment, disable_write_protection=disable_write_protection, force_executable=force_executable
)
)

return segments
4 changes: 4 additions & 0 deletions test/regression/test_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
# disable write protection for specific tests with writes to .text section
exclude_write_protection = ["rv32uc-rvc"]

# force executable bit for memory segments in specific tests
force_executable_memory = ["rv32ui-fence_i"]


class MMIO(MemorySegment):
def __init__(self, on_finish: Callable[[], None]):
Expand All @@ -41,6 +44,7 @@ async def run_test(sim_backend: SimulationBackend, test_name: str):
mem_segments += load_segments_from_elf(
str(riscv_tests_dir.joinpath("test-" + test_name)),
disable_write_protection=test_name in exclude_write_protection,
force_executable=test_name in force_executable_memory,
)
mem_segments.append(mmio)

Expand Down