Skip to content

Commit

Permalink
Exclusive branches (#551)
Browse files Browse the repository at this point in the history
  • Loading branch information
tilk authored Jan 2, 2024
1 parent dc55268 commit cf30bbe
Show file tree
Hide file tree
Showing 5 changed files with 329 additions and 61 deletions.
17 changes: 2 additions & 15 deletions coreblocks/fu/jumpbranch.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@
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
Expand Down Expand Up @@ -171,30 +169,19 @@ def _(arg):
AsyncInterruptInsertSignalKey()
)

exception_entry = Record(self.gen_params.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)
m.d.comb += assign(
exception_entry,
{"rob_id": arg.rob_id, "cause": ExceptionCause.INSTRUCTION_ADDRESS_MISALIGNED, "pc": arg.pc},
)
exception_report(m, 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)
exception_report(m, rob_id=arg.rob_id, cause=ExceptionCause._COREBLOCKS_ASYNC_INTERRUPT, pc=jump_result)

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

Expand Down
22 changes: 7 additions & 15 deletions coreblocks/structs_common/csr.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from coreblocks.params.genparams import GenParams
from coreblocks.params.dependencies import DependencyManager, ListKey
from coreblocks.params.fu_params import BlockComponentParams
from coreblocks.params.layouts import ExceptionRegisterLayouts, FetchLayouts, FuncUnitLayouts, CSRLayouts
from coreblocks.params.layouts import FetchLayouts, FuncUnitLayouts, CSRLayouts
from coreblocks.params.isa import Funct3, ExceptionCause
from coreblocks.params.keys import (
AsyncInterruptInsertSignalKey,
Expand Down Expand Up @@ -333,29 +333,21 @@ def _():

report = self.dependency_manager.get_dependency(ExceptionReportKey())
interrupt = self.dependency_manager.get_dependency(AsyncInterruptInsertSignalKey())
exception_entry = Record(self.gen_params.get(ExceptionRegisterLayouts).report)

with m.If(exception):
m.d.comb += assign(
exception_entry,
{"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)
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."
# At this time CSR operation is finished. If it caused triggering an interrupt, it would be represented
# by interrupt signal in this cycle.
# CSR instructions are never compressed, PC+4 is always next instruction
m.d.comb += assign(
exception_entry,
{
"rob_id": instr.rob_id,
"cause": ExceptionCause._COREBLOCKS_ASYNC_INTERRUPT,
"pc": instr.pc + self.gen_params.isa.ilen_bytes,
},
report(
m,
rob_id=instr.rob_id,
cause=ExceptionCause._COREBLOCKS_ASYNC_INTERRUPT,
pc=instr.pc + self.gen_params.isa.ilen_bytes,
)
with m.If(exception | interrupt):
report(m, exception_entry)

m.d.sync += exception.eq(0)

Expand Down
98 changes: 98 additions & 0 deletions test/transactions/test_branches.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
from amaranth import *
from itertools import product
from transactron.core import (
CtrlPath,
MethodMap,
TModule,
Method,
Transaction,
TransactionManager,
TransactionModule,
def_method,
)
from unittest import TestCase
from ..common import TestCaseWithSimulator


class TestExclusivePath(TestCase):
def test_exclusive_path(self):
m = TModule()
m._MustUse__silence = True # type: ignore

with m.If(0):
cp0 = m.ctrl_path
with m.Switch(3):
with m.Case(0):
cp0a0 = m.ctrl_path
with m.Case(1):
cp0a1 = m.ctrl_path
with m.Default():
cp0a2 = m.ctrl_path
with m.If(1):
cp0b0 = m.ctrl_path
with m.Else():
cp0b1 = m.ctrl_path
with m.Elif(1):
cp1 = m.ctrl_path
with m.FSM():
with m.State("start"):
cp10 = m.ctrl_path
with m.State("next"):
cp11 = m.ctrl_path
with m.Else():
cp2 = m.ctrl_path

def mutually_exclusive(*cps: CtrlPath):
return all(cpa.exclusive_with(cpb) for i, cpa in enumerate(cps) for cpb in cps[i + 1 :])

def pairwise_exclusive(cps1: list[CtrlPath], cps2: list[CtrlPath]):
return all(cpa.exclusive_with(cpb) for cpa, cpb in product(cps1, cps2))

def pairwise_not_exclusive(cps1: list[CtrlPath], cps2: list[CtrlPath]):
return all(not cpa.exclusive_with(cpb) for cpa, cpb in product(cps1, cps2))

self.assertTrue(mutually_exclusive(cp0, cp1, cp2))
self.assertTrue(mutually_exclusive(cp0a0, cp0a1, cp0a2))
self.assertTrue(mutually_exclusive(cp0b0, cp0b1))
self.assertTrue(mutually_exclusive(cp10, cp11))
self.assertTrue(pairwise_exclusive([cp0, cp0a0, cp0a1, cp0a2, cp0b0, cp0b1], [cp1, cp10, cp11]))
self.assertTrue(pairwise_not_exclusive([cp0, cp0a0, cp0a1, cp0a2], [cp0, cp0b0, cp0b1]))


class ExclusiveConflictRemovalCircuit(Elaboratable):
def __init__(self):
self.sel = Signal()

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

called_method = Method(i=[], o=[])

@def_method(m, called_method)
def _():
pass

with m.If(self.sel):
with Transaction().body(m):
called_method(m)
with m.Else():
with Transaction().body(m):
called_method(m)

return m


class TestExclusiveConflictRemoval(TestCaseWithSimulator):
def test_conflict_removal(self):
circ = ExclusiveConflictRemovalCircuit()

tm = TransactionManager()
dut = TransactionModule(circ, tm)

with self.run_simulation(dut):
pass

cgr, _, _ = tm._conflict_graph(MethodMap(tm.transactions))

for s in cgr.values():
self.assertFalse(s)
23 changes: 23 additions & 0 deletions test/transactions/test_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,29 @@ def elaborate(self, platform):

self.assert_re("called twice", Twice())

def test_twice_cond(self):
class Twice(Elaboratable):
def __init__(self):
self.meth1 = Method()
self.meth2 = Method()

def elaborate(self, platform):
m = TModule()
m._MustUse__silence = True # type: ignore

with self.meth1.body(m):
pass

with self.meth2.body(m):
with m.If(1):
self.meth1(m)
with m.Else():
self.meth1(m)

return m

Fragment.get(TransactionModule(Twice()), platform=None)

def test_diamond(self):
class Diamond(Elaboratable):
def __init__(self):
Expand Down
Loading

0 comments on commit cf30bbe

Please sign in to comment.