Skip to content

Commit 0a57cd4

Browse files
committed
Add documentation and examples for Const.val, Register.reset_value, WireVector.__ior__.
Also: * Add a lot of links. * Update Sphinx dependencies.
1 parent ef456b6 commit 0a57cd4

File tree

4 files changed

+375
-264
lines changed

4 files changed

+375
-264
lines changed

docs/basic.rst

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ the output of that adder. :class:`.Block` stores the description of the
1111
hardware as you build it.
1212

1313
:class:`.Input`, :class:`.Output`, :class:`.Const`, and :class:`.Register` all
14-
derive from :class:`.WireVector`. :class:`.Input` represents an input pin,
15-
serving as a placeholder for an external value provided during simulation.
16-
:class:`.Output` represents an output pin, which does not drive any wires in
17-
the design. :class:`.Const` is useful for specifying hard-wired values and
18-
:class:`.Register` is how sequential elements are created (they all have an
19-
implicit clock).
14+
derive from :class:`.WireVector`. :class:`.Input` represents an input pin,
15+
serving as a placeholder for an external value provided during
16+
:class:`.Simulation`. :class:`.Output` represents an output pin, which does not
17+
drive any wires in the design. :class:`.Const` is useful for specifying
18+
hard-wired values and :class:`.Register` is how sequential elements are created
19+
(they all have an implicit clock).
2020

2121
.. inheritance-diagram:: pyrtl.WireVector
2222
pyrtl.Input
@@ -32,7 +32,7 @@ WireVector
3232
:members:
3333
:special-members: __init__, __add__, __sub__, __mul__, __getitem__,
3434
__len__, __ilshift__, __invert__, __and__, __or__, __xor__, __lt__,
35-
__le__, __eq__, __ne__, __gt__, __ge__, __len__
35+
__le__, __eq__, __ne__, __gt__, __ge__, __len__, __ior__
3636

3737
Input Pins
3838
----------
@@ -191,8 +191,9 @@ These default values can be changed by passing a ``defaults`` dict to
191191
The Conditional Assigment Operator (``|=``)
192192
-------------------------------------------
193193

194-
Conditional assignments are written with the ``|=`` operator, and not the usual ``<<=``
195-
operator.
194+
Conditional assignments are written with the ``|=``
195+
(:meth:`~.WireVector.__ior__`) operator, and not the usual ``<<=``
196+
(:meth:`~.WireVector.__ilshift__`) operator.
196197

197198
* The ``|=`` operator is a *conditional* assignment. Conditional assignments can only be
198199
written in a :data:`.conditional_assignment` block.

docs/requirements.txt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,21 @@
44
#
55
# pip-compile requirements.in
66
#
7+
accessible-pygments==0.0.5
8+
# via furo
79
alabaster==1.0.0
810
# via sphinx
911
babel==2.17.0
1012
# via sphinx
1113
beautifulsoup4==4.13.4
1214
# via furo
13-
certifi==2025.6.15
15+
certifi==2025.8.3
1416
# via requests
1517
charset-normalizer==3.4.2
1618
# via requests
1719
docutils==0.21.2
1820
# via sphinx
19-
furo==2024.8.6
21+
furo==2025.7.19
2022
# via -r requirements.in
2123
idna==3.10
2224
# via requests
@@ -30,6 +32,7 @@ packaging==25.0
3032
# via sphinx
3133
pygments==2.19.2
3234
# via
35+
# accessible-pygments
3336
# furo
3437
# sphinx
3538
requests==2.32.4
@@ -65,7 +68,7 @@ sphinxcontrib-qthelp==2.0.0
6568
# via sphinx
6669
sphinxcontrib-serializinghtml==2.0.0
6770
# via sphinx
68-
typing-extensions==4.14.0
71+
typing-extensions==4.14.1
6972
# via beautifulsoup4
7073
urllib3==2.5.0
7174
# via requests

pyrtl/memory.py

Lines changed: 65 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
import collections
2020
import numbers
2121
import types
22-
from typing import NamedTuple
22+
from collections.abc import Sequence
23+
from typing import Callable, NamedTuple
2324

2425
from pyrtl.core import Block, LogicNet, _NameIndexer, working_block
2526
from pyrtl.corecircuits import as_wires
@@ -95,28 +96,29 @@ def name(self, n):
9596

9697

9798
class 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):
403406
class 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

Comments
 (0)