From 54355abbeb20a4ae7549b487f38c33519a4b22f6 Mon Sep 17 00:00:00 2001 From: Piotr Wegrzyn Date: Thu, 16 Jan 2025 17:03:38 +0100 Subject: [PATCH] Implement Zalrsc --- coreblocks/arch/isa.py | 5 +- coreblocks/arch/isa_consts.py | 2 + coreblocks/arch/optypes.py | 4 + .../frontend/decoder/instr_description.py | 4 + .../func_blocks/fu/lsu/lsu_atomic_wrapper.py | 91 +++++++++++++++---- test/common/test_isa.py | 11 ++- test/frontend/test_instr_decoder.py | 1 + .../lsu/test_lsu_atomic_wrapper.py | 47 +++++++--- test/params/test_configurations.py | 6 +- 9 files changed, 138 insertions(+), 33 deletions(-) diff --git a/coreblocks/arch/isa.py b/coreblocks/arch/isa.py index d5c416611..92a69030a 100644 --- a/coreblocks/arch/isa.py +++ b/coreblocks/arch/isa.py @@ -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) @@ -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, } diff --git a/coreblocks/arch/isa_consts.py b/coreblocks/arch/isa_consts.py index e55fbb71d..d04da380c 100644 --- a/coreblocks/arch/isa_consts.py +++ b/coreblocks/arch/isa_consts.py @@ -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 diff --git a/coreblocks/arch/optypes.py b/coreblocks/arch/optypes.py index 799c1aac1..4232dd0fb 100644 --- a/coreblocks/arch/optypes.py +++ b/coreblocks/arch/optypes.py @@ -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() @@ -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, ], diff --git a/coreblocks/frontend/decoder/instr_description.py b/coreblocks/frontend/decoder/instr_description.py index f2cb34331..73fe93d24 100644 --- a/coreblocks/frontend/decoder/instr_description.py +++ b/coreblocks/frontend/decoder/instr_description.py @@ -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), + ], } diff --git a/coreblocks/func_blocks/fu/lsu/lsu_atomic_wrapper.py b/coreblocks/func_blocks/fu/lsu/lsu_atomic_wrapper.py index f96294c3f..cd5321d4b 100644 --- a/coreblocks/func_blocks/fu/lsu/lsu_atomic_wrapper.py +++ b/coreblocks/func_blocks/fu/lsu/lsu_atomic_wrapper.py @@ -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 @@ -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"])) @@ -82,24 +85,48 @@ 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) @@ -107,11 +134,26 @@ def _(): 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) @@ -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) @@ -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 @@ -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() diff --git a/test/common/test_isa.py b/test/common/test_isa.py index 4b7d3f3c8..e37ef1c85 100644 --- a/test/common/test_isa.py +++ b/test/common/test_isa.py @@ -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", @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/test/frontend/test_instr_decoder.py b/test/frontend/test_instr_decoder.py index a5ad33102..844db25ae 100644 --- a/test/frontend/test_instr_decoder.py +++ b/test/frontend/test_instr_decoder.py @@ -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): diff --git a/test/func_blocks/lsu/test_lsu_atomic_wrapper.py b/test/func_blocks/lsu/test_lsu_atomic_wrapper.py index e63bf7489..8d219e1fe 100644 --- a/test/func_blocks/lsu/test_lsu_atomic_wrapper.py +++ b/test/func_blocks/lsu/test_lsu_atomic_wrapper.py @@ -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 @@ -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) @@ -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) @@ -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 @@ -165,9 +189,10 @@ 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, @@ -175,7 +200,7 @@ def twos(x): "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: @@ -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) diff --git a/test/params/test_configurations.py b/test/params/test_configurations.py index 1ba9535b8..7d88ed7bc 100644 --- a/test/params/test_configurations.py +++ b/test/params/test_configurations.py @@ -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"),