Skip to content

Commit 5207b5d

Browse files
committed
Document RFC 62.
This includes a few minor code changes: - Removing redundant `lib.memory.Memory.Init = hdl.MemoryData.Init` re-export. - Renaming `FrozenError` to `FrozenMemory` and moving it to `.hdl`; - Marking `ReadPort` and `WritePort` as `@final`.
1 parent 38ad357 commit 5207b5d

File tree

5 files changed

+105
-44
lines changed

5 files changed

+105
-44
lines changed

amaranth/hdl/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from ._cd import DomainError, ClockDomain
99
from ._ir import UnusedElaboratable, Elaboratable, DriverConflict, Fragment
1010
from ._ir import Instance, IOBufferInstance
11-
from ._mem import MemoryData, MemoryInstance, Memory, ReadPort, WritePort, DummyPort
11+
from ._mem import FrozenMemory, MemoryData, MemoryInstance, Memory, ReadPort, WritePort, DummyPort
1212
from ._rec import Record
1313
from ._xfrm import DomainRenamer, ResetInserter, EnableInserter
1414

@@ -29,7 +29,7 @@
2929
"UnusedElaboratable", "Elaboratable", "DriverConflict", "Fragment",
3030
"Instance", "IOBufferInstance",
3131
# _mem
32-
"MemoryData", "MemoryInstance", "Memory", "ReadPort", "WritePort", "DummyPort",
32+
"FrozenMemory", "MemoryData", "MemoryInstance", "Memory", "ReadPort", "WritePort", "DummyPort",
3333
# _rec
3434
"Record",
3535
# _xfrm

amaranth/hdl/_mem.py

+53-9
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,50 @@
99
from .._utils import deprecated, final
1010

1111

12-
__all__ = ["MemoryData", "Memory", "ReadPort", "WritePort", "DummyPort"]
12+
__all__ = ["FrozenMemory", "MemoryData", "Memory", "ReadPort", "WritePort", "DummyPort"]
1313

1414

1515
@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-
"""
16+
class FrozenMemory(Exception):
17+
"""This exception is raised when a memory array is being modified after elaboration."""
2018

2119

2220
@final
2321
class MemoryData:
22+
"""Abstract description of a memory array.
23+
24+
A :class:`MemoryData` object describes the geometry (shape and depth) and the initial contents
25+
of a memory array, without specifying the way in which it is accessed. It is conceptually
26+
similar to an array of :class:`Signal`\\ s.
27+
28+
The :py:`init` parameter and assignment to the :py:`init` attribute have the same effect, with
29+
:class:`MemoryData.Init` converting elements of the iterable to match :py:`shape` and using
30+
a default value for rows that are not explicitly initialized.
31+
32+
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`.
34+
35+
.. warning::
36+
37+
Uninitialized memories (including ASIC memories and some FPGA memories) are
38+
`not yet supported <https://github.com/amaranth-lang/amaranth/issues/270>`_, and
39+
the :py:`init` parameter must be always provided, if only as :py:`init=[]`.
40+
41+
Parameters
42+
----------
43+
shape : :ref:`shape-like <lang-shapelike>` object
44+
Shape of each memory row.
45+
depth : :class:`int`
46+
Number of memory rows.
47+
init : iterable of initial values
48+
Initial values for memory rows.
49+
"""
50+
2451
@final
2552
class Init(MutableSequence):
26-
"""Memory initialization data.
53+
"""Init(...)
54+
55+
Memory initialization data.
2756
2857
This is a special container used only for initial contents of memories. It is similar
2958
to :class:`list`, but does not support inserting or deleting elements; its length is always
@@ -72,7 +101,7 @@ def __getitem__(self, index):
72101

73102
def __setitem__(self, index, value):
74103
if self._frozen:
75-
raise FrozenError("Cannot set 'init' on a memory that has already been elaborated")
104+
raise FrozenMemory("Cannot set 'init' on a memory that has already been elaborated")
76105

77106
if isinstance(index, slice):
78107
indices = range(*index.indices(len(self._elems)))
@@ -117,7 +146,7 @@ def shape(self):
117146
def _lhs_signals(self):
118147
# This value cannot ever appear in a design.
119148
raise NotImplementedError # :nocov:
120-
149+
121150
_rhs_signals = _lhs_signals
122151

123152
def __repr__(self):
@@ -152,13 +181,28 @@ def init(self):
152181
@init.setter
153182
def init(self, init):
154183
if self._frozen:
155-
raise FrozenError("Cannot set 'init' on a memory that has already been elaborated")
184+
raise FrozenMemory("Cannot set 'init' on a memory that has already been elaborated")
156185
self._init = MemoryData.Init(init, shape=self._shape, depth=self._depth)
157186

158187
def __repr__(self):
159188
return f"(memory-data {self.name})"
160189

161190
def __getitem__(self, index):
191+
"""Retrieve a memory row for simulation.
192+
193+
A :class:`MemoryData` object can be indexed with an :class:`int` to construct a special
194+
value that can be used to read and write the selected memory row in a simulation testbench,
195+
without having to create a memory port.
196+
197+
.. important::
198+
199+
Even in a simulation, the value returned by this function cannot be used in a module;
200+
it can only be used with :py:`sim.get()` and :py:`sim.set()`.
201+
202+
Returns
203+
-------
204+
:class:`~amaranth.hdl.Value`, :ref:`assignable <lang-assignable>`
205+
"""
162206
index = operator.index(index)
163207
if index not in range(self.depth):
164208
raise IndexError(f"Index {index} is out of bounds (memory has {self.depth} rows)")

amaranth/lib/memory.py

+19-24
Original file line numberDiff line numberDiff line change
@@ -3,53 +3,46 @@
33
from collections.abc import MutableSequence
44

55
from ..hdl import MemoryData, MemoryInstance, Shape, ShapeCastable, Const
6-
from ..hdl._mem import FrozenError
6+
from ..hdl._mem import FrozenMemory
77
from ..utils import ceil_log2
88
from .._utils import final
99
from .. import tracer
1010
from . import wiring, data
1111

1212

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

1515

1616
class Memory(wiring.Component):
1717
"""Addressable array of rows.
1818
1919
This :ref:`component <wiring>` is used to construct a memory array by first specifying its
20-
dimensions and initial contents using the :py:`shape`, :py:`depth`, and :py:`init` parameters,
21-
and then adding memory ports using the :meth:`read_port` and :meth:`write_port` methods.
22-
Because it is mutable, it should be created and used locally within
23-
the :ref:`elaborate <lang-elaboration>` method. It is an error to add ports to or change
24-
initial contents of a memory after it has been elaborated.
20+
dimensions and initial contents using the :class:`~amaranth.hdl.MemoryData` object and
21+
the :py:`data` parameter (or by providing :py:`shape`, :py:`depth`, and :py:`init` parameters
22+
directly instead) and then adding memory ports using the :meth:`read_port` and
23+
:meth:`write_port` methods. Because it is mutable, it should be created and used locally within
24+
the :ref:`elaborate <lang-elaboration>` method.
2525
26-
The :py:`init` parameter and assignment to the :py:`init` attribute have the same effect, with
27-
:class:`Memory.Init` converting elements of the iterable to match :py:`shape` and using
28-
a default value for rows that are not explicitly initialized.
26+
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`.
2928
30-
.. warning::
31-
32-
Uninitialized memories (including ASIC memories and some FPGA memories) are
33-
`not yet supported <https://github.com/amaranth-lang/amaranth/issues/270>`_, and
34-
the :py:`init` parameter must be always provided, if only as :py:`init=[]`.
29+
Platform overrides
30+
------------------
31+
Define the :py:`get_memory()` platform method to override the implementation of
32+
:class:`Memory`, e.g. to instantiate library cells directly.
3533
3634
Parameters
3735
----------
36+
data : :class:`~amaranth.hdl.MemoryData`
37+
Representation of memory geometry and contents.
3838
shape : :ref:`shape-like <lang-shapelike>` object
3939
Shape of each memory row.
4040
depth : :class:`int`
4141
Number of memory rows.
4242
init : iterable of initial values
4343
Initial values for memory rows.
44-
45-
Platform overrides
46-
------------------
47-
Define the :py:`get_memory()` platform method to override the implementation of
48-
:class:`Memory`, e.g. to instantiate library cells directly.
4944
"""
5045

51-
Init = MemoryData.Init
52-
5346
def __init__(self, data=None, *, shape=None, depth=None, init=None, attrs=None, src_loc_at=0):
5447
if data is None:
5548
if shape is None:
@@ -128,7 +121,7 @@ def read_port(self, *, domain="sync", transparent_for=(), src_loc_at=0):
128121
:class:`ReadPort`
129122
"""
130123
if self._frozen:
131-
raise FrozenError("Cannot add a memory port to a memory that has already been elaborated")
124+
raise FrozenMemory("Cannot add a memory port to a memory that has already been elaborated")
132125
signature = ReadPort.Signature(shape=self.shape, addr_width=ceil_log2(self.depth))
133126
return ReadPort(signature, memory=self, domain=domain, transparent_for=transparent_for,
134127
src_loc_at=1 + src_loc_at)
@@ -153,7 +146,7 @@ def write_port(self, *, domain="sync", granularity=None, src_loc_at=0):
153146
:class:`WritePort`
154147
"""
155148
if self._frozen:
156-
raise FrozenError("Cannot add a memory port to a memory that has already been elaborated")
149+
raise FrozenMemory("Cannot add a memory port to a memory that has already been elaborated")
157150
signature = WritePort.Signature(
158151
shape=self.shape, addr_width=ceil_log2(self.depth), granularity=granularity)
159152
return WritePort(signature, memory=self, domain=domain,
@@ -195,6 +188,7 @@ def elaborate(self, platform):
195188
return instance
196189

197190

191+
@final
198192
class ReadPort:
199193
"""A read memory port.
200194
@@ -318,6 +312,7 @@ def transparent_for(self):
318312
return self._transparent_for
319313

320314

315+
@final
321316
class WritePort:
322317
"""A write memory port.
323318

docs/stdlib/memory.rst

+27-5
Original file line numberDiff line numberDiff line change
@@ -170,17 +170,39 @@ However, the memory read port is also configured to be *transparent* relative to
170170
}
171171

172172

173-
Memories
174-
========
173+
Simulation
174+
==========
175+
176+
.. todo::
177+
178+
This section will be written once the simulator itself is documented.
179+
180+
181+
Memory description
182+
==================
183+
184+
.. autoexception:: amaranth.hdl.FrozenMemory
185+
186+
.. autoclass:: amaranth.hdl.MemoryData
187+
188+
189+
Memory component
190+
================
175191

176192
..
177193
attributes are not documented because they can be easily used to break soundness and we don't
178194
document them for signals either; they are rarely necessary for interoperability
179195
180-
.. autoclass:: Memory(*, depth, shape, init, src_loc_at=0)
181-
:no-members:
196+
..
197+
the following two directives document a pair of overloads of :class:`Memory`; this is a little
198+
weird and not really how rst/sphinx are supposed to work but it results in a comprehensible
199+
generated document. be careful to not break this!
200+
201+
.. class:: Memory(data, *, src_loc_at=0)
182202

183-
.. autoclass:: amaranth.lib.memory::Memory.Init(...)
203+
.. autoclass:: Memory(*, shape, depth, init, src_loc_at=0)
204+
:noindex:
205+
:no-members:
184206

185207
.. automethod:: read_port
186208

tests/test_lib_memory.py

+4-4
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.FrozenError,
442+
with self.assertRaisesRegex(memory.FrozenMemory,
443443
r"^Cannot add a memory port to a memory that has already been elaborated$"):
444444
m.write_port()
445-
with self.assertRaisesRegex(memory.FrozenError,
445+
with self.assertRaisesRegex(memory.FrozenMemory,
446446
r"^Cannot add a memory port to a memory that has already been elaborated$"):
447447
m.read_port()
448-
with self.assertRaisesRegex(memory.FrozenError,
448+
with self.assertRaisesRegex(memory.FrozenMemory,
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.FrozenError,
451+
with self.assertRaisesRegex(memory.FrozenMemory,
452452
r"^Cannot set 'init' on a memory that has already been elaborated$"):
453453
m.init[0] = 1

0 commit comments

Comments
 (0)