Skip to content

Commit bc04b8f

Browse files
committed
hdl._mem: add MemoryData class.
This implements half of RFC 62. The `MemoryData._Row` class will be implemented later, as a follow-up.
1 parent 1deaf70 commit bc04b8f

File tree

12 files changed

+281
-196
lines changed

12 files changed

+281
-196
lines changed

amaranth/hdl/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from ._cd import DomainError, ClockDomain
88
from ._ir import UnusedElaboratable, Elaboratable, DriverConflict, Fragment
99
from ._ir import Instance, IOBufferInstance
10-
from ._mem import MemoryIdentity, MemoryInstance, Memory, ReadPort, WritePort, DummyPort
10+
from ._mem import MemoryData, MemoryInstance, Memory, ReadPort, WritePort, DummyPort
1111
from ._rec import Record
1212
from ._xfrm import DomainRenamer, ResetInserter, EnableInserter
1313

@@ -27,7 +27,7 @@
2727
"UnusedElaboratable", "Elaboratable", "DriverConflict", "Fragment",
2828
"Instance", "IOBufferInstance",
2929
# _mem
30-
"MemoryIdentity", "MemoryInstance", "Memory", "ReadPort", "WritePort", "DummyPort",
30+
"MemoryData", "MemoryInstance", "Memory", "ReadPort", "WritePort", "DummyPort",
3131
# _rec
3232
"Record",
3333
# _xfrm

amaranth/hdl/_ir.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,9 +1134,9 @@ def emit_iobuffer(self, module_idx: int, instance: _ir.IOBufferInstance):
11341134

11351135
def emit_memory(self, module_idx: int, fragment: '_mem.MemoryInstance', name: str):
11361136
cell = _nir.Memory(module_idx,
1137-
width=fragment._width,
1138-
depth=fragment._depth,
1139-
init=fragment._init,
1137+
width=_ast.Shape.cast(fragment._data._shape).width,
1138+
depth=fragment._data._depth,
1139+
init=fragment._data._init._raw,
11401140
name=name,
11411141
attributes=fragment._attrs,
11421142
src_loc=fragment.src_loc,

amaranth/hdl/_mem.py

Lines changed: 152 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,160 @@
11
import operator
22
from collections import OrderedDict
3+
from collections.abc import MutableSequence
34

45
from .. import tracer
56
from ._ast import *
67
from ._ir import Elaboratable, Fragment
78
from ..utils import ceil_log2
8-
from .._utils import deprecated
9+
from .._utils import deprecated, final
910

1011

11-
__all__ = ["Memory", "ReadPort", "WritePort", "DummyPort"]
12+
__all__ = ["MemoryData", "Memory", "ReadPort", "WritePort", "DummyPort"]
1213

1314

14-
class MemoryIdentity: pass
15+
@final
16+
class FrozenError(Exception):
17+
"""This exception is raised when ports are added to a :class:`Memory` or its
18+
:attr:`~Memory.init` attribute is changed after it has been elaborated once.
19+
"""
20+
21+
22+
@final
23+
class MemoryData:
24+
@final
25+
class Init(MutableSequence):
26+
"""Memory initialization data.
27+
28+
This is a special container used only for initial contents of memories. It is similar
29+
to :class:`list`, but does not support inserting or deleting elements; its length is always
30+
the same as the depth of the memory it belongs to.
31+
32+
If :py:`shape` is a :ref:`custom shape-castable object <lang-shapecustom>`, then:
33+
34+
* Each element must be convertible to :py:`shape` via :meth:`.ShapeCastable.const`, and
35+
* Elements that are not explicitly initialized default to :py:`shape.const(None)`.
36+
37+
Otherwise (when :py:`shape` is a :class:`.Shape`):
38+
39+
* Each element must be an :class:`int`, and
40+
* Elements that are not explicitly initialized default to :py:`0`.
41+
"""
42+
def __init__(self, elems, *, shape, depth):
43+
Shape.cast(shape)
44+
if not isinstance(depth, int) or depth < 0:
45+
raise TypeError("Memory depth must be a non-negative integer, not {!r}"
46+
.format(depth))
47+
self._shape = shape
48+
self._depth = depth
49+
self._frozen = False
50+
51+
if isinstance(shape, ShapeCastable):
52+
self._elems = [None] * depth
53+
self._raw = [Const.cast(Const(None, shape)).value] * depth
54+
else:
55+
self._elems = [0] * depth
56+
self._raw = self._elems # intentionally mutably aliased
57+
elems = list(elems)
58+
if len(elems) > depth:
59+
raise ValueError(f"Memory initialization value count exceeds memory depth ({len(elems)} > {depth})")
60+
for index, item in enumerate(elems):
61+
try:
62+
self[index] = item
63+
except (TypeError, ValueError) as e:
64+
raise type(e)(f"Memory initialization value at address {index:x}: {e}") from None
65+
66+
@property
67+
def shape(self):
68+
return self._shape
69+
70+
def __getitem__(self, index):
71+
return self._elems[index]
72+
73+
def __setitem__(self, index, value):
74+
if self._frozen:
75+
raise FrozenError("Cannot set 'init' on a memory that has already been elaborated")
76+
77+
if isinstance(index, slice):
78+
indices = range(*index.indices(len(self._elems)))
79+
if len(value) != len(indices):
80+
raise ValueError("Changing length of Memory.init is not allowed")
81+
for actual_index, actual_value in zip(indices, value):
82+
self[actual_index] = actual_value
83+
else:
84+
if isinstance(self._shape, ShapeCastable):
85+
self._raw[index] = Const.cast(Const(value, self._shape)).value
86+
else:
87+
value = operator.index(value)
88+
# self._raw[index] assigned by the following line
89+
self._elems[index] = value
90+
91+
def __delitem__(self, index):
92+
raise TypeError("Deleting elements from Memory.init is not allowed")
93+
94+
def insert(self, index, value):
95+
""":meta private:"""
96+
raise TypeError("Inserting elements into Memory.init is not allowed")
97+
98+
def __len__(self):
99+
return self._depth
100+
101+
def __repr__(self):
102+
return f"MemoryData.Init({self._elems!r}, shape={self._shape!r}, depth={self._depth})"
103+
104+
105+
def __init__(self, *, shape, depth, init, src_loc_at=0):
106+
# shape and depth validation is performed in MemoryData.Init()
107+
self._shape = shape
108+
self._depth = depth
109+
self._init = MemoryData.Init(init, shape=shape, depth=depth)
110+
self.src_loc = tracer.get_src_loc(src_loc_at=src_loc_at)
111+
self.name = tracer.get_var_name(depth=2+src_loc_at, default="$memory")
112+
self._frozen = False
113+
114+
def freeze(self):
115+
self._frozen = True
116+
self._init._frozen = True
117+
118+
@property
119+
def shape(self):
120+
return self._shape
121+
122+
@property
123+
def depth(self):
124+
return self._depth
125+
126+
@property
127+
def init(self):
128+
return self._init
129+
130+
@init.setter
131+
def init(self, init):
132+
if self._frozen:
133+
raise FrozenError("Cannot set 'init' on a memory that has already been elaborated")
134+
self._init = MemoryData.Init(init, shape=self._shape, depth=self._depth)
135+
136+
def __repr__(self):
137+
return f"(memory-data {self.name})"
138+
139+
def __getitem__(self, index):
140+
"""Simulation only."""
141+
return MemorySimRead(self, index)
15142

16143

17144
class MemorySimRead:
18-
def __init__(self, identity, addr):
19-
assert isinstance(identity, MemoryIdentity)
20-
self._identity = identity
145+
def __init__(self, memory, addr):
146+
assert isinstance(memory, MemoryData)
147+
self._memory = memory
21148
self._addr = Value.cast(addr)
22149

23150
def eq(self, value):
24-
return MemorySimWrite(self._identity, self._addr, value)
151+
return MemorySimWrite(self._memory, self._addr, value)
25152

26153

27154
class MemorySimWrite:
28-
def __init__(self, identity, addr, data):
29-
assert isinstance(identity, MemoryIdentity)
30-
self._identity = identity
155+
def __init__(self, memory, addr, data):
156+
assert isinstance(memory, MemoryData)
157+
self._memory = memory
31158
self._addr = Value.cast(addr)
32159
self._data = Value.cast(data)
33160

@@ -66,26 +193,20 @@ def _granularity(self):
66193
return len(self._data) // len(self._en)
67194

68195

69-
def __init__(self, *, identity, width, depth, init=None, attrs=None, src_loc=None):
196+
def __init__(self, *, data, attrs=None, src_loc=None):
70197
super().__init__(src_loc=src_loc)
71-
assert isinstance(identity, MemoryIdentity)
72-
self._identity = identity
73-
self._width = operator.index(width)
74-
self._depth = operator.index(depth)
75-
mask = (1 << self._width) - 1
76-
self._init = tuple(item & mask for item in init) if init is not None else ()
77-
assert len(self._init) <= self._depth
78-
self._init += (0,) * (self._depth - len(self._init))
79-
for x in self._init:
80-
assert isinstance(x, int)
198+
assert isinstance(data, MemoryData)
199+
data.freeze()
200+
self._data = data
81201
self._attrs = attrs or {}
82202
self._read_ports: "list[MemoryInstance._ReadPort]" = []
83203
self._write_ports: "list[MemoryInstance._WritePort]" = []
84204

85205
def read_port(self, *, domain, addr, data, en, transparent_for):
86206
port = self._ReadPort(domain=domain, addr=addr, data=data, en=en, transparent_for=transparent_for)
87-
assert len(port._data) == self._width
88-
assert len(port._addr) == ceil_log2(self._depth)
207+
shape = Shape.cast(self._data.shape)
208+
assert len(port._data) == shape.width
209+
assert len(port._addr) == ceil_log2(self._data.depth)
89210
for idx in port._transparent_for:
90211
assert isinstance(idx, int)
91212
assert idx in range(len(self._write_ports))
@@ -96,8 +217,9 @@ def read_port(self, *, domain, addr, data, en, transparent_for):
96217

97218
def write_port(self, *, domain, addr, data, en):
98219
port = self._WritePort(domain=domain, addr=addr, data=data, en=en)
99-
assert len(port._data) == self._width
100-
assert len(port._addr) == ceil_log2(self._depth)
220+
shape = Shape.cast(self._data.shape)
221+
assert len(port._data) == shape.width
222+
assert len(port._addr) == ceil_log2(self._data.depth)
101223
self._write_ports.append(port)
102224
return len(self._write_ports) - 1
103225

@@ -145,28 +267,17 @@ def __init__(self, *, width, depth, init=None, name=None, attrs=None, simulate=T
145267
self.depth = depth
146268
self.attrs = OrderedDict(() if attrs is None else attrs)
147269

148-
self.init = init
149270
self._read_ports = []
150271
self._write_ports = []
151-
self._identity = MemoryIdentity()
272+
self._data = MemoryData(shape=width, depth=depth, init=init or [])
152273

153274
@property
154275
def init(self):
155-
return self._init
276+
return self._data.init
156277

157278
@init.setter
158279
def init(self, new_init):
159-
self._init = [] if new_init is None else list(new_init)
160-
if len(self.init) > self.depth:
161-
raise ValueError("Memory initialization value count exceed memory depth ({} > {})"
162-
.format(len(self.init), self.depth))
163-
164-
try:
165-
for addr, val in enumerate(self._init):
166-
operator.index(val)
167-
except TypeError as e:
168-
raise TypeError("Memory initialization value at address {:x}: {}"
169-
.format(addr, e)) from None
280+
self._data.init = new_init
170281

171282
def read_port(self, *, src_loc_at=0, **kwargs):
172283
"""Get a read port.
@@ -202,10 +313,10 @@ def write_port(self, *, src_loc_at=0, **kwargs):
202313

203314
def __getitem__(self, index):
204315
"""Simulation only."""
205-
return MemorySimRead(self._identity, index)
316+
return MemorySimRead(self._data, index)
206317

207318
def elaborate(self, platform):
208-
f = MemoryInstance(identity=self._identity, width=self.width, depth=self.depth, init=self.init, attrs=self.attrs, src_loc=self.src_loc)
319+
f = MemoryInstance(data=self._data, attrs=self.attrs, src_loc=self.src_loc)
209320
write_ports = {}
210321
for port in self._write_ports:
211322
port._MustUse__used = True

amaranth/hdl/_xfrm.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -272,10 +272,7 @@ def map_memory_ports(self, fragment, new_fragment):
272272
def on_fragment(self, fragment):
273273
if isinstance(fragment, MemoryInstance):
274274
new_fragment = MemoryInstance(
275-
identity=fragment._identity,
276-
width=fragment._width,
277-
depth=fragment._depth,
278-
init=fragment._init,
275+
data=fragment._data,
279276
attrs=fragment._attrs,
280277
src_loc=fragment.src_loc
281278
)

0 commit comments

Comments
 (0)