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

Implement Zalrsc - load-reserved/store-conditional instructions extension #782

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
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
Loading