diff --git a/constants/ecp5_platforms.py b/constants/ecp5_platforms.py index 0e3545690..7230ae70a 100644 --- a/constants/ecp5_platforms.py +++ b/constants/ecp5_platforms.py @@ -97,8 +97,8 @@ def make_resources(pins: PinManager) -> list[Resource]: number, en=pins.p(), done=pins.p(), - data_in=pins.p(adapter.data_in.shape().width), - data_out=pins.p(adapter.data_out.shape().width), + data_in=pins.p(adapter.data_in.shape().size), + data_out=pins.p(adapter.data_out.shape().size), ) ] diff --git a/coreblocks/fu/fu_decoder.py b/coreblocks/fu/fu_decoder.py index 510ee30f0..30373e677 100644 --- a/coreblocks/fu/fu_decoder.py +++ b/coreblocks/fu/fu_decoder.py @@ -21,7 +21,7 @@ class Decoder(Elaboratable): def __init__(self, gen_params: GenParams, decode_fn: Type[IntFlag], ops: Sequence[tuple], check_optype: bool): layouts = gen_params.get(CommonLayoutFields) - self.exec_fn = Record(layouts.exec_fn_layout) + self.exec_fn = Signal(layouts.exec_fn_layout) self.decode_fn = Signal(decode_fn) self.ops = ops self.check_optype = check_optype diff --git a/coreblocks/fu/mul_unit.py b/coreblocks/fu/mul_unit.py index b55ff604e..0deba543a 100644 --- a/coreblocks/fu/mul_unit.py +++ b/coreblocks/fu/mul_unit.py @@ -45,13 +45,13 @@ def get_instructions(self) -> Sequence[tuple]: ] -def get_input(arg: Record) -> tuple[Value, Value]: +def get_input(arg: MethodStruct) -> tuple[Value, Value]: """ Operation of getting two input values. Parameters ---------- - arg: Record + arg: MethodStruct Arguments of functional unit issue call. Returns diff --git a/coreblocks/lsu/dummyLsu.py b/coreblocks/lsu/dummyLsu.py index 7a81333af..fceef8344 100644 --- a/coreblocks/lsu/dummyLsu.py +++ b/coreblocks/lsu/dummyLsu.py @@ -1,4 +1,5 @@ from amaranth import * +from amaranth.lib.data import View from transactron import Method, def_method, Transaction, TModule from coreblocks.params import * @@ -220,7 +221,7 @@ def elaborate(self, platform): precommiting = Signal() # start execution issued = Signal() # instruction was issued to the bus flush = Signal() # exception handling, requests are not issued - current_instr = Record(self.lsu_layouts.rs.data_layout) + current_instr = Signal(self.lsu_layouts.rs.data_layout) m.submodules.pma_checker = pma_checker = PMAChecker(self.gen_params) m.submodules.requester = requester = LSURequester(self.gen_params, self.bus) @@ -248,7 +249,7 @@ def _(): return {"rs_entry_id": 0} @def_method(m, self.insert) - def _(rs_data: Record, rs_entry_id: Value): + def _(rs_data: View, rs_entry_id: Value): m.d.sync += assign(current_instr, rs_data) m.d.sync += valid.eq(1) diff --git a/coreblocks/params/layouts.py b/coreblocks/params/layouts.py index d8fb5a523..066c6dd43 100644 --- a/coreblocks/params/layouts.py +++ b/coreblocks/params/layouts.py @@ -1,6 +1,8 @@ +from amaranth.lib.data import StructLayout from coreblocks.params import GenParams, OpType, Funct7, Funct3 from coreblocks.params.isa import ExceptionCause from transactron.utils import LayoutList, LayoutListField, layout_subset +from transactron.utils.transactron_helpers import from_method_layout, make_layout __all__ = [ "CommonLayoutFields", @@ -84,7 +86,7 @@ def __init__(self, gen_params: GenParams): self.instr: LayoutListField = ("instr", gen_params.isa.ilen) """RISC V instruction.""" - self.exec_fn_layout: LayoutList = [self.op_type, self.funct3, self.funct7] + self.exec_fn_layout = make_layout(self.op_type, self.funct3, self.funct7) """Decoded instruction, in layout form.""" self.exec_fn: LayoutListField = ("exec_fn", self.exec_fn_layout) @@ -136,48 +138,48 @@ def __init__(self, gen_params: GenParams): ) """Logical register number for the destination operand, before ROB allocation.""" - self.reg_alloc_in: LayoutList = [ + self.reg_alloc_in = make_layout( fields.exec_fn, fields.regs_l, fields.imm, fields.csr, fields.pc, - ] + ) - self.reg_alloc_out: LayoutList = [ + self.reg_alloc_out = make_layout( fields.exec_fn, fields.regs_l, self.regs_p_alloc_out, fields.imm, fields.csr, fields.pc, - ] + ) self.renaming_in = self.reg_alloc_out - self.renaming_out: LayoutList = [ + self.renaming_out = make_layout( fields.exec_fn, self.regs_l_rob_in, fields.regs_p, fields.imm, fields.csr, fields.pc, - ] + ) self.rob_allocate_in = self.renaming_out - self.rob_allocate_out: LayoutList = [ + self.rob_allocate_out = make_layout( fields.exec_fn, fields.regs_p, fields.rob_id, fields.imm, fields.csr, fields.pc, - ] + ) self.rs_select_in = self.rob_allocate_out - self.rs_select_out: LayoutList = [ + self.rs_select_out = make_layout( fields.exec_fn, fields.regs_p, fields.rob_id, @@ -186,11 +188,11 @@ def __init__(self, gen_params: GenParams): fields.imm, fields.csr, fields.pc, - ] + ) self.rs_insert_in = self.rs_select_out - self.free_rf_layout: LayoutList = [fields.reg_id] + self.free_rf_layout = make_layout(fields.reg_id) class RFLayouts: @@ -202,10 +204,10 @@ def __init__(self, gen_params: GenParams): self.valid: LayoutListField = ("valid", 1) """Physical register was assigned a value.""" - self.rf_read_in: LayoutList = [fields.reg_id] - self.rf_free: LayoutList = [fields.reg_id] - self.rf_read_out: LayoutList = [fields.reg_val, self.valid] - self.rf_write: LayoutList = [fields.reg_id, fields.reg_val] + self.rf_read_in = make_layout(fields.reg_id) + self.rf_free = make_layout(fields.reg_id) + self.rf_read_out = make_layout(fields.reg_val, self.valid) + self.rf_write = make_layout(fields.reg_id, fields.reg_val) class RATLayouts: @@ -217,19 +219,19 @@ def __init__(self, gen_params: GenParams): self.old_rp_dst: LayoutListField = ("old_rp_dst", gen_params.phys_regs_bits) """Physical register previously associated with the given logical register in RRAT.""" - self.frat_rename_in: LayoutList = [ + self.frat_rename_in = make_layout( fields.rl_s1, fields.rl_s2, fields.rl_dst, fields.rp_dst, - ] - self.frat_rename_out: LayoutList = [fields.rp_s1, fields.rp_s2] + ) + self.frat_rename_out = make_layout(fields.rp_s1, fields.rp_s2) - self.rrat_commit_in: LayoutList = [fields.rl_dst, fields.rp_dst] - self.rrat_commit_out: LayoutList = [self.old_rp_dst] + self.rrat_commit_in = make_layout(fields.rl_dst, fields.rp_dst) + self.rrat_commit_out = make_layout(self.old_rp_dst) - self.rrat_peek_in: LayoutList = [fields.rl_dst] - self.rrat_peek_out: LayoutList = self.rrat_commit_out + self.rrat_peek_in = make_layout(fields.rl_dst) + self.rrat_peek_out = self.rrat_commit_out class ROBLayouts: @@ -238,10 +240,10 @@ class ROBLayouts: def __init__(self, gen_params: GenParams): fields = gen_params.get(CommonLayoutFields) - self.data_layout: LayoutList = [ + self.data_layout = make_layout( fields.rl_dst, fields.rp_dst, - ] + ) self.rob_data: LayoutListField = ("rob_data", self.data_layout) """Data stored in a reorder buffer entry.""" @@ -255,26 +257,26 @@ def __init__(self, gen_params: GenParams): self.end: LayoutListField = ("end", gen_params.rob_entries_bits) """Index of the entry following the last (the latest) entry in the reorder buffer.""" - self.id_layout: LayoutList = [fields.rob_id] + self.id_layout = make_layout(fields.rob_id) - self.internal_layout: LayoutList = [ + self.internal_layout = make_layout( self.rob_data, self.done, fields.exception, - ] + ) - self.mark_done_layout: LayoutList = [ + self.mark_done_layout = make_layout( fields.rob_id, fields.exception, - ] + ) - self.peek_layout: LayoutList = [ + self.peek_layout = make_layout( self.rob_data, fields.rob_id, fields.exception, - ] + ) - self.get_indices: LayoutList = [self.start, self.end] + self.get_indices = make_layout(self.start, self.end) class RSLayoutFields: @@ -294,7 +296,7 @@ class RSFullDataLayout: def __init__(self, gen_params: GenParams): fields = gen_params.get(CommonLayoutFields) - self.data_layout: LayoutList = [ + self.data_layout = make_layout( fields.rp_s1, fields.rp_s2, ("rp_s1_reg", gen_params.phys_regs_bits), @@ -307,7 +309,7 @@ def __init__(self, gen_params: GenParams): fields.imm, fields.csr, fields.pc, - ] + ) class RSInterfaceLayouts: @@ -317,13 +319,13 @@ def __init__(self, gen_params: GenParams, *, rs_entries_bits: int, data_layout: fields = gen_params.get(CommonLayoutFields) rs_fields = gen_params.get(RSLayoutFields, rs_entries_bits=rs_entries_bits, data_layout=data_layout) - self.data_layout: LayoutList = data_layout + self.data_layout = from_method_layout(data_layout) - self.select_out: LayoutList = [rs_fields.rs_entry_id] + self.select_out = make_layout(rs_fields.rs_entry_id) - self.insert_in: LayoutList = [rs_fields.rs_data, rs_fields.rs_entry_id] + self.insert_in = make_layout(rs_fields.rs_data, rs_fields.rs_entry_id) - self.update_in: LayoutList = [fields.reg_id, fields.reg_val] + self.update_in = make_layout(fields.reg_id, fields.reg_val) class RetirementLayouts: @@ -332,7 +334,7 @@ class RetirementLayouts: def __init__(self, gen_params: GenParams): fields = gen_params.get(CommonLayoutFields) - self.precommit: LayoutList = [fields.rob_id, fields.side_fx] + self.precommit = make_layout(fields.rob_id, fields.side_fx) self.flushing = ("flushing", 1) """ Core is currently flushed """ @@ -367,7 +369,7 @@ def __init__(self, gen_params: GenParams, *, rs_entries_bits: int): self.rs = gen_params.get(RSInterfaceLayouts, rs_entries_bits=rs_entries_bits, data_layout=data_layout) rs_fields = gen_params.get(RSLayoutFields, rs_entries_bits=rs_entries_bits, data_layout=data_layout) - self.take_in: LayoutList = [rs_fields.rs_entry_id] + self.take_in = make_layout(rs_fields.rs_entry_id) self.take_out = layout_subset( data.data_layout, @@ -382,7 +384,7 @@ def __init__(self, gen_params: GenParams, *, rs_entries_bits: int): }, ) - self.get_ready_list_out: LayoutList = [self.ready_list] + self.get_ready_list_out = make_layout(self.ready_list) class ICacheLayouts: @@ -394,23 +396,23 @@ def __init__(self, gen_params: GenParams): self.error: LayoutListField = ("last", 1) """This is the last cache refill result.""" - self.issue_req: LayoutList = [fields.addr] + self.issue_req = make_layout(fields.addr) - self.accept_res: LayoutList = [ + self.accept_res = make_layout( fields.instr, fields.error, - ] + ) - self.start_refill: LayoutList = [ + self.start_refill = make_layout( fields.addr, - ] + ) - self.accept_refill: LayoutList = [ + self.accept_refill = make_layout( fields.addr, fields.data, fields.error, self.error, - ] + ) class FetchLayouts: @@ -425,14 +427,14 @@ def __init__(self, gen_params: GenParams): self.rvc: LayoutListField = ("rvc", 1) """Instruction is a compressed (two-byte) one.""" - self.raw_instr: LayoutList = [ + self.raw_instr = make_layout( fields.instr, fields.pc, self.access_fault, self.rvc, - ] + ) - self.resume: LayoutList = [("pc", gen_params.isa.xlen), ("resume_from_exception", 1)] + self.resume = make_layout(("pc", gen_params.isa.xlen), ("resume_from_exception", 1)) class DecodeLayouts: @@ -441,13 +443,13 @@ class DecodeLayouts: def __init__(self, gen_params: GenParams): fields = gen_params.get(CommonLayoutFields) - self.decoded_instr: LayoutList = [ + self.decoded_instr = make_layout( fields.exec_fn, fields.regs_l, fields.imm, fields.csr, fields.pc, - ] + ) class FuncUnitLayouts: @@ -459,7 +461,7 @@ def __init__(self, gen_params: GenParams): self.result: LayoutListField = ("result", gen_params.isa.xlen) """The result value produced in a functional unit.""" - self.issue: LayoutList = [ + self.issue = make_layout( fields.s1_val, fields.s2_val, fields.rp_dst, @@ -467,44 +469,46 @@ def __init__(self, gen_params: GenParams): fields.exec_fn, fields.imm, fields.pc, - ] + ) - self.accept: LayoutList = [ + self.accept = make_layout( fields.rob_id, self.result, fields.rp_dst, fields.exception, - ] + ) class UnsignedMulUnitLayouts: def __init__(self, gen_params: GenParams): - self.issue: LayoutList = [ + self.issue = make_layout( ("i1", gen_params.isa.xlen), ("i2", gen_params.isa.xlen), - ] + ) - self.accept: LayoutList = [ + self.accept = make_layout( ("o", 2 * gen_params.isa.xlen), - ] + ) class DivUnitLayouts: def __init__(self, gen_params: GenParams): - self.issue: LayoutList = [ + self.issue = make_layout( ("dividend", gen_params.isa.xlen), ("divisor", gen_params.isa.xlen), - ] + ) - self.accept: LayoutList = [ + self.accept = make_layout( ("quotient", gen_params.isa.xlen), ("remainder", gen_params.isa.xlen), - ] + ) class JumpBranchLayouts: def __init__(self, gen_params: GenParams): - self.verify_branch = [("from_pc", gen_params.isa.xlen), ("next_pc", gen_params.isa.xlen), ("misprediction", 1)] + self.verify_branch = make_layout( + ("from_pc", gen_params.isa.xlen), ("next_pc", gen_params.isa.xlen), ("misprediction", 1) + ) """ Hint for Branch Predictor about branch result """ @@ -540,11 +544,11 @@ def __init__(self, gen_params: GenParams): self.store: LayoutListField = ("store", 1) - self.issue: LayoutList = [fields.addr, fields.data, fields.funct3, self.store] + self.issue = make_layout(fields.addr, fields.data, fields.funct3, self.store) - self.issue_out: LayoutList = [fields.exception, fields.cause] + self.issue_out = make_layout(fields.exception, fields.cause) - self.accept: LayoutList = [fields.data, fields.exception, fields.cause] + self.accept = make_layout(fields.data, fields.exception, fields.cause) class PMALayouts: @@ -561,16 +565,16 @@ def __init__(self, gen_params: GenParams): self.rs_entries_bits = 0 - self.read: LayoutList = [ + self.read = make_layout( fields.data, ("read", 1), ("written", 1), - ] + ) - self.write: LayoutList = [fields.data] + self.write = make_layout(fields.data) - self._fu_read: LayoutList = [fields.data] - self._fu_write: LayoutList = [fields.data] + self._fu_read = make_layout(fields.data) + self._fu_write = make_layout(fields.data) data_layout = layout_subset( data.data_layout, @@ -600,15 +604,15 @@ class ExceptionRegisterLayouts: def __init__(self, gen_params: GenParams): fields = gen_params.get(CommonLayoutFields) - self.valid = ("valid", 1) + self.valid: LayoutListField = ("valid", 1) - self.report: LayoutList = [ + self.report = make_layout( fields.cause, fields.rob_id, fields.pc, - ] + ) - self.get = self.report + [self.valid] + self.get = StructLayout(self.report.members | make_layout(self.valid).members) class CoreInstructionCounterLayouts: diff --git a/coreblocks/peripherals/axi_lite.py b/coreblocks/peripherals/axi_lite.py index 98fa36a8f..4059e30cd 100644 --- a/coreblocks/peripherals/axi_lite.py +++ b/coreblocks/peripherals/axi_lite.py @@ -113,18 +113,18 @@ class AXILiteMasterMethodLayouts: def __init__(self, axil_params: AXILiteParameters): self.ra_request_layout = [ - ("addr", axil_params.addr_width, DIR_FANIN), - ("prot", 3, DIR_FANIN), + ("addr", axil_params.addr_width), + ("prot", 3), ] self.wa_request_layout = [ - ("addr", axil_params.addr_width, DIR_FANIN), - ("prot", 3, DIR_FANIN), + ("addr", axil_params.addr_width), + ("prot", 3), ] self.wd_request_layout = [ - ("data", axil_params.data_width, DIR_FANIN), - ("strb", axil_params.data_width // 8, DIR_FANIN), + ("data", axil_params.data_width), + ("strb", axil_params.data_width // 8), ] self.rd_response_layout = [ diff --git a/coreblocks/peripherals/bus_adapter.py b/coreblocks/peripherals/bus_adapter.py index 123aec2ba..139c5a1b3 100644 --- a/coreblocks/peripherals/bus_adapter.py +++ b/coreblocks/peripherals/bus_adapter.py @@ -1,7 +1,6 @@ from typing import Protocol from amaranth import * -from amaranth.hdl.rec import DIR_FANIN from coreblocks.peripherals.wishbone import WishboneMaster from coreblocks.peripherals.axi_lite import AXILiteMaster @@ -9,6 +8,7 @@ from transactron import Method, def_method, TModule from transactron.utils import HasElaborate from transactron.lib import Serializer +from transactron.utils.transactron_helpers import make_layout __all__ = ["BusMasterInterface", "WishboneMasterAdapter", "AXILiteMasterAdapter"] @@ -87,20 +87,20 @@ class CommonBusMasterMethodLayout: def __init__(self, bus_params: BusParametersInterface): self.bus_params = bus_params - self.request_read_layout = [ - ("addr", self.bus_params.addr_width, DIR_FANIN), - ("sel", self.bus_params.data_width // self.bus_params.granularity, DIR_FANIN), - ] + self.request_read_layout = make_layout( + ("addr", self.bus_params.addr_width), + ("sel", self.bus_params.data_width // self.bus_params.granularity), + ) - self.request_write_layout = [ - ("addr", self.bus_params.addr_width, DIR_FANIN), - ("data", self.bus_params.data_width, DIR_FANIN), - ("sel", self.bus_params.data_width // self.bus_params.granularity, DIR_FANIN), - ] + self.request_write_layout = make_layout( + ("addr", self.bus_params.addr_width), + ("data", self.bus_params.data_width), + ("sel", self.bus_params.data_width // self.bus_params.granularity), + ) - self.read_response_layout = [("data", self.bus_params.data_width), ("err", 1)] + self.read_response_layout = make_layout(("data", self.bus_params.data_width), ("err", 1)) - self.write_response_layout = [("err", 1)] + self.write_response_layout = make_layout(("err", 1)) class WishboneMasterAdapter(Elaboratable, BusMasterInterface): diff --git a/coreblocks/peripherals/wishbone.py b/coreblocks/peripherals/wishbone.py index 8aaded640..75f71522a 100644 --- a/coreblocks/peripherals/wishbone.py +++ b/coreblocks/peripherals/wishbone.py @@ -9,6 +9,7 @@ from transactron.lib import AdapterTrans, BasicFifo from transactron.utils import OneHotSwitchDynamic, assign, RoundRobin from transactron.lib.connectors import Forwarder +from transactron.utils.transactron_helpers import make_layout class WishboneParameters: @@ -96,14 +97,14 @@ class WishboneMasterMethodLayout: """ def __init__(self, wb_params: WishboneParameters): - self.request_layout = [ - ("addr", wb_params.addr_width, DIR_FANIN), - ("data", wb_params.data_width, DIR_FANIN), - ("we", 1, DIR_FANIN), - ("sel", wb_params.data_width // wb_params.granularity, DIR_FANIN), - ] + self.request_layout = make_layout( + ("addr", wb_params.addr_width), + ("data", wb_params.data_width), + ("we", 1), + ("sel", wb_params.data_width // wb_params.granularity), + ) - self.result_layout = [("data", wb_params.data_width), ("err", 1)] + self.result_layout = make_layout(("data", wb_params.data_width), ("err", 1)) class WishboneMaster(Elaboratable): @@ -139,7 +140,7 @@ def __init__(self, wb_params: WishboneParameters): self.result = Method(o=self.method_layouts.result_layout) # latched input signals - self.txn_req = Record(self.method_layouts.request_layout) + self.txn_req = Signal(self.method_layouts.request_layout) def elaborate(self, platform): m = TModule() diff --git a/coreblocks/scheduler/scheduler.py b/coreblocks/scheduler/scheduler.py index 31479c115..6e7e152bd 100644 --- a/coreblocks/scheduler/scheduler.py +++ b/coreblocks/scheduler/scheduler.py @@ -47,7 +47,7 @@ def elaborate(self, platform): m = TModule() free_reg = Signal(self.gen_params.phys_regs_bits) - data_out = Record(self.output_layout) + data_out = Signal(self.output_layout) with Transaction().body(m): instr = self.get_instr(m) @@ -95,7 +95,7 @@ def __init__(self, *, get_instr: Method, push_instr: Method, rename: Method, gen def elaborate(self, platform): m = TModule() - data_out = Record(self.output_layout) + data_out = Signal(self.output_layout) with Transaction().body(m): instr = self.get_instr(m) @@ -152,7 +152,7 @@ def __init__(self, *, get_instr: Method, push_instr: Method, rob_put: Method, ge def elaborate(self, platform): m = TModule() - data_out = Record(self.output_layout) + data_out = Signal(self.output_layout) with Transaction().body(m): instr = self.get_instr(m) @@ -239,7 +239,7 @@ def elaborate(self, platform): instr = self.get_instr(m) forwarder.write(m, instr) - data_out = Record(self.output_layout) + data_out = Signal(self.output_layout) for i, (alloc, optypes) in enumerate(self.rs_select): # checks if RS can perform this kind of operation @@ -332,7 +332,7 @@ def elaborate(self, platform): for i, rs_insert in enumerate(self.rs_insert): # connect only matching fields - arg = Record.like(rs_insert.data_in) + arg = Signal.like(rs_insert.data_in) m.d.comb += assign(arg, data, fields=AssignType.COMMON) # this assignment truncates signal width from max rs_entry_bits to target RS specific width m.d.comb += arg.rs_entry_id.eq(instr.rs_entry_id) diff --git a/coreblocks/scheduler/wakeup_select.py b/coreblocks/scheduler/wakeup_select.py index fbe6d40a4..724d6ffe7 100644 --- a/coreblocks/scheduler/wakeup_select.py +++ b/coreblocks/scheduler/wakeup_select.py @@ -41,14 +41,14 @@ def elaborate(self, platform): with Transaction().body(m): ready = self.get_ready(m) - ready_width = len(ready) + ready_width = ready.shape().size last = Signal(range(ready_width)) for i in range(ready_width): - with m.If(ready[i]): + with m.If(ready.ready_list[i]): m.d.comb += last.eq(i) row = self.take_row(m, last) - issue_rec = Record(self.gen_params.get(FuncUnitLayouts).issue) + issue_rec = Signal(self.gen_params.get(FuncUnitLayouts).issue) m.d.comb += assign(issue_rec, row, fields=AssignType.ALL) self.issue(m, issue_rec) diff --git a/coreblocks/stages/retirement.py b/coreblocks/stages/retirement.py index b32174bd3..35459f7df 100644 --- a/coreblocks/stages/retirement.py +++ b/coreblocks/stages/retirement.py @@ -197,7 +197,7 @@ def flush_instr(rob_entry): handler_pc = Signal(self.gen_params.isa.xlen) # mtvec without mode is [mxlen-1:2], mode is two last bits. Only direct mode is supported - m.d.av_comb += handler_pc.eq(m_csr.mtvec.read(m) & ~(0b11)) + m.d.av_comb += handler_pc.eq(m_csr.mtvec.read(m).data & ~(0b11)) resume_pc = Mux(continue_pc_override, continue_pc, handler_pc) m.d.sync += continue_pc_override.eq(0) diff --git a/coreblocks/structs_common/csr.py b/coreblocks/structs_common/csr.py index c3c409b2c..a01a028fa 100644 --- a/coreblocks/structs_common/csr.py +++ b/coreblocks/structs_common/csr.py @@ -1,4 +1,5 @@ from amaranth import * +from amaranth.lib.data import StructLayout from amaranth.lib.enum import IntEnum from dataclasses import dataclass @@ -17,6 +18,7 @@ ) from coreblocks.params.optypes import OpType from coreblocks.utils.protocols import FuncBlock +from transactron.utils.transactron_helpers import from_method_layout class PrivilegeLevel(IntEnum, shape=2): @@ -112,7 +114,7 @@ def __init__(self, csr_number: int, gen_params: GenParams, *, ro_bits: int = 0): self._fu_write = Method(i=csr_layouts._fu_write) self.value = Signal(gen_params.isa.xlen) - self.side_effects = Record([("read", 1), ("write", 1)]) + self.side_effects = Signal(StructLayout({"read": 1, "write": 1})) # append to global CSR list dm = gen_params.get(DependencyManager) @@ -121,9 +123,9 @@ def __init__(self, csr_number: int, gen_params: GenParams, *, ro_bits: int = 0): def elaborate(self, platform): m = TModule() - internal_method_layout = [("data", self.gen_params.isa.xlen), ("active", 1)] - write_internal = Record(internal_method_layout) - fu_write_internal = Record(internal_method_layout) + internal_method_layout = from_method_layout([("data", self.gen_params.isa.xlen), ("active", 1)]) + write_internal = Signal(internal_method_layout) + fu_write_internal = Signal(internal_method_layout) m.d.sync += self.side_effects.eq(0) @@ -228,7 +230,7 @@ def elaborate(self, platform): current_result = Signal(self.gen_params.isa.xlen) - instr = Record(self.csr_layouts.rs.data_layout + [("valid", 1)]) + instr = Signal(StructLayout(self.csr_layouts.rs.data_layout.members | {"valid": 1})) m.d.comb += ready_to_process.eq(precommitting & instr.valid & (instr.rp_s1 == 0)) diff --git a/coreblocks/structs_common/rob.py b/coreblocks/structs_common/rob.py index 0f01a6abf..54d9aa5fd 100644 --- a/coreblocks/structs_common/rob.py +++ b/coreblocks/structs_common/rob.py @@ -13,7 +13,7 @@ def __init__(self, gen_params: GenParams) -> None: self.mark_done = Method(i=layouts.mark_done_layout) self.peek = Method(o=layouts.peek_layout, nonexclusive=True) self.retire = Method() - self.data = Array(Record(layouts.internal_layout) for _ in range(2**gen_params.rob_entries_bits)) + self.data = Array(Signal(layouts.internal_layout) for _ in range(2**gen_params.rob_entries_bits)) self.get_indices = Method(o=layouts.get_indices, nonexclusive=True) def elaborate(self, platform): diff --git a/coreblocks/structs_common/rs.py b/coreblocks/structs_common/rs.py index 255f48a63..fe8d04ba4 100644 --- a/coreblocks/structs_common/rs.py +++ b/coreblocks/structs_common/rs.py @@ -5,6 +5,7 @@ from transactron import Method, def_method, TModule from coreblocks.params import RSLayouts, GenParams, OpType from transactron.core import RecordDict +from transactron.utils.transactron_helpers import make_layout __all__ = ["RS"] @@ -18,12 +19,11 @@ def __init__( self.rs_entries = rs_entries self.rs_entries_bits = (rs_entries - 1).bit_length() self.layouts = gen_params.get(RSLayouts, rs_entries_bits=self.rs_entries_bits) - self.internal_layout = [ + self.internal_layout = make_layout( ("rs_data", self.layouts.rs.data_layout), ("rec_full", 1), - ("rec_ready", 1), ("rec_reserved", 1), - ] + ) self.insert = Method(i=self.layouts.rs.insert_in) self.select = Method(o=self.layouts.rs.select_out) @@ -33,22 +33,23 @@ def __init__( self.ready_for = [list(op_list) for op_list in ready_for] self.get_ready_list = [Method(o=self.layouts.get_ready_list_out, nonexclusive=True) for _ in self.ready_for] - self.data = Array(Record(self.internal_layout) for _ in range(self.rs_entries)) + self.data = Array(Signal(self.internal_layout) for _ in range(self.rs_entries)) + self.data_ready = Signal(self.rs_entries) def elaborate(self, platform): m = TModule() m.submodules.enc_select = PriorityEncoder(width=self.rs_entries) - for record in self.data: - m.d.comb += record.rec_ready.eq( + for i, record in enumerate(self.data): + m.d.comb += self.data_ready[i].eq( ~record.rs_data.rp_s1.bool() & ~record.rs_data.rp_s2.bool() & record.rec_full.bool() ) select_vector = Cat(~record.rec_reserved for record in self.data) select_possible = select_vector.any() - take_vector = Cat(record.rec_ready & record.rec_full for record in self.data) + take_vector = Cat(self.data_ready[i] & record.rec_full for i, record in enumerate(self.data)) take_possible = take_vector.any() ready_lists: list[Value] = [] diff --git a/stubs/amaranth/hdl/ast.pyi b/stubs/amaranth/hdl/ast.pyi index fa115b316..98c2be9f2 100644 --- a/stubs/amaranth/hdl/ast.pyi +++ b/stubs/amaranth/hdl/ast.pyi @@ -7,12 +7,14 @@ from collections.abc import Callable, MutableMapping, MutableSequence, MutableSe from typing import Any, Generic, Iterable, Iterator, Mapping, NoReturn, Optional, Sequence, TypeVar, final, overload from enum import Enum from transactron.utils import ValueLike, ShapeLike, StatementLike +from amaranth.lib.data import View __all__ = ["Shape", "ShapeCastable", "signed", "unsigned", "Value", "Const", "C", "AnyConst", "AnySeq", "Operator", "Mux", "Part", "Slice", "Cat", "Repl", "Array", "ArrayProxy", "Signal", "ClockSignal", "ResetSignal", "ValueCastable", "Sample", "Past", "Stable", "Rose", "Fell", "Initial", "Statement", "Switch", "Property", "Assign", "Assert", "Assume", "Cover", "ValueKey", "ValueDict", "ValueSet", "SignalKey", "SignalDict", "SignalSet", "ValueLike", "ShapeLike", "StatementLike", "SwitchKey"] T = TypeVar("T") U = TypeVar("U") +_T_ShapeCastable = TypeVar("_T_ShapeCastable", bound=ShapeCastable, covariant=True) Flattenable = T | Iterable[Flattenable[T]] SwitchKey = str | int | Enum @@ -425,9 +427,19 @@ class Signal(Value, DUID, metaclass=_SignalMeta): Pa""" def __init__(self, shape: Optional[ShapeLike] = ..., *, name: Optional[str] = ..., reset: int | Enum = ..., reset_less: bool = ..., attrs: dict = ..., decoder: type[Enum] | Callable[[int], str] = ..., src_loc_at=...) -> None: ... - + + @overload + @staticmethod + def like(other: View[_T_ShapeCastable], *, name: Optional[str] = ..., name_suffix: Optional[str] =..., src_loc_at=..., **kwargs) -> View[_T_ShapeCastable]: + ... + + @overload @staticmethod def like(other: ValueLike, *, name: Optional[str] = ..., name_suffix: Optional[str] =..., src_loc_at=..., **kwargs) -> Signal: + ... + + @staticmethod + def like(other: ValueLike, *, name: Optional[str] = ..., name_suffix: Optional[str] =..., src_loc_at=..., **kwargs): """Create Signal based on another. """ ... @@ -438,6 +450,7 @@ class Signal(Value, DUID, metaclass=_SignalMeta): def __repr__(self) -> str: ... + name: str decoder: Any diff --git a/test/common/functions.py b/test/common/functions.py index eb7abf886..a8d64af8e 100644 --- a/test/common/functions.py +++ b/test/common/functions.py @@ -1,5 +1,6 @@ from amaranth import * from amaranth.hdl.ast import Statement +from amaranth.lib.data import Layout, StructLayout, View from amaranth.sim.core import Command from typing import TypeVar, Any, Generator, TypeAlias, TYPE_CHECKING, Union from transactron.utils._typing import RecordValueDict, RecordIntDict @@ -13,7 +14,7 @@ TestGen: TypeAlias = Generator[Union[Command, Value, Statement, "CoreblocksCommand", None], Any, T] -def set_inputs(values: RecordValueDict, field: Record) -> TestGen[None]: +def set_inputs(values: RecordValueDict, field: View) -> TestGen[None]: for name, value in values.items(): if isinstance(value, dict): yield from set_inputs(value, getattr(field, name)) @@ -21,14 +22,18 @@ def set_inputs(values: RecordValueDict, field: Record) -> TestGen[None]: yield getattr(field, name).eq(value) -def get_outputs(field: Record) -> TestGen[RecordIntDict]: +def get_outputs(field: View) -> TestGen[RecordIntDict]: # return dict of all signal values in a record because amaranth's simulator can't read all - # values of a Record in a single yield - it can only read Values (Signals) + # values of a View in a single yield - it can only read Values (Signals) result = {} - for name, _, _ in field.layout: - val = getattr(field, name) - if isinstance(val, Signal): + layout = field.shape() + assert isinstance(layout, StructLayout) + for name, fld in layout: + val = field[name] + if isinstance(fld.shape, Layout): + result[name] = yield from get_outputs(View(fld.shape, val)) + elif isinstance(val, Value): result[name] = yield val - else: # field is a Record - result[name] = yield from get_outputs(val) + else: + raise ValueError return result diff --git a/test/fu/test_jb_unit.py b/test/fu/test_jb_unit.py index 36242865c..559062989 100644 --- a/test/fu/test_jb_unit.py +++ b/test/fu/test_jb_unit.py @@ -1,4 +1,5 @@ from amaranth import * +from amaranth.lib.data import StructLayout from parameterized import parameterized_class from coreblocks.params import * @@ -16,7 +17,11 @@ class JumpBranchWrapper(Elaboratable): def __init__(self, gen_params: GenParams): self.jb = JumpBranchFuncUnit(gen_params) self.issue = self.jb.issue - self.accept = Method(o=gen_params.get(FuncUnitLayouts).accept + gen_params.get(JumpBranchLayouts).verify_branch) + self.accept = Method( + o=StructLayout( + gen_params.get(FuncUnitLayouts).accept.members | gen_params.get(JumpBranchLayouts).verify_branch.members + ) + ) def elaborate(self, platform): m = TModule() diff --git a/test/gtkw_extension.py b/test/gtkw_extension.py index 1229bad2c..835886273 100644 --- a/test/gtkw_extension.py +++ b/test/gtkw_extension.py @@ -1,5 +1,6 @@ from typing import Iterable, Mapping from contextlib import contextmanager +from amaranth.lib.data import View from amaranth.sim.pysim import _VCDWriter from amaranth import * from transactron.utils import flatten_signals @@ -13,6 +14,12 @@ def __init__(self, fragment, *, vcd_file, gtkw_file, traces): self._tree_traces = traces def close(self, timestamp): + def save_signal(value: Value): + for signal in value._rhs_signals(): # type: ignore + if signal in self.gtkw_names: + for name in self.gtkw_names[signal]: + self.gtkw_save.trace(name) + def gtkw_traces(traces): if isinstance(traces, Mapping): for k, v in traces.items(): @@ -28,12 +35,12 @@ def gtkw_traces(traces): gtkw_traces(v) elif len(traces.fields) == 1: # to make gtkwave view less verbose gtkw_traces(next(iter(traces.fields.values()))) - elif isinstance(traces, Signal): - if len(traces) > 1 and not traces.decoder: - suffix = "[{}:0]".format(len(traces) - 1) - else: - suffix = "" - self.gtkw_save.trace(".".join(self.gtkw_names[traces]) + suffix) + elif isinstance(traces, View): + v = Value.cast(traces) + with self.gtkw_save.group(v.name if isinstance(v, Signal) else ""): + save_signal(v) + elif isinstance(traces, Value): + save_signal(traces) if self.vcd_writer is not None: self.vcd_writer.close(timestamp) diff --git a/test/scheduler/test_wakeup_select.py b/test/scheduler/test_wakeup_select.py index a24ae7114..6e0ff320b 100644 --- a/test/scheduler/test_wakeup_select.py +++ b/test/scheduler/test_wakeup_select.py @@ -1,5 +1,6 @@ from typing import Optional, cast from amaranth import * +from amaranth.lib.data import StructLayout from amaranth.sim import Settle from collections import deque @@ -49,14 +50,14 @@ def setUp(self): random.seed(42) - def random_entry(self, layout) -> RecordIntDict: + def random_entry(self, layout: StructLayout) -> RecordIntDict: result = {} - for key, width_or_layout in layout: + for key, width_or_layout in layout.members.items(): if isinstance(width_or_layout, int): result[key] = random.randrange(width_or_layout) elif isclass(width_or_layout) and issubclass(width_or_layout, Enum): result[key] = random.choice(list(width_or_layout)) - else: + elif isinstance(width_or_layout, StructLayout): result[key] = self.random_entry(width_or_layout) return result diff --git a/test/structs_common/test_rs.py b/test/structs_common/test_rs.py index aa5fe67ea..64403c1a0 100644 --- a/test/structs_common/test_rs.py +++ b/test/structs_common/test_rs.py @@ -8,14 +8,11 @@ def create_check_list(rs_entries_bits: int, insert_list: list[dict]) -> list[dict]: - check_list = [ - {"rs_data": None, "rec_ready": 0, "rec_reserved": 0, "rec_full": 0} for _ in range(2**rs_entries_bits) - ] + check_list = [{"rs_data": None, "rec_reserved": 0, "rec_full": 0} for _ in range(2**rs_entries_bits)] for params in insert_list: entry_id = params["rs_entry_id"] check_list[entry_id]["rs_data"] = params["rs_data"] - check_list[entry_id]["rec_ready"] = 1 if params["rs_data"]["rp_s1"] | params["rs_data"]["rp_s2"] == 0 else 0 check_list[entry_id]["rec_full"] = 1 check_list[entry_id]["rec_reserved"] = 1 @@ -111,7 +108,6 @@ def simulation_process(self): # Check if RS state is as expected for expected, record in zip(self.check_list, self.m._dut.data): self.assertEqual((yield record.rec_full), expected["rec_full"]) - self.assertEqual((yield record.rec_ready), expected["rec_ready"]) self.assertEqual((yield record.rec_reserved), expected["rec_reserved"]) # Reserve the last entry, then select ready should be false @@ -174,12 +170,12 @@ def simulation_process(self): # Update second entry first SP, instruction should be not ready value_sp1 = 1010 - self.assertEqual((yield self.m._dut.data[1].rec_ready), 0) + self.assertEqual((yield self.m._dut.data_ready[1]), 0) yield from self.m.update.call(reg_id=2, reg_val=value_sp1) yield Settle() self.assertEqual((yield self.m._dut.data[1].rs_data.rp_s1), 0) self.assertEqual((yield self.m._dut.data[1].rs_data.s1_val), value_sp1) - self.assertEqual((yield self.m._dut.data[1].rec_ready), 0) + self.assertEqual((yield self.m._dut.data_ready[1]), 0) # Update second entry second SP, instruction should be ready value_sp2 = 2020 @@ -187,7 +183,7 @@ def simulation_process(self): yield Settle() self.assertEqual((yield self.m._dut.data[1].rs_data.rp_s2), 0) self.assertEqual((yield self.m._dut.data[1].rs_data.s2_val), value_sp2) - self.assertEqual((yield self.m._dut.data[1].rec_ready), 1) + self.assertEqual((yield self.m._dut.data_ready[1]), 1) # Insert new instruction to entries 0 and 1, check if update of multiple registers works reg_id = 4 @@ -210,7 +206,7 @@ def simulation_process(self): for index in range(2): yield from self.m.insert.call(rs_entry_id=index, rs_data=data) yield Settle() - self.assertEqual((yield self.m._dut.data[index].rec_ready), 0) + self.assertEqual((yield self.m._dut.data_ready[index]), 0) yield from self.m.update.call(reg_id=reg_id, reg_val=value_spx) yield Settle() @@ -219,7 +215,7 @@ def simulation_process(self): self.assertEqual((yield self.m._dut.data[index].rs_data.rp_s2), 0) self.assertEqual((yield self.m._dut.data[index].rs_data.s1_val), value_spx) self.assertEqual((yield self.m._dut.data[index].rs_data.s2_val), value_spx) - self.assertEqual((yield self.m._dut.data[index].rec_ready), 1) + self.assertEqual((yield self.m._dut.data_ready[index]), 1) class TestRSMethodTake(TestCaseWithSimulator): @@ -305,8 +301,8 @@ def simulation_process(self): for index in range(2): yield from self.m.insert.call(rs_entry_id=index, rs_data=entry_data) yield Settle() - self.assertEqual((yield self.m._dut.data[index].rec_ready), 1) self.assertEqual((yield self.m._dut.take.ready), 1) + self.assertEqual((yield self.m._dut.data_ready[index]), 1) data = yield from self.m.take.call(rs_entry_id=0) for key in data: diff --git a/test/transactions/test_assign.py b/test/transactions/test_assign.py index 0659a01e4..88ad6a412 100644 --- a/test/transactions/test_assign.py +++ b/test/transactions/test_assign.py @@ -24,7 +24,7 @@ def mkproxy(layout): - arr = Array([Record(layout) for _ in range(4)]) + arr = Array([Signal(reclayout2datalayout(layout)) for _ in range(4)]) sig = Signal(2) return arr[sig] @@ -40,7 +40,6 @@ def mkstruct(layout): params_mk = [ - ("rec", Record), ("proxy", mkproxy), ("struct", mkstruct), ] diff --git a/test/transactions/test_methods.py b/test/transactions/test_methods.py index 838199e0a..6298dfabe 100644 --- a/test/transactions/test_methods.py +++ b/test/transactions/test_methods.py @@ -5,6 +5,7 @@ from ..common import TestCaseWithSimulator, TestbenchIO, data_layout from transactron import * +from transactron.utils import MethodStruct from transactron.lib import * from parameterized import parameterized @@ -41,7 +42,7 @@ def definition(arg): self.do_test_definition(definition) def test_fields_valid2(self): - rec = Record([("bar1", 4), ("bar2", 6)]) + rec = Signal(from_method_layout([("bar1", 4), ("bar2", 6)])) def definition(arg): return {"foo1": Signal(3), "foo2": rec} @@ -55,7 +56,7 @@ def definition(arg): self.do_test_definition(definition) def test_fields_valid4(self): - def definition(arg: Record): + def definition(arg: MethodStruct): return arg self.do_test_definition(definition) @@ -560,8 +561,8 @@ def __init__(self, n=2, ready_function=lambda arg: arg.data != 3): self.method = Method(i=data_layout(n)) self.ready_function = ready_function - self.in_t1 = Record(data_layout(n)) - self.in_t2 = Record(data_layout(n)) + self.in_t1 = Signal(from_method_layout(data_layout(n))) + self.in_t2 = Signal(from_method_layout(data_layout(n))) self.ready = Signal() self.req_t1 = Signal() self.req_t2 = Signal() diff --git a/test/transactions/test_simultaneous.py b/test/transactions/test_simultaneous.py index 9a55bce36..6b5d2d0b9 100644 --- a/test/transactions/test_simultaneous.py +++ b/test/transactions/test_simultaneous.py @@ -108,7 +108,7 @@ def elaborate(self, platform): m = TModule() with Transaction().body(m, request=self.request): - self.target(m, self.data ^ self.source(m)) + self.target(m, self.data ^ self.source(m).data) return m diff --git a/test/transactions/test_transaction_lib.py b/test/transactions/test_transaction_lib.py index 8c05cea66..d37d3ad1f 100644 --- a/test/transactions/test_transaction_lib.py +++ b/test/transactions/test_transaction_lib.py @@ -13,7 +13,7 @@ from transactron.core import RecordDict from transactron.lib import * from coreblocks.utils import * -from transactron.utils._typing import LayoutLike, ModuleLike +from transactron.utils._typing import ModuleLike, MethodStruct from transactron.utils import ModuleConnector from ..common import ( SimpleTestCircuit, @@ -25,7 +25,7 @@ class RevConnect(Elaboratable): - def __init__(self, layout: LayoutLike): + def __init__(self, layout: MethodLayout): self.connect = Connect(rev_layout=layout) self.read = self.connect.write self.write = self.connect.read @@ -235,7 +235,7 @@ def process(): class ManyToOneConnectTransTestCircuit(Elaboratable): - def __init__(self, count: int, lay: LayoutLike): + def __init__(self, count: int, lay: MethodLayout): self.count = count self.lay = lay self.inputs = [] @@ -353,20 +353,20 @@ def elaborate(self, platform): layout = data_layout(self.iosize) - def itransform_rec(m: ModuleLike, v: Record) -> Record: - s = Record.like(v) + def itransform_rec(m: ModuleLike, v: MethodStruct) -> MethodStruct: + s = Signal.like(v) m.d.comb += s.data.eq(v.data + 1) return s - def otransform_rec(m: ModuleLike, v: Record) -> Record: - s = Record.like(v) + def otransform_rec(m: ModuleLike, v: MethodStruct) -> MethodStruct: + s = Signal.like(v) m.d.comb += s.data.eq(v.data - 1) return s - def itransform_dict(_, v: Record) -> RecordDict: + def itransform_dict(_, v: MethodStruct) -> RecordDict: return {"data": v.data + 1} - def otransform_dict(_, v: Record) -> RecordDict: + def otransform_dict(_, v: MethodStruct) -> RecordDict: return {"data": v.data - 1} if self.use_dicts: @@ -383,11 +383,11 @@ def otransform_dict(_, v: Record) -> RecordDict: ometh = Method(i=layout, o=layout) @def_method(m, imeth) - def _(arg: Record): + def _(arg: MethodStruct): return itransform(m, arg) @def_method(m, ometh) - def _(arg: Record): + def _(arg: MethodStruct): return otransform(m, arg) trans = MethodMap(self.target.adapter.iface, i_transform=(layout, imeth), o_transform=(layout, ometh)) @@ -471,7 +471,7 @@ def test_method_filter(self, use_condition): self.initialize() def condition(_, v): - return v[0] + return v.data[0] self.tc = SimpleTestCircuit(MethodFilter(self.target.adapter.iface, condition, use_condition=use_condition)) m = ModuleConnector(test_circuit=self.tc, target=self.target, cmeth=self.cmeth) @@ -501,7 +501,7 @@ def elaborate(self, platform): combiner = None if self.add_combiner: - combiner = (layout, lambda _, vs: {"data": sum(vs)}) + combiner = (layout, lambda _, vs: {"data": sum(x.data for x in vs)}) product = MethodProduct(methods, combiner) diff --git a/transactron/core.py b/transactron/core.py index 99c4e6734..6ef868c9a 100644 --- a/transactron/core.py +++ b/transactron/core.py @@ -23,6 +23,8 @@ from itertools import count, chain, filterfalse, product from amaranth.hdl.dsl import FSM, _ModuleBuilderDomain +from transactron.utils.assign import AssignArg + from .graph import Owned, OwnershipGraph, Direction from transactron.utils import * from transactron.utils.transactron_helpers import _graph_ccs @@ -518,9 +520,9 @@ def visual_graph(self, fragment): graph = OwnershipGraph(fragment) method_map = MethodMap(self.transactions) for method, transactions in method_map.transactions_by_method.items(): - if len(method.data_in) > len(method.data_out): + if len(method.data_in.as_value()) > len(method.data_out.as_value()): direction = Direction.IN - elif len(method.data_in) < len(method.data_out): + elif method.data_in.shape().size < method.data_out.shape().size: direction = Direction.OUT else: direction = Direction.INOUT @@ -880,8 +882,8 @@ class TransactionBase(Owned, Protocol): defined: bool = False name: str src_loc: SrcLoc - method_uses: dict["Method", tuple[Record, Signal]] - method_calls: defaultdict["Method", list[tuple[CtrlPath, Record, ValueLike]]] + method_uses: dict["Method", tuple[MethodStruct, Signal]] + method_calls: defaultdict["Method", list[tuple[CtrlPath, MethodStruct, ValueLike]]] relations: list[RelationBase] simultaneous_list: list[TransactionOrMethod] independent_list: list[TransactionOrMethod] @@ -1145,9 +1147,10 @@ class Method(TransactionBase): behavior.) Calling a `Method` always takes a single clock cycle. Data is combinationally transferred between to and from `Method`\\s - using Amaranth `Record`\\s. The transfer can take place in both directions - at the same time: from the called `Method` to the caller (`data_out`) - and from the caller to the called `Method` (`data_in`). + using Amaranth structures (`View` with a `StructLayout`). The transfer + can take place in both directions at the same time: from the called + `Method` to the caller (`data_out`) and from the caller to the called + `Method` (`data_in`). A module which defines a `Method` should use `body` or `def_method` to describe the method's effect on the module state. @@ -1162,10 +1165,10 @@ class Method(TransactionBase): run: Signal, out Signals that the method is called in the current cycle by some `Transaction`. Defined by the `TransactionManager`. - data_in: Record, out + data_in: MethodStruct, out Contains the data passed to the `Method` by the caller (a `Transaction` or another `Method`). - data_out: Record, in + data_out: MethodStruct, in Contains the data passed from the `Method` to the caller (a `Transaction` or another `Method`). Typically defined by calling `body`. @@ -1187,12 +1190,10 @@ def __init__( name: str or None Name hint for this `Method`. If `None` (default) the name is inferred from the variable name this `Method` is assigned to. - i: record layout - The format of `data_in`. - An `int` corresponds to a `Record` with a single `data` field. - o: record layout + i: method layout The format of `data_in`. - An `int` corresponds to a `Record` with a single `data` field. + o: method layout + The format of `data_out`. nonexclusive: bool If true, the method is non-exclusive: it can be called by multiple transactions in the same clock cycle. If such a situation happens, @@ -1211,13 +1212,21 @@ def __init__( self.name = name or tracer.get_var_name(depth=2, default=owner_name) self.ready = Signal(name=self.owned_name + "_ready") self.run = Signal(name=self.owned_name + "_run") - self.data_in = Record(i) - self.data_out = Record(o) + self.data_in: MethodStruct = Signal(from_method_layout(i)) + self.data_out: MethodStruct = Signal(from_method_layout(o)) self.nonexclusive = nonexclusive self.single_caller = single_caller self.validate_arguments: Optional[Callable[..., ValueLike]] = None if nonexclusive: - assert len(self.data_in) == 0 + assert len(self.data_in.as_value()) == 0 + + @property + def layout_in(self): + return self.data_in.shape() + + @property + def layout_out(self): + return self.data_out.shape() @staticmethod def like(other: "Method", *, name: Optional[str] = None, src_loc: int | SrcLoc = 0) -> "Method": @@ -1241,7 +1250,7 @@ def like(other: "Method", *, name: Optional[str] = None, src_loc: int | SrcLoc = Method The freshly constructed `Method`. """ - return Method(name=name, i=other.data_in.layout, o=other.data_out.layout, src_loc=get_src_loc(src_loc)) + return Method(name=name, i=other.layout_in, o=other.layout_out, src_loc=get_src_loc(src_loc)) def proxy(self, m: TModule, method: "Method"): """Define as a proxy for another method. @@ -1269,7 +1278,7 @@ def body( ready: ValueLike = C(1), out: ValueLike = C(0, 0), validate_arguments: Optional[Callable[..., ValueLike]] = None, - ) -> Iterator[Record]: + ) -> Iterator[MethodStruct]: """Define method body The `body` context manager can be used to define the actions @@ -1288,7 +1297,7 @@ def body( Signal to indicate if the method is ready to be run. By default it is `Const(1)`, so the method is always ready. Assigned combinationially to the `ready` attribute. - out : Record, in + out : Value, in Data generated by the `Method`, which will be passed to the caller (a `Transaction` or another `Method`). Assigned combinationally to the `data_out` attribute. @@ -1327,14 +1336,14 @@ def body( with m.AvoidedIf(self.run): yield self.data_in - def _validate_arguments(self, arg_rec: Record) -> ValueLike: + def _validate_arguments(self, arg_rec: MethodStruct) -> ValueLike: if self.validate_arguments is not None: return self.ready & method_def_helper(self, self.validate_arguments, arg_rec) return self.ready def __call__( - self, m: TModule, arg: Optional[RecordDict] = None, enable: ValueLike = C(1), /, **kwargs: RecordDict - ) -> Record: + self, m: TModule, arg: Optional[AssignArg] = None, enable: ValueLike = C(1), /, **kwargs: AssignArg + ) -> MethodStruct: """Call a method. Methods can only be called from transaction and method bodies. @@ -1348,7 +1357,7 @@ def __call__( m : TModule Module in which operations on signals should be executed, arg : Value or dict of Values - Call argument. Can be passed as a `Record` of the method's + Call argument. Can be passed as a `View` of the method's input layout or as a dictionary. Alternative syntax uses keyword arguments. enable : Value @@ -1361,7 +1370,7 @@ def __call__( Returns ------- - data_out : Record + data_out : MethodStruct The result of the method call. Examples @@ -1381,7 +1390,7 @@ def __call__( with Transaction().body(m): ret = my_sum_method(m, {"arg1": 2, "arg2": 3}) """ - arg_rec = Record.like(self.data_in) + arg_rec = Signal.like(self.data_in) if arg is not None and kwargs: raise ValueError(f"Method '{self.name}' call with both keyword arguments and legacy record argument") @@ -1399,7 +1408,7 @@ def __call__( caller.method_calls[self].append((m.ctrl_path, arg_rec, enable_sig)) if self not in caller.method_uses: - arg_rec_use = Record.like(self.data_in) + arg_rec_use = Signal(self.layout_in) arg_rec_enable_sig = Signal() caller.method_uses[self] = (arg_rec_use, arg_rec_enable_sig) @@ -1427,9 +1436,9 @@ def def_method( The decorated function should take keyword arguments corresponding to the fields of the method's input layout. The `**kwargs` syntax is supported. Alternatively, it can take one argument named `arg`, which will be a - record with input signals. + structure with input signals. - The returned value can be either a record with the method's output layout + The returned value can be either a structure with the method's output layout or a dictionary of outputs. Parameters @@ -1469,7 +1478,7 @@ def _(arg1, arg2): def _(**args): return args["arg1"] + args["arg2"] - Alternative syntax (arg record): + Alternative syntax (arg structure): .. highlight:: python .. code-block:: python @@ -1479,8 +1488,8 @@ def _(arg): return {"res": arg.arg1 + arg.arg2} """ - def decorator(func: Callable[..., Optional[RecordDict]]): - out = Record.like(method.data_out) + def decorator(func: Callable[..., Optional[AssignArg]]): + out = Signal(method.layout_out) ret_out = None with method.body(m, ready=ready, out=out, validate_arguments=validate_arguments) as arg: diff --git a/transactron/lib/adapters.py b/transactron/lib/adapters.py index 6f99e8983..ed7f2640f 100644 --- a/transactron/lib/adapters.py +++ b/transactron/lib/adapters.py @@ -1,6 +1,6 @@ from amaranth import * -from ..utils import SrcLoc, get_src_loc +from ..utils import SrcLoc, get_src_loc, MethodStruct from ..core import * from ..core import SignalBundle from ..utils._typing import type_self_kwargs_as @@ -13,8 +13,8 @@ class AdapterBase(Elaboratable): - data_in: Record - data_out: Record + data_in: MethodStruct + data_out: MethodStruct def __init__(self, iface: Method): self.iface = iface @@ -56,8 +56,8 @@ def __init__(self, iface: Method, *, src_loc: int | SrcLoc = 0): """ super().__init__(iface) self.src_loc = get_src_loc(src_loc) - self.data_in = Record.like(iface.data_in) - self.data_out = Record.like(iface.data_out) + self.data_in = Signal.like(iface.data_in) + self.data_out = Signal.like(iface.data_out) def elaborate(self, platform): m = TModule() @@ -105,8 +105,8 @@ def __init__(self, **kwargs): kwargs["src_loc"] = get_src_loc(kwargs.setdefault("src_loc", 0)) super().__init__(Method(**kwargs)) - self.data_in = Record.like(self.iface.data_out) - self.data_out = Record.like(self.iface.data_in) + self.data_in = Signal.like(self.iface.data_out) + self.data_out = Signal.like(self.iface.data_in) def elaborate(self, platform): m = TModule() diff --git a/transactron/lib/buttons.py b/transactron/lib/buttons.py index ec3c796ce..59bf081b5 100644 --- a/transactron/lib/buttons.py +++ b/transactron/lib/buttons.py @@ -1,4 +1,6 @@ from amaranth import * + +from transactron.utils.transactron_helpers import from_method_layout from ..core import * from ..utils import SrcLoc, get_src_loc @@ -17,10 +19,10 @@ class ClickIn(Elaboratable): ---------- get: Method The method for retrieving data from the input. Accepts an empty - argument, returns a `Record`. + argument, returns a structure. btn: Signal, in The button input. - dat: Record, in + dat: MethodStruct, in The data input. """ @@ -28,7 +30,7 @@ def __init__(self, layout: MethodLayout, src_loc: int | SrcLoc = 0): """ Parameters ---------- - layout: record layout + layout: method layout The data format for the input. src_loc: int | SrcLoc How many stack frames deep the source location is taken from. @@ -37,7 +39,7 @@ def __init__(self, layout: MethodLayout, src_loc: int | SrcLoc = 0): src_loc = get_src_loc(src_loc) self.get = Method(o=layout, src_loc=src_loc) self.btn = Signal() - self.dat = Record(layout) + self.dat = Signal(from_method_layout(layout)) def elaborate(self, platform): m = TModule() @@ -73,11 +75,11 @@ class ClickOut(Elaboratable): Attributes ---------- put: Method - The method for retrieving data from the input. Accepts a `Record`, + The method for retrieving data from the input. Accepts a structure, returns empty result. btn: Signal, in The button input. - dat: Record, out + dat: MethodStruct, out The data output. """ @@ -85,7 +87,7 @@ def __init__(self, layout: MethodLayout, *, src_loc: int | SrcLoc = 0): """ Parameters ---------- - layout: record layout + layout: method layout The data format for the output. src_loc: int | SrcLoc How many stack frames deep the source location is taken from. @@ -94,7 +96,7 @@ def __init__(self, layout: MethodLayout, *, src_loc: int | SrcLoc = 0): src_loc = get_src_loc(src_loc) self.put = Method(i=layout, src_loc=src_loc) self.btn = Signal() - self.dat = Record(layout) + self.dat = Signal(from_method_layout(layout)) def elaborate(self, platform): m = TModule() diff --git a/transactron/lib/connectors.py b/transactron/lib/connectors.py index 81918c2a6..98c839246 100644 --- a/transactron/lib/connectors.py +++ b/transactron/lib/connectors.py @@ -1,5 +1,7 @@ from amaranth import * import amaranth.lib.fifo + +from transactron.utils.transactron_helpers import from_method_layout from ..core import * from ..utils import SrcLoc, get_src_loc @@ -24,9 +26,9 @@ class FIFO(Elaboratable): Attributes ---------- read: Method - The read method. Accepts an empty argument, returns a `Record`. + The read method. Accepts an empty argument, returns a structure. write: Method - The write method. Accepts a `Record`, returns empty result. + The write method. Accepts a structure, returns empty result. """ def __init__( @@ -35,8 +37,8 @@ def __init__( """ Parameters ---------- - layout: record layout - The format of records stored in the FIFO. + layout: method layout + The format of structures stored in the FIFO. depth: int Size of the FIFO. fifoType: Elaboratable @@ -46,7 +48,7 @@ def __init__( How many stack frames deep the source location is taken from. Alternatively, the source location to use instead of the default. """ - self.width = len(Record(layout)) + self.width = from_method_layout(layout).size self.depth = depth self.fifoType = fifo_type @@ -92,17 +94,17 @@ class Forwarder(Elaboratable): Attributes ---------- read: Method - The read method. Accepts an empty argument, returns a `Record`. + The read method. Accepts an empty argument, returns a structure. write: Method - The write method. Accepts a `Record`, returns empty result. + The write method. Accepts a structure, returns empty result. """ def __init__(self, layout: MethodLayout, *, src_loc: int | SrcLoc = 0): """ Parameters ---------- - layout: record layout - The format of records forwarded. + layout: method layout + The format of structures forwarded. src_loc: int | SrcLoc How many stack frames deep the source location is taken from. Alternatively, the source location to use instead of the default. @@ -111,7 +113,7 @@ def __init__(self, layout: MethodLayout, *, src_loc: int | SrcLoc = 0): self.read = Method(o=layout, src_loc=src_loc) self.write = Method(i=layout, src_loc=src_loc) self.clear = Method(src_loc=src_loc) - self.head = Record.like(self.read.data_out) + self.head = Signal.like(self.read.data_out) self.clear.add_conflict(self.read, Priority.LEFT) self.clear.add_conflict(self.write, Priority.LEFT) @@ -119,9 +121,9 @@ def __init__(self, layout: MethodLayout, *, src_loc: int | SrcLoc = 0): def elaborate(self, platform): m = TModule() - reg = Record.like(self.read.data_out) + reg = Signal.like(self.read.data_out) reg_valid = Signal() - read_value = Record.like(self.read.data_out) + read_value = Signal.like(self.read.data_out) m.d.comb += self.head.eq(read_value) self.write.schedule_before(self.read) # to avoid combinational loops @@ -159,21 +161,21 @@ class Connect(Elaboratable): Attributes ---------- read: Method - The read method. Accepts a (possibly empty) `Record`, returns - a `Record`. + The read method. Accepts a (possibly empty) structure, returns + a structure. write: Method - The write method. Accepts a `Record`, returns a (possibly empty) - `Record`. + The write method. Accepts a structure, returns a (possibly empty) + structure. """ def __init__(self, layout: MethodLayout = (), rev_layout: MethodLayout = (), *, src_loc: int | SrcLoc = 0): """ Parameters ---------- - layout: record layout - The format of records forwarded. - rev_layout: record layout - The format of records forwarded in the reverse direction. + layout: method layout + The format of structures forwarded. + rev_layout: method layout + The format of structures forwarded in the reverse direction. src_loc: int | SrcLoc How many stack frames deep the source location is taken from. Alternatively, the source location to use instead of the default. @@ -185,8 +187,8 @@ def __init__(self, layout: MethodLayout = (), rev_layout: MethodLayout = (), *, def elaborate(self, platform): m = TModule() - read_value = Record.like(self.read.data_out) - rev_read_value = Record.like(self.write.data_out) + read_value = Signal.like(self.read.data_out) + rev_read_value = Signal.like(self.write.data_out) self.write.simultaneous(self.read) @@ -232,8 +234,8 @@ def elaborate(self, platform): m = TModule() with Transaction(src_loc=self.src_loc).body(m): - data1 = Record.like(self.method1.data_out) - data2 = Record.like(self.method2.data_out) + data1 = Signal.like(self.method1.data_out) + data2 = Signal.like(self.method2.data_out) m.d.top_comb += data1.eq(self.method1(m, data2)) m.d.top_comb += data2.eq(self.method2(m, data1)) diff --git a/transactron/lib/fifo.py b/transactron/lib/fifo.py index 3c74dad04..92ac0f7bb 100644 --- a/transactron/lib/fifo.py +++ b/transactron/lib/fifo.py @@ -1,8 +1,8 @@ from amaranth import * from transactron import Method, def_method, Priority, TModule -from transactron.utils._typing import ValueLike, MethodLayout, SrcLoc +from transactron.utils._typing import ValueLike, MethodLayout, SrcLoc, MethodStruct from transactron.utils.amaranth_ext import mod_incr -from transactron.utils.transactron_helpers import get_src_loc +from transactron.utils.transactron_helpers import from_method_layout, get_src_loc class BasicFifo(Elaboratable): @@ -11,10 +11,10 @@ class BasicFifo(Elaboratable): Attributes ---------- read: Method - Reads from the FIFO. Accepts an empty argument, returns a `Record`. + Reads from the FIFO. Accepts an empty argument, returns a structure. Ready only if the FIFO is not empty. write: Method - Writes to the FIFO. Accepts a `Record`, returns empty result. + Writes to the FIFO. Accepts a structure, returns empty result. Ready only if the FIFO is not full. clear: Method Clears the FIFO entries. Has priority over `read` and `write` methods. @@ -26,9 +26,8 @@ def __init__(self, layout: MethodLayout, depth: int, *, src_loc: int | SrcLoc = """ Parameters ---------- - layout: record layout + layout: method layout Layout of data stored in the FIFO. - If integer is given, Record with field `data` and width of this paramter is used as internal layout. depth: int Size of the FIFO. src_loc: int | SrcLoc @@ -36,14 +35,14 @@ def __init__(self, layout: MethodLayout, depth: int, *, src_loc: int | SrcLoc = Alternatively, the source location to use instead of the default. """ self.layout = layout - self.width = len(Record(self.layout)) + self.width = from_method_layout(self.layout).size self.depth = depth src_loc = get_src_loc(src_loc) self.read = Method(o=self.layout, src_loc=src_loc) self.write = Method(i=self.layout, src_loc=src_loc) self.clear = Method(src_loc=src_loc) - self.head = Record(self.layout) + self.head = Signal(from_method_layout(layout)) self.buff = Memory(width=self.width, depth=self.depth) @@ -82,7 +81,7 @@ def elaborate(self, platform): m.d.comb += self.head.eq(self.buff_rdport.data) @def_method(m, self.write, ready=self.write_ready) - def _(arg: Record) -> None: + def _(arg: MethodStruct) -> None: m.d.top_comb += self.buff_wrport.addr.eq(self.write_idx) m.d.top_comb += self.buff_wrport.data.eq(arg) m.d.comb += self.buff_wrport.en.eq(1) diff --git a/transactron/lib/reqres.py b/transactron/lib/reqres.py index 9bfcdee2b..80b752c49 100644 --- a/transactron/lib/reqres.py +++ b/transactron/lib/reqres.py @@ -45,16 +45,16 @@ class ArgumentsToResultsZipper(Elaboratable): Method to save results with `results_layout` in the Forwarder. read: Method Reads latest entries from the fifo and the forwarder and return them as - record with two fields: 'args' and 'results'. + a structure with two fields: 'args' and 'results'. """ def __init__(self, args_layout: MethodLayout, results_layout: MethodLayout, src_loc: int | SrcLoc = 0): """ Parameters ---------- - args_layout: record layout + args_layout: method layout The format of arguments. - results_layout: record layout + results_layout: method layout The format of results. src_loc: int | SrcLoc How many stack frames deep the source location is taken from. diff --git a/transactron/lib/storage.py b/transactron/lib/storage.py index 4b33f8eb1..9549527f8 100644 --- a/transactron/lib/storage.py +++ b/transactron/lib/storage.py @@ -1,9 +1,11 @@ from amaranth import * from amaranth.utils import * + +from transactron.utils.transactron_helpers import from_method_layout from ..core import * from ..utils import SrcLoc, get_src_loc from typing import Optional -from transactron.utils import assign, AssignType +from transactron.utils import assign, AssignType, LayoutList from .reqres import ArgumentsToResultsZipper __all__ = ["MemoryBank"] @@ -40,12 +42,12 @@ def __init__( """ Parameters ---------- - data_layout: record layout - The format of records stored in the Memory. + data_layout: method layout + The format of structures stored in the Memory. elem_count: int Number of elements stored in Memory. granularity: Optional[int] - Granularity of write, forwarded to Amaranth. If `None` the whole record is always saved at once. + Granularity of write, forwarded to Amaranth. If `None` the whole structure is always saved at once. If not, the width of `data_layout` is split into `granularity` parts, which can be saved independently. safe_writes: bool Set to `False` if an optimisation can be done to increase throughput of writes. This will cause that @@ -59,11 +61,11 @@ def __init__( self.data_layout = data_layout self.elem_count = elem_count self.granularity = granularity - self.width = len(Record(self.data_layout)) + self.width = from_method_layout(self.data_layout).size self.addr_width = bits_for(self.elem_count - 1) self.safe_writes = safe_writes - self.read_req_layout = [("addr", self.addr_width)] + self.read_req_layout: LayoutList = [("addr", self.addr_width)] self.write_layout = [("addr", self.addr_width), ("data", self.data_layout)] if self.granularity is not None: self.write_layout.append(("mask", self.width // self.granularity)) diff --git a/transactron/lib/transformers.py b/transactron/lib/transformers.py index c0034b67e..a1445fcf5 100644 --- a/transactron/lib/transformers.py +++ b/transactron/lib/transformers.py @@ -6,7 +6,7 @@ from ..utils import SrcLoc from typing import Optional, Protocol from collections.abc import Callable -from transactron.utils import ValueLike, assign, AssignType, ModuleLike, HasElaborate +from transactron.utils import ValueLike, assign, AssignType, ModuleLike, MethodStruct, HasElaborate from .connectors import Forwarder, ManyToOneConnectTrans, ConnectTrans from .simultaneous import condition @@ -62,7 +62,7 @@ class MethodMap(Elaboratable, Transformer): Takes a target method and creates a transformed method which calls the original target method, mapping the input and output values with functions. The mapping functions take two parameters, a `Module` and the - `Record` being transformed. Alternatively, a `Method` can be + structure being transformed. Alternatively, a `Method` can be passed. Attributes @@ -75,8 +75,8 @@ def __init__( self, target: Method, *, - i_transform: Optional[tuple[MethodLayout, Callable[[TModule, Record], RecordDict]]] = None, - o_transform: Optional[tuple[MethodLayout, Callable[[TModule, Record], RecordDict]]] = None, + i_transform: Optional[tuple[MethodLayout, Callable[[TModule, MethodStruct], RecordDict]]] = None, + o_transform: Optional[tuple[MethodLayout, Callable[[TModule, MethodStruct], RecordDict]]] = None, src_loc: int | SrcLoc = 0 ): """ @@ -84,11 +84,11 @@ def __init__( ---------- target: Method The target method. - i_transform: (record layout, function or Method), optional + i_transform: (method layout, function or Method), optional Input mapping function. If specified, it should be a pair of a function and a input layout for the transformed method. If not present, input is passed unmodified. - o_transform: (record layout, function or Method), optional + o_transform: (method layout, function or Method), optional Output mapping function. If specified, it should be a pair of a function and a output layout for the transformed method. If not present, output is passed unmodified. @@ -97,9 +97,9 @@ def __init__( Alternatively, the source location to use instead of the default. """ if i_transform is None: - i_transform = (target.data_in.layout, lambda _, x: x) + i_transform = (target.layout_in, lambda _, x: x) if o_transform is None: - o_transform = (target.data_out.layout, lambda _, x: x) + o_transform = (target.layout_out, lambda _, x: x) self.target = target src_loc = get_src_loc(src_loc) @@ -122,7 +122,7 @@ class MethodFilter(Elaboratable, Transformer): Takes a target method and creates a method which calls the target method only when some condition is true. The condition function takes two - parameters, a module and the input `Record` of the method. Non-zero + parameters, a module and the input structure of the method. Non-zero return value is interpreted as true. Alternatively to using a function, a `Method` can be passed as a condition. By default, the target method is locked for use even if it is not called. @@ -139,7 +139,7 @@ class MethodFilter(Elaboratable, Transformer): def __init__( self, target: Method, - condition: Callable[[TModule, Record], ValueLike], + condition: Callable[[TModule, MethodStruct], ValueLike], default: Optional[RecordDict] = None, *, use_condition: bool = False, @@ -164,21 +164,19 @@ def __init__( Alternatively, the source location to use instead of the default. """ if default is None: - default = Record.like(target.data_out) + default = Signal.like(target.data_out) self.target = target self.use_condition = use_condition src_loc = get_src_loc(src_loc) - self.method = Method( - i=target.data_in.layout, o=target.data_out.layout, single_caller=self.use_condition, src_loc=src_loc - ) + self.method = Method(i=target.layout_in, o=target.layout_out, single_caller=self.use_condition, src_loc=src_loc) self.condition = condition self.default = default def elaborate(self, platform): m = TModule() - ret = Record.like(self.target.data_out) + ret = Signal.like(self.target.data_out) m.d.comb += assign(ret, self.default, fields=AssignType.ALL) @def_method(m, self.method) @@ -203,7 +201,7 @@ class MethodProduct(Elaboratable, Unifier): def __init__( self, targets: list[Method], - combiner: Optional[tuple[MethodLayout, Callable[[TModule, list[Record]], RecordDict]]] = None, + combiner: Optional[tuple[MethodLayout, Callable[[TModule, list[MethodStruct]], RecordDict]]] = None, *, src_loc: int | SrcLoc = 0 ): @@ -234,11 +232,11 @@ def __init__( The product method. """ if combiner is None: - combiner = (targets[0].data_out.layout, lambda _, x: x[0]) + combiner = (targets[0].layout_out, lambda _, x: x[0]) self.targets = targets self.combiner = combiner src_loc = get_src_loc(src_loc) - self.method = Method(i=targets[0].data_in.layout, o=combiner[0], src_loc=src_loc) + self.method = Method(i=targets[0].layout_in, o=combiner[0], src_loc=src_loc) def elaborate(self, platform): m = TModule() @@ -257,7 +255,9 @@ class MethodTryProduct(Elaboratable, Unifier): def __init__( self, targets: list[Method], - combiner: Optional[tuple[MethodLayout, Callable[[TModule, list[tuple[Value, Record]]], RecordDict]]] = None, + combiner: Optional[ + tuple[MethodLayout, Callable[[TModule, list[tuple[Value, MethodStruct]]], RecordDict]] + ] = None, *, src_loc: int | SrcLoc = 0 ): @@ -293,14 +293,14 @@ def __init__( self.targets = targets self.combiner = combiner self.src_loc = get_src_loc(src_loc) - self.method = Method(i=targets[0].data_in.layout, o=combiner[0], src_loc=self.src_loc) + self.method = Method(i=targets[0].layout_in, o=combiner[0], src_loc=self.src_loc) def elaborate(self, platform): m = TModule() @def_method(m, self.method) def _(arg): - results: list[tuple[Value, Record]] = [] + results: list[tuple[Value, MethodStruct]] = [] for target in self.targets: success = Signal() with Transaction(src_loc=self.src_loc).body(m): @@ -335,18 +335,18 @@ def __init__(self, targets: list[Method], *, src_loc: int | SrcLoc = 0): Alternatively, the source location to use instead of the default. """ self.method_list = targets - layout = targets[0].data_out.layout + layout = targets[0].layout_out self.src_loc = get_src_loc(src_loc) self.method = Method(o=layout, src_loc=self.src_loc) for method in targets: - if layout != method.data_out.layout: + if layout != method.layout_out: raise Exception("Not all methods have this same layout") def elaborate(self, platform): m = TModule() - m.submodules.forwarder = forwarder = Forwarder(self.method.data_out.layout, src_loc=self.src_loc) + m.submodules.forwarder = forwarder = Forwarder(self.method.layout_out, src_loc=self.src_loc) m.submodules.connect = ManyToOneConnectTrans( get_results=[get for get in self.method_list], put_result=forwarder.write, src_loc=self.src_loc @@ -386,7 +386,7 @@ def elaborate(self, platform): with Transaction().body(m): sdata1 = self.src1(m) sdata2 = self.src2(m) - ddata = Record.like(self.dst.data_in) + ddata = Signal.like(self.dst.data_in) self.dst(m, ddata) m.d.comb += ddata.eq(Cat(sdata1, sdata2)) @@ -400,7 +400,7 @@ class ConnectAndMapTrans(Elaboratable): Behaves like `ConnectTrans`, but modifies the transferred data using functions or `Method`s. Equivalent to a combination of `ConnectTrans` and `MethodMap`. The mapping functions take two parameters, a `Module` - and the `Record` being transformed. + and the structure being transformed. """ def __init__( @@ -408,8 +408,8 @@ def __init__( method1: Method, method2: Method, *, - i_fun: Optional[Callable[[TModule, Record], RecordDict]] = None, - o_fun: Optional[Callable[[TModule, Record], RecordDict]] = None, + i_fun: Optional[Callable[[TModule, MethodStruct], RecordDict]] = None, + o_fun: Optional[Callable[[TModule, MethodStruct], RecordDict]] = None, src_loc: int | SrcLoc = 0 ): """ @@ -438,8 +438,8 @@ def elaborate(self, platform): m.submodules.transformer = transformer = MethodMap( self.method2, - i_transform=(self.method1.data_out.layout, self.i_fun), - o_transform=(self.method1.data_in.layout, self.o_fun), + i_transform=(self.method1.layout_out, self.i_fun), + o_transform=(self.method1.layout_in, self.o_fun), src_loc=self.src_loc, ) m.submodules.connect = ConnectTrans(self.method1, transformer.method) diff --git a/transactron/utils/_typing.py b/transactron/utils/_typing.py index 49e33db1b..3cb889a7d 100644 --- a/transactron/utils/_typing.py +++ b/transactron/utils/_typing.py @@ -17,7 +17,7 @@ from contextlib import AbstractContextManager from enum import Enum from amaranth import * -from amaranth.lib.data import View +from amaranth.lib.data import StructLayout, View from amaranth.hdl.ast import ShapeCastable, Statement, ValueCastable from amaranth.hdl.dsl import _ModuleBuilderSubmodules, _ModuleBuilderDomainSet, _ModuleBuilderDomain, FSM from amaranth.hdl.rec import Direction, Layout @@ -25,14 +25,17 @@ __all__ = [ "FragmentLike", "ValueLike", + "ShapeLike", "StatementLike", "LayoutLike", "SwitchKey", "MethodLayout", + "MethodStruct", "SrcLoc", "SignalBundle", "LayoutListField", "LayoutList", + "LayoutIterable", "RecordIntDict", "RecordIntDictRet", "RecordValueDict", @@ -54,13 +57,15 @@ Layout | Sequence[tuple[str, "ShapeLike | LayoutLike"] | tuple[str, "ShapeLike | LayoutLike", Direction]] ) SwitchKey: TypeAlias = str | int | Enum -MethodLayout: TypeAlias = LayoutLike SrcLoc: TypeAlias = tuple[str, int] # Internal Coreblocks types SignalBundle: TypeAlias = Signal | Record | View | Iterable["SignalBundle"] | Mapping[str, "SignalBundle"] LayoutListField: TypeAlias = tuple[str, "ShapeLike | LayoutList"] LayoutList: TypeAlias = list[LayoutListField] +LayoutIterable: TypeAlias = Iterable[LayoutListField] +MethodLayout: TypeAlias = StructLayout | LayoutIterable +MethodStruct: TypeAlias = "View[StructLayout]" RecordIntDict: TypeAlias = Mapping[str, Union[int, "RecordIntDict"]] RecordIntDictRet: TypeAlias = Mapping[str, Any] # full typing hard to work with diff --git a/transactron/utils/assign.py b/transactron/utils/assign.py index b28ac738f..e2b528828 100644 --- a/transactron/utils/assign.py +++ b/transactron/utils/assign.py @@ -31,8 +31,8 @@ def flatten_elems(proxy: ArrayProxy): yield elem elems = list(flatten_elems(proxy)) - if elems and all(isinstance(el, Record) for el in elems): - return set.intersection(*[set(cast(Record, el).fields) for el in elems]) + if elems and all(isinstance(el, data.View) for el in elems): + return set.intersection(*[set(cast(data.View, el).shape().members.keys()) for el in elems]) def assign_arg_fields(val: AssignArg) -> Optional[set[str]]: @@ -43,7 +43,7 @@ def assign_arg_fields(val: AssignArg) -> Optional[set[str]]: elif isinstance(val, data.View): layout = val.shape() if isinstance(layout, data.StructLayout): - return set(k for k, _ in layout) + return set(k for k in layout.members) elif isinstance(val, dict): return set(val.keys()) @@ -51,7 +51,7 @@ def assign_arg_fields(val: AssignArg) -> Optional[set[str]]: def assign( lhs: AssignArg, rhs: AssignArg, *, fields: AssignFields = AssignType.RHS, lhs_strict=False, rhs_strict=False ) -> Iterable[Assign]: - """Safe record assignment. + """Safe structured assignment. This function recursively generates assignment statements for field-containing structures. This includes: Amaranth `Record`\\s, @@ -84,12 +84,12 @@ def assign( All fields in `rhs` are assigned. If one of them is not present in `lhs`, an exception is raised. AssignType.ALL - Assume that both records have the same layouts. All fields present + Assume that both structures have the same layouts. All fields present in `lhs` or `rhs` are assigned. Mapping Keys are field names, values follow the format for `fields`. Iterable - Items are field names. For subrecords, AssignType.ALL is assumed. + Items are field names. For subfields, AssignType.ALL is assumed. Returns ------- @@ -152,7 +152,7 @@ def assign( ) else: if not isinstance(fields, AssignType): - raise ValueError("Fields on assigning non-records") + raise ValueError("Fields on assigning non-structures lhs: {} rhs: {}".format(lhs, rhs)) if not isinstance(lhs, ValueLike) or not isinstance(rhs, ValueLike): raise TypeError("Unsupported assignment lhs: {} rhs: {}".format(lhs, rhs)) diff --git a/transactron/utils/data_repr.py b/transactron/utils/data_repr.py index d5d4029db..acd7c7505 100644 --- a/transactron/utils/data_repr.py +++ b/transactron/utils/data_repr.py @@ -1,7 +1,8 @@ from collections.abc import Iterable, Mapping -from ._typing import LayoutList, ShapeLike, LayoutLike +from ._typing import ShapeLike, MethodLayout from typing import Any, Sized from statistics import fmean +from amaranth.lib.data import StructLayout __all__ = [ @@ -18,8 +19,8 @@ ] -def layout_subset(layout: LayoutList, *, fields: set[str]) -> LayoutList: - return [item for item in layout if item[0] in fields] +def layout_subset(layout: StructLayout, *, fields: set[str]) -> StructLayout: + return StructLayout({item: value for item, value in layout.members.items() if item in fields}) def make_hashable(val): @@ -77,7 +78,7 @@ def bits_from_int(num: int, lower: int, length: int): return (num >> lower) & ((1 << (length)) - 1) -def data_layout(val: ShapeLike) -> LayoutLike: +def data_layout(val: ShapeLike) -> MethodLayout: return [("data", val)] diff --git a/transactron/utils/transactron_helpers.py b/transactron/utils/transactron_helpers.py index 80bee6ffd..048a2bb61 100644 --- a/transactron/utils/transactron_helpers.py +++ b/transactron/utils/transactron_helpers.py @@ -2,11 +2,12 @@ from contextlib import contextmanager from typing import Optional, Any, Concatenate, TypeGuard, TypeVar from collections.abc import Callable, Mapping -from ._typing import ROGraph, GraphCC, SrcLoc +from ._typing import ROGraph, GraphCC, SrcLoc, MethodLayout, MethodStruct, ShapeLike, LayoutList, LayoutListField from inspect import Parameter, signature from itertools import count from amaranth import * from amaranth import tracer +from amaranth.lib.data import StructLayout __all__ = [ @@ -16,6 +17,7 @@ "method_def_helper", "mock_def_helper", "get_src_loc", + "from_method_layout", ] T = TypeVar("T") @@ -89,8 +91,9 @@ def mock_def_helper(tb, func: Callable[..., T], arg: Mapping[str, Any]) -> T: return def_helper(f"mock definition for {tb}", func, Mapping[str, Any], arg, **arg) -def method_def_helper(method, func: Callable[..., T], arg: Record) -> T: - return def_helper(f"method definition for {method}", func, Record, arg, **arg.fields) +def method_def_helper(method, func: Callable[..., T], arg: MethodStruct) -> T: + kwargs = {k: arg[k] for k in arg.shape().members} + return def_helper(f"method definition for {method}", func, MethodStruct, arg, **kwargs) def get_caller_class_name(default: Optional[str] = None) -> tuple[Optional[Elaboratable], str]: @@ -121,3 +124,21 @@ def silence_mustuse(elaboratable: Elaboratable): def get_src_loc(src_loc: int | SrcLoc) -> SrcLoc: return tracer.get_src_loc(1 + src_loc) if isinstance(src_loc, int) else src_loc + + +def from_layout_field(shape: ShapeLike | LayoutList) -> ShapeLike: + if isinstance(shape, list): + return from_method_layout(shape) + else: + return shape + + +def make_layout(*fields: LayoutListField): + return from_method_layout(fields) + + +def from_method_layout(layout: MethodLayout) -> StructLayout: + if isinstance(layout, StructLayout): + return layout + else: + return StructLayout({k: from_layout_field(v) for k, v in layout})