1919import collections
2020import numbers
2121import types
22- from typing import NamedTuple
22+ from collections .abc import Sequence
23+ from typing import Callable , NamedTuple
2324
2425from pyrtl .core import Block , LogicNet , _NameIndexer , working_block
2526from pyrtl .corecircuits import as_wires
@@ -95,28 +96,29 @@ def name(self, n):
9596
9697
9798class MemBlock :
98- """`` MemBlock` ` is the object for specifying block memories.
99+ """:class:` MemBlock` is the object for specifying block memories.
99100
100101 .. doctest only::
101102
102103 >>> import pyrtl
103104 >>> pyrtl.reset_working_block()
104105
105- `` MemBlock` ` can be indexed like an array for reads and writes. Example::
106+ :class:` MemBlock` can be indexed like an array for reads and writes. Example::
106107
107108 >>> mem = pyrtl.MemBlock(bitwidth=8, addrwidth=2)
108109
109110 >>> # Write to each address, starting from address 1.
110111 >>> write_addr = pyrtl.Register(name="write_addr", bitwidth=2, reset_value=1)
111112 >>> write_addr.next <<= write_addr + 1
112113
114+ >>> mem[write_addr] <<= write_addr + 10 # Creates a write port.
115+
113116 >>> # Read from each address, starting from address 0.
114117 >>> read_addr = pyrtl.Register(name="read_addr", bitwidth=2)
115118 >>> read_addr.next <<= read_addr + 1
116119
117120 >>> read_data = pyrtl.Output(name="read_data")
118121 >>> read_data <<= mem[read_addr] # Creates a read port.
119- >>> mem[write_addr] <<= write_addr + 10 # Creates a write port.
120122
121123 >>> sim = pyrtl.Simulation()
122124 >>> sim.step_multiple(nsteps=6)
@@ -132,9 +134,9 @@ class MemBlock:
132134 >>> pyrtl.reset_working_block()
133135
134136 When the address of a memory is assigned to using an :class:`EnabledWrite` object,
135- data will only be written to the memory when the `` EnabledWrite``'s
136- :attr:`~EnabledWrite.enable` ``WireVector`` is set to high (``1``). In the following
137- example, the ``MemBlock`` is only written when ``write_addr`` is odd::
137+ data will only be written to the memory when :attr:` EnabledWrite.enable` is high
138+ (``1``). In the following example, the :class:`MemBlock` is only written when
139+ ``write_addr`` is odd::
138140
139141 >>> mem = pyrtl.MemBlock(bitwidth=8, addrwidth=2)
140142
@@ -148,26 +150,27 @@ class MemBlock:
148150 >>> sorted(sim.inspect_mem(mem).items())
149151 [(1, 11), (3, 13)]
150152
151- Writes under :ref:`conditional_assignment` are automatically converted to
152- :class:`EnabledWrites<EnabledWrite>`.
153+ Writes under :ref:`conditional_assignment` with ``|=`` (:meth:`~WireVector.__ior__`)
154+ are automatically converted to :class:`EnabledWrites<EnabledWrite>`.
153155
154156 .. _asynchronous_memories:
155157
156158 Asynchronous Memories
157159 ---------------------
158160
159161 It is best practice to have memory operations start on a rising clock edge if you
160- want them to synthesize into efficient hardware, so ``MemBlocks`` are `synchronous`
161- by default (``asynchronous=False``). ``MemBlocks`` will enforce this by checking
162- that all their inputs are ready at each rising clock edge. This implies that all
163- ``MemBlock`` inputs - the address to read/write, the data to write, and the
164- write-enable bit - must be registers, inputs, or constants, unless you explicitly
165- declare the memory as `asynchronous` with ``asynchronous=True``.
166-
167- Asynchronous memories can be convenient and tempting, but they are rarely a good
168- idea. They can't be mapped to block RAMs in FPGAs and will be converted to registers
169- by most design tools. They are not a realistic option for memories with more than a
170- few hundred elements.
162+ want them to synthesize into efficient hardware, so :class:`MemBlocks<MemBlock>` are
163+ `synchronous` by default (``asynchronous=False``). :class:`MemBlocks<MemBlock>` will
164+ enforce this by checking that all their inputs are ready at each rising clock edge.
165+ This implies that `all` :class:`MemBlock` inputs - the address to read/write, the
166+ data to write, and the write-enable bit - must be :class:`Registers<.Register>`,
167+ :class:`Inputs<.Input>`, or :class:`Consts<.Const>`, unless you explicitly declare
168+ the memory as `asynchronous` with ``asynchronous=True``.
169+
170+ Asynchronous memories are convenient, but they are rarely a good idea. They can't be
171+ mapped to block RAMs in FPGAs and will be converted to registers by most design
172+ tools. They are not a realistic option for memories with more than a few hundred
173+ elements.
171174
172175 Read and Write Ports
173176 --------------------
@@ -182,9 +185,9 @@ class MemBlock:
182185 Default Values
183186 --------------
184187
185- In PyRTL :class:`Simulation`, all `` MemBlocks`` are zero-initialized by default.
186- Initial data can be specified for each MemBlock in :meth:`Simulation.__init__`'s
187- ``memory_value_map``.
188+ In PyRTL :class:`Simulation`, all :class:` MemBlocks<MemBlock>` are zero-initialized
189+ by default. Initial data can be specified for each MemBlock in
190+ :meth:`Simulation.__init__`'s ``memory_value_map``.
188191
189192 Simultaneous Read and Write
190193 ---------------------------
@@ -195,8 +198,8 @@ class MemBlock:
195198 >>> pyrtl.reset_working_block()
196199
197200 In PyRTL :class:`Simulation`, if the same address is read and written in the same
198- cycle, the read will return the `last` value stored in the `` MemBlock`` , not the
199- newly written value. Example::
201+ cycle, the read will return the `last` value stored in the :class:` MemBlock`, not
202+ the newly written value. Example::
200203
201204 >>> mem = pyrtl.MemBlock(addrwidth=1, bitwidth=1)
202205 >>> mem[0] <<= 1
@@ -215,18 +218,18 @@ class MemBlock:
215218 >>> sim.inspect("read_data")
216219 1
217220
218- Mapping `` MemBlocks` ` to Hardware
219- ---------------------------------
221+ Mapping :class:` MemBlocks<MemBlock> ` to Hardware
222+ ------------------------------------------------
220223
221- Synchronous `` MemBlocks`` can generally be mapped to FPGA block RAMs and similar
222- hardware, but there are many pitfalls:
224+ Synchronous :class:` MemBlocks<MemBlock>` can generally be mapped to FPGA block RAMs
225+ and similar hardware, but there are many pitfalls:
223226
224227 #. ``asynchronous=False`` is generally necessary, but may not be sufficient, for
225228 mapping a design to FPGA block RAMs. Block RAMs may have additional timing
226229 constraints, like requiring register outputs for each block RAM.
227230 ``asynchronous=False`` only requires register inputs.
228231
229- #. Block RAMs may offer more or less read and write ports than `` MemBlock` `'s
232+ #. Block RAMs may offer more or less read and write ports than :class:` MemBlock`'s
230233 defaults.
231234
232235 #. Block RAMs may not zero-initialize by default.
@@ -241,7 +244,7 @@ class EnabledWrite(NamedTuple):
241244 data : WireVector
242245 """Data to write."""
243246 enable : WireVector
244- """Single-bit `` WireVector` ` indicating if a write should occur."""
247+ """Single-bit :class:`. WireVector` indicating if a write should occur."""
245248
246249 def __init__ (
247250 self ,
@@ -298,13 +301,13 @@ def read_ports(self):
298301 raise PyrtlError (msg )
299302
300303 def __getitem__ (self , addr : WireVectorLike ) -> WireVector :
301- """Create a read port to read data from the `` MemBlock` `.
304+ """Create a read port to read data from the :class:` MemBlock`.
302305
303- :param addr: `` MemBlock`` address to read. A `` WireVector`` , or any type that
304- can be coerced to `` WireVector` ` by :func:`as_wires`.
306+ :param addr: :class:` MemBlock` address to read. A :class:`. WireVector`, or any
307+ type that can be coerced to :class:`. WireVector` by :func:`as_wires`.
305308
306- :return: A `` WireVector`` containing the data read from the ``MemBlock`` at
307- address ``addr``.
309+ :return: A :class:`. WireVector` containing the data read from the
310+ :class:`MemBlock` at address ``addr``.
308311 """
309312 addr = as_wires (addr , bitwidth = self .addrwidth , truncating = False )
310313 if len (addr ) > self .addrwidth :
@@ -315,13 +318,13 @@ def __getitem__(self, addr: WireVectorLike) -> WireVector:
315318 def __setitem__ (
316319 self , addr : WireVectorLike , data : MemBlock .EnabledWrite | WireVectorLike
317320 ):
318- """Create a write port to write data to the `` MemBlock` `.
321+ """Create a write port to write data to the :class:` MemBlock`.
319322
320- :param addr: `` MemBlock`` address to write. A `` WireVector`` , or any type that
321- can be coerced to `` WireVector` ` by :func:`as_wires`.
322- :param data: `` MemBlock` ` data to write. An :class:`EnabledWrite`,
323- `` WireVector`` , or any type that can be coerced to ``WireVector`` by
324- :func:`as_wires`.
323+ :param addr: :class:` MemBlock` address to write. A :class:`. WireVector`, or any
324+ type that can be coerced to :class:`. WireVector` by :func:`as_wires`.
325+ :param data: :class:` MemBlock` data to write. An :class:`EnabledWrite`,
326+ :class:`. WireVector`, or any type that can be coerced to
327+ :class:`.WireVector` by : func:`as_wires`.
325328 """
326329 if isinstance (data , _MemAssignment ):
327330 self ._assignment (addr , data .rhs , is_conditional = data .is_conditional )
@@ -403,9 +406,10 @@ def _make_copy(self, block=None):
403406class RomBlock (MemBlock ):
404407 """PyRTL Read Only Memory (ROM).
405408
406- ``RomBlocks`` are PyRTL's read only memory block. They support the same read
407- interface as :class:`MemBlock`, but they cannot be written to (i.e. there are no
408- write ports). The ROM's contents are specified when the ROM is constructed.
409+ :class:`RomBlocks<RomBlock>` are PyRTL's read only memory block. They support the
410+ same read interface as :class:`MemBlock`, but they cannot be written to (i.e. there
411+ are no write ports). The ROM's contents are specified when the ROM is constructed,
412+ as ``romdata``.
409413
410414 .. doctest only::
411415
@@ -432,7 +436,7 @@ def __init__(
432436 self ,
433437 bitwidth : int ,
434438 addrwidth : int ,
435- romdata ,
439+ romdata : Sequence | Callable [[ int ], int ] ,
436440 name : str = "" ,
437441 max_read_ports : int = 2 ,
438442 build_new_roms : bool = False ,
@@ -445,14 +449,15 @@ def __init__(
445449 :param bitwidth: The bitwidth of each element in the ROM.
446450 :param addrwidth: The number of bits used to address an element in the ROM. The
447451 ROM can store ``2 ** addrwidth`` elements.
448- :param romdata: Specifies the data stored in the ROM. This can either be a
449- function or an array (iterable) that maps from address to data.
452+ :param romdata: Specifies the data stored in the ROM. This can be an array or a
453+ function that maps from address to data.
450454 :param name: The identifier for the memory.
451455 :param max_read_ports: Limits the number of read ports each block can create;
452456 passing ``None`` indicates there is no limit.
453457 :param build_new_roms: Indicates whether :meth:`RomBlock.__getitem__` should
454- create copies of the ``RomBlock`` to avoid exceeding ``max_read_ports``.
455- :param asynchronous: If ``False``, ensure that all ``RomBlock`` inputs are
458+ create copies of the :class:`RomBlock` to avoid exceeding
459+ ``max_read_ports``.
460+ :param asynchronous: If ``False``, ensure that all :class:`RomBlock` inputs are
456461 registers, inputs, or constants. See :ref:`asynchronous_memories`.
457462 :param pad_with_zeros: If ``True``, fill any missing ``romdata`` with zeros so
458463 all accesses to the ROM are well defined. Otherwise, :class:`Simulation`
@@ -479,19 +484,20 @@ def __init__(
479484 self .pad_with_zeros = pad_with_zeros
480485
481486 def __getitem__ (self , addr : WireVector ) -> WireVector :
482- """Create a read port to read data from the `` RomBlock` `.
487+ """Create a read port to read data from the :class:` RomBlock`.
483488
484- If ``build_new_roms`` was specified, create a new copy of the `` RomBlock`` if
485- the number of read ports exceeds ``max_read_ports``.
489+ If ``build_new_roms`` was specified, create a new copy of the :class:` RomBlock`
490+ if the number of read ports exceeds ``max_read_ports``.
486491
487- :param addr: `` MemBlock` ` address to read.
492+ :param addr: :class:` MemBlock` address to read.
488493
489- :raises PyrtlError: If ``addr`` is an ``int``. ``RomBlocks`` hold constant data,
490- so they don't need to be read when the read address is statically known.
491- Create a :class:`Const` with the data at the read address instead.
494+ :raises PyrtlError: If ``addr`` is an :class:`int`. :class:`RomBlocks<RomBlock>`
495+ hold constant data, so they don't need to be read when the read address is
496+ statically known. Create a :class:`Const` with the data at the read address
497+ instead.
492498
493- :return: A `` WireVector`` containing the data read from the ``RomBlock`` at
494- address ``addr``.
499+ :return: A :class:`. WireVector` containing the data read from the
500+ :class:`RomBlock` at address ``addr``.
495501 """
496502 if isinstance (addr , numbers .Number ):
497503 msg = (
0 commit comments