diff --git a/.github/workflows/pages-deploy.yml b/.github/workflows/pages-deploy.yml new file mode 100644 index 00000000..81d27e87 --- /dev/null +++ b/.github/workflows/pages-deploy.yml @@ -0,0 +1,36 @@ +name: Deploy to GitHub Pages + +on: + push: + paths: + - www/** + - docs/screenshots/** + - docs/brand/** + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + - name: Generate pages with Sphinx + run: uv run just www + - name: Upload static files as artifact + id: deployment + uses: actions/upload-pages-artifact@v3 + with: + path: www/_build/html + + deploy: + needs: build + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 9652cf77..160d2b3c 100644 --- a/.gitignore +++ b/.gitignore @@ -39,7 +39,7 @@ __pycache__ # Stuff specifically for PyRTL spice.net -docs/_build +_build # Verilog files # There must be a very good reason for someone to add a verilog file to the repo diff --git a/README.md b/README.md index e6e3797f..e274763f 100644 --- a/README.md +++ b/README.md @@ -13,25 +13,25 @@ PyRTL PyRTL provides a collection of classes for Pythonic [register-transfer level](https://en.wikipedia.org/wiki/Register-transfer_level) design, simulation, tracing, and testing suitable for teaching and research. -Simplicity, usability, clarity, and extensibility rather than performance or -optimization is the overarching goal. Features include: +Simplicity, usability, clarity, and extensibility are overarching goals, rather +than performance or optimization. Features include: * Elaboration-through-execution, meaning all of Python can be used including - introspection -* Design, instantiate, and simulate all in one file and without leaving Python + introspection. +* Design, instantiate, and simulate all in one file and without leaving Python. * Export to, or import from, common HDLs (BLIF-in, Verilog-out currently - supported) -* Examine execution with waveforms on the terminal or export to `.vcd` as - projects scale -* Elaboration, synthesis, and basic optimizations all included + supported). +* Examine execution with waveforms on the terminal or export to + [`.vcd`](https://en.wikipedia.org/wiki/Value_change_dump) as projects scale. +* Elaboration, synthesis, and basic optimizations all included. * Small and well-defined internal core structure means writing new transforms - is easier -* Batteries included means many useful components are already available and - more are coming every week + is easier. +* Batteries included means many useful components are already available. What README would be complete without a screenshot? Below you can see the -waveform rendered right on the terminal for a small state machine written in -PyRTL. +waveform rendered right on the terminal for a [small state +machine](https://github.com/UCSBarchlab/PyRTL/blob/development/examples/example3-statemachine.py) +written in PyRTL. ![Command-line waveform for PyRTL state machine](https://github.com/UCSBarchlab/PyRTL/blob/development/docs/screenshots/pyrtl-statemachine.png?raw=true "PyRTL State Machine Screenshot") @@ -43,44 +43,46 @@ PyRTL. [`examples`](https://github.com/UCSBarchlab/PyRTL/tree/development/examples) directory. You can also [try the examples on MyBinder](https://mybinder.org/v2/gh/UCSBarchlab/PyRTL/development?filepath=%2Fipynb-examples%2F). -* Full reference documentation is available at https://pyrtl.readthedocs.io/ +* [Full reference documentation](https://pyrtl.readthedocs.io/) is available. ### Package Contents -If you are just getting started with PyRTL it is suggested that you start with -the -[`examples`](https://github.com/UCSBarchlab/PyRTL/tree/development/examples) -first to get a sense of the "thinking with PyRTLs" required to design hardware -in this way. If you are looking for a deeper understanding, dive into the code -for the object `Block`. It is the core data structure at the heart of PyRTL and -defines its semantics at a high level -- everything is converted to or from the -small, simple set of primitives defined there. +If you are just getting started with PyRTL, try starting with the +[`examples`](https://github.com/UCSBarchlab/PyRTL/tree/development/examples) to +get a sense of the "thinking with PyRTLs" required to design hardware in this +way. If you are looking for a deeper understanding, dive into the code for the +object [`Block`](https://pyrtl.readthedocs.io/en/latest/blocks.html#blocks). It +is the core data structure at the heart of PyRTL and defines its semantics at a +high level -- everything is converted to or from the small, simple set of +primitives defined there. The package contains the following files and directories: * [`pyrtl`](https://github.com/UCSBarchlab/PyRTL/tree/development/pyrtl) - The src directory for the module. + The module's source code. * [`pyrtl/rtllib/`](https://github.com/UCSBarchlab/PyRTL/tree/development/pyrtl/rtllib) Finished PyRTL libraries which are hopefully both useful and documented. * [`examples`](https://github.com/UCSBarchlab/PyRTL/tree/development/examples) - A set of hardware design examples that show the main idea behind PyRTL. + A set of hardware design examples that show the main ideas behind PyRTL. * [`tests`](https://github.com/UCSBarchlab/PyRTL/tree/development/tests) A set of unit tests for PyRTL which you can run with `pytest`. * [`docs`](https://github.com/UCSBarchlab/PyRTL/tree/development/docs) - Location of the Sphinx documentation. + Documentation written in + [Sphinx](https://www.sphinx-doc.org/en/master/index.html) + [reStructuredText](https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#rst-primer). -### The PyRTL Development Environment +### PyRTL Development Environment -All PyRTL developers should use the exact same tools to avoid confusing +All PyRTL developers should use the same tool versions to avoid confusing situations where a test fails only on one person's computer, or the generated documentation looks weird on another person's computer. -PyRTL uses [`uv`](https://docs.astral.sh/uv/) to ensure everyone's development -environments are consistent. `uv` manages the installation and versioning for -all other PyRTL developer tools, like `pytest` and `ruff`. +PyRTL uses [`uv`](https://docs.astral.sh/uv/) to ensure all developers work in +the same environment. `uv` manages the installation and versioning for all +other PyRTL developer tools, like `pytest` and `ruff`. -So to set up a PyRTL development environment, you only need to install `uv`, by -following the -[`uv` installation instructions](https://docs.astral.sh/uv/getting-started/installation/) +To set up a PyRTL development environment, you only need to install `uv`, by +following the [`uv` installation +instructions](https://docs.astral.sh/uv/getting-started/installation/). After installing [`uv`](https://docs.astral.sh/uv/), you can run all the tests with: @@ -108,77 +110,78 @@ needed. `uv` caches installed software so future `uv` invocations will be fast. example, pick a `PyrtlError` that is not covered and add a unit test in [`tests`](https://github.com/UCSBarchlab/PyRTL/tree/development/tests) that will hit it. -* After you have that down check in the [PyRTL - Issues](https://github.com/UCSBarchlab/PyRTL/issues) list for a feature that - is marked as "beginner friendly". +* After you have that down check [PyRTL + Issues](https://github.com/UCSBarchlab/PyRTL/issues) for a feature that is + marked as "beginner friendly". * Once you have that down, ask for access to the PyRTL-research repo where we - keep a list of more advanced features and designs that could use more help! + keep experimental features and designs that could use more help! *Coding style* * All major functionality should have unit tests covering and documenting their - use -* All public functions and methods should have useful docstrings + use. +* All public functions and methods should have useful docstrings. * All code needs to conform to - [PEP8](https://www.python.org/dev/peps/pep-0008/) conventions + [PEP8](https://www.python.org/dev/peps/pep-0008/) conventions. * No new root-level dependencies on external libs, import locally if required - for special functions + for special functions. *Workflow* -* A useful reference for working with Git is this [Git - tutorial](https://www.atlassian.com/git/tutorials/) -* A useful Git Fork workflow for working on this repo is [found - here](http://blog.scottlowe.org/2015/01/27/using-fork-branch-git-workflow/) +* [This Git tutorial](https://www.atlassian.com/git/tutorials/) is a useful + reference for working with Git. +* [This blog + post](http://blog.scottlowe.org/2015/01/27/using-fork-branch-git-workflow/) + covers a useful Git Fork workflow for working on PyRTL. * The `development` branch is the primary stable working branch (everyone is - invited to submit pull requests) -* Bugs and minor enhancements tracked directly through the [issue - tracker](https://github.com/UCSBarchlab/PyRTL/issues) -* When posting a bug please post a small chunk of code that captures the bug, - e.g. [Issue #56](https://github.com/UCSBarchlab/PyRTL/issues/56) -* When pushing a fix to a bug or enhancement please reference issue number in + invited to submit pull requests)., +* Bugs and minor enhancements tracked directly through [GitHub + Issues](https://github.com/UCSBarchlab/PyRTL/issues). +* When posting a bug please include a small code sample that triggers the bug, + e.g. [Issue #56](https://github.com/UCSBarchlab/PyRTL/issues/56). +* When pushing a fix to a bug or enhancement please reference the Issue in the commit message, e.g. [Fix to Issue - #56](https://github.com/UCSBarchlab/PyRTL/commit/1d5730db168a9e4490c580cb930075715468047a) + #56](https://github.com/UCSBarchlab/PyRTL/commit/1d5730db168a9e4490c580cb930075715468047a). * Before sending a pull request, please run: ```shell $ uv run just presubmit ``` - to verify that all tests pass and that documentation can be generated with - your changes. + to verify that all tests pass and that all documentation can be generated + with your changes. *Documentation* * All important functionality should have an executable example in - [`examples`](https://github.com/UCSBarchlab/PyRTL/tree/development/examples) -* All classes should have a block comment with high level description of the - class -* All functions should follow the following (Sphinx parsable) docstring format: + [`examples`](https://github.com/UCSBarchlab/PyRTL/tree/development/examples). + +* All classes should have a [docstring](https://peps.python.org/pep-0257) that + describes the class at a high level. + +* All methods and functions should include a docstring in the following + Sphinx-parsable format: ```python - """One Line Summary (< 80 chars) of the function, followed by period. + """One Line Summary (< 88 chars) of the function, followed by period. - A long description of what this function does. Talk about what the user - should expect from this function and also what the users needs to do to use - the function (this part is optional). + A longer description of what this function does and does not do. Describe any + assumptions or invariants. Provide an example for user-facing functions. - :param param_name : Description of this parameter. - :param param_name : Longer parameter descriptions take up a newline with four + :param param1: Description of this parameter. + :param param2: Longer parameter descriptions take up a newline with four leading spaces like this. + + :raises Exception: If this function raises an exception, explain when that + occurs here. + :return: Description of function's return value. """ - # Developer Notes (Optional): - # - # These would be anything that the user does not need to know in order to use - # the functions. - # These notes can include internal workings of the function, the logic behind - # it, or how to extend it. ``` * Sphinx parses [Python type annotations](https://docs.python.org/3/library/typing.html), so put type information into annotations instead of docstrings. * The Sphinx-generated documentation is published to - https://pyrtl.readthedocs.io/ + https://pyrtl.readthedocs.io/ . * PyRTL's Sphinx build process is documented in [`docs/README.md`](https://github.com/UCSBarchlab/PyRTL/blob/development/docs/README.md). * PyRTL's release process is documented in diff --git a/docs/README.md b/docs/README.md index 52f03c75..87dc2ad4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -24,11 +24,9 @@ Follow the instructions on this page to build a local copy of PyRTL's documentation. This is useful for verifying that PyRTL's documentation still renders correctly after making a local change. -There is additional PyRTL documentation in the [`gh-pages` -branch](https://github.com/UCSBarchlab/PyRTL/tree/gh-pages). This additional -documentation is pushed to https://ucsbarchlab.github.io/PyRTL/ by the -`pages-build-deployment` GitHub Action. This additional documentation is -written HTML and is not described further in this README. +There is additional PyRTL documentation in [GitHub +Pages](https://ucsbarchlab.github.io/PyRTL/), see +[`www/README.md`](https://github.com/UCSBarchlab/PyRTL/blob/development/www/README.md). ## Testing Documentation Examples @@ -65,3 +63,27 @@ $ uv run just docs This builds a local copy of PyRTL's documentation in `docs/_build/html`. `docs/_build/html/index.html` is the home page. + +## Screenshots + +PyRTL is difficult to screenshot consistently, because it typically runs in a +terminal window, and we rarely want to capture the whole window. + +To make our screenshots more consistent, we post-process them with `imagemagick`. +They are first trimmed, to remove any existing inconsistent border, then we add +a consistent 15-pixel black border: + +```shell +$ mogrify -strip -trim -bordercolor black -border 15 screenshot.png +``` + +When importing screenshots into documentation, it's important to set the image +width, because the screenshots typically contain text, and the screenshot's +text should be about the same size as the surrounding text. To compute the +proper `:width:`, take the width of the screenshot's text in characters (`">>> +sim.tracer.render_trace()"` is 29 characters, for example), add two (for the +15-pixel borders), then divide by two. Set width to `:width: {n}em`, where +`{n}` is the computed number (`{n}` is `15` for the example). + +PyRTL's current screenshots were taken in the Ghostty terminal, with 15-point +"JetBrains Mono", using the "Peppermint" theme. diff --git a/docs/conf.py b/docs/conf.py index c9d4bc56..fab1848c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -23,8 +23,6 @@ # -- Project information ----------------------------------------------------- project = "PyRTL" -copyright = "2026, Timothy Sherwood" -author = "Timothy Sherwood" # -- General configuration --------------------------------------------------- @@ -96,6 +94,9 @@ ], } html_logo = "brand/pyrtl_logo.png" +html_show_sphinx = False +html_show_copyright = False +html_show_sourcelink = False # Force a light blue background color for inheritance-diagrams. The default is # transparent, which does not work well with Furo's dark mode. diff --git a/docs/images/gcd-graph.png b/docs/images/gcd-graph.png deleted file mode 100644 index f8985ff9..00000000 Binary files a/docs/images/gcd-graph.png and /dev/null differ diff --git a/docs/screenshots/gtkwave.png b/docs/images/gtkwave.png similarity index 100% rename from docs/screenshots/gtkwave.png rename to docs/images/gtkwave.png diff --git a/docs/index.rst b/docs/index.rst index f17cad47..fdc82ef2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -54,6 +54,7 @@ After installing PyRTL, you can copy and paste the code above into a Python interpreter. You should see simulation waveforms drawn right in your terminal: .. image:: ../docs/screenshots/index-demo.png + :width: 15em Line 14's call to :meth:`.Simulation.step_multiple` runs the simulation for five cycles, providing input values for ``a`` and ``b`` for each cycle. For @@ -158,6 +159,151 @@ encountered when constructing designs in the normal ways. If you hit a confusing .. autoclass:: pyrtl.PyrtlInternalError :members: +A Few Gotchas +============= + +While Python is an amazing language, DSLs in Python are always forced to make a +few compromises which can sometimes catch users in some unexpected ways. Watch +out for these "somewhat surprising features": + +* PyRTL never uses any of the "in-place arithmetic assignments" such as ``+=`` + or ``&=`` in the traditional ways. Instead only ``<<=`` and ``|=`` are + defined and they are used for wire-assignment and conditional-wire-assignment + respectively (more on both of these in `Example 3 + `_). + + If you declare:: + + x = WireVector(bitwidth=3) + + and:: + + y = WireVector(bitwidth=5) + + how do you assign ``x`` the value of ``y + 1``? If you do ``x = y + 1`` that + will replace the old definition of ``x`` entirely. Instead you need to write + ``x <<= y + 1`` which you can read as "``x`` gets its value from ``y + 1``". + +* The example above also shows off another aspect of PyRTL. The + :attr:`~.WireVector.bitwidth` of ``y`` is 5. The :attr:`~.WireVector.bitwidth` + of ``y + 1`` is actually 6 (PyRTL infers this automatically). But then when + you assign ``x <<= y + 1`` you are taking a 6-bit value and assigning it to a + 3-bit value. This is completely legal, and the value will be :meth:`truncated + <.WireVector.truncate>`, so only the least significant bits will be assigned. + Mind your bitwidths! + +* PyRTL's :class:`WireVectors <.WireVector>` overloads many useful operators, + including ``==`` and ``<`` which evaluate to a new :class:`~.WireVector` with + :attr:`~.WireVector.bitwidth` 1 to hold the result of the comparison. The + bitwise operators ``&``, ``|``, ``~`` and ``^`` are also defined (however + logic operations such as ``and`` and ``not`` are not defined). A really + tricky gotcha happens when you start combining these operators. Consider:: + + do_it = ready & state == 3 + + In Python, the bitwise ``&`` operator has `higher precedence + `_ + than ``==``, thus Python parses this as:: + + do_it = (ready & state) == 3 + + which is probably not what you intended! Make sure to use parentheses when + using comparisons with logic operations to be clear:: + + do_it = ready & (state == 3) + +* In PyRTL, all :class:`WireVectors <.WireVector>` are `unsigned`, so + :class:`~.WireVector` comparisons with ``<`` are unsigned comparisons. You + must explicitly call functions like :func:`~.signed_lt` for signed + comparisons. Similarly, if you pass a :class:`~.WireVector` to a function + that requires more bits that you have provided, the :class:`~.WireVector` + will be zero-extended by default. You must explicitly call + :meth:`~.WireVector.sign_extended` to sign-extend. `Example 1.1 + `_ + provides more examples of signed arithmetic in PyRTL. + +Related Projects +================ + +`Amaranth (previously nMigen) `_ + Another Python hardware project providing an open-source toolchain that has + a lot of wonderful stuff for working with FPGAs in particular. It has + support for evaluation board definitions, a System-on-Chip toolkit, and + more. I think it has a similar philosophy of trying to be easy to learn and + use and simplify the design of complex hardware with reusable components. + Amaranth (at the time of writing) has much better support on the back end + for a variety of real devices and low level stuff like managing clock + domains, but I think PyRTL provides some value in getting going right in + the command line and how it handles memories etc. I would be eager to see + the power of these tools combined in some way! + +`Chisel `_ + A project with similar goals to PyRTL but based on Scala instead of Python. + Scala provides some very helpful embedded language features and a rich type + system. Chisel is (like PyRTL) a elaborate-through-execution hardware + design language. With support for signed types, named hierarchies of wires + useful for hardware protocols, which inspired our :func:`.wire_struct`, + and a neat control structure call ``when`` that inspired our + :data:`~.conditional_assignment` contexts, Chisel is a powerful tool used + in some great research projects including RISC-V. Unlike Chisel, PyRTL has + concentrated on a simple to use and complete tool chain which is useful for + instructional projects, and provides a clearly defined and relatively + easy-to-manipulate intermediate structure in the class :class:`~.Block` + which allows rapid prototyping of hardware analysis routines which can then + be co-designed with the architecture. + +`SpinalHDL `_ + A different approach to HDL in Scala, very much aligned with the way PyRTL + is built. Invented independently, it is neat to see the convergent + evolution which, I think, points to something deeper about hardware design. + It has a lot of support and really well thought out structures. + +`MyHDL `_ + Another neat Python hardware project built around generators and + decorators. The semantics of this embedded language are close to Verilog + and unlike PyRTL, MyHDL allows asynchronous logic and higher level + modeling. Much like Verilog, only a structural "convertible subset" of the + language can be automatically synthesized into real hardware. PyRTL + requires all logic to be both synchronous and synthesizable which avoids a + common trap for beginners, it elaborates the design during execution + allowing the full power of Python in describing recursive or complex + hardware structures, and it allows for hardware synthesis, simulation, test + bench creation, and optimization all in the same framework. + +`Yosys `_ + An open source tool for Verilog RTL synthesis. It supports a huge subset of + Verilog-2005 and provides a basic set of synthesis algorithms. The goals of + this tool are quite different from PyRTL, but the two play together very + nicely. PyRTL's :func:`~.output_to_verilog` produces Verilog that can be + synthesized with Yosys. Similarly, PyRTL's :func:`~.input_from_verilog` + uses Yosys to synthesize complex Verilog designs to a simple library of + gates, before importing into PyRTL. + +`PyMTL3 (a.k.a. Mamba) `_ + A beta stage "open-source Python-based hardware generation, simulation, and + verification framework with multi-level hardware modeling support". One of + the neat things about this project is that they are trying to allow + simulation, modeling, and verification at multiple different levels of the + design from the functional level, the cycle-close level, and down to the + register-transfer level (where PyRTL really is built to play). Like MyHDL + they do some meta-programming tricks like parsing the Python AST to allow + executable software descriptions to be (under certain restrictions, sort of + like Verilog) automatically converted into implementable hardware. PyRTL, + on the other hand, is about providing a limited and composable set of data + structures for specifying an RTL implementation, thus avoiding the + distinction between synthesizable and non-synthesizable code (the execution + is the elaboration step). + +`ClaSH `_ + An embedded hardware description language in Haskell. Like PyRTL it + provides an approach suitable for both combinational and synchronous + sequential circuits and transforms these high-level descriptions to + low-level synthesizable Verilog HDL. Unlike PyRTL, designs are statically + typed (like VHDL), yet with a very high degree of type inference, enabling + both safe and fast prototying using concise descriptions. If you like + functional programming and hardware also check out `Lava + `_. + Reference Guide =============== .. toctree:: diff --git a/docs/screenshots/adder.png b/docs/screenshots/adder.png new file mode 100644 index 00000000..beb54a84 Binary files /dev/null and b/docs/screenshots/adder.png differ diff --git a/docs/screenshots/fir.png b/docs/screenshots/fir.png new file mode 100644 index 00000000..be57ce22 Binary files /dev/null and b/docs/screenshots/fir.png differ diff --git a/docs/screenshots/gcd.png b/docs/screenshots/gcd.png new file mode 100644 index 00000000..5fd25cff Binary files /dev/null and b/docs/screenshots/gcd.png differ diff --git a/docs/screenshots/index-demo.png b/docs/screenshots/index-demo.png index 4f6ccc97..d894ea4c 100644 Binary files a/docs/screenshots/index-demo.png and b/docs/screenshots/index-demo.png differ diff --git a/docs/screenshots/maxn.png b/docs/screenshots/maxn.png new file mode 100644 index 00000000..d8dbfa3a Binary files /dev/null and b/docs/screenshots/maxn.png differ diff --git a/docs/screenshots/mul.png b/docs/screenshots/mul.png new file mode 100644 index 00000000..5ae2a320 Binary files /dev/null and b/docs/screenshots/mul.png differ diff --git a/docs/screenshots/pyrtl-counter.png b/docs/screenshots/pyrtl-counter.png deleted file mode 100644 index c4737e0c..00000000 Binary files a/docs/screenshots/pyrtl-counter.png and /dev/null differ diff --git a/docs/screenshots/pyrtl-renderer-demo-ascii.png b/docs/screenshots/pyrtl-renderer-demo-ascii.png index 32ba0004..4e3023e1 100644 Binary files a/docs/screenshots/pyrtl-renderer-demo-ascii.png and b/docs/screenshots/pyrtl-renderer-demo-ascii.png differ diff --git a/docs/screenshots/pyrtl-renderer-demo-powerline.png b/docs/screenshots/pyrtl-renderer-demo-powerline.png index 0f963e54..d5c6a04a 100644 Binary files a/docs/screenshots/pyrtl-renderer-demo-powerline.png and b/docs/screenshots/pyrtl-renderer-demo-powerline.png differ diff --git a/docs/screenshots/pyrtl-renderer-demo-utf-8-alt.png b/docs/screenshots/pyrtl-renderer-demo-utf-8-alt.png index 4bb47b9f..5c5dc28b 100644 Binary files a/docs/screenshots/pyrtl-renderer-demo-utf-8-alt.png and b/docs/screenshots/pyrtl-renderer-demo-utf-8-alt.png differ diff --git a/docs/screenshots/pyrtl-renderer-demo-utf-8-basic.png b/docs/screenshots/pyrtl-renderer-demo-utf-8-basic.png index 03b91dc7..76320d98 100644 Binary files a/docs/screenshots/pyrtl-renderer-demo-utf-8-basic.png and b/docs/screenshots/pyrtl-renderer-demo-utf-8-basic.png differ diff --git a/docs/screenshots/pyrtl-renderer-demo-utf-8.png b/docs/screenshots/pyrtl-renderer-demo-utf-8.png index 997faa6d..e090c601 100644 Binary files a/docs/screenshots/pyrtl-renderer-demo-utf-8.png and b/docs/screenshots/pyrtl-renderer-demo-utf-8.png differ diff --git a/docs/screenshots/pyrtl-statemachine.png b/docs/screenshots/pyrtl-statemachine.png index a72bc502..6876edbb 100644 Binary files a/docs/screenshots/pyrtl-statemachine.png and b/docs/screenshots/pyrtl-statemachine.png differ diff --git a/docs/screenshots/ripple-carry.png b/docs/screenshots/ripple-carry.png new file mode 100644 index 00000000..5b543a67 Binary files /dev/null and b/docs/screenshots/ripple-carry.png differ diff --git a/examples/example2-counter.py b/examples/example2-counter.py index abd087bd..79f81ec7 100644 --- a/examples/example2-counter.py +++ b/examples/example2-counter.py @@ -22,20 +22,19 @@ def one_bit_add( # If we call `one_bit_add` above with the arguments `x`, `y`, and `z`, it will make a # one-bit adder to add those values together, returning `WireVectors` for `sum` and -# `carry_out` as applied to `x`, `y`, and `z`. If I call it again on `i`, `j`, and `k` -# it will build a new one-bit adder for those inputs and return the resulting `sum` and -# `carry_out` for that adder. +# `carry_out` as applied to `x`, `y`, and `z`. If I call `one_bit_add` again on `i`, +# `j`, and `k` it will build a new one-bit adder for those inputs and return the +# resulting `sum` and `carry_out` for that adder. # -# While PyRTL actually provides an `+` operator for `WireVectors` which generates -# adders, a ripple carry adder is something people can understand easily but has enough +# While PyRTL actually provides a `+` operator for `WireVectors` which generates adders, +# a ripple carry adder is something people can understand easily but has enough # structure to be mildly interesting. Let's define an adder of arbitrary length # recursively and (hopefully) Pythonically. More comments after the code. def ripple_add( a: pyrtl.WireVector, b: pyrtl.WireVector, carry_in: pyrtl.WireVector = 0 ) -> tuple[pyrtl.WireVector, pyrtl.WireVector]: + # `match_bitwidth` zero-extends its arguments to the widest argument's bitwidth. a, b = pyrtl.match_bitwidth(a, b) - # This function is a function that allows us to match the bitwidth of multiple - # different wires. By default, it zero extends the shorter bits if len(a) == 1: return one_bit_add(a, b, carry_in) lsb, ripple_carry = one_bit_add(a[0], b[0], carry_in) @@ -47,7 +46,7 @@ def ripple_add( # # 1. If `a` is one-bit wide, just do a `one_bit_add`. # 2. Otherwise, do a `one_bit_add` on the least significant bits, `ripple_add` the rest, -# and then stick the results back together into one `WireVector`. +# and then `concat` the results back together into one `WireVector`. # # ## A couple interesting features of PyRTL can be seen here: # @@ -62,26 +61,26 @@ def ripple_add( # and PyRTL will cast integers and some other types to `WireVectors` when it can. # # Now let's build a 3-bit counter from our N-bit ripple carry adder. -counter = pyrtl.Register(bitwidth=3, name="counter") -sum, carry_out = ripple_add(counter, pyrtl.Const("1'b1")) +counter = pyrtl.Register(name="counter", bitwidth=3) +sum, _carry_out = ripple_add(counter, pyrtl.Const(1)) counter.next <<= sum # ## A few new things in the above code: # # * The two remaining types of basic `WireVectors`, `Const` and `Register`, both appear. # `Const`, unsurprisingly, is just for holding constants (such as the `0` in -# `ripple_add`), but here we create one explicitly with a Verilog-like string which -# includes both the value and the bitwidth. +# `ripple_add`), but here we create one explicitly. # * `Registers` are just like `WireVectors`, except their updates are delayed to the # next clock cycle. This is made explicit in the syntax through the property `.next` -# which should always be set for registers. +# which must be set for registers. # * In this simple example, we make the counter's value on the next cycle equal to the # counter's value this cycle plus one. # # Now let's run the bugger. No need for `Inputs`, as this circuit doesn't have any. -# Finally we'll print the trace to the screen and check that it counts up correctly. +# Finally we'll print the trace and check that `counter` counts up to 7, then wraps +# around to 0. sim = pyrtl.Simulation() -for cycle in range(15): +for cycle in range(12): sim.step() - assert sim.value[counter] == cycle % 8 + assert sim.inspect("counter") == cycle % 8 sim.tracer.render_trace() diff --git a/ipynb-examples/example2-counter.ipynb b/ipynb-examples/example2-counter.ipynb index 739e27b1..5862a6b8 100644 --- a/ipynb-examples/example2-counter.ipynb +++ b/ipynb-examples/example2-counter.ipynb @@ -61,12 +61,12 @@ "source": [ " If we call `one_bit_add` above with the arguments `x`, `y`, and `z`, it will make a\n", " one-bit adder to add those values together, returning [WireVectors](https://pyrtl.readthedocs.io/en/latest/basic.html#pyrtl.WireVector) for `sum` and\n", - " `carry_out` as applied to `x`, `y`, and `z`. If I call it again on `i`, `j`, and `k`\n", - " it will build a new one-bit adder for those inputs and return the resulting `sum` and\n", - " `carry_out` for that adder.\n", + " `carry_out` as applied to `x`, `y`, and `z`. If I call `one_bit_add` again on `i`,\n", + " `j`, and `k` it will build a new one-bit adder for those inputs and return the\n", + " resulting `sum` and `carry_out` for that adder.\n", "\n", - " While PyRTL actually provides an `+` operator for [WireVectors](https://pyrtl.readthedocs.io/en/latest/basic.html#pyrtl.WireVector) which generates\n", - " adders, a ripple carry adder is something people can understand easily but has enough\n", + " While PyRTL actually provides a `+` operator for [WireVectors](https://pyrtl.readthedocs.io/en/latest/basic.html#pyrtl.WireVector) which generates adders,\n", + " a ripple carry adder is something people can understand easily but has enough\n", " structure to be mildly interesting. Let's define an adder of arbitrary length\n", " recursively and (hopefully) Pythonically. More comments after the code.\n" ] @@ -82,9 +82,8 @@ "def ripple_add(\n", " a: pyrtl.WireVector, b: pyrtl.WireVector, carry_in: pyrtl.WireVector = 0\n", ") -> tuple[pyrtl.WireVector, pyrtl.WireVector]:\n", + " # `match_bitwidth` zero-extends its arguments to the widest argument's bitwidth.\n", " a, b = pyrtl.match_bitwidth(a, b)\n", - " # This function is a function that allows us to match the bitwidth of multiple\n", - " # different wires. By default, it zero extends the shorter bits\n", " if len(a) == 1:\n", " return one_bit_add(a, b, carry_in)\n", " lsb, ripple_carry = one_bit_add(a[0], b[0], carry_in)\n", @@ -100,7 +99,7 @@ "\n", " 1. If `a` is one-bit wide, just do a `one_bit_add`.\n", " 2. Otherwise, do a `one_bit_add` on the least significant bits, `ripple_add` the rest,\n", - " and then stick the results back together into one [WireVector](https://pyrtl.readthedocs.io/en/latest/basic.html#pyrtl.WireVector).\n", + " and then `concat` the results back together into one [WireVector](https://pyrtl.readthedocs.io/en/latest/basic.html#pyrtl.WireVector).\n", "\n", " ## A couple interesting features of PyRTL can be seen here:\n", "\n", @@ -125,8 +124,8 @@ }, "outputs": [], "source": [ - "counter = pyrtl.Register(bitwidth=3, name=\"counter\")\n", - "sum, carry_out = ripple_add(counter, pyrtl.Const(\"1'b1\"))\n", + "counter = pyrtl.Register(name=\"counter\", bitwidth=3)\n", + "sum, _carry_out = ripple_add(counter, pyrtl.Const(1))\n", "counter.next <<= sum\n" ] }, @@ -138,16 +137,16 @@ "\n", " * The two remaining types of basic [WireVectors](https://pyrtl.readthedocs.io/en/latest/basic.html#pyrtl.WireVector), [Const](https://pyrtl.readthedocs.io/en/latest/basic.html#pyrtl.Const) and [Register](https://pyrtl.readthedocs.io/en/latest/basic.html#pyrtl.Register), both appear.\n", " [Const](https://pyrtl.readthedocs.io/en/latest/basic.html#pyrtl.Const), unsurprisingly, is just for holding constants (such as the `0` in\n", - " `ripple_add`), but here we create one explicitly with a Verilog-like string which\n", - " includes both the value and the bitwidth.\n", + " `ripple_add`), but here we create one explicitly.\n", " * [Registers](https://pyrtl.readthedocs.io/en/latest/basic.html#pyrtl.Register) are just like [WireVectors](https://pyrtl.readthedocs.io/en/latest/basic.html#pyrtl.WireVector), except their updates are delayed to the\n", " next clock cycle. This is made explicit in the syntax through the property `.next`\n", - " which should always be set for registers.\n", + " which must be set for registers.\n", " * In this simple example, we make the counter's value on the next cycle equal to the\n", " counter's value this cycle plus one.\n", "\n", " Now let's run the bugger. No need for [Inputs](https://pyrtl.readthedocs.io/en/latest/basic.html#pyrtl.Input), as this circuit doesn't have any.\n", - " Finally we'll print the trace to the screen and check that it counts up correctly.\n" + " Finally we'll print the trace and check that `counter` counts up to 7, then wraps\n", + " around to 0.\n" ] }, { @@ -159,9 +158,9 @@ "outputs": [], "source": [ "sim = pyrtl.Simulation()\n", - "for cycle in range(15):\n", + "for cycle in range(12):\n", " sim.step()\n", - " assert sim.value[counter] == cycle % 8\n", + " assert sim.inspect(\"counter\") == cycle % 8\n", "sim.tracer.render_trace()\n" ] } diff --git a/justfile b/justfile index e41e7e93..589d844c 100644 --- a/justfile +++ b/justfile @@ -4,7 +4,7 @@ set windows-shell := ["powershell.exe", "-NoLogo", "-Command"] -presubmit: tests docs +presubmit: tests docs www tests: # Run `pytest` with the latest version of Python supported by PyRTL, @@ -39,3 +39,9 @@ docs: # # Output: docs/_build/html/index.html uv run sphinx-build -M html docs/ docs/_build + +www: + # Run `sphinx-build` to generate github.io webpage. + # + # Output: www/_build/html/index.html + uv run sphinx-build -M html www/ www/_build diff --git a/pyproject.toml b/pyproject.toml index c42d638a..8127d23c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -127,4 +127,5 @@ dev = [ "sphinx>=7.4.7", "sphinx-autodoc-typehints>=2.3.0", "sphinx-copybutton>=0.5.2", + "sphinx-design>=0.6.1", ] diff --git a/pyrtl/simulation.py b/pyrtl/simulation.py index 63a2d8e8..8ce6569c 100644 --- a/pyrtl/simulation.py +++ b/pyrtl/simulation.py @@ -1374,7 +1374,7 @@ class Utf8RendererConstants(RendererConstants): vertical rising and falling edges. Multi-bit :class:`WireVector` values are rendered in reverse-video rectangles. - This is the default renderer on non-Windows platforms. + This is the default renderer. Enable this renderer by default by setting the ``PYRTL_RENDERER`` environment variable to ``utf-8``:: @@ -1382,6 +1382,7 @@ class Utf8RendererConstants(RendererConstants): export PYRTL_RENDERER=utf-8 .. image:: ../docs/screenshots/pyrtl-renderer-demo-utf-8.png + :width: 35em """ # Start reverse-video, reset all attributes @@ -1422,6 +1423,7 @@ class Utf8AltRendererConstants(RendererConstants): export PYRTL_RENDERER=utf-8-alt .. image:: ../docs/screenshots/pyrtl-renderer-demo-utf-8-alt.png + :width: 25em """ # Start reverse-video, reset all attributes @@ -1452,7 +1454,7 @@ class PowerlineRendererConstants(Utf8RendererConstants): in reverse-video hexagons. This renderer requires a `terminal font that supports Powerline glyphs - `_ + `_. Enable this renderer by default by setting the ``PYRTL_RENDERER`` environment variable to ``powerline``:: @@ -1460,6 +1462,7 @@ class PowerlineRendererConstants(Utf8RendererConstants): export PYRTL_RENDERER=powerline .. image:: ../docs/screenshots/pyrtl-renderer-demo-powerline.png + :width: 35em """ # Start reverse-video, reset all attributes @@ -1490,6 +1493,7 @@ class Utf8BasicRendererConstants(RendererConstants): export PYRTL_RENDERER=utf-8-basic .. image:: ../docs/screenshots/pyrtl-renderer-demo-utf-8-basic.png + :width: 25em """ _tick = "│" @@ -1521,6 +1525,7 @@ class AsciiRendererConstants(RendererConstants): export PYRTL_RENDERER=ascii .. image:: ../docs/screenshots/pyrtl-renderer-demo-ascii.png + :width: 25em """ _tick = "|" @@ -1823,7 +1828,7 @@ def print_vcd(self, file: TextIO | None = None, include_clock: bool = False): This VCD output can be saved to a ``file`` and opened in `GTKWave `_: - .. image:: ../docs/screenshots/gtkwave.png + .. image:: ../docs/images/gtkwave.png :param file: File to output VCD data to. Defaults to ``stdout``. :param include_clock: Boolean specifying if the implicit ``clk`` should be diff --git a/tests/test_examples.py b/tests/test_examples.py index b2e930a2..b761e228 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -1,29 +1,38 @@ import os import subprocess +import sys +from itertools import chain from pathlib import Path import pytest -import pyrtl -""" -Tests all of the files in the example folder - -Note that this file is structure dependent, so don't forget to change it if the relative -location of the examples changes -""" +def get_scripts(directory: str) -> Path: + return (Path(os.path.dirname(__file__)) / ".." / directory).glob("*.py") @pytest.mark.parametrize( "file", - (Path(os.path.dirname(__file__)) / ".." / "examples").glob("*.py"), + chain(get_scripts("examples"), get_scripts("www/examples")), ids=lambda path: path.name, ) def test_all_examples(file): + """Test all Python scripts in ``examples/`` and ``www/examples``. + + This just checks that all Python scripts terminate with exit status 0. + """ # Always use the ASCII renderer for deterministic example output. os.environ["PYRTL_RENDERER"] = "ascii" - pyrtl.reset_working_block() + try: subprocess.check_output(["uv", "run", file]) - except subprocess.CalledProcessError as e: - raise e + except subprocess.CalledProcessError: + pytest.fail(f"Failed to execute {file}") + + +if __name__ == "__main__": + print( + "ERROR: This test must be run with `pytest`. Try " + "`uv run pytest tests/test_examples.py`." + ) + sys.exit(1) diff --git a/uv.lock b/uv.lock index 2dfe81b7..cac79b06 100644 --- a/uv.lock +++ b/uv.lock @@ -321,7 +321,7 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -553,6 +553,8 @@ dev = [ { name = "sphinx-autodoc-typehints", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, { name = "sphinx-autodoc-typehints", version = "3.10.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "sphinx-copybutton" }, + { name = "sphinx-design", version = "0.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx-design", version = "0.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] [package.metadata] @@ -575,6 +577,7 @@ dev = [ { name = "sphinx", specifier = ">=7.4.7" }, { name = "sphinx-autodoc-typehints", specifier = ">=2.3.0" }, { name = "sphinx-copybutton", specifier = ">=0.5.2" }, + { name = "sphinx-design", specifier = ">=0.6.1" }, ] [[package]] @@ -879,6 +882,38 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9e/48/1ea60e74949eecb12cdd6ac43987f9fd331156388dcc2319b45e2ebb81bf/sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e", size = 13343, upload-time = "2023-04-14T08:10:20.844Z" }, ] +[[package]] +name = "sphinx-design" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2b/69/b34e0cb5336f09c6866d53b4a19d76c227cdec1bbc7ac4de63ca7d58c9c7/sphinx_design-0.6.1.tar.gz", hash = "sha256:b44eea3719386d04d765c1a8257caca2b3e6f8421d7b3a5e742c0fd45f84e632", size = 2193689, upload-time = "2024-08-02T13:48:44.277Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/43/65c0acbd8cc6f50195a3a1fc195c404988b15c67090e73c7a41a9f57d6bd/sphinx_design-0.6.1-py3-none-any.whl", hash = "sha256:b11f37db1a802a183d61b159d9a202314d4d2fe29c163437001324fe2f19549c", size = 2215338, upload-time = "2024-08-02T13:48:42.106Z" }, +] + +[[package]] +name = "sphinx-design" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/7b/804f311da4663a4aecc6cf7abd83443f3d4ded970826d0c958edc77d4527/sphinx_design-0.7.0.tar.gz", hash = "sha256:d2a3f5b19c24b916adb52f97c5f00efab4009ca337812001109084a740ec9b7a", size = 2203582, upload-time = "2026-01-19T13:12:53.297Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/cf/45dd359f6ca0c3762ce0490f681da242f0530c49c81050c035c016bfdd3a/sphinx_design-0.7.0-py3-none-any.whl", hash = "sha256:f82bf179951d58f55dca78ab3706aeafa496b741a91b1911d371441127d64282", size = 2220350, upload-time = "2026-01-19T13:12:51.077Z" }, +] + [[package]] name = "sphinxcontrib-applehelp" version = "2.0.0" diff --git a/www/README.md b/www/README.md new file mode 100644 index 00000000..39eef71c --- /dev/null +++ b/www/README.md @@ -0,0 +1,40 @@ +# PyRTL's GitHub Pages Webpage + +PyRTL's GitHub Pages webpage is at https://ucsbarchlab.github.io/PyRTL/ . + +The sources for this webpage are in this `www` directory. The page is built +with [Sphinx](https://www.sphinx-doc.org/en/master/), and written in +[reStructuredText](https://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html). +The main Sphinx configuration file is +[`www/conf.py`](https://github.com/UCSBarchlab/PyRTL/blob/development/www/conf.py), +and source for the main page is +[`www/index.rst`](https://github.com/UCSBarchlab/PyRTL/blob/development/www/index.rst) + +Follow the instructions on this page to build a local copy of PyRTL's webpage. +This is useful for verifying that PyRTL's webpage still renders correctly after +making a local change. + +There is additional PyRTL documentation in [Read the +Docs](https://pyrtl.readthedocs.io/), see +[`docs/README.md`](https://github.com/UCSBarchlab/PyRTL/blob/development/docs/README.md). + +## Running Sphinx + +Run Sphinx with the provided +[`justfile`](https://github.com/UCSBarchlab/PyRTL/blob/development/justfile), +from the repository's root directory: + +```shell +# Run Sphinx to build PyRTL's webpage. +$ uv run just www +``` + +This builds a local copy of PyRTL's webpage in `www/_build/html`. +`www/_build/html/index.html` is the home page. + +## GitHub Actions Workflow + +When a commit is pushed that changes `www/`, the +[`pages-deploy`](https://github.com/UCSBarchlab/PyRTL/blob/development/.github/workflows/pages-deploy.yml) +workflow automatically runs Sphinx to regenerate the webpage and deploy it to +GitHub Pages. diff --git a/www/_static/custom.css b/www/_static/custom.css new file mode 100644 index 00000000..f584543d --- /dev/null +++ b/www/_static/custom.css @@ -0,0 +1,77 @@ +body { + line-height: 1.5; +} + +div.body h1 { + font-size: 120%; +} + +/* Spacing adjustments. */ +pre { + padding: 12px; +} +div.bodywrapper { + margin: 0px; +} +div.body { + max-width: 100%; +} + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + color: #2980B9; +} + +/* Code blocks. */ +.highlight { + border: 1px solid #ccc; + background-color: #f1f1f1; + font-size: 1rem; +} + +/* Syntax highlighting. */ +.highlight .n, +.highlight .p { + color: #567482; +} +.highlight .nf { + color: #795da3; +} +.highlight .k, +.highlight .kn { + color: #a71d5d; + font-weight: inherit; +} + +/* Documentation links. */ +a code { + color: inherit; +} + +/* Tabs. */ +.sd-tab-set { + margin: 0px; +} + +/* Buttons. */ +:root { + --sd-color-secondary: #6a7e8c; + --sd-color-secondary-highlight: #7a8e9c; + --sd-color-tabs-underline: rgba(222, 222, 222, 0); +} + +.title-card { + background-image: linear-gradient(180deg, #2980B9, #3a4e5c, #4a5e6c); +} + +/* Hide the sidebar. It may appear as a footer on small screens. */ +div.sphinxsidebar { + height: 0px; + width: 0px; + margin: 0px; + padding: 0px; +} diff --git a/www/conf.py b/www/conf.py new file mode 100644 index 00000000..330b3661 --- /dev/null +++ b/www/conf.py @@ -0,0 +1,51 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- + +project = "PyRTL" + +# -- General configuration --------------------------------------------------- + +master_doc = "index" + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = ["sphinx.ext.intersphinx", "sphinx_copybutton", "sphinx_design"] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + +# Enable links to Python standard library classes (str, list, dict, etc). +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), + "pyrtl": ("https://pyrtl.readthedocs.io/en/latest/", None), +} + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_baseurl = "https://ucsbarchlab.github.io/PyRTL" +html_theme = "alabaster" +html_theme_options = { + "font_family": '"Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif', + "code_font_family": 'Consolas, "Liberation Mono", Menlo, Courier, monospace', + "font_size": "1.1rem", + "body_text": "#606c71", + "link": "#1e6bb8", +} +html_title = "Home" +html_show_sphinx = False +html_show_copyright = False +html_show_sourcelink = False +html_static_path = ["_static"] diff --git a/www/examples/example-adder.py b/www/examples/example-adder.py new file mode 100644 index 00000000..4b17638a --- /dev/null +++ b/www/examples/example-adder.py @@ -0,0 +1,37 @@ +import pyrtl + + +def fa( + x: pyrtl.WireVector, y: pyrtl.WireVector, cin: pyrtl.WireVector +) -> tuple[pyrtl.WireVector, pyrtl.WireVector]: + """Full adder.""" + sum = x ^ y ^ cin + cout = x & y | y & cin | x & cin + return sum, cout + + +def adder( + a: pyrtl.WireVector, b: pyrtl.WireVector, cin: pyrtl.WireVector +) -> tuple[pyrtl.WireVector, pyrtl.WireVector]: + """n-bit ripple carry adder with carry in and carry out.""" + a, b = pyrtl.match_bitwidth(a, b) + + sum = [None] * a.bitwidth + for i in range(a.bitwidth): + sum[i], cout = fa(a[i], b[i], cin) + cin = cout + + full_sum = pyrtl.concat_list(sum) + return full_sum, cout + + +a = pyrtl.Input(name="a", bitwidth=4) +b = pyrtl.Input(name="b", bitwidth=4) +sum = pyrtl.Output(name="sum", bitwidth=8) + +sum_, cout_ = adder(a, b, pyrtl.Const(0)) +sum <<= sum_ + +sim = pyrtl.Simulation() +sim.step_multiple({"a": [1, 2, 3], "b": [2, 3, 4]}) +sim.tracer.render_trace() diff --git a/www/examples/example-fir.py b/www/examples/example-fir.py new file mode 100644 index 00000000..81d72e02 --- /dev/null +++ b/www/examples/example-fir.py @@ -0,0 +1,22 @@ +import pyrtl + + +def fir(x: pyrtl.WireVector, bs: list[int]): + rwidth = x.bitwidth # Bitwidth of the registers. + ntaps = len(bs) # Number of coefficients. + + zs = [x] + [pyrtl.Register(rwidth) for _ in range(ntaps - 1)] + for i in range(1, ntaps): + zs[i].next <<= zs[i - 1] + + # Produce the final sum of products. + return sum(z * b for z, b in zip(zs, bs, strict=True)) + + +x = pyrtl.Input(name="x", bitwidth=8) +y = pyrtl.Output(name="y", bitwidth=8) +y <<= fir(x, bs=[0, 1]) + +sim = pyrtl.Simulation() +sim.step_multiple({"x": [0, 9, 18, 8, 17, 7, 16, 6, 15, 5]}) +sim.tracer.render_trace() diff --git a/www/examples/example-gcd.py b/www/examples/example-gcd.py new file mode 100644 index 00000000..5a78cad9 --- /dev/null +++ b/www/examples/example-gcd.py @@ -0,0 +1,39 @@ +import pyrtl + + +def gcd( + a: pyrtl.WireVector, b: pyrtl.WireVector, begin: pyrtl.WireVector +) -> tuple[pyrtl.WireVector, pyrtl.WireVector]: + x = pyrtl.Register(bitwidth=a.bitwidth) + y = pyrtl.Register(bitwidth=b.bitwidth) + done = pyrtl.WireVector(bitwidth=1) + + with pyrtl.conditional_assignment: + with begin: + x.next |= a + y.next |= b + with x > y: + x.next |= x - y + with y > x: + y.next |= y - x + with pyrtl.otherwise: + done |= True + return x, done + + +a = pyrtl.Input(name="a", bitwidth=8) +b = pyrtl.Input(name="b", bitwidth=8) +begin = pyrtl.Input(name="begin", bitwidth=1) + +x = pyrtl.Output(name="x", bitwidth=8) +done = pyrtl.Output(name="done", bitwidth=1) + +x_, done_ = gcd(a, b, begin) +x <<= x_ +done <<= done_ + +sim = pyrtl.Simulation() +sim.step({"a": 12, "b": 9, "begin": True}) +while not sim.inspect("done"): + sim.step({"a": 0, "b": 0, "begin": False}) +sim.tracer.render_trace() diff --git a/www/examples/example-maxn.py b/www/examples/example-maxn.py new file mode 100644 index 00000000..cec198ac --- /dev/null +++ b/www/examples/example-maxn.py @@ -0,0 +1,22 @@ +from functools import reduce + +import pyrtl + + +def max_n(*inputs): + def max_2(x, y): + return pyrtl.select(x > y, x, y) + + return reduce(max_2, inputs) + + +a = pyrtl.Input(name="a", bitwidth=8) +b = pyrtl.Input(name="b", bitwidth=8) +c = pyrtl.Input(name="c", bitwidth=8) +max = pyrtl.Output(name="max", bitwidth=8) + +max <<= max_n(a, b, c) + +sim = pyrtl.Simulation() +sim.step_multiple({"a": [1, 5, 9], "b": [2, 6, 7], "c": [3, 4, 8]}) +sim.tracer.render_trace() diff --git a/www/examples/example-mul.py b/www/examples/example-mul.py new file mode 100644 index 00000000..c65b8695 --- /dev/null +++ b/www/examples/example-mul.py @@ -0,0 +1,23 @@ +import pyrtl + + +def mul(x: pyrtl.WireVector, y: pyrtl.WireVector) -> pyrtl.WireVector: + assert x.bitwidth == 4 + assert y.bitwidth == 4 + + def romdata(addr: int) -> int: + return (addr >> 4) * (addr & 0xF) + + tbl = pyrtl.RomBlock(bitwidth=8, addrwidth=8, romdata=romdata) + return tbl[pyrtl.concat(x, y)] + + +a = pyrtl.Input(name="a", bitwidth=4) +b = pyrtl.Input(name="b", bitwidth=4) +product = pyrtl.Output(name="product", bitwidth=8) + +product <<= mul(a, b) + +sim = pyrtl.Simulation() +sim.step_multiple({"a": [1, 2, 3], "b": [2, 3, 4]}) +sim.tracer.render_trace() diff --git a/www/examples/example-ripple-carry.py b/www/examples/example-ripple-carry.py new file mode 100644 index 00000000..a81443f5 --- /dev/null +++ b/www/examples/example-ripple-carry.py @@ -0,0 +1,34 @@ +import pyrtl + + +def one_bit_add( + a: pyrtl.WireVector, b: pyrtl.WireVector, carry_in: pyrtl.WireVector | int +) -> tuple[pyrtl.WireVector, pyrtl.WireVector]: + assert len(a) == len(b) == 1 # `len` returns the bitwidth. + sum = a ^ b ^ carry_in # WireVector operators build the hardware. + carry_out = a & b | a & carry_in | b & carry_in + return sum, carry_out + + +def ripple_add( + a: pyrtl.WireVector, b: pyrtl.WireVector, carry_in: pyrtl.WireVector | int = 0 +) -> tuple[pyrtl.WireVector, pyrtl.WireVector]: + a, b = pyrtl.match_bitwidth(a, b) + if len(a) == 1: + sumbits, carry_out = one_bit_add(a, b, carry_in) + else: + lsbit, ripplecarry = one_bit_add(a[0], b[0], carry_in) + msbits, carry_out = ripple_add(a[1:], b[1:], ripplecarry) + sumbits = pyrtl.concat(msbits, lsbit) + return sumbits, carry_out + + +# Use the adder in a 3-bit counter. +counter = pyrtl.Register(bitwidth=3, name="counter") +sum, _carry_out = ripple_add(counter, pyrtl.Const(1)) +counter.next <<= sum + +# Simulate the instantiated design for 12 cycles. +sim = pyrtl.Simulation() +sim.step_multiple(nsteps=12) +sim.tracer.render_trace() diff --git a/www/index.rst b/www/index.rst new file mode 100644 index 00000000..651fd648 --- /dev/null +++ b/www/index.rst @@ -0,0 +1,244 @@ +.. meta:: + :google-site-verification: ls9UjPExLzNwr4BVvQ_THSjcGqyNM-KO9Rs9njbX318 + +.. card:: + :text-align: center + :class-card: title-card + + .. image:: ../docs/brand/pyrtl_logo.png + + .. grid:: 1 2 2 4 + :gutter: 2 + + .. grid-item:: + + .. button-link:: http://pyrtl.readthedocs.org/ + :color: secondary + :expand: + :shadow: + + Documentation + + .. grid-item:: + + .. button-link:: https://github.com/UCSBarchlab/pyrtl + :color: secondary + :expand: + :shadow: + + GitHub + + .. grid-item:: + + .. button-link:: https://github.com/UCSBarchlab/PyRTL/tree/development/examples + :color: secondary + :expand: + :shadow: + + Examples + + .. grid-item:: + + .. button-link:: https://mybinder.org/v2/gh/UCSBarchlab/PyRTL/development?filepath=%2Fipynb-examples%2F + :color: secondary + :expand: + :shadow: + + Notebooks + +PyRTL +===== + +PyRTL is a Python library for register-transfer-level hardware design and +simulation. Get started with: + +.. code-block:: shell + + $ pip install pyrtl + +Features +======== + +PyRTL provides a collection of classes for Pythonic `register-transfer level +`_ design, simulation, +tracing, and testing suitable for teaching and research. Simplicity, usability, +clarity, and extensibility are overarching goals, rather than performance or +optimization. Features include: + +* Elaboration-through-execution, meaning all of Python can be used, including + introspection. +* Design, instantiate, and simulate all in one file, and without leaving Python. +* Export to, or import from, common HDLs (BLIF-in, Verilog-out currently + supported). +* Examine execution with waveforms in a terminal or export to `.vcd + `_ as projects scale. +* Elaboration, synthesis, and basic optimizations all included. +* Small and well-defined internal core structure means writing new transforms + is easier. +* Batteries included means many useful components are already available. + +.. card:: :octicon:`rocket` New in `PyRTL 1.0.0 `_ + + The new :mod:`pyrtl.rtllib.float` module generates floating point hardware! + +Simple Examples +=============== + +Here are some simple examples of PyRTL in action. These examples implement the +same functionality as those highlighted in the wonderful related work `Chisel +`_, which in turn allows us to see the stylistic +differences between the approaches. + +.. grid:: 1 1 1 1 + :outline: + + .. grid-item:: + :columns: auto + + .. tab-set:: + .. tab-item:: GCD + + A greatest common denominator calculator: ``gcd`` generates a sequential + circuit that saves inputs ``a`` and ``b`` when ``begin`` goes high, and + then, while ``begin`` is low, calculates the GCD with `Euclid's algorithm + `_. The function + returns two :class:`WireVectors `, one which holds the + GCD when the computation is ``done``, and the other which is a boolean + ``done`` signal. + + The code below provides everything needed to instantiate, simulate, and + visualize the resulting design. + + .. literalinclude:: examples/example-gcd.py + + .. image:: ../docs/screenshots/gcd.png + :width: 16em + + .. tab-item:: FIR + + A finite impulse response filter: ``fir`` generates a sequential circuit + that accepts inputs ``x`` and a list of coefficients ``bs``. From the + `Wikipedia FIR description + `_, the list + ``zs`` is the registers required to implement the delay. ``fir`` returns + an output ``y`` which is the resulting sum of products and is valid every + cycle (since the design is naturally fully pipelined). + + .. literalinclude:: examples/example-fir.py + + .. image:: ../docs/screenshots/fir.png + :width: 30em + + .. tab-item:: MaxN + + ``max_n`` generates hardware that identifies the largest of N input + values. This example makes use of Python's `notation for handling + multiple inputs + `_ + by packing them into a :class:`list`. It also demonstrates that the full + power of Python is available to you in PyRTL, including functional tools + like :func:`~functools.reduce`, which is used to chain together multiple + ``max_2`` elements into a bigger ``max_n``. + + .. literalinclude:: examples/example-maxn.py + + .. image:: ../docs/screenshots/maxn.png + :width: 15em + + .. tab-item:: Mul + + ``mul`` generates a small 4 x 4 multiplier with a simple `ROM + `_ lookup. The first two + lines simply check that the inputs are each 4-bits wide. ``romdata`` is a + Python function that calculates the values we want stored in the ROM, as + a function of the ROM address. :class:`~pyrtl.RomBlock` automatically + initializes the ROM with values computed by ``romdata``. The generated + hardware simply :func:`concats ` the two 4-bit inputs into + an 8-bit ROM address and returns the value stored in the ROM at that + address. + + .. literalinclude:: examples/example-mul.py + + .. image:: ../docs/screenshots/mul.png + :width: 15em + + .. tab-item:: Adder + + The classic ripple-carry adder: ``adder`` generates a ripple carry adder + of arbitrary length including both carry in and carry out. The `full + adder `_ + (``fa``) takes 1-bit inputs and produces 1-bit outputs. We iteratively + create full adders and link the carry in of each new full adder to the + carry out of the last full adder. ``adder``'s ``sum`` is a Python + :class:`list` that keeps track of the wires carrying the sum bits. The + final ``full_sum`` is produced by concatenating the wires in ``sum`` with + :func:`~pyrtl.concat_list`. + + .. literalinclude:: examples/example-adder.py + + .. image:: ../docs/screenshots/adder.png + :width: 15em + +The 10,000 Foot Overview +======================== + +At a high level, PyRTL builds hardware that you `explicitly define`. If you are +looking for a tool to take your random Python code and turn it into hardware, +you will have to look elsewhere: this is `not` `HLS +`_. Instead, PyRTL helps +you concisely and precisely describe a digital hardware structure, which you +already have worked out in detail, in Python. + +PyRTL restricts you to a set of reasonable digital designs practices: the clock +and resets are implicit, block memories are synchronous by default, there are +no "undriven" states, and un-registered feedback loops are not allowed. Instead +of worrying about these "analog-ish" tricks that are horrible ideas in modern +processes anyways, PyRTL lets you treat hardware design like a software +problem: build recursive hardware, write introspective containers, and have fun +building digital designs again! + +To the user it provides a set of Python classes that allow them to Pythonically +express their hardware designs. For example, with :class:`~pyrtl.WireVector` +you get a structure that acts very much like a Python list of 1-bit wires, so +``mywire[:-1]`` selects everything except the most-significant-bit. Of course +you can :meth:`add `, :meth:`subtract +`, and :meth:`multiply ` +these :class:`WireVectors `, or :func:`~pyrtl.concat` multiple +bit-vectors end-to-end as well. + +You can even put :class:`WireVectors ` in Python collections +and process them in bulk. For example, if ``x`` is a :class:`list` of +:class:`WireVectors `, and you want to multiply each of them +by 2 and sum them into a :class:`~pyrtl.WireVector` ``y``:: + + y = sum([elem * 2 for elem in x]) + +Hardware comprehensions are surprisingly useful. We'll cover an example in more +detail below, but if you just want to play around with PyRTL `try Jupyter +Notebooks on any of our examples on MyBinder +`_. + +Hello N-bit Ripple-Carry Adder! +=============================== + +While adders are a builtin primitive for PyRTL, most people doing RTL are +familiar with the idea of a `Ripple-Carry Adder +`_ and so it is useful to +see how you might express one in PyRTL. Rather than the typical `Verilog +introduction to fixed 4-bit adders +`_, let's go ahead and build an +`arbitrary` bitwidth adder. + +.. literalinclude:: examples/example-ripple-carry.py + +The code above includes ``ripple_add``, an adder generator with Python-style +slices on wires, ``counter``, an instance of :class:`~pyrtl.Register`, and all +the code needed to simulate the design, generate a waveform, and render it to +the terminal. `Example 2 +`_'s +comments describe this code in much more detail. Running this code shows a +counter running from 0 to 7 and repeating: + +.. image:: ../docs/screenshots/ripple-carry.png + :width: 34em +