Skip to content

Commit 77dab78

Browse files
wanda-phiwhitequark
authored andcommitted
hdl._dsl: raise an error when modifying an already-elaborated Module.
This renames the `FrozenMemory` exception to `AlreadyElaborated` and reuses it for modules. Fixes amaranth-lang#1350.
1 parent 1d03c34 commit 77dab78

File tree

8 files changed

+73
-28
lines changed

8 files changed

+73
-28
lines changed

amaranth/hdl/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
from ._ast import IOValue, IOPort
77
from ._dsl import Module
88
from ._cd import DomainError, ClockDomain
9-
from ._ir import UnusedElaboratable, Elaboratable, DriverConflict, Fragment
9+
from ._ir import AlreadyElaborated, UnusedElaboratable, Elaboratable, DriverConflict, Fragment
1010
from ._ir import Instance, IOBufferInstance
11-
from ._mem import FrozenMemory, MemoryData, MemoryInstance, Memory, ReadPort, WritePort, DummyPort
11+
from ._mem import MemoryData, MemoryInstance, Memory, ReadPort, WritePort, DummyPort
1212
from ._nir import CombinationalCycle
1313
from ._rec import Record
1414
from ._xfrm import DomainRenamer, ResetInserter, EnableInserter
@@ -27,12 +27,12 @@
2727
# _cd
2828
"DomainError", "ClockDomain",
2929
# _ir
30-
"UnusedElaboratable", "Elaboratable", "DriverConflict", "Fragment",
30+
"AlreadyElaborated", "UnusedElaboratable", "Elaboratable", "DriverConflict", "Fragment",
3131
"Instance", "IOBufferInstance",
3232
# _nir
3333
"CombinationalCycle",
3434
# _mem
35-
"FrozenMemory", "MemoryData", "MemoryInstance", "Memory", "ReadPort", "WritePort", "DummyPort",
35+
"MemoryData", "MemoryInstance", "Memory", "ReadPort", "WritePort", "DummyPort",
3636
# _rec
3737
"Record",
3838
# _xfrm

amaranth/hdl/_dsl.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,8 +288,11 @@ def __init__(self):
288288
self._domains = {}
289289
self._generated = {}
290290
self._src_loc = tracer.get_src_loc()
291+
self._frozen = False
291292

292293
def _check_context(self, construct, context):
294+
if self._frozen:
295+
raise AlreadyElaborated(f"Cannot modify a module that has already been elaborated")
293296
if self._ctrl_context != context:
294297
if self._ctrl_context is None:
295298
raise SyntaxError("{} is not permitted outside of {}"
@@ -616,6 +619,9 @@ def _pop_ctrl(self):
616619
src_loc=src_loc))
617620

618621
def _add_statement(self, assigns, domain, depth):
622+
if self._frozen:
623+
raise AlreadyElaborated(f"Cannot modify a module that has already been elaborated")
624+
619625
while len(self._ctrl_stack) > self.domain._depth:
620626
self._pop_ctrl()
621627

@@ -643,6 +649,8 @@ def _add_submodule(self, submodule, name=None, src_loc=None):
643649
if not hasattr(submodule, "elaborate"):
644650
raise TypeError("Trying to add {!r}, which does not implement .elaborate(), as "
645651
"a submodule".format(submodule))
652+
if self._frozen:
653+
raise AlreadyElaborated(f"Cannot modify a module that has already been elaborated")
646654
if name == None:
647655
self._anon_submodules.append((submodule, src_loc))
648656
else:
@@ -660,6 +668,8 @@ def _get_submodule(self, name):
660668
def _add_domain(self, cd):
661669
if cd.name in self._domains:
662670
raise NameError(f"Clock domain named '{cd.name}' already exists")
671+
if self._frozen:
672+
raise AlreadyElaborated(f"Cannot modify a module that has already been elaborated")
663673
self._domains[cd.name] = cd
664674

665675
def _flush(self):
@@ -668,6 +678,7 @@ def _flush(self):
668678

669679
def elaborate(self, platform):
670680
self._flush()
681+
self._frozen = True
671682

672683
fragment = Fragment(src_loc=self._src_loc)
673684
for name, (submodule, src_loc) in self._named_submodules.items():

amaranth/hdl/_ir.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,23 @@
33
import enum
44
import warnings
55

6-
from .._utils import flatten, to_binary
6+
from .._utils import flatten, to_binary, final
77
from .. import tracer, _unused
88
from . import _ast, _cd, _ir, _nir
99

1010

1111
__all__ = [
12-
"UnusedElaboratable", "Elaboratable", "DuplicateElaboratable", "DriverConflict", "Fragment",
13-
"Instance", "IOBufferInstance", "PortDirection", "Design", "build_netlist",
12+
"AlreadyElaborated", "UnusedElaboratable", "Elaboratable", "DuplicateElaboratable",
13+
"DriverConflict", "Fragment", "Instance", "IOBufferInstance", "PortDirection", "Design",
14+
"build_netlist",
1415
]
1516

1617

18+
@final
19+
class AlreadyElaborated(Exception):
20+
"""Exception raised when an elaboratable is being modified after elaboration."""
21+
22+
1723
class UnusedElaboratable(_unused.UnusedMustUse):
1824
# The warning is initially silenced. If everything that has been constructed remains unused,
1925
# it means the application likely crashed (with an exception, or in another way that does not

amaranth/hdl/_mem.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,12 @@
44

55
from .. import tracer
66
from ._ast import *
7-
from ._ir import Elaboratable, Fragment
7+
from ._ir import Elaboratable, Fragment, AlreadyElaborated
88
from ..utils import ceil_log2
99
from .._utils import deprecated, final
1010

1111

12-
__all__ = ["FrozenMemory", "MemoryData", "Memory", "ReadPort", "WritePort", "DummyPort"]
13-
14-
15-
@final
16-
class FrozenMemory(Exception):
17-
"""Exception raised when a memory array is being modified after elaboration."""
12+
__all__ = ["MemoryData", "Memory", "ReadPort", "WritePort", "DummyPort"]
1813

1914

2015
@final
@@ -30,7 +25,7 @@ class MemoryData:
3025
a default value for rows that are not explicitly initialized.
3126
3227
Changing the initial contents of a :class:`MemoryData` is only possible until it is used to
33-
elaborate a memory; afterwards, attempting to do so will raise :exc:`FrozenMemory`.
28+
elaborate a memory; afterwards, attempting to do so will raise :exc:`AlreadyElaborated`.
3429
3530
.. warning::
3631
@@ -101,7 +96,7 @@ def __getitem__(self, index):
10196

10297
def __setitem__(self, index, value):
10398
if self._frozen:
104-
raise FrozenMemory("Cannot set 'init' on a memory that has already been elaborated")
99+
raise AlreadyElaborated("Cannot set 'init' on a memory that has already been elaborated")
105100

106101
if isinstance(index, slice):
107102
indices = range(*index.indices(len(self._elems)))
@@ -181,7 +176,7 @@ def init(self):
181176
@init.setter
182177
def init(self, init):
183178
if self._frozen:
184-
raise FrozenMemory("Cannot set 'init' on a memory that has already been elaborated")
179+
raise AlreadyElaborated("Cannot set 'init' on a memory that has already been elaborated")
185180
self._init = MemoryData.Init(init, shape=self._shape, depth=self._depth)
186181

187182
def __repr__(self):

amaranth/lib/memory.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@
22
from collections import OrderedDict
33
from collections.abc import MutableSequence
44

5-
from ..hdl import MemoryData, MemoryInstance, Shape, ShapeCastable, Const
6-
from ..hdl._mem import FrozenMemory
5+
from ..hdl import MemoryData, MemoryInstance, Shape, ShapeCastable, Const, AlreadyElaborated
76
from ..utils import ceil_log2
87
from .._utils import final
98
from .. import tracer
109
from . import wiring, data
1110

1211

13-
__all__ = ["FrozenMemory", "Memory", "ReadPort", "WritePort"]
12+
__all__ = ["Memory", "ReadPort", "WritePort"]
1413

1514

1615
class Memory(wiring.Component):
@@ -24,7 +23,7 @@ class Memory(wiring.Component):
2423
the :ref:`elaborate <lang-elaboration>` method.
2524
2625
Adding ports or changing initial contents of a :class:`Memory` is only possible until it is
27-
elaborated; afterwards, attempting to do so will raise :class:`~amaranth.hdl.FrozenMemory`.
26+
elaborated; afterwards, attempting to do so will raise :class:`~amaranth.hdl.AlreadyElaborated`.
2827
2928
Platform overrides
3029
------------------
@@ -121,7 +120,7 @@ def read_port(self, *, domain="sync", transparent_for=(), src_loc_at=0):
121120
:class:`ReadPort`
122121
"""
123122
if self._frozen:
124-
raise FrozenMemory("Cannot add a memory port to a memory that has already been elaborated")
123+
raise AlreadyElaborated("Cannot add a memory port to a memory that has already been elaborated")
125124
signature = ReadPort.Signature(shape=self.shape, addr_width=ceil_log2(self.depth))
126125
return ReadPort(signature, memory=self, domain=domain, transparent_for=transparent_for,
127126
src_loc_at=1 + src_loc_at)
@@ -146,7 +145,7 @@ def write_port(self, *, domain="sync", granularity=None, src_loc_at=0):
146145
:class:`WritePort`
147146
"""
148147
if self._frozen:
149-
raise FrozenMemory("Cannot add a memory port to a memory that has already been elaborated")
148+
raise AlreadyElaborated("Cannot add a memory port to a memory that has already been elaborated")
150149
signature = WritePort.Signature(
151150
shape=self.shape, addr_width=ceil_log2(self.depth), granularity=granularity)
152151
return WritePort(signature, memory=self, domain=domain,

docs/stdlib/memory.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,8 @@ Simulation
181181
Memory description
182182
==================
183183

184-
.. autoexception:: amaranth.hdl.FrozenMemory
184+
.. autoexception:: amaranth.hdl.AlreadyElaborated
185+
:noindex:
185186

186187
.. autoclass:: amaranth.hdl.MemoryData
187188

tests/test_hdl_dsl.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from amaranth.hdl._ast import *
77
from amaranth.hdl._cd import *
88
from amaranth.hdl._dsl import *
9+
from amaranth.hdl._ir import *
910
from amaranth.lib.enum import Enum
1011

1112
from .utils import *
@@ -975,3 +976,35 @@ def test_bug_1331(self):
975976
r"^Domain name should not be prefixed with 'cd_' in `m.domains`, "
976977
r"use `m.domains.rx = ...` instead$"):
977978
m.domains.cd_rx = ClockDomain()
979+
980+
def test_freeze(self):
981+
a = Signal()
982+
m = Module()
983+
f = Fragment.get(m, None)
984+
985+
with self.assertRaisesRegex(AlreadyElaborated,
986+
r"^Cannot modify a module that has already been elaborated$"):
987+
m.d.comb += a.eq(1)
988+
989+
with self.assertRaisesRegex(AlreadyElaborated,
990+
r"^Cannot modify a module that has already been elaborated$"):
991+
with m.If(a):
992+
pass
993+
994+
with self.assertRaisesRegex(AlreadyElaborated,
995+
r"^Cannot modify a module that has already been elaborated$"):
996+
with m.Switch(a):
997+
pass
998+
999+
with self.assertRaisesRegex(AlreadyElaborated,
1000+
r"^Cannot modify a module that has already been elaborated$"):
1001+
with m.FSM():
1002+
pass
1003+
1004+
with self.assertRaisesRegex(AlreadyElaborated,
1005+
r"^Cannot modify a module that has already been elaborated$"):
1006+
m.submodules.a = Module()
1007+
1008+
with self.assertRaisesRegex(AlreadyElaborated,
1009+
r"^Cannot modify a module that has already been elaborated$"):
1010+
m.domains.sync = ClockDomain()

tests/test_lib_memory.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -439,15 +439,15 @@ def test_freeze(self):
439439
m = memory.Memory(shape=unsigned(8), depth=4, init=[])
440440
m.write_port()
441441
m.elaborate(None)
442-
with self.assertRaisesRegex(memory.FrozenMemory,
442+
with self.assertRaisesRegex(AlreadyElaborated,
443443
r"^Cannot add a memory port to a memory that has already been elaborated$"):
444444
m.write_port()
445-
with self.assertRaisesRegex(memory.FrozenMemory,
445+
with self.assertRaisesRegex(AlreadyElaborated,
446446
r"^Cannot add a memory port to a memory that has already been elaborated$"):
447447
m.read_port()
448-
with self.assertRaisesRegex(memory.FrozenMemory,
448+
with self.assertRaisesRegex(AlreadyElaborated,
449449
r"^Cannot set 'init' on a memory that has already been elaborated$"):
450450
m.init = [1, 2, 3, 4]
451-
with self.assertRaisesRegex(memory.FrozenMemory,
451+
with self.assertRaisesRegex(AlreadyElaborated,
452452
r"^Cannot set 'init' on a memory that has already been elaborated$"):
453453
m.init[0] = 1

0 commit comments

Comments
 (0)