Skip to content

Commit

Permalink
Implement Zalrsc
Browse files Browse the repository at this point in the history
  • Loading branch information
piotro888 committed Jan 19, 2025
1 parent c53cbe3 commit 54355ab
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 33 deletions.
5 changes: 4 additions & 1 deletion coreblocks/arch/isa.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ class Extension(enum.IntFlag):
ZICOND = auto()
#: Atomic memory operations
ZAAMO = auto()
#: Load-Reserved/Store-Conditional Instructions
ZALRSC = auto()
#: Misaligned atomic operations
ZAM = auto()
#: Half precision floating-point operations (16-bit)
Expand Down Expand Up @@ -109,12 +111,13 @@ class Extension(enum.IntFlag):
extension_implications = {
Extension.F: Extension.ZICSR,
Extension.M: Extension.ZMMUL,
Extension.A: Extension.ZAAMO,
Extension.A: Extension.ZAAMO | Extension.ZALRSC,
Extension.B: Extension.ZBA | Extension.ZBB | Extension.ZBC | Extension.ZBS,
}

# Extensions (not aliases) that only imply other sub-extensions, but don't add any new OpTypes.
extension_only_implies = {
Extension.A,
Extension.B,
}

Expand Down
2 changes: 2 additions & 0 deletions coreblocks/arch/isa_consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ class Funct7(IntEnum, shape=7):
ZEXTH = AMOSWAP = 0b0000100
MAX = MIN = CLMUL = 0b0000101
CZERO = 0b0000111
LR = 0b0001000
SFENCEVMA = 0b0001001
SC = 0b0001100
SH1ADD = SH2ADD = SH3ADD = AMOXOR = 0b0010000
BSET = ORCB = 0b0010100
SA = SUB = ANDN = ORN = XNOR = AMOOR = 0b0100000
Expand Down
4 changes: 4 additions & 0 deletions coreblocks/arch/optypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class OpType(IntEnum):
SFENCEVMA = auto()
CZERO = auto()
ATOMIC_MEMORY_OP = auto()
ATOMIC_LR_SC = auto()
#: Internal Coreblocks OpType, specifing that instruction caused Exception before FU execution
EXCEPTION = auto()

Expand Down Expand Up @@ -133,6 +134,9 @@ def is_jalr(val: ValueLike) -> Value:
Extension.ZAAMO: [
OpType.ATOMIC_MEMORY_OP,
],
Extension.ZALRSC: [
OpType.ATOMIC_LR_SC,
],
Extension.ZBS: [
OpType.SINGLE_BIT_MANIPULATION,
],
Expand Down
4 changes: 4 additions & 0 deletions coreblocks/frontend/decoder/instr_description.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,4 +219,8 @@ class Encoding:
Encoding(Opcode.AMO, Funct3.W, Funct7.AMOMAX),
Encoding(Opcode.AMO, Funct3.W, Funct7.AMOMIN),
],
OpType.ATOMIC_LR_SC: [
Encoding(Opcode.AMO, Funct3.W, Funct7.LR),
Encoding(Opcode.AMO, Funct3.W, Funct7.SC),
],
}
91 changes: 74 additions & 17 deletions coreblocks/func_blocks/fu/lsu/lsu_atomic_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from dataclasses import dataclass

from transactron.core import TModule, Method, Transaction, def_method
from transactron.core import Priority, TModule, Method, Transaction, def_method
from transactron.lib.connectors import Forwarder
from transactron.utils import assign, layout_subset, AssignType

Expand Down Expand Up @@ -39,6 +39,9 @@ def __init__(self, gen_params: GenParams, lsu: FuncUnit):
def elaborate(self, platform):
m = TModule()

# Simplified due to no other harts, reservation set size == adress space (implementation defined)
reservation_valid = Signal()

atomic_in_progress = Signal()
atomic_op = Signal(
layout_subset(self.fu_layouts.issue, fields=set(["rob_id", "s1_val", "s2_val", "rp_dst", "exec_fn"]))
Expand Down Expand Up @@ -82,36 +85,75 @@ def atomic_op_res(v1: Value, v2: Value):

accept_forwarder = Forwarder(self.fu_layouts.accept)

atomic_is_lr_sc = Signal()
sc_failed = Signal()

@def_method(m, self.issue, ready=~atomic_in_progress)
def _(arg):
is_atomic = arg.exec_fn.op_type == OpType.ATOMIC_MEMORY_OP

atomic_load_op = Signal(self.fu_layouts.issue)
m.d.av_comb += assign(atomic_load_op, arg)
m.d.av_comb += assign(atomic_load_op.exec_fn, {"op_type": OpType.LOAD, "funct3": Funct3.W})
m.d.av_comb += atomic_load_op.imm.eq(0)

with m.If(is_atomic):
self.lsu.issue(m, atomic_load_op)
with m.Else():
is_amo = arg.exec_fn.op_type == OpType.ATOMIC_MEMORY_OP
is_lr_sc = arg.exec_fn.op_type == OpType.ATOMIC_LR_SC

issue_store = Signal()
sc_fail = Signal()

funct7 = arg.exec_fn.funct7 & ~0b11
with m.If(is_lr_sc & (funct7 == Funct7.LR)):
m.d.sync += reservation_valid.eq(1)
with m.Elif(is_lr_sc & (funct7 == Funct7.SC)):
m.d.sync += reservation_valid.eq(0)
m.d.av_comb += issue_store.eq(1)
m.d.av_comb += sc_fail.eq(~reservation_valid)

atomic_issue_op = Signal(self.fu_layouts.issue)
m.d.av_comb += assign(atomic_issue_op, arg)
m.d.av_comb += assign(
atomic_issue_op.exec_fn,
{
"op_type": Mux(issue_store, OpType.STORE, OpType.LOAD),
"funct3": Funct3.W,
},
)
m.d.av_comb += atomic_issue_op.imm.eq(0)

with m.If((is_amo | is_lr_sc) & ~sc_fail):
self.lsu.issue(m, atomic_issue_op)
with m.Elif(~sc_fail):
self.lsu.issue(m, arg)

with m.If(is_atomic):
with m.If(is_amo | is_lr_sc):
m.d.sync += atomic_in_progress.eq(1)
m.d.sync += atomic_is_lr_sc.eq(is_lr_sc)
m.d.sync += assign(atomic_op, arg, fields=AssignType.LHS)

m.d.sync += sc_failed.eq(sc_fail)

@def_method(m, self.accept)
def _():
return accept_forwarder.read(m)

atomic_second_reqest = Signal()
atomic_fin = Signal()

with Transaction().body(m):
with Transaction().body(m) as accept_trans:
res = self.lsu.accept(m)

# 1st atomic result
with m.If((res.rob_id == atomic_op.rob_id) & atomic_in_progress & ~atomic_second_reqest & ~res.exception):
funct7 = atomic_op.exec_fn.funct7 & ~0b11

# LR/SC
with m.If((res.rob_id == atomic_op.rob_id) & atomic_in_progress & atomic_is_lr_sc):
atomic_res = Signal(self.fu_layouts.accept)
m.d.av_comb += assign(atomic_res, res)
with m.If(funct7 == Funct7.SC):
m.d.av_comb += atomic_res.result.eq(0)

with m.If(res.exception):
m.d.sync += reservation_valid.eq(0)

accept_forwarder.write(m, atomic_res)
m.d.sync += atomic_in_progress.eq(0)

# 1st AMO result
with m.Elif((res.rob_id == atomic_op.rob_id) & atomic_in_progress & ~atomic_second_reqest & ~res.exception):
# NOTE: This branch could be optimised by replacing Ifs with condition to be independent of
# accept.ready, but it causes combinational loop because of `rob_id` data dependency.
m.d.sync += load_val.eq(res.result)
Expand All @@ -120,7 +162,7 @@ def _():
accept_forwarder.write(m, res)
m.d.sync += atomic_in_progress.eq(0)

# 2nd atomic result
# 2nd AMO result
with m.Elif(atomic_in_progress & atomic_fin):
atomic_res = Signal(self.fu_layouts.accept)
m.d.av_comb += assign(atomic_res, res)
Expand All @@ -146,6 +188,21 @@ def _():

m.d.sync += atomic_fin.eq(1)

with Transaction().body(m, request=atomic_in_progress & sc_failed) as sc_failed_trans:
m.d.sync += sc_failed.eq(0)
m.d.sync += atomic_in_progress.eq(0)
accept_forwarder.write(
m,
{
"result": 1,
"rob_id": atomic_op.rob_id,
"rp_dst": atomic_op.rp_dst,
"exception": 0,
},
)

sc_failed_trans.add_conflict(accept_trans, priority=Priority.RIGHT)

m.submodules.accept_forwarder = accept_forwarder
m.submodules.lsu = self.lsu

Expand All @@ -160,4 +217,4 @@ def get_module(self, gen_params: GenParams) -> FuncUnit:
return LSUAtomicWrapper(gen_params, self.lsu.get_module(gen_params))

def get_optypes(self) -> set[OpType]:
return {OpType.ATOMIC_MEMORY_OP} | self.lsu.get_optypes()
return {OpType.ATOMIC_MEMORY_OP, OpType.ATOMIC_LR_SC} | self.lsu.get_optypes()
11 changes: 10 additions & 1 deletion test/common/test_isa.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ def __init__(self, isa_str, valid, xlen=None, reg_cnt=None, extensions=None):
ISATestEntry("rv32i", True, 32, 32, Extension.I),
ISATestEntry("RV32I", True, 32, 32, Extension.I),
ISATestEntry(
"rv32ima", True, 32, 32, Extension.I | Extension.M | Extension.ZMMUL | Extension.A | Extension.ZAAMO
"rv32ima",
True,
32,
32,
Extension.I | Extension.M | Extension.ZMMUL | Extension.A | Extension.ZAAMO | Extension.ZALRSC,
),
ISATestEntry(
"rv32imafdc_zicsr",
Expand All @@ -28,6 +32,7 @@ def __init__(self, isa_str, valid, xlen=None, reg_cnt=None, extensions=None):
| Extension.M
| Extension.A
| Extension.ZAAMO
| Extension.ZALRSC
| Extension.F
| Extension.D
| Extension.C
Expand Down Expand Up @@ -72,6 +77,7 @@ def __init__(self, isa_str, valid, xlen=None, reg_cnt=None, extensions=None):
| Extension.ZMMUL
| Extension.A
| Extension.ZAAMO
| Extension.ZALRSC
| Extension.F
| Extension.D
| Extension.C
Expand All @@ -88,6 +94,7 @@ def __init__(self, isa_str, valid, xlen=None, reg_cnt=None, extensions=None):
| Extension.ZMMUL
| Extension.A
| Extension.ZAAMO
| Extension.ZALRSC
| Extension.F
| Extension.D
| Extension.C
Expand All @@ -104,6 +111,7 @@ def __init__(self, isa_str, valid, xlen=None, reg_cnt=None, extensions=None):
| Extension.ZMMUL
| Extension.A
| Extension.ZAAMO
| Extension.ZALRSC
| Extension.F
| Extension.D
| Extension.C
Expand All @@ -122,6 +130,7 @@ def __init__(self, isa_str, valid, xlen=None, reg_cnt=None, extensions=None):
| Extension.ZMMUL
| Extension.A
| Extension.ZAAMO
| Extension.ZALRSC
| Extension.F
| Extension.D
| Extension.ZIFENCEI
Expand Down
1 change: 1 addition & 0 deletions test/frontend/test_instr_decoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ def __init__(
InstrTest(
0x0C21A22F, Opcode.AMO, Funct3.W, Funct7.AMOSWAP | 0x2, rd=4, rs2=2, rs1=3, op=OpType.ATOMIC_MEMORY_OP
),
InstrTest(0x1812A1AF, Opcode.AMO, Funct3.W, Funct7.SC, rd=3, rs2=1, rs1=5, op=OpType.ATOMIC_LR_SC),
]

def setup_method(self):
Expand Down
47 changes: 36 additions & 11 deletions test/func_blocks/lsu/test_lsu_atomic_wrapper.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from collections import deque
import random
from amaranth import *
from amaranth.utils import ceil_log2
from transactron import TModule
from transactron.lib import Adapter
from transactron.testing import MethodMock, SimpleTestCircuit, TestCaseWithSimulator, TestbenchIO, def_method_mock
Expand Down Expand Up @@ -36,17 +37,17 @@ def elaborate(self, platform):
class TestLSUAtomicWrapper(TestCaseWithSimulator):
def setup_method(self):
random.seed(1258)
self.gen_params = GenParams(test_core_config)
self.inst_cnt = 255
self.gen_params = GenParams(test_core_config.replace(rob_entries_bits=ceil_log2(self.inst_cnt)))
self.lsu = FuncUnitMock(self.gen_params)
self.dut = SimpleTestCircuit(LSUAtomicWrapper(self.gen_params, self.lsu))

self.mem_cell = 0
self.instr_q = deque()
self.result_q = deque()
self.results = {}
self.lsu_res_q = deque()
self.lsu_except_q = deque()

self.inst_cnt = 200
self.generate_instrs(self.inst_cnt)

@def_method_mock(lambda self: self.lsu.issue_tb, enable=lambda _: random.random() < 0.9)
Expand Down Expand Up @@ -87,8 +88,9 @@ def _():

def generate_instrs(self, cnt):
generation_mem_cell = 0
generation_reservation_valid = 0
for i in range(cnt):
optype = random.choice([OpType.LOAD, OpType.STORE, OpType.ATOMIC_MEMORY_OP])
optype = random.choice([OpType.LOAD, OpType.STORE, OpType.ATOMIC_MEMORY_OP, OpType.ATOMIC_LR_SC])
funct7 = 0

imm = random.randint(0, 1)
Expand Down Expand Up @@ -155,6 +157,28 @@ def twos(x):

if not exception_on_load:
self.lsu_except_q.append({"addr": s1_val, "exception": exception})
elif optype == OpType.ATOMIC_LR_SC:
is_load = random.random() < 0.5
exception = random.random() < 0.3
sc_fail = False
if is_load:
funct7 = Funct7.LR
generation_reservation_valid = not exception
result = generation_mem_cell if not exception else 0
else:
funct7 = Funct7.SC
if generation_reservation_valid:
if not exception:
generation_mem_cell = s2_val
else:
sc_fail = True
exception = 0

result = 0 if generation_reservation_valid or exception else 1
generation_reservation_valid = 0

if not sc_fail:
self.lsu_except_q.append({"addr": s1_val, "exception": exception})

elif optype == OpType.LOAD:
result = generation_mem_cell
Expand All @@ -165,17 +189,18 @@ def twos(x):
self.lsu_except_q.append({"addr": s1_val + imm, "exception": 0})

exec_fn = {"op_type": optype, "funct3": Funct3.W, "funct7": funct7}
rob_id = i
instr = {
"rp_dst": rp_dst,
"rob_id": i,
"rob_id": rob_id,
"exec_fn": exec_fn,
"s1_val": s1_val,
"s2_val": s2_val,
"imm": imm,
"pc": 0,
}
self.instr_q.append(instr)
self.result_q.append({"rob_id": 0, "rp_dst": rp_dst, "result": result, "exception": exception})
self.results[rob_id] = {"rob_id": rob_id, "rp_dst": rp_dst, "result": result, "exception": exception}

async def issue_process(self, sim):
while self.instr_q:
Expand All @@ -186,13 +211,13 @@ async def issue_process(self, sim):
async def accept_process(self, sim):
for _ in range(self.inst_cnt):
res = await self.dut.accept.call(sim)
assert res["exception"] == self.result_q[0]["exception"]
assert res["result"] == self.result_q[0]["result"]
assert res["rp_dst"] == self.result_q[0]["rp_dst"]
self.result_q.popleft()
expected = self.results[res["rob_id"]]
assert res["rp_dst"] == expected["rp_dst"]
assert res["exception"] == expected["exception"]
assert res["result"] == expected["result"]
await self.random_wait_geom(sim, 0.9)

def test_randomized(self):
with self.run_simulation(self.dut, max_cycles=600) as sim:
with self.run_simulation(self.dut, max_cycles=700) as sim:
sim.add_testbench(self.issue_process)
sim.add_testbench(self.accept_process)
6 changes: 3 additions & 3 deletions test/params/test_configurations.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ class ISAStrTest:
),
ISAStrTest(
full_core_config,
"rv32imcbzicsr_zifencei_zicond_zaamo_xintmachinemode",
"rv32imcbzicsr_zifencei_zicond_zaamo_xintmachinemode",
"rv32imcbzicsr_zifencei_zicond_zaamo_xintmachinemode",
"rv32imacbzicsr_zifencei_zicond_xintmachinemode",
"rv32imacbzicsr_zifencei_zicond_xintmachinemode",
"rv32imacbzicsr_zifencei_zicond_xintmachinemode",
),
ISAStrTest(tiny_core_config, "rv32e", "rv32e", "rv32e"),
ISAStrTest(test_core_config, "rv32", "rv32", "rv32i"),
Expand Down

0 comments on commit 54355ab

Please sign in to comment.