hdl._dsl: raise an error when modifying an already-elaborated Module.

This renames the `FrozenMemory` exception to `AlreadyElaborated`
and reuses it for modules.

Fixes #1350.
This commit is contained in:
Wanda 2024-05-08 01:15:35 +02:00 committed by Catherine
parent 1d03c3498d
commit 77dab7884c
8 changed files with 73 additions and 28 deletions

View file

@ -6,9 +6,9 @@ from ._ast import Format, Print, Assert, Assume, Cover
from ._ast import IOValue, IOPort
from ._dsl import Module
from ._cd import DomainError, ClockDomain
from ._ir import UnusedElaboratable, Elaboratable, DriverConflict, Fragment
from ._ir import AlreadyElaborated, UnusedElaboratable, Elaboratable, DriverConflict, Fragment
from ._ir import Instance, IOBufferInstance
from ._mem import FrozenMemory, MemoryData, MemoryInstance, Memory, ReadPort, WritePort, DummyPort
from ._mem import MemoryData, MemoryInstance, Memory, ReadPort, WritePort, DummyPort
from ._nir import CombinationalCycle
from ._rec import Record
from ._xfrm import DomainRenamer, ResetInserter, EnableInserter
@ -27,12 +27,12 @@ __all__ = [
# _cd
"DomainError", "ClockDomain",
# _ir
"UnusedElaboratable", "Elaboratable", "DriverConflict", "Fragment",
"AlreadyElaborated", "UnusedElaboratable", "Elaboratable", "DriverConflict", "Fragment",
"Instance", "IOBufferInstance",
# _nir
"CombinationalCycle",
# _mem
"FrozenMemory", "MemoryData", "MemoryInstance", "Memory", "ReadPort", "WritePort", "DummyPort",
"MemoryData", "MemoryInstance", "Memory", "ReadPort", "WritePort", "DummyPort",
# _rec
"Record",
# _xfrm

View file

@ -288,8 +288,11 @@ class Module(_ModuleBuilderRoot, Elaboratable):
self._domains = {}
self._generated = {}
self._src_loc = tracer.get_src_loc()
self._frozen = False
def _check_context(self, construct, context):
if self._frozen:
raise AlreadyElaborated(f"Cannot modify a module that has already been elaborated")
if self._ctrl_context != context:
if self._ctrl_context is None:
raise SyntaxError("{} is not permitted outside of {}"
@ -616,6 +619,9 @@ class Module(_ModuleBuilderRoot, Elaboratable):
src_loc=src_loc))
def _add_statement(self, assigns, domain, depth):
if self._frozen:
raise AlreadyElaborated(f"Cannot modify a module that has already been elaborated")
while len(self._ctrl_stack) > self.domain._depth:
self._pop_ctrl()
@ -643,6 +649,8 @@ class Module(_ModuleBuilderRoot, Elaboratable):
if not hasattr(submodule, "elaborate"):
raise TypeError("Trying to add {!r}, which does not implement .elaborate(), as "
"a submodule".format(submodule))
if self._frozen:
raise AlreadyElaborated(f"Cannot modify a module that has already been elaborated")
if name == None:
self._anon_submodules.append((submodule, src_loc))
else:
@ -660,6 +668,8 @@ class Module(_ModuleBuilderRoot, Elaboratable):
def _add_domain(self, cd):
if cd.name in self._domains:
raise NameError(f"Clock domain named '{cd.name}' already exists")
if self._frozen:
raise AlreadyElaborated(f"Cannot modify a module that has already been elaborated")
self._domains[cd.name] = cd
def _flush(self):
@ -668,6 +678,7 @@ class Module(_ModuleBuilderRoot, Elaboratable):
def elaborate(self, platform):
self._flush()
self._frozen = True
fragment = Fragment(src_loc=self._src_loc)
for name, (submodule, src_loc) in self._named_submodules.items():

View file

@ -3,17 +3,23 @@ from collections import defaultdict, OrderedDict
import enum
import warnings
from .._utils import flatten, to_binary
from .._utils import flatten, to_binary, final
from .. import tracer, _unused
from . import _ast, _cd, _ir, _nir
__all__ = [
"UnusedElaboratable", "Elaboratable", "DuplicateElaboratable", "DriverConflict", "Fragment",
"Instance", "IOBufferInstance", "PortDirection", "Design", "build_netlist",
"AlreadyElaborated", "UnusedElaboratable", "Elaboratable", "DuplicateElaboratable",
"DriverConflict", "Fragment", "Instance", "IOBufferInstance", "PortDirection", "Design",
"build_netlist",
]
@final
class AlreadyElaborated(Exception):
"""Exception raised when an elaboratable is being modified after elaboration."""
class UnusedElaboratable(_unused.UnusedMustUse):
# The warning is initially silenced. If everything that has been constructed remains unused,
# it means the application likely crashed (with an exception, or in another way that does not

View file

@ -4,17 +4,12 @@ from collections.abc import MutableSequence
from .. import tracer
from ._ast import *
from ._ir import Elaboratable, Fragment
from ._ir import Elaboratable, Fragment, AlreadyElaborated
from ..utils import ceil_log2
from .._utils import deprecated, final
__all__ = ["FrozenMemory", "MemoryData", "Memory", "ReadPort", "WritePort", "DummyPort"]
@final
class FrozenMemory(Exception):
"""Exception raised when a memory array is being modified after elaboration."""
__all__ = ["MemoryData", "Memory", "ReadPort", "WritePort", "DummyPort"]
@final
@ -30,7 +25,7 @@ class MemoryData:
a default value for rows that are not explicitly initialized.
Changing the initial contents of a :class:`MemoryData` is only possible until it is used to
elaborate a memory; afterwards, attempting to do so will raise :exc:`FrozenMemory`.
elaborate a memory; afterwards, attempting to do so will raise :exc:`AlreadyElaborated`.
.. warning::
@ -101,7 +96,7 @@ class MemoryData:
def __setitem__(self, index, value):
if self._frozen:
raise FrozenMemory("Cannot set 'init' on a memory that has already been elaborated")
raise AlreadyElaborated("Cannot set 'init' on a memory that has already been elaborated")
if isinstance(index, slice):
indices = range(*index.indices(len(self._elems)))
@ -181,7 +176,7 @@ class MemoryData:
@init.setter
def init(self, init):
if self._frozen:
raise FrozenMemory("Cannot set 'init' on a memory that has already been elaborated")
raise AlreadyElaborated("Cannot set 'init' on a memory that has already been elaborated")
self._init = MemoryData.Init(init, shape=self._shape, depth=self._depth)
def __repr__(self):

View file

@ -2,15 +2,14 @@ import operator
from collections import OrderedDict
from collections.abc import MutableSequence
from ..hdl import MemoryData, MemoryInstance, Shape, ShapeCastable, Const
from ..hdl._mem import FrozenMemory
from ..hdl import MemoryData, MemoryInstance, Shape, ShapeCastable, Const, AlreadyElaborated
from ..utils import ceil_log2
from .._utils import final
from .. import tracer
from . import wiring, data
__all__ = ["FrozenMemory", "Memory", "ReadPort", "WritePort"]
__all__ = ["Memory", "ReadPort", "WritePort"]
class Memory(wiring.Component):
@ -24,7 +23,7 @@ class Memory(wiring.Component):
the :ref:`elaborate <lang-elaboration>` method.
Adding ports or changing initial contents of a :class:`Memory` is only possible until it is
elaborated; afterwards, attempting to do so will raise :class:`~amaranth.hdl.FrozenMemory`.
elaborated; afterwards, attempting to do so will raise :class:`~amaranth.hdl.AlreadyElaborated`.
Platform overrides
------------------
@ -121,7 +120,7 @@ class Memory(wiring.Component):
:class:`ReadPort`
"""
if self._frozen:
raise FrozenMemory("Cannot add a memory port to a memory that has already been elaborated")
raise AlreadyElaborated("Cannot add a memory port to a memory that has already been elaborated")
signature = ReadPort.Signature(shape=self.shape, addr_width=ceil_log2(self.depth))
return ReadPort(signature, memory=self, domain=domain, transparent_for=transparent_for,
src_loc_at=1 + src_loc_at)
@ -146,7 +145,7 @@ class Memory(wiring.Component):
:class:`WritePort`
"""
if self._frozen:
raise FrozenMemory("Cannot add a memory port to a memory that has already been elaborated")
raise AlreadyElaborated("Cannot add a memory port to a memory that has already been elaborated")
signature = WritePort.Signature(
shape=self.shape, addr_width=ceil_log2(self.depth), granularity=granularity)
return WritePort(signature, memory=self, domain=domain,

View file

@ -181,7 +181,8 @@ Simulation
Memory description
==================
.. autoexception:: amaranth.hdl.FrozenMemory
.. autoexception:: amaranth.hdl.AlreadyElaborated
:noindex:
.. autoclass:: amaranth.hdl.MemoryData

View file

@ -6,6 +6,7 @@ from collections import OrderedDict
from amaranth.hdl._ast import *
from amaranth.hdl._cd import *
from amaranth.hdl._dsl import *
from amaranth.hdl._ir import *
from amaranth.lib.enum import Enum
from .utils import *
@ -975,3 +976,35 @@ class DSLTestCase(FHDLTestCase):
r"^Domain name should not be prefixed with 'cd_' in `m.domains`, "
r"use `m.domains.rx = ...` instead$"):
m.domains.cd_rx = ClockDomain()
def test_freeze(self):
a = Signal()
m = Module()
f = Fragment.get(m, None)
with self.assertRaisesRegex(AlreadyElaborated,
r"^Cannot modify a module that has already been elaborated$"):
m.d.comb += a.eq(1)
with self.assertRaisesRegex(AlreadyElaborated,
r"^Cannot modify a module that has already been elaborated$"):
with m.If(a):
pass
with self.assertRaisesRegex(AlreadyElaborated,
r"^Cannot modify a module that has already been elaborated$"):
with m.Switch(a):
pass
with self.assertRaisesRegex(AlreadyElaborated,
r"^Cannot modify a module that has already been elaborated$"):
with m.FSM():
pass
with self.assertRaisesRegex(AlreadyElaborated,
r"^Cannot modify a module that has already been elaborated$"):
m.submodules.a = Module()
with self.assertRaisesRegex(AlreadyElaborated,
r"^Cannot modify a module that has already been elaborated$"):
m.domains.sync = ClockDomain()

View file

@ -439,15 +439,15 @@ class MemoryTestCase(FHDLTestCase):
m = memory.Memory(shape=unsigned(8), depth=4, init=[])
m.write_port()
m.elaborate(None)
with self.assertRaisesRegex(memory.FrozenMemory,
with self.assertRaisesRegex(AlreadyElaborated,
r"^Cannot add a memory port to a memory that has already been elaborated$"):
m.write_port()
with self.assertRaisesRegex(memory.FrozenMemory,
with self.assertRaisesRegex(AlreadyElaborated,
r"^Cannot add a memory port to a memory that has already been elaborated$"):
m.read_port()
with self.assertRaisesRegex(memory.FrozenMemory,
with self.assertRaisesRegex(AlreadyElaborated,
r"^Cannot set 'init' on a memory that has already been elaborated$"):
m.init = [1, 2, 3, 4]
with self.assertRaisesRegex(memory.FrozenMemory,
with self.assertRaisesRegex(AlreadyElaborated,
r"^Cannot set 'init' on a memory that has already been elaborated$"):
m.init[0] = 1