diff --git a/coreblocks/cache/icache.py b/coreblocks/cache/icache.py index 7b54b9675..09899afb6 100644 --- a/coreblocks/cache/icache.py +++ b/coreblocks/cache/icache.py @@ -2,6 +2,7 @@ import operator from amaranth import * +from amaranth.lib.data import View from amaranth.utils import exact_log2 from transactron.core import def_method, Priority, TModule @@ -12,6 +13,7 @@ from coreblocks.peripherals.bus_adapter import BusMasterInterface from coreblocks.cache.iface import CacheInterface, CacheRefillerInterface +from transactron.utils.transactron_helpers import make_layout __all__ = [ "ICache", @@ -109,11 +111,11 @@ def __init__(self, layouts: ICacheLayouts, params: ICacheParameters, refiller: C self.flush = Method() self.flush.add_conflict(self.issue_req, Priority.LEFT) - self.addr_layout = [ + self.addr_layout = make_layout( ("offset", self.params.offset_bits), ("index", self.params.index_bits), ("tag", self.params.tag_bits), - ] + ) self.perf_loads = HwCounter("frontend.icache.loads", "Number of requests to the L1 Instruction Cache") self.perf_hits = HwCounter("frontend.icache.hits") @@ -131,7 +133,7 @@ def deserialize_addr(self, raw_addr: Value) -> dict[str, Value]: "tag": raw_addr[-self.params.tag_bits :], } - def serialize_addr(self, addr: Record) -> Value: + def serialize_addr(self, addr: View) -> Value: return Cat(addr.offset, addr.index, addr.tag) def elaborate(self, platform): @@ -186,7 +188,7 @@ def elaborate(self, platform): # Fast path - read requests request_valid = self.req_fifo.read.ready - request_addr = Record(self.addr_layout) + request_addr = Signal(self.addr_layout) tag_hit = [tag_data.valid & (tag_data.tag == request_addr.tag) for tag_data in self.mem.tag_rd_data] tag_hit_any = reduce(operator.or_, tag_hit) @@ -195,7 +197,7 @@ def elaborate(self, platform): for i in OneHotSwitchDynamic(m, Cat(tag_hit)): m.d.comb += mem_out.eq(self.mem.data_rd_data[i]) - instr_out = extract_instr_from_word(m, self.params, mem_out, request_addr[:]) + instr_out = extract_instr_from_word(m, self.params, mem_out, Value.cast(request_addr)) refill_error_saved = Signal() m.d.comb += needs_refill.eq(request_valid & ~tag_hit_any & ~refill_error_saved) @@ -214,7 +216,7 @@ def _(): self.req_latency.stop(m) return self.res_fwd.read(m) - mem_read_addr = Record(self.addr_layout) + mem_read_addr = Signal(self.addr_layout) m.d.comb += assign(mem_read_addr, request_addr) @def_method(m, self.issue_req, ready=accepting_requests) @@ -304,21 +306,21 @@ class ICacheMemory(Elaboratable): def __init__(self, params: ICacheParameters) -> None: self.params = params - self.tag_data_layout = [("valid", 1), ("tag", self.params.tag_bits)] + self.tag_data_layout = make_layout(("valid", 1), ("tag", self.params.tag_bits)) self.way_wr_en = Signal(self.params.num_of_ways) self.tag_rd_index = Signal(self.params.index_bits) - self.tag_rd_data = Array([Record(self.tag_data_layout) for _ in range(self.params.num_of_ways)]) + self.tag_rd_data = Array([Signal(self.tag_data_layout) for _ in range(self.params.num_of_ways)]) self.tag_wr_index = Signal(self.params.index_bits) self.tag_wr_en = Signal() - self.tag_wr_data = Record(self.tag_data_layout) + self.tag_wr_data = Signal(self.tag_data_layout) - self.data_addr_layout = [("index", self.params.index_bits), ("offset", self.params.offset_bits)] + self.data_addr_layout = make_layout(("index", self.params.index_bits), ("offset", self.params.offset_bits)) - self.data_rd_addr = Record(self.data_addr_layout) + self.data_rd_addr = Signal(self.data_addr_layout) self.data_rd_data = Array([Signal(self.params.word_width) for _ in range(self.params.num_of_ways)]) - self.data_wr_addr = Record(self.data_addr_layout) + self.data_wr_addr = Signal(self.data_addr_layout) self.data_wr_en = Signal() self.data_wr_data = Signal(self.params.word_width) @@ -328,7 +330,7 @@ def elaborate(self, platform): for i in range(self.params.num_of_ways): way_wr = self.way_wr_en[i] - tag_mem = Memory(width=len(self.tag_wr_data), depth=self.params.num_of_sets) + tag_mem = Memory(width=len(Value.cast(self.tag_wr_data)), depth=self.params.num_of_sets) tag_mem_rp = tag_mem.read_port() tag_mem_wp = tag_mem.write_port() m.submodules[f"tag_mem_{i}_rp"] = tag_mem_rp diff --git a/coreblocks/fu/div_unit.py b/coreblocks/fu/div_unit.py index a4767a0b0..9e3f3dfc6 100644 --- a/coreblocks/fu/div_unit.py +++ b/coreblocks/fu/div_unit.py @@ -3,6 +3,7 @@ from collections.abc import Sequence from amaranth import * +from amaranth.lib import data from coreblocks.params.fu_params import FunctionalComponentParams from coreblocks.params import Funct3, GenParams, FuncUnitLayouts, OpType @@ -33,7 +34,7 @@ def get_instructions(self) -> Sequence[tuple]: ] -def get_input(arg: Record) -> tuple[Value, Value]: +def get_input(arg: data.View) -> tuple[Value, Value]: return arg.s1_val, Mux(arg.imm, arg.imm, arg.s2_val) diff --git a/coreblocks/fu/fu_decoder.py b/coreblocks/fu/fu_decoder.py index 30373e677..eeaae8bf1 100644 --- a/coreblocks/fu/fu_decoder.py +++ b/coreblocks/fu/fu_decoder.py @@ -15,7 +15,7 @@ class Decoder(Elaboratable): Attributes ---------- decode_fn: Signal - exec_fn: Record + exec_fn: View """ def __init__(self, gen_params: GenParams, decode_fn: Type[IntFlag], ops: Sequence[tuple], check_optype: bool): diff --git a/coreblocks/lsu/pma.py b/coreblocks/lsu/pma.py index 8e474a6bf..cd91c98f0 100644 --- a/coreblocks/lsu/pma.py +++ b/coreblocks/lsu/pma.py @@ -2,6 +2,7 @@ from functools import reduce from operator import or_ from amaranth import * +from amaranth.lib import data from coreblocks.params import * from transactron.utils import HasElaborate @@ -29,6 +30,11 @@ class PMARegion: mmio: bool = False +class PMALayout(data.StructLayout): + def __init__(self): + super().__init__({"mmio": unsigned(1)}) + + class PMAChecker(Elaboratable): """ Implementation of physical memory attributes checker. It may or may not be a part of LSU. @@ -38,21 +44,20 @@ class PMAChecker(Elaboratable): ---------- addr : Signal Memory address, for which PMAs are requested. - result : Record + result : View PMAs for given address. """ def __init__(self, gen_params: GenParams) -> None: # poor man's interval list self.segments = gen_params.pma - self.attr_layout = gen_params.get(PMALayouts).pma_attrs_layout - self.result = Record(self.attr_layout) + self.result = Signal(PMALayout()) self.addr = Signal(gen_params.isa.xlen) def elaborate(self, platform) -> HasElaborate: m = TModule() - outputs = [Record(self.attr_layout) for _ in self.segments] + outputs = [Signal(PMALayout()) for _ in self.segments] # zero output if addr not in region, propagate value if addr in region for i, segment in enumerate(self.segments): @@ -64,6 +69,6 @@ def elaborate(self, platform) -> HasElaborate: m.d.comb += outputs[i].eq(segment.mmio) # OR all outputs - m.d.comb += self.result.eq(reduce(or_, outputs, 0)) + m.d.comb += self.result.eq(reduce(or_, [Value.cast(o) for o in outputs], 0)) return m diff --git a/coreblocks/params/layouts.py b/coreblocks/params/layouts.py index 066c6dd43..98f69344c 100644 --- a/coreblocks/params/layouts.py +++ b/coreblocks/params/layouts.py @@ -18,7 +18,6 @@ "UnsignedMulUnitLayouts", "RATLayouts", "LSULayouts", - "PMALayouts", "CSRLayouts", "ICacheLayouts", "JumpBranchLayouts", @@ -551,11 +550,6 @@ def __init__(self, gen_params: GenParams): self.accept = make_layout(fields.data, fields.exception, fields.cause) -class PMALayouts: - def __init__(self, gen_params: GenParams): - self.pma_attrs_layout = [("mmio", 1)] - - class CSRLayouts: """Layouts used in the control and status registers.""" diff --git a/coreblocks/structs_common/rf.py b/coreblocks/structs_common/rf.py index 461fab8ed..899e99593 100644 --- a/coreblocks/structs_common/rf.py +++ b/coreblocks/structs_common/rf.py @@ -1,6 +1,7 @@ from amaranth import * from transactron import Method, def_method, TModule from coreblocks.params import RFLayouts, GenParams +from transactron.utils.transactron_helpers import make_layout __all__ = ["RegisterFile"] @@ -9,9 +10,9 @@ class RegisterFile(Elaboratable): def __init__(self, *, gen_params: GenParams): self.gen_params = gen_params layouts = gen_params.get(RFLayouts) - self.internal_layout = [("reg_val", gen_params.isa.xlen), ("valid", 1)] + self.internal_layout = make_layout(("reg_val", gen_params.isa.xlen), ("valid", 1)) self.read_layout = layouts.rf_read_out - self.entries = Array(Record(self.internal_layout) for _ in range(2**gen_params.phys_regs_bits)) + self.entries = Array(Signal(self.internal_layout) for _ in range(2**gen_params.phys_regs_bits)) self.read1 = Method(i=layouts.rf_read_in, o=layouts.rf_read_out) self.read2 = Method(i=layouts.rf_read_in, o=layouts.rf_read_out) diff --git a/stubs/amaranth/hdl/_ast.pyi b/stubs/amaranth/hdl/_ast.pyi index 98c2be9f2..8892c6f6e 100644 --- a/stubs/amaranth/hdl/_ast.pyi +++ b/stubs/amaranth/hdl/_ast.pyi @@ -410,14 +410,14 @@ class Repl(Value): class _SignalMeta(ABCMeta): @overload - def __call__(cls, shape: ShapeCastable[T], src_loc_at = ..., **kwargs) -> T: + def __call__(cls, shape: ShapeCastable[T], src_loc_at: int = ..., **kwargs) -> T: ... @overload - def __call__(cls, shape = ..., src_loc_at = ..., **kwargs) -> Signal: + def __call__(cls, shape: ShapeLike = ..., src_loc_at: int = ..., **kwargs) -> Signal: ... - def __call__(cls, shape = ..., src_loc_at = ..., **kwargs): + def __call__(cls, shape: ShapeLike = ..., src_loc_at: int = ..., **kwargs): ... diff --git a/test/transactions/test_assign.py b/test/transactions/test_assign.py index 47f72800b..73d5b28f9 100644 --- a/test/transactions/test_assign.py +++ b/test/transactions/test_assign.py @@ -3,7 +3,7 @@ from amaranth.lib import data from amaranth.hdl._ast import ArrayProxy, Slice -from transactron.utils._typing import LayoutLike +from transactron.utils._typing import MethodLayout from transactron.utils import AssignType, assign from transactron.utils.assign import AssignArg, AssignFields @@ -54,15 +54,15 @@ def mkstruct(layout): ], ) class TestAssign(TestCase): - # constructs `assign` arguments (records, proxies, dicts) which have an "inner" and "outer" part - # parameterized with a Record-like constructor and a layout of the inner part - build: Callable[[Callable[[LayoutLike], AssignArg], LayoutLike], AssignArg] + # constructs `assign` arguments (views, proxies, dicts) which have an "inner" and "outer" part + # parameterized with a constructor and a layout of the inner part + build: Callable[[Callable[[MethodLayout], AssignArg], MethodLayout], AssignArg] # constructs field specifications for `assign`, takes field specifications for the inner part wrap: Callable[[AssignFields], AssignFields] # extracts the inner part of the structure - extr: Callable[[AssignArg], Record | ArrayProxy] - # Record-like constructor, takes a record layout - mk: Callable[[LayoutLike], AssignArg] + extr: Callable[[AssignArg], ArrayProxy] + # constructor, takes a layout + mk: Callable[[MethodLayout], AssignArg] def test_rhs_exception(self): with self.assertRaises(KeyError): @@ -99,7 +99,7 @@ def test_wrong_bits(self): ("list", layout_ab, layout_ab, ["a", "a"]), ] ) - def test_assign_a(self, name, layout1: LayoutLike, layout2: LayoutLike, atype: AssignType): + def test_assign_a(self, name, layout1: MethodLayout, layout2: MethodLayout, atype: AssignType): lhs = self.build(self.mk, layout1) rhs = self.build(self.mk, layout2) alist = list(assign(lhs, rhs, fields=self.wrap(atype))) diff --git a/transactron/lib/storage.py b/transactron/lib/storage.py index 9549527f8..e6d3e5cf5 100644 --- a/transactron/lib/storage.py +++ b/transactron/lib/storage.py @@ -1,7 +1,7 @@ from amaranth import * from amaranth.utils import * -from transactron.utils.transactron_helpers import from_method_layout +from transactron.utils.transactron_helpers import from_method_layout, make_layout from ..core import * from ..utils import SrcLoc, get_src_loc from typing import Optional @@ -23,7 +23,7 @@ class MemoryBank(Elaboratable): The read request method. Accepts an `addr` from which data should be read. Only ready if there is there is a place to buffer response. read_resp: Method - The read response method. Return `data_layout` Record which was saved on `addr` given by last + The read response method. Return `data_layout` View which was saved on `addr` given by last `read_req` method call. Only ready after `read_req` call. write: Method The write method. Accepts `addr` where data should be saved, `data` in form of `data_layout` @@ -33,7 +33,7 @@ class MemoryBank(Elaboratable): def __init__( self, *, - data_layout: MethodLayout, + data_layout: LayoutList, elem_count: int, granularity: Optional[int] = None, safe_writes: bool = True, @@ -58,7 +58,7 @@ def __init__( Alternatively, the source location to use instead of the default. """ self.src_loc = get_src_loc(src_loc) - self.data_layout = data_layout + self.data_layout = make_layout(*data_layout) self.elem_count = elem_count self.granularity = granularity self.width = from_method_layout(self.data_layout).size @@ -66,9 +66,10 @@ def __init__( self.safe_writes = safe_writes self.read_req_layout: LayoutList = [("addr", self.addr_width)] - self.write_layout = [("addr", self.addr_width), ("data", self.data_layout)] + 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)) + write_layout.append(("mask", self.width // self.granularity)) + self.write_layout = make_layout(*write_layout) self.read_req = Method(i=self.read_req_layout, src_loc=self.src_loc) self.read_resp = Method(o=self.data_layout, src_loc=self.src_loc) @@ -85,8 +86,8 @@ def elaborate(self, platform) -> TModule: prev_read_addr = Signal(self.addr_width) write_pending = Signal() write_req = Signal() - write_args = Record(self.write_layout) - write_args_prev = Record(self.write_layout) + write_args = Signal(self.write_layout) + write_args_prev = Signal(self.write_layout) m.d.comb += read_port.addr.eq(prev_read_addr) zipper = ArgumentsToResultsZipper([("valid", 1)], self.data_layout) diff --git a/transactron/utils/_typing.py b/transactron/utils/_typing.py index a44dc6610..5acd3ab70 100644 --- a/transactron/utils/_typing.py +++ b/transactron/utils/_typing.py @@ -14,14 +14,13 @@ Any, TYPE_CHECKING, ) -from collections.abc import Iterable, Iterator, Mapping, Sequence +from collections.abc import Iterable, Iterator, Mapping from contextlib import AbstractContextManager from enum import Enum from amaranth import * from amaranth.lib.data import StructLayout, View from amaranth.lib.wiring import Flow, Member from amaranth.hdl import ShapeCastable, ValueCastable -from amaranth.hdl.rec import Direction, Layout if TYPE_CHECKING: from amaranth.hdl._ast import Statement @@ -33,7 +32,6 @@ "ValueLike", "ShapeLike", "StatementLike", - "LayoutLike", "SwitchKey", "SrcLoc", "MethodLayout", @@ -60,9 +58,6 @@ ValueLike: TypeAlias = Value | int | Enum | ValueCastable ShapeLike: TypeAlias = Shape | ShapeCastable | int | range | type[Enum] StatementLike: TypeAlias = Union["Statement", Iterable["StatementLike"]] -LayoutLike: TypeAlias = ( - Layout | Sequence[tuple[str, "ShapeLike | LayoutLike"] | tuple[str, "ShapeLike | LayoutLike", Direction]] -) SwitchKey: TypeAlias = str | int | Enum SrcLoc: TypeAlias = tuple[str, int] diff --git a/transactron/utils/assign.py b/transactron/utils/assign.py index b3bee191e..0be471e80 100644 --- a/transactron/utils/assign.py +++ b/transactron/utils/assign.py @@ -41,8 +41,6 @@ def flatten_elems(proxy: ArrayProxy): def assign_arg_fields(val: AssignArg) -> Optional[set[str]]: if isinstance(val, ArrayProxy): return arrayproxy_fields(val) - elif isinstance(val, Record): - return set(val.fields) elif isinstance(val, data.View): layout = val.shape() if isinstance(layout, data.StructLayout): @@ -57,7 +55,7 @@ def assign( """Safe structured assignment. This function recursively generates assignment statements for - field-containing structures. This includes: Amaranth `Record`\\s, + field-containing structures. This includes: Amaranth `View`\\s using `StructLayout`, Python `dict`\\s. In case of mismatching fields or bit widths, error is raised. @@ -68,16 +66,16 @@ def assign( The bit width check is performed if: - - Any of `lhs` or `rhs` is a `Record` or `View`. + - Any of `lhs` or `rhs` is a `View`. - Both `lhs` and `rhs` have an explicitly defined shape (e.g. are a - `Signal`, a field of a `Record` or a `View`). + `Signal`, a field of a `View`). Parameters ---------- - lhs : Record or View or Value-castable or dict - Record, signal or dict being assigned. - rhs : Record or View or Value-castable or dict - Record, signal or dict containing assigned values. + lhs : View or Value-castable or dict + View, signal or dict being assigned. + rhs : View or Value-castable or dict + View, signal or dict containing assigned values. fields : AssignType or Iterable or Mapping, optional Determines which fields will be assigned. Possible values: @@ -109,18 +107,8 @@ def assign( if lhs_fields is not None and rhs_fields is not None: # asserts for type checking - assert ( - isinstance(lhs, Record) - or isinstance(lhs, ArrayProxy) - or isinstance(lhs, Mapping) - or isinstance(lhs, data.View) - ) - assert ( - isinstance(rhs, Record) - or isinstance(rhs, ArrayProxy) - or isinstance(rhs, Mapping) - or isinstance(rhs, data.View) - ) + assert isinstance(lhs, ArrayProxy) or isinstance(lhs, Mapping) or isinstance(lhs, data.View) + assert isinstance(rhs, ArrayProxy) or isinstance(rhs, Mapping) or isinstance(rhs, data.View) if fields is AssignType.COMMON: names = lhs_fields & rhs_fields @@ -166,9 +154,7 @@ def has_explicit_shape(val: ValueLike): return isinstance(val, Signal) or isinstance(val, ArrayProxy) if ( - isinstance(lhs, Record) - or isinstance(rhs, Record) - or isinstance(lhs, data.View) + isinstance(lhs, data.View) or isinstance(rhs, data.View) or (lhs_strict or has_explicit_shape(lhs)) and (rhs_strict or has_explicit_shape(rhs))