Skip to content

Commit e7e007a

Browse files
committed
Update Memory and Simulation documentation. Lots of improvements:
Memory: * Add `MemBlock` and `RomBlock` doctest examples. * Rename `MemBlock` and `RomBlock`'s `__getitem__`` and ``__setitem__`` args to improve readability and documentation. * Convert `EnabledWrite` from `namedtuple` to `NamedTuple` so we can document the fields and their types. * Move some `MemBlock` documentation from constructor to class. Simulation: * Replace `FastSimulation` and `CompiledSimulation`'s copy-and-pasted docstrings with references to `Simulation`'s docstrings. * Add a note on how `FastSimulation` can sometimes be a better choice than `CompiledSimulation`. * Add documentation for `Simulation.tracer` and `SimulationTrace.trace`. * Enable doctests for `simulation.py`. * Do not display documentation for `CompiledSimulation.run`, `SimulationTrace.add_step`, or `SimulationTrace.add_fast_step`, which are implementation details. * Move `enum_name` documentation to the `SimulationTrace` section. Also: * Add some documentation on `doctest` to `docs/README.md`. * Add links to `conditional_assignment`, `working_block` * In example blocks, the copy button no longer copies the sample output.
1 parent dd0a663 commit e7e007a

17 files changed

+836
-787
lines changed

docs/README.md

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,42 @@ The main Sphinx configuration file is
1515

1616
Most of PyRTL's documentation is automatically extracted from Python
1717
docstrings, see [docstring
18-
formating](https://www.sphinx-doc.org/en/master/usage/domains/python.html)
19-
for supported directives and fields. Sphinx parses [Python type
18+
formating](https://www.sphinx-doc.org/en/master/usage/domains/python.html) for
19+
supported directives and fields. Sphinx parses [Python type
2020
annotations](https://docs.python.org/3/library/typing.html), so put type
21-
information into annotations instead of docstrings.
21+
information in annotations instead of docstrings.
2222

2323
Follow the instructions on this page to build a local copy of PyRTL's
2424
documentation. This is useful for verifying that PyRTL's documentation still
2525
renders correctly after making a local change.
2626

27-
There is additional PyRTL documentation in the
28-
[`gh-pages` branch](https://github.com/UCSBarchlab/PyRTL/tree/gh-pages).
29-
This additional documentation is pushed to https://ucsbarchlab.github.io/PyRTL/
30-
by the `pages-build-deployment` GitHub Action. The additional documentation is
31-
written in [GitHub MarkDown](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax),
32-
and is not described further in this README.
27+
There is additional PyRTL documentation in the [`gh-pages`
28+
branch](https://github.com/UCSBarchlab/PyRTL/tree/gh-pages). This additional
29+
documentation is pushed to https://ucsbarchlab.github.io/PyRTL/ by the
30+
`pages-build-deployment` GitHub Action. This additional documentation is
31+
written HTML and is not described further in this README.
32+
33+
## Testing Documentation Examples
34+
35+
PyRTL's documentation contains many examples that are tested with
36+
[`doctest`](https://docs.python.org/3/library/doctest.html). It is important to
37+
test these examples so we can be sure that they keep working as we change the
38+
code. These tests run via test fixtures called `TestDocTest`, see the example
39+
in
40+
[`test_core.py`](https://github.com/UCSBarchlab/PyRTL/blob/development/tests/test_core.py).
41+
42+
When adding a new `doctest`, you'll need to to add a preceding comment block
43+
that imports PyRTL and resets the working block before running your new
44+
`doctest`. This comment block contains additional code necessary for `doctest`
45+
to successfully run the test, but the lines are commented out because they are
46+
not worth showing in every example. These blocks look like:
47+
48+
```
49+
..
50+
# For ``doctest``.
51+
>>> import pyrtl
52+
>>> pyrtl.reset_working_block()
53+
```
3354

3455
## Installing Sphinx
3556

docs/basic.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,10 @@ Constants
5656
:show-inheritance:
5757
:special-members: __init__
5858

59-
Conditionals
60-
------------
59+
.. _conditional_assignment:
60+
61+
Conditional Assignment
62+
----------------------
6163

6264
.. automodule:: pyrtl.conditional
6365
:members:

docs/blocks.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Blocks
88
:members:
99
:exclude-members: sanity_check_memblock, sanity_check_memory_sync, sanity_check_net, sanity_check_wirevector
1010

11+
.. _working_block:
1112

1213
``working_block``
1314
^^^^^^^^^^^^^^^^^

docs/conf.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,6 @@
7171
inheritance_graph_attrs = {
7272
'bgcolor': 'aliceblue',
7373
}
74+
75+
# The copy button excludes line numbers, prompts, and outputs.
76+
copybutton_exclude = '.linenos, .gp, .go'

docs/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ markupsafe==3.0.2
2828
# via jinja2
2929
packaging==25.0
3030
# via sphinx
31-
pygments==2.19.1
31+
pygments==2.19.2
3232
# via
3333
# furo
3434
# sphinx

docs/simtest.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,17 @@ Compiled (JIT to C) Simulation
2121
.. autoclass:: pyrtl.compilesim.CompiledSimulation
2222
:members:
2323
:special-members: __init__
24+
:exclude-members: run
2425

2526
Simulation Trace
2627
----------------
2728

2829
.. autoclass:: pyrtl.simulation.SimulationTrace
2930
:members:
3031
:special-members: __init__
32+
:exclude-members: add_fast_step, add_step
33+
34+
.. autofunction:: pyrtl.simulation.enum_name
3135

3236
Wave Renderer
3337
-------------
@@ -36,7 +40,7 @@ Wave Renderer
3640
:members:
3741
:special-members: __init__
3842
:exclude-members: render_ruler_segment, render_val, val_to_str
39-
.. autofunction:: pyrtl.simulation.enum_name
43+
.. autoclass:: pyrtl.simulation.RendererConstants
4044
.. autoclass:: pyrtl.simulation.PowerlineRendererConstants
4145
:show-inheritance:
4246
.. autoclass:: pyrtl.simulation.Utf8RendererConstants

pyrtl/analysis.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,8 @@ class TimingAnalysis:
147147
def __init__(self, block=None, gate_delay_funcs=None):
148148
""" Calculates timing delays in the block.
149149
150-
:param Block block: PyRTL block to analyze
150+
:param Block block: PyRTL block to analyze. Defaults to the
151+
:ref:`working_block`.
151152
:param gate_delay_funcs: a map with keys corresponding to the gate op and
152153
a function returning the delay as the value.
153154
It takes the gate as an argument.
@@ -449,7 +450,7 @@ def paths(src=None, dst=None, dst_nets=None, block=None):
449450
:param dict[WireVector, LogicNet] dst_nets: map from wire to set of nets where the
450451
wire is an argument; will compute it internally if not given via a
451452
call to pyrtl.net_connections()
452-
:param Block block: block to use (defaults to working block)
453+
:param Block block: block to use (defaults to :ref:`working_block`)
453454
:return: a map of the form `{src_wire: {dst_wire: [path]}}` for each `src_wire` in `src`
454455
(or all inputs if `src` is None), `dst_wire` in `dst` (or all outputs if `dst` is None),
455456
where `path` is a list of nets. This map is also an instance of :py:class:`.PathsResult`,
@@ -552,7 +553,7 @@ def distance(src, dst, f, block=None):
552553
:param Callable[[LogicNet], int] f: function from a net to number,
553554
representing the 'value' of a net that you want to sum
554555
across all nets in the path
555-
:param Block block: block to use (defaults to working block)
556+
:param Block block: block to use (defaults to :ref:`working_block`)
556557
:return: a map from each path (a tuple) to its calculated distance
557558
558559
This calls the given function `f` on each net in a path, summing the result.

pyrtl/compilesim.py

Lines changed: 60 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from pyrtl.wire import Input, Output, Const, WireVector, Register
1515
from pyrtl.memory import MemBlock, RomBlock
1616
from pyrtl.pyrtlexceptions import PyrtlError, PyrtlInternalError
17-
from pyrtl.simulation import SimulationTrace, _trace_sort_key
17+
from pyrtl.simulation import Simulation, SimulationTrace, _trace_sort_key
1818
from pyrtl.helperfuncs import infer_val_and_bitwidth
1919

2020

@@ -53,48 +53,45 @@ def __eq__(self, other):
5353

5454

5555
class CompiledSimulation:
56-
"""Simulate a block, compiling to C for efficiency.
57-
58-
This module provides significant speed improvements over
59-
:class:`.FastSimulation`, at the cost of somewhat longer setup time.
60-
Generally this will do better than :class:`.FastSimulation` for simulations
61-
requiring over 1000 steps. It is not built to be a debugging tool, though
62-
it may help with debugging. Note that only :class:`.Input` and
63-
:class:`.Output` wires can be traced using CompiledSimulation. This code
64-
is still experimental, but has been used on designs of significant scale to
65-
good effect.
66-
67-
In order to use this, you need:
68-
- A 64-bit processor
69-
- GCC (tested on version 4.8.4)
70-
- A 64-bit build of Python
56+
"""Simulate a block by generating, compiling, and running C code.
57+
58+
``CompiledSimulation`` provides significant execution speed improvements over
59+
:class:`.FastSimulation`, at the cost of even longer start-up time. Generally this
60+
will do better than :class:`.FastSimulation` for simulations requiring over 1000
61+
steps.
62+
63+
``CompiledSimulation`` is not built to be a debugging tool, though it may help with
64+
debugging. Note that only :class:`.Input` and :class:`.Output` wires can be traced
65+
with ``CompiledSimulation``.
66+
67+
.. note::
68+
For very large circuits, :class:`.FastSimulation` can sometimes be a better
69+
choice than ``CompiledSimulation`` because ``CompiledSimulation`` will generate
70+
an extremely large ``.c`` file, which can take prohibitively long to compile and
71+
optimize. :class:`.FastSimulation` will generate an extremely large ``.py``
72+
file, but Python will interpret that generated code as needed, instead of trying
73+
to process all the generated code at once.
74+
75+
.. WARNING::
76+
This code is still experimental, but has been used on designs of significant
77+
scale to good effect.
78+
79+
To use ``CompiledSimulation``, you'll need:
80+
81+
- A 64-bit processor
82+
- GCC (tested on version 4.8.4)
83+
- A 64-bit build of Python
7184
7285
If using the multiplication operand, only some architectures are supported:
73-
- x86-64 / amd64
74-
- arm64 / aarch64
75-
- mips64 (untested)
76-
77-
``default_value`` is currently only implemented for registers, not memories.
78-
79-
A Simulation step works as follows:
80-
81-
1. Registers are updated:
82-
1. (If this is the first step) With the default values passed in
83-
to the Simulation during instantiation and/or any reset values
84-
specified in the individual registers.
85-
2. (Otherwise) With their next values calculated in the previous step
86-
(``r`` logic nets).
87-
2. The new values of these registers as well as the values of block inputs
88-
are propagated through the combinational logic.
89-
3. Memory writes are performed (``@`` logic nets).
90-
4. The current values of all wires are recorded in the trace.
91-
5. The next values for the registers are saved, ready to be applied at the
92-
beginning of the next step.
93-
94-
Note that the register values saved in the trace after each simulation step
95-
are from *before* the register has latched in its newly calculated values,
96-
since that latching in occurs at the beginning of the *next* step.
9786
87+
- x86-64 / amd64
88+
- arm64 / aarch64
89+
- mips64 (untested)
90+
91+
``default_value`` is currently only implemented for :class:`Registers<.Register>`,
92+
not :class:`MemBlocks<.MemBlock>`.
93+
94+
See :class:`.Simulation` for an overview of PyRTL simulations.
9895
"""
9996

10097
def __init__(
@@ -131,12 +128,17 @@ def __init__(
131128
self._create_dll()
132129
self._initialize_mems()
133130

131+
# Use Simulation.__init__'s docstring as CompiledSimulation.__init__'s docstring.
132+
__init__.__doc__ = Simulation.__init__.__doc__
133+
134134
def inspect_mem(self, mem: MemBlock) -> dict[int, int]:
135-
"""Get a view into the contents of a MemBlock."""
136135
return DllMemInspector(self, mem)
137136

137+
# Use Simulation.inspect_mem's docstring as CompiledSimulation.inspect_mem's
138+
# docstring.
139+
inspect_mem.__doc__ = Simulation.inspect_mem.__doc__
140+
138141
def inspect(self, w: str) -> int:
139-
"""Get the latest value of the wire given, if possible."""
140142
if isinstance(w, WireVector):
141143
w = w.name
142144
try:
@@ -149,23 +151,10 @@ def inspect(self, w: str) -> int:
149151
return vals[-1]
150152
raise PyrtlError('CompiledSimulation does not support inspecting internal WireVectors')
151153

152-
def step(self, provided_inputs: dict[str, int] = {}, inputs=None):
153-
"""Run one step of the simulation.
154-
155-
:param provided_inputs: A mapping from input names to the values for
156-
the step.
157-
158-
A step causes the block to be updated as follows, in order:
159-
160-
1. Registers are updated with their :attr:`~.Register.next` values
161-
computed in the previous cycle
162-
2. Block inputs and these new register values propagate through the
163-
combinational logic
164-
3. Memories are updated
165-
4. The :attr:`~.Register.next` values of the registers are saved for
166-
use in step 1 of the next cycle.
154+
# Use Simulation.inspect's docstring as CompiledSimulation.inspect's docstring.
155+
inspect.__doc__ = Simulation.inspect.__doc__
167156

168-
"""
157+
def step(self, provided_inputs: dict[str, int] = {}, inputs=None):
169158
if inputs is not None:
170159
import warnings
171160
warnings.warn(
@@ -174,65 +163,13 @@ def step(self, provided_inputs: dict[str, int] = {}, inputs=None):
174163
provided_inputs = inputs
175164
self.run([provided_inputs])
176165

166+
# Use Simulation.step's docstring as CompiledSimulation.step's docstring.
167+
step.__doc__ = Simulation.step.__doc__
168+
177169
def step_multiple(self, provided_inputs: dict[str, list[int]] = {},
178170
expected_outputs: dict[str, int] = {},
179171
nsteps: int = None, file=sys.stdout,
180172
stop_after_first_error: bool = False):
181-
"""Take the simulation forward N cycles, where N is the number of values
182-
for each provided input.
183-
184-
:param provided_inputs: a dictionary mapping wirevectors to their values for N steps
185-
:param expected_outputs: a dictionary mapping wirevectors to their expected values
186-
for N steps; use ``?`` to indicate you don't care what the value at that step is
187-
:param nsteps: number of steps to take (defaults to None, meaning step for each
188-
supplied input value)
189-
:param file: where to write the output (if there are unexpected outputs detected)
190-
:param stop_after_first_error: a boolean flag indicating whether to stop the simulation
191-
after the step where the first errors are encountered (defaults to False)
192-
193-
All input wires must be in the `provided_inputs` in order for the simulation
194-
to accept these values. Additionally, the length of the array of provided values for each
195-
input must be the same.
196-
197-
When `nsteps` is specified, then it must be *less than or equal* to the number of values
198-
supplied for each input when `provided_inputs` is non-empty. When `provided_inputs` is
199-
empty (which may be a legitimate case for a design that takes no inputs), then `nsteps`
200-
will be used. When `nsteps` is not specified, then the simulation will take the number
201-
of steps equal to the number of values supplied for each input.
202-
203-
Example: if we have inputs named ``a`` and ``b`` and output ``o``, we can call::
204-
205-
sim.step_multiple({'a': [0,1], 'b': [23,32]}, {'o': [42, 43]})
206-
207-
to simulate 2 cycles, where in the first cycle ``a`` and ``b`` take on
208-
0 and 23, respectively, and ``o`` is expected to have the value 42, and
209-
in the second cycle ``a`` and ``b`` take on 1 and 32, respectively, and
210-
``o`` is expected to have the value 43.
211-
212-
If your values are all single digit, you can also specify them in a single string, e.g.::
213-
214-
sim.step_multiple({'a': '01', 'b': '01'})
215-
216-
will simulate 2 cycles, with ``a`` and ``b`` taking on
217-
0 and 0, respectively, on the first cycle and 1 and 1, respectively, on the second
218-
cycle.
219-
220-
Example: if the design had no inputs, like so::
221-
222-
a = pyrtl.Register(8)
223-
b = pyrtl.Output(8, 'b')
224-
225-
a.next <<= a + 1
226-
b <<= a
227-
228-
sim = pyrtl.Simulation()
229-
sim.step_multiple(nsteps=3)
230-
231-
Using ``sim.step_multiple(nsteps=3)`` simulates 3 cycles, after which
232-
we would expect the value of ``b`` to be 2.
233-
234-
"""
235-
236173
if not nsteps and len(provided_inputs) == 0:
237174
raise PyrtlError('need to supply either input values or a number of steps to simulate')
238175

@@ -297,11 +234,18 @@ def _sort_tuple(t):
297234
file.write("{0:>5} {1:>10} {2:>8} {3:>8}\n".format(step, name, expected, actual))
298235
file.flush()
299236

237+
# Use Simulation.step_multiple's docstring as CompiledSimulation.step_multiple's
238+
# docstring.
239+
step_multiple.__doc__ = Simulation.step_multiple.__doc__
240+
300241
def run(self, inputs: list[dict[str, int]]):
301-
""" Run many steps of the simulation.
242+
"""Run many steps of the ``CompiledSimulation``.
243+
244+
:meth:`CompiledSimulation.step` and :meth:`CompiledSimulation.step_multiple` are
245+
wrappers around this lower-level method.
302246
303-
:param inputs: A list of input mappings for each step;
304-
its length is the number of steps to be executed.
247+
:param inputs: A list of input mappings for each step; its length is the number
248+
of steps to be executed.
305249
"""
306250
steps = len(inputs)
307251
# create i/o arrays of the appropriate length

0 commit comments

Comments
 (0)