Skip to content

Commit 6ffafef

Browse files
wanda-phiwhitequark
authored andcommitted
lib.memory: raise an error on mutating already-elaborated memory.
1 parent 3d5c36a commit 6ffafef

File tree

2 files changed

+40
-1
lines changed

2 files changed

+40
-1
lines changed

amaranth/lib/memory.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,30 @@
55
from ..hdl import MemoryIdentity, MemoryInstance, Shape, ShapeCastable, Const
66
from ..hdl._mem import MemorySimRead
77
from ..utils import ceil_log2
8+
from .._utils import final
89
from .. import tracer
910
from . import wiring, data
1011

1112

1213
__all__ = ["Memory", "ReadPort", "WritePort"]
1314

1415

16+
@final
17+
class FrozenError(Exception):
18+
"""This exception is raised when ports are added to a :class:`Memory` or its
19+
:attr:`~Memory.init` attribute is changed after it has been elaborated once.
20+
"""
21+
22+
1523
class Memory(wiring.Component):
1624
"""Addressable array of rows.
1725
1826
This :ref:`component <wiring>` is used to construct a memory array by first specifying its
1927
dimensions and initial contents using the :py:`shape`, :py:`depth`, and :py:`init` parameters,
2028
and then adding memory ports using the :meth:`read_port` and :meth:`write_port` methods.
2129
Because it is mutable, it should be created and used locally within
22-
the :ref:`elaborate <lang-elaboration>` method.
30+
the :ref:`elaborate <lang-elaboration>` method. It is an error to add ports to or change
31+
initial contents of a memory after it has been elaborated.
2332
2433
The :py:`init` parameter and assignment to the :py:`init` attribute have the same effect, with
2534
:class:`Memory.Init` converting elements of the iterable to match :py:`shape` and using
@@ -70,6 +79,7 @@ def __init__(self, elems, *, shape, depth):
7079
.format(depth))
7180
self._shape = shape
7281
self._depth = depth
82+
self._frozen = False
7383

7484
if isinstance(shape, ShapeCastable):
7585
self._elems = [None] * depth
@@ -92,6 +102,9 @@ def __getitem__(self, index):
92102
return self._elems[index]
93103

94104
def __setitem__(self, index, value):
105+
if self._frozen:
106+
raise FrozenError("Cannot set 'init' on a memory that has already been elaborated")
107+
95108
if isinstance(index, slice):
96109
indices = range(*index.indices(len(self._elems)))
97110
if len(value) != len(indices):
@@ -131,6 +144,7 @@ def __init__(self, *, shape, depth, init, attrs=None, src_loc_at=0):
131144
self._identity = MemoryIdentity()
132145
self._read_ports: "list[ReadPort]" = []
133146
self._write_ports: "list[WritePort]" = []
147+
self._frozen = False
134148

135149
super().__init__(wiring.Signature({}))
136150

@@ -148,6 +162,8 @@ def init(self):
148162

149163
@init.setter
150164
def init(self, init):
165+
if self._frozen:
166+
raise FrozenError("Cannot set 'init' on a memory that has already been elaborated")
151167
self._init = Memory.Init(init, shape=self._shape, depth=self._depth)
152168

153169
@property
@@ -179,6 +195,8 @@ def read_port(self, *, domain="sync", transparent_for=(), src_loc_at=0):
179195
-------
180196
:class:`ReadPort`
181197
"""
198+
if self._frozen:
199+
raise FrozenError("Cannot add a memory port to a memory that has already been elaborated")
182200
signature = ReadPort.Signature(shape=self.shape, addr_width=ceil_log2(self.depth))
183201
return ReadPort(signature, memory=self, domain=domain, transparent_for=transparent_for,
184202
src_loc_at=1 + src_loc_at)
@@ -202,6 +220,8 @@ def write_port(self, *, domain="sync", granularity=None, src_loc_at=0):
202220
-------
203221
:class:`WritePort`
204222
"""
223+
if self._frozen:
224+
raise FrozenError("Cannot add a memory port to a memory that has already been elaborated")
205225
signature = WritePort.Signature(
206226
shape=self.shape, addr_width=ceil_log2(self.depth), granularity=granularity)
207227
return WritePort(signature, memory=self, domain=domain,
@@ -224,6 +244,8 @@ def write_ports(self):
224244
return tuple(self._write_ports)
225245

226246
def elaborate(self, platform):
247+
self._frozen = True
248+
self._init._frozen = True
227249
if hasattr(platform, "get_memory"):
228250
return platform.get_memory(self)
229251

tests/test_lib_memory.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,3 +404,20 @@ def test_elaborate(self):
404404
self.assertIs(f._read_ports[1]._addr, rp1.addr)
405405
self.assertIs(f._read_ports[1]._data, rp1.data.as_value())
406406
self.assertIs(f._read_ports[1]._en, rp1.en)
407+
408+
def test_freeze(self):
409+
m = memory.Memory(shape=unsigned(8), depth=4, init=[])
410+
m.write_port()
411+
m.elaborate(None)
412+
with self.assertRaisesRegex(memory.FrozenError,
413+
r"^Cannot add a memory port to a memory that has already been elaborated$"):
414+
m.write_port()
415+
with self.assertRaisesRegex(memory.FrozenError,
416+
r"^Cannot add a memory port to a memory that has already been elaborated$"):
417+
m.read_port()
418+
with self.assertRaisesRegex(memory.FrozenError,
419+
r"^Cannot set 'init' on a memory that has already been elaborated$"):
420+
m.init = [1, 2, 3, 4]
421+
with self.assertRaisesRegex(memory.FrozenError,
422+
r"^Cannot set 'init' on a memory that has already been elaborated$"):
423+
m.init[0] = 1

0 commit comments

Comments
 (0)