├── .coveragerc
├── .github
└── workflows
│ ├── python-release.yml
│ └── python-test.yml
├── .gitignore
├── .readthedocs.yaml
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── codecov.yml
├── docs
├── Makefile
├── README.md
├── analysis.rst
├── basic.rst
├── blocks.rst
├── brand
│ └── pyrtl_logo.png
├── conf.py
├── export.rst
├── helpers.rst
├── images
│ └── gcd-graph.png
├── index.rst
├── regmem.rst
├── release
│ ├── README.md
│ └── requirements.txt
├── requirements.in
├── requirements.txt
├── rtllib.rst
├── screenshots
│ ├── pyrtl-counter.png
│ ├── pyrtl-renderer-demo-ascii.png
│ ├── pyrtl-renderer-demo-cp437.png
│ ├── pyrtl-renderer-demo-powerline.png
│ ├── pyrtl-renderer-demo-utf-8-alt.png
│ ├── pyrtl-renderer-demo-utf-8.png
│ └── pyrtl-statemachine.png
└── simtest.rst
├── examples
├── example0-minimum-viable-hardware.py
├── example1-combologic.py
├── example1.1-signed-numbers.py
├── example1.2-wire-struct.py
├── example2-counter.py
├── example3-statemachine.py
├── example4-debuggingtools.py
├── example5-introspection.py
├── example6-memory.py
├── example7-synth-timing.py
├── example8-verilog.py
├── example9-transforms-draft.py
├── introduction-to-hardware.py
└── renderer-demo.py
├── ipynb-examples
├── example1-combologic.ipynb
├── example2-counter.ipynb
├── example3-statemachine.ipynb
├── example4-debuggingtools.ipynb
├── example5-introspection.ipynb
├── example6-memory.ipynb
├── example7-synth-timing.ipynb
├── example8-verilog.ipynb
└── introduction-to-hardware.ipynb
├── pyproject.toml
├── pyrtl
├── __init__.py
├── analysis.py
├── compilesim.py
├── conditional.py
├── core.py
├── corecircuits.py
├── helperfuncs.py
├── importexport.py
├── memory.py
├── passes.py
├── pyrtlexceptions.py
├── rtllib
│ ├── __init__.py
│ ├── adders.py
│ ├── aes.py
│ ├── barrel.py
│ ├── libutils.py
│ ├── matrix.py
│ ├── multipliers.py
│ ├── muxes.py
│ ├── prngs.py
│ └── testingutils.py
├── simulation.py
├── transform.py
├── visualization.py
└── wire.py
├── requirements.txt
├── tests
├── __init__.py
├── rtllib
│ ├── test_adders.py
│ ├── test_aes.py
│ ├── test_barrel.py
│ ├── test_libutils.py
│ ├── test_matrix.py
│ ├── test_multipliers.py
│ ├── test_muxes.py
│ └── test_prngs.py
├── test_analysis.py
├── test_compilesim.py
├── test_conditional.py
├── test_core.py
├── test_examples.py
├── test_helperfuncs.py
├── test_importexport.py
├── test_memblock.py
├── test_passes.py
├── test_signed.py
├── test_simulation.py
├── test_transform.py
├── test_visualization.py
└── test_wire.py
└── tox.ini
/.coveragerc:
--------------------------------------------------------------------------------
1 | # .coveragerc to control coverage.py
2 | [run]
3 | source = pyrtl
4 |
5 | [report]
6 | # Regexes for lines to exclude from consideration
7 | exclude_lines =
8 | # Have to re-enable the standard pragma
9 | pragma: no cover
10 |
11 | # ignore pass statements that are not run:
12 | # sometimes you need them to make a block (especially for unittests)
13 | # need to be really careful with this one because 'pass' shows up in other places as well
14 | \spass\s
15 | \spass$
16 |
17 | # Don't complain about missing debug-only code:
18 | def __repr__
19 | if self\.debug
20 |
21 | # Don't complain if tests don't hit defensive assertion code:
22 | raise AssertionError
23 | raise NotImplementedError
24 |
25 | # Don't complain if non-runnable code isn't run:
26 | if 0:
27 | if __name__ == .__main__.:
28 |
29 | ignore_errors = True
30 |
31 | omit =
32 | .tox/*
33 | */tests/*
34 | */ctypes/*
35 | six.py
36 | pyparsing.py
37 |
--------------------------------------------------------------------------------
/.github/workflows/python-release.yml:
--------------------------------------------------------------------------------
1 | # See PyRTL's release documentation in docs/release/README.md
2 | #
3 | # This file configures GitHub actions for building distribution archives and
4 | # uploading them to PyPI.
5 | #
6 | # This configuration is based on the "Publishing package distribution releases
7 | # using GitHub Actions" tutorial at
8 | # https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/
9 |
10 | name: Build and publish release
11 |
12 | on: push
13 |
14 | jobs:
15 | # Verify that distribution archives can be built on every push.
16 | build:
17 | runs-on: ubuntu-latest
18 |
19 | steps:
20 | - uses: actions/checkout@v4
21 | - name: Set up Python
22 | uses: actions/setup-python@v5
23 | with:
24 | python-version: "3.x"
25 | - name: Install pypa/build
26 | run: python3 -m pip install build
27 | - name: Build distribution archives
28 | run: python3 -m build
29 | - name: Upload distribution archives
30 | uses: actions/upload-artifact@v4
31 | with:
32 | name: python-distribution-archives
33 | path: dist/
34 | compression-level: 0
35 |
36 | # Publish distribution archive to TestPyPI on tag pushes.
37 | publish-testpypi:
38 | # Only publish to TestPyPI on tag pushes.
39 | if: startsWith(github.ref, 'refs/tags/')
40 | needs:
41 | - build
42 | runs-on: ubuntu-latest
43 | environment:
44 | name: testpypi
45 | url: https://test.pypi.org/p/pyrtl
46 | permissions:
47 | # Required for trusted publishing.
48 | id-token: write
49 |
50 | steps:
51 | - name: Download distribution archives
52 | uses: actions/download-artifact@v4
53 | with:
54 | name: python-distribution-archives
55 | path: dist/
56 | - name: Publish distribution archives
57 | uses: pypa/gh-action-pypi-publish@release/v1
58 | with:
59 | repository-url: https://test.pypi.org/legacy/
60 |
61 | # Publish distribution archive to PyPI on tag pushes. The 'pypi' environment
62 | # requires manual approval on GitHub, so this job won't start automatically.
63 | publish-pypi:
64 | # Only publish to PyPI on tag pushes.
65 | if: startsWith(github.ref, 'refs/tags/')
66 | needs:
67 | - build
68 | runs-on: ubuntu-latest
69 | environment:
70 | name: pypi
71 | url: https://pypi.org/p/pyrtl
72 | permissions:
73 | # Required for trusted publishing.
74 | id-token: write
75 |
76 | steps:
77 | - name: Download distribution archives
78 | uses: actions/download-artifact@v4
79 | with:
80 | name: python-distribution-archives
81 | path: dist/
82 | - name: Publish distribution archives
83 | uses: pypa/gh-action-pypi-publish@release/v1
84 |
--------------------------------------------------------------------------------
/.github/workflows/python-test.yml:
--------------------------------------------------------------------------------
1 | name: Run Python tests
2 |
3 | on:
4 | - push
5 | - pull_request
6 |
7 | jobs:
8 | test:
9 | runs-on: ubuntu-latest
10 | strategy:
11 | matrix:
12 | # When changing the following line, be sure to update `envlist` in
13 | # tox.ini
14 | python-version: [3.9, 3.13]
15 |
16 | steps:
17 | - uses: actions/checkout@v4
18 | - name: Set up Python ${{ matrix.python-version }}
19 | uses: actions/setup-python@v5
20 | with:
21 | python-version: ${{ matrix.python-version }}
22 | - name: Install dependencies
23 | run: python3 -m pip install tox tox-gh-actions
24 | - name: Test with tox
25 | run: tox
26 | - name: Upload coverage to Codecov
27 | uses: codecov/codecov-action@v4
28 | env:
29 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.py[cod]
2 |
3 | # coverage
4 | .coverage/
5 | coverage.xml
6 |
7 | # C extensions
8 | *.so
9 |
10 | # Packages
11 | *.egg-info
12 | dist
13 | build
14 | parts
15 | bin
16 | var
17 | sdist
18 | .installed.cfg
19 | lib
20 | lib64
21 | __pycache__
22 |
23 | # Unit test / coverage reports
24 | .coverage
25 | .tox
26 |
27 | # Translations
28 | *.mo
29 |
30 | # Mr Developer
31 | .mr.developer.cfg
32 | .project
33 | .pydevproject
34 |
35 | # pycharm
36 | .idea
37 |
38 | # Apple crap
39 | .DS_Store
40 |
41 | # Stuff specifically for PyRTL
42 | spice.net
43 | docs/_build
44 |
45 | # Verilog files
46 | # There must be a very good reason for someone to add a verilog file to the repo
47 | .v
48 |
49 | # ipynb
50 | ipynb-examples/.ipynb_checkpoints
51 |
52 | # VS Code
53 | .vscode
54 |
55 | # Python venv
56 | pyvenv.cfg
--------------------------------------------------------------------------------
/.readthedocs.yaml:
--------------------------------------------------------------------------------
1 | # Read the Docs configuration file
2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
3 |
4 | # Required
5 | version: 2
6 |
7 | # Set the version of Python and other tools you might need
8 | build:
9 | # Use the latest Ubuntu LTS version available on Read the Docs.
10 | os: ubuntu-lts-latest
11 | tools:
12 | # Use the latest 3.x version available on Read the Docs.
13 | python: "3"
14 | apt_packages:
15 | - graphviz
16 |
17 | # Build documentation in the docs/ directory with Sphinx
18 | sphinx:
19 | configuration: docs/conf.py
20 |
21 | # Optionally build your docs in additional formats such as PDF
22 | formats:
23 | - pdf
24 |
25 | # Optionally set the requirements required to build your docs
26 | python:
27 | install:
28 | - requirements: docs/requirements.txt
29 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file. Only
4 | releases published to PyPI are tracked here. No release candidates!
5 |
6 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
7 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
8 |
9 | ## [0.11.2] - 2024-07-16
10 |
11 | ### Added
12 |
13 | - Added an `initialize_registers` option to `output_to_verilog`
14 | ([documentation](https://pyrtl.readthedocs.io/en/latest/export.html#pyrtl.importexport.output_to_verilog))
15 |
16 | ### Changed
17 |
18 | - Improved handling of signed integers.
19 |
20 | ### Fixed
21 |
22 | - Fixed a `wire_matrix` bug involving single-element matrices of `Inputs` or `Registers`.
23 |
24 | ## [0.11.1] - 2024-04-22
25 |
26 | ### Added
27 |
28 | - Named `WireVector` slices with `wire_struct` and `wire_matrix`. See documentation:
29 | - [wire_struct](https://pyrtl.readthedocs.io/en/latest/helpers.html#pyrtl.helperfuncs.wire_struct)
30 | - [wire_matrix](https://pyrtl.readthedocs.io/en/latest/helpers.html#pyrtl.helperfuncs.wire_matrix)
31 |
32 | ### Changed
33 |
34 | - Major changes to `render_trace` visualization. See [examples and
35 | documentation](https://pyrtl.readthedocs.io/en/latest/simtest.html#wave-renderer)
36 | - Many documentation and release process improvements.
37 |
38 | ### Fixed
39 |
40 | - Python 3.11 compatibility.
41 |
42 | ### Removed
43 |
44 | - Python 2.7 support.
45 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015, Regents of the University of California
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | * Neither the name of PyRTL nor the names of its
15 | contributors may be used to endorse or promote products derived from
16 | this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
3 |
4 | PyRTL
5 | =====
6 |
7 | [](http://badge.fury.io/py/pyrtl)
8 | [](https://github.com/UCSBarchlab/PyRTL/actions/workflows/python-test.yml)
9 | [](https://codecov.io/github/UCSBarchlab/PyRTL?branch=development)
10 | [](http://pyrtl.readthedocs.org/en/latest/?badge=latest)
11 | [](https://mybinder.org/v2/gh/UCSBarchlab/PyRTL/development?filepath=%2Fipynb-examples%2F)
12 |
13 | PyRTL provides a collection of classes for Pythonic [register-transfer
14 | level](https://en.wikipedia.org/wiki/Register-transfer_level) design,
15 | simulation, tracing, and testing suitable for teaching and research.
16 | Simplicity, usability, clarity, and extensibility rather than performance or
17 | optimization is the overarching goal. Features include:
18 |
19 | * Elaboration-through-execution, meaning all of Python can be used including
20 | introspection
21 | * Design, instantiate, and simulate all in one file and without leaving Python
22 | * Export to, or import from, common HDLs (BLIF-in, Verilog-out currently
23 | supported)
24 | * Examine execution with waveforms on the terminal or export to `.vcd` as
25 | projects scale
26 | * Elaboration, synthesis, and basic optimizations all included
27 | * Small and well-defined internal core structure means writing new transforms
28 | is easier
29 | * Batteries included means many useful components are already available and
30 | more are coming every week
31 |
32 | What README would be complete without a screenshot? Below you can see the
33 | waveform rendered right on the terminal for a small state machine written in
34 | PyRTL.
35 |
36 | 
37 |
38 | ### Tutorials and Documentation
39 |
40 | * For users, more info and demo code is available on the [PyRTL project web
41 | page](http://ucsbarchlab.github.io/PyRTL/).
42 | * Try the examples in the `examples/` directory. You can also [try the examples
43 | on
44 | MyBinder](https://mybinder.org/v2/gh/UCSBarchlab/PyRTL/development?filepath=%2Fipynb-examples%2F).
45 | * Full reference documentation is available at https://pyrtl.readthedocs.io/
46 |
47 | ### Package Contents
48 |
49 | If you are just getting started with PyRTL it is suggested that you start with
50 | the `examples/` first to get a sense of the "thinking with PyRTLs" required to
51 | design hardware in this way. If you are looking for a deeper understanding,
52 | dive into the code for the object `Block`. It is the core data structure at the
53 | heart of PyRTL and defines its semantics at a high level -- everything is
54 | converted to or from the small, simple set of primitives defined there.
55 |
56 | The package contains the following files and directories:
57 | * **`pyrtl/`** The src directory for the module
58 | * **`pyrtl/rtllib/`** Finished PyRTL libraries which are hopefully both useful
59 | and documented
60 | * **`examples/`** A set of hardware design examples that show the main idea
61 | behind pyrtl
62 | * **`tests/`** A set of unit tests for PyRTL which you can run with `pytest`
63 | * **`docs/`** Location of the sphinx documentation
64 |
65 | Testing requires the Python packages `tox` and `pytest`. Once installed a
66 | complete test of the system should be possible with the simple command `tox`
67 | and nothing more.
68 |
69 | ### Contributing to PyRTL
70 |
71 | *Picking a first project*
72 |
73 | * One of the earliest things you should submit is a unit test that hits some
74 | [uncovered lines of code in
75 | PyRTL](https://codecov.io/github/UCSBarchlab/PyRTL?branch=development). For
76 | example, pick a `PyrtlError` that is not covered and add a unit test in
77 | `tests/` that will hit it.
78 | * After you have that down check in the [PyRTL
79 | Issues](https://github.com/UCSBarchlab/PyRTL/issues) list for a feature that
80 | is marked as "beginner friendly".
81 | * Once you have that down, ask for access to the PyRTL-research repo where we
82 | keep a list of more advanced features and designs that could use more help!
83 |
84 | *Coding style*
85 |
86 | * All major functionality should have unit tests covering and documenting their
87 | use
88 | * All public functions and methods should have useful docstrings
89 | * All code needs to conform to
90 | [PEP8](https://www.python.org/dev/peps/pep-0008/) conventions
91 | * No new root-level dependencies on external libs, import locally if required
92 | for special functions
93 |
94 | *Workflow*
95 |
96 | * A useful reference for working with Git is this [Git
97 | tutorial](https://www.atlassian.com/git/tutorials/)
98 | * A useful Git Fork workflow for working on this repo is [found
99 | here](http://blog.scottlowe.org/2015/01/27/using-fork-branch-git-workflow/)
100 | * The `development` branch is the primary stable working branch (everyone is
101 | invited to submit pull requests)
102 | * Bugs and minor enhancements tracked directly through the [issue
103 | tracker](https://github.com/UCSBarchlab/PyRTL/issues)
104 | * When posting a bug please post a small chunk of code that captures the bug,
105 | e.g. [Issue #56](https://github.com/UCSBarchlab/PyRTL/issues/56)
106 | * When pushing a fix to a bug or enhancement please reference issue number in
107 | commit message, e.g. [Fix to Issue
108 | #56](https://github.com/UCSBarchlab/PyRTL/commit/1d5730db168a9e4490c580cb930075715468047a)
109 |
110 | *Documentation*
111 |
112 | * All important functionality should have an executable example in `examples/`
113 | * All classes should have a block comment with high level description of the
114 | class
115 | * All functions should follow the following (Sphinx parsable) docstring format:
116 | ```python
117 | """One Line Summary (< 80 chars) of the function, followed by period.
118 |
119 | :param param_name : Description of this parameter.
120 | :param param_name : Longer parameter descriptions take up a newline with four
121 | leading spaces like this.
122 | :return: Description of function's return value.
123 |
124 | A long description of what this function does. Talk about what the user
125 | should expect from this function and also what the users needs to do to use
126 | the function (this part is optional).
127 |
128 | """
129 |
130 | # Developer Notes (Optional):
131 | #
132 | # These would be anything that the user does not need to know in order to use
133 | # the functions.
134 | # These notes can include internal workings of the function, the logic behind
135 | # it, or how to extend it.
136 | ```
137 | * Sphinx parses [Python type
138 | annotations](https://docs.python.org/3/library/typing.html), so put type
139 | information into annotations instead of docstrings.
140 | * The Sphinx-generated documentation is published to
141 | https://pyrtl.readthedocs.io/
142 | * PyRTL's Sphinx build process is documented in
143 | [`docs/README.md`](https://github.com/UCSBarchlab/PyRTL/blob/development/docs/README.md).
144 | * PyRTL's release process is documented in
145 | [`docs/release/README.md`](https://github.com/UCSBarchlab/PyRTL/blob/development/docs/release/README.md).
146 |
147 | ### Using PyRTL
148 |
149 | We love to hear from users about their projects, and if there are issues we
150 | will try our best to push fixes quickly. You can read more about how we have
151 | been using it in our research at UCSB both in simulation and on FPGAs in [our
152 | PyRTL paper at FPL](http://www.cs.ucsb.edu/~sherwood/pubs/FPL-17-pyrtl.pdf).
153 |
154 | ### Related Projects
155 |
156 | It is always important to point out that PyRTL builds on the ideas of several
157 | other related projects as we all share the common goal of trying to make
158 | hardware design a better experience! You can read more about those
159 | relationships on our [PyRTL project web
160 | page](http://ucsbarchlab.github.io/PyRTL/).
161 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | coverage:
2 | status:
3 | project:
4 | default:
5 | informational: true
6 | patch:
7 | default:
8 | informational: true
9 | precision: 1
10 | round: up
11 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation.
2 |
3 | # You can set these variables from the command line, and also
4 | # from the environment for the first two.
5 | SPHINXOPTS ?=
6 | SPHINXBUILD ?= sphinx-build
7 | SOURCEDIR = .
8 | BUILDDIR = _build
9 |
10 | # Route the 'html' target to Sphinx using the "make mode" option. $(O) is
11 | # meant as a shortcut for $(SPHINXOPTS).
12 | html: Makefile
13 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | requirements.txt: requirements.in FORCE
16 | pip install --upgrade pip-tools
17 | pip-compile --upgrade requirements.in
18 |
19 | FORCE:
20 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # PyRTL's Documentation
2 |
3 | PyRTL's documentation is published to [Read the Docs](https://readthedocs.org/)
4 | at https://pyrtl.readthedocs.io/ . There is a
5 | [build dashboard](https://readthedocs.org/projects/pyrtl/builds/)
6 | and the main configuration file is
7 | [`.readthedocs.yaml`](https://github.com/UCSBarchlab/PyRTL/blob/development/.readthedocs.yaml)
8 | in the repository's root directory.
9 |
10 | PyRTL's documentation is in this `docs` directory. It is built with
11 | [Sphinx](https://www.sphinx-doc.org/en/master/), and written in
12 | [reStructuredText](https://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html).
13 | The main Sphinx configuration file is
14 | [`docs/conf.py`](https://github.com/UCSBarchlab/PyRTL/blob/development/docs/conf.py).
15 |
16 | Most of PyRTL's documentation is automatically extracted from Python
17 | 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
20 | annotations](https://docs.python.org/3/library/typing.html), so put type
21 | information into annotations instead of docstrings.
22 |
23 | Follow the instructions on this page to build a local copy of PyRTL's
24 | documentation. This is useful for verifying that PyRTL's documentation still
25 | renders correctly after making a local change.
26 |
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.
33 |
34 | ## Installing Sphinx
35 |
36 | Sphinx and its dependencies are all pinned to specific versions for
37 | [reproducible documentation builds](https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html).
38 | This avoids problems where documentation builds randomly fail due to bugs or
39 | incompatibilities in the newest version of Sphinx or one of its
40 | dependencies.
41 |
42 | Use of an environment manager like [`conda`](https://docs.conda.io/en/latest/)
43 | or [`virtualenv`](https://virtualenv.pypa.io/en/latest/) is strongly
44 | recommended. To install Sphinx locally, run the following commands from the
45 | repository root:
46 |
47 | ```shell
48 | # Install Sphinx.
49 | $ pip install --upgrade -r docs/requirements.txt
50 | ```
51 |
52 | ## Installing Graphviz
53 |
54 | [Install graphviz](https://www.graphviz.org/download/#executable-packages). Use
55 | of a package manager like
56 | [`apt`](https://ubuntu.com/server/docs/package-management) or
57 | [`brew`](https://brew.sh/) is strongly recommended. Instructions vary depending
58 | on your operating system, see the installation link for details.
59 |
60 | ## Running Sphinx
61 |
62 | Run Sphinx with the provided [`docs/Makefile`](https://github.com/UCSBarchlab/PyRTL/blob/development/docs/Makefile):
63 |
64 | ```shell
65 | # Run Sphinx to build PyRTL's documentation.
66 | $ make -C docs
67 | ```
68 |
69 | A local copy of PyRTL's documentation should be available in
70 | `docs/_build/html`. `docs/_build/html/index.html` is the home page.
71 |
72 | ## Updating Sphinx
73 |
74 | To update the pinned version of Sphinx, run
75 |
76 | ```shell
77 | # Run pip-compile to generate docs/requirements.txt from docs/requirements.in.
78 | $ make -C docs requirements.txt
79 | ```
80 |
81 | It's a good idea to update the pinned version of Sphinx whenever you update the
82 | documentation.
83 |
--------------------------------------------------------------------------------
/docs/analysis.rst:
--------------------------------------------------------------------------------
1 | .. PyRTL analysis master file
2 |
3 | .. default-domain:: pyrtl.rtllib
4 |
5 | =========================
6 | Analysis and Optimization
7 | =========================
8 |
9 | Tools for analyzing and optimizing aspects of PyRTL designs.
10 |
11 | Estimation
12 | ----------
13 |
14 | .. automodule:: pyrtl.analysis
15 | :members:
16 | :special-members:
17 | :undoc-members:
18 | :exclude-members: __dict__,__weakref__,__module__
19 |
20 | Optimization
21 | ------------
22 |
23 | .. autofunction:: pyrtl.passes.optimize
24 |
25 | Synthesis
26 | ---------
27 |
28 | .. autofunction:: pyrtl.passes.synthesize
29 |
30 | .. autoclass:: pyrtl.core.PostSynthBlock
31 | :show-inheritance:
32 |
33 | Individual Passes
34 | -----------------
35 |
36 | .. autofunction:: pyrtl.passes.common_subexp_elimination
37 | .. autofunction:: pyrtl.passes.constant_propagation
38 | .. autofunction:: pyrtl.passes.nand_synth
39 | .. autofunction:: pyrtl.passes.and_inverter_synth
40 | .. autofunction:: pyrtl.passes.one_bit_selects
41 | .. autofunction:: pyrtl.passes.two_way_concat
42 |
--------------------------------------------------------------------------------
/docs/basic.rst:
--------------------------------------------------------------------------------
1 | Wires and Logic
2 | ===============
3 |
4 | Wires define the relationship between logic blocks in PyRTL. They are treated
5 | like normal wires in traditional RTL systems except the :class:`.Register`
6 | wire. Logic is then created when wires are combined with one another using the
7 | provided operators. For example, if ``a`` and ``b`` are both of type
8 | :class:`.WireVector`, then ``a + b`` will make an adder, plug ``a`` and ``b``
9 | into the inputs of that adder, and return a new :class:`.WireVector` which is
10 | the output of that adder. :class:`.Block` stores the description of the
11 | hardware as you build it.
12 |
13 | :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).
20 |
21 | .. inheritance-diagram:: pyrtl.wire.WireVector
22 | pyrtl.wire.Input
23 | pyrtl.wire.Output
24 | pyrtl.wire.Const
25 | pyrtl.wire.Register
26 | :parts: 1
27 |
28 | WireVector
29 | ----------
30 |
31 | .. autoclass:: pyrtl.wire.WireVector
32 | :members:
33 | :special-members: __init__, __add__, __sub__, __mul__, __getitem___,
34 | __len__, __ilshift__, __and__, __or__, __xor__, __lt__,
35 | __le__, __eq__, __ne__, __gt__, __ge__, __len__
36 |
37 | Input Pins
38 | ----------
39 |
40 | .. autoclass:: pyrtl.wire.Input
41 | :members:
42 | :show-inheritance:
43 | :special-members: __init__
44 |
45 | Output Pins
46 | -----------
47 |
48 | .. autoclass:: pyrtl.wire.Output
49 | :members:
50 | :show-inheritance:
51 | :special-members: __init__
52 |
53 | Constants
54 | ---------
55 |
56 | .. autoclass:: pyrtl.wire.Const
57 | :members:
58 | :show-inheritance:
59 | :special-members: __init__
60 |
61 | Conditionals
62 | ------------
63 |
64 | .. automodule:: pyrtl.conditional
65 | :members:
66 | :show-inheritance:
67 | :special-members:
68 | :undoc-members:
69 | :exclude-members: __dict__,__weakref__,__module__
70 |
--------------------------------------------------------------------------------
/docs/blocks.rst:
--------------------------------------------------------------------------------
1 | Logic Nets and Blocks
2 | =====================
3 |
4 | LogicNets
5 | ---------
6 |
7 | .. autoclass:: pyrtl.core.LogicNet
8 |
9 |
10 | Blocks
11 | ------
12 |
13 | .. autoclass:: pyrtl.core.Block
14 |
15 | .. autofunction:: pyrtl.core.working_block
16 |
17 | .. autofunction:: pyrtl.core.reset_working_block
18 |
19 | .. autofunction:: pyrtl.core.set_working_block
20 |
21 | .. autofunction:: pyrtl.core.temp_working_block
22 |
23 | .. autofunction:: pyrtl.core.Block.add_wirevector
24 |
25 | .. autofunction:: pyrtl.core.Block.remove_wirevector
26 |
27 | .. autofunction:: pyrtl.core.Block.add_net
28 |
29 | .. autofunction:: pyrtl.core.Block.get_memblock_by_name
30 |
31 | .. autofunction:: pyrtl.core.Block.wirevector_subset
32 |
33 | .. autofunction:: pyrtl.core.Block.logic_subset
34 |
35 | .. autofunction:: pyrtl.core.Block.get_wirevector_by_name
36 |
37 | .. autofunction:: pyrtl.core.Block.net_connections
38 |
39 | .. autofunction:: pyrtl.core.Block.sanity_check
40 |
--------------------------------------------------------------------------------
/docs/brand/pyrtl_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSBarchlab/PyRTL/d5f8dbe53f54e61e1d54722449e4894b885243c7/docs/brand/pyrtl_logo.png
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # Configuration file for the Sphinx documentation builder.
2 | #
3 | # This file only contains a selection of the most common options. For a full
4 | # list see the documentation:
5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html
6 |
7 | # -- Path setup --------------------------------------------------------------
8 |
9 | # If extensions (or modules to document with autodoc) are in another directory,
10 | # add these directories to sys.path here. If the directory is relative to the
11 | # documentation root, use os.path.abspath to make it absolute, like shown here.
12 | #
13 | import os
14 | import sys
15 | sys.path.insert(0, os.path.abspath('..'))
16 |
17 |
18 | # -- Project information -----------------------------------------------------
19 |
20 | project = 'PyRTL'
21 | copyright = '2024, Timothy Sherwood'
22 | author = 'Timothy Sherwood'
23 |
24 | # -- General configuration ---------------------------------------------------
25 |
26 | master_doc = 'index'
27 |
28 | # Add any Sphinx extension module names here, as strings. They can be
29 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
30 | # ones.
31 | extensions = [
32 | 'sphinx.ext.autodoc',
33 | 'sphinx.ext.inheritance_diagram',
34 | 'sphinx.ext.viewcode',
35 | 'sphinx_autodoc_typehints',
36 | 'sphinx_copybutton',
37 | ]
38 |
39 | graphviz_output_format = 'svg'
40 |
41 | # Omit redundant method names in right sidebar (step() instead of Simulation.step()).
42 | toc_object_entries_show_parents = 'hide'
43 |
44 | # Add any paths that contain templates here, relative to this directory.
45 | templates_path = ['_templates']
46 |
47 | # List of patterns, relative to source directory, that match files and
48 | # directories to ignore when looking for source files.
49 | # This pattern also affects html_static_path and html_extra_path.
50 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
51 |
52 | primary_domain = 'py'
53 |
54 | # -- Options for HTML output -------------------------------------------------
55 |
56 | # The theme to use for HTML and HTML Help pages. See the documentation for
57 | # a list of builtin themes.
58 | #
59 | html_theme = "furo"
60 | html_theme_options = {
61 | 'sidebar_hide_name': True,
62 | }
63 | html_logo = 'brand/pyrtl_logo.png'
64 |
65 | # Force a light blue background color for inheritance-diagrams. The default is
66 | # transparent, which does not work well with Furo's dark mode.
67 | inheritance_graph_attrs = {
68 | 'bgcolor': 'aliceblue',
69 | }
70 |
--------------------------------------------------------------------------------
/docs/export.rst:
--------------------------------------------------------------------------------
1 | Exporting and Importing Designs
2 | ===============================
3 |
4 | Exporting Hardware Designs
5 | --------------------------
6 |
7 | .. autofunction:: pyrtl.importexport.output_to_verilog
8 |
9 | .. autofunction:: pyrtl.importexport.output_to_firrtl
10 |
11 | Exporting Testbenches
12 | ---------------------
13 |
14 | .. autofunction:: pyrtl.importexport.output_verilog_testbench
15 |
16 | Importing Verilog
17 | -----------------
18 |
19 | .. autofunction:: pyrtl.importexport.input_from_blif
20 | .. autofunction:: pyrtl.importexport.input_from_verilog
21 |
22 | Outputting for Visualization
23 | ----------------------------
24 |
25 | .. autofunction:: pyrtl.visualization.output_to_trivialgraph
26 | .. autofunction:: pyrtl.visualization.output_to_graphviz
27 | .. autofunction:: pyrtl.visualization.graphviz_detailed_namer
28 | .. autofunction:: pyrtl.visualization.output_to_svg
29 | .. autofunction:: pyrtl.visualization.block_to_graphviz_string
30 | .. autofunction:: pyrtl.visualization.block_to_svg
31 | .. autofunction:: pyrtl.visualization.trace_to_html
32 | .. autofunction:: pyrtl.visualization.net_graph
33 |
--------------------------------------------------------------------------------
/docs/helpers.rst:
--------------------------------------------------------------------------------
1 | Helper Functions
2 | ================
3 |
4 | Cutting and Extending WireVectors
5 | ---------------------------------
6 |
7 | The functions below provide ways of combining, slicing, and extending
8 | :class:`WireVectors<.WireVector>` in ways that are often useful in hardware
9 | design. The functions below extend those member functions of the
10 | :class:`.WireVector` class itself (which provides support for the Python
11 | builtin ``len``, slicing e.g. ``wire[3:6]``,
12 | :meth:`~pyrtl.wire.WireVector.zero_extended`,
13 | :meth:`~pyrtl.wire.WireVector.sign_extended`, and many operators such as
14 | addition and multiplication).
15 |
16 | .. autofunction:: pyrtl.corecircuits.concat
17 | .. autofunction:: pyrtl.corecircuits.concat_list
18 | .. autofunction:: pyrtl.corecircuits.match_bitwidth
19 | .. autofunction:: pyrtl.helperfuncs.truncate
20 | .. autofunction:: pyrtl.helperfuncs.chop
21 | .. autofunction:: pyrtl.helperfuncs.wire_struct
22 | .. autofunction:: pyrtl.helperfuncs.wire_matrix
23 |
24 | Coercion to WireVector
25 | ----------------------
26 |
27 | In PyRTL there is only one function in charge of coercing values into
28 | :class:`WireVectors<.WireVector>`, and that is :func:`.as_wires`. This
29 | function is called in almost all helper functions and classes to manage the
30 | mixture of constants and WireVectors that naturally occur in hardware
31 | development.
32 |
33 | .. autofunction:: pyrtl.corecircuits.as_wires
34 |
35 | Control Flow Hardware
36 | ---------------------
37 |
38 | .. autofunction:: pyrtl.corecircuits.mux
39 | .. autofunction:: pyrtl.corecircuits.select
40 | .. autofunction:: pyrtl.corecircuits.enum_mux
41 | .. autofunction:: pyrtl.corecircuits.bitfield_update
42 | .. autofunction:: pyrtl.corecircuits.bitfield_update_set
43 | .. autofunction:: pyrtl.helperfuncs.match_bitpattern
44 |
45 | Creating Lists of WireVectors
46 | -----------------------------
47 |
48 | .. autofunction:: pyrtl.helperfuncs.input_list
49 | .. autofunction:: pyrtl.helperfuncs.output_list
50 | .. autofunction:: pyrtl.helperfuncs.register_list
51 | .. autofunction:: pyrtl.helperfuncs.wirevector_list
52 |
53 | Interpreting Vectors of Bits
54 | ----------------------------
55 |
56 | Under the hood, every single `value` a PyRTL design operates on is a bit vector
57 | (which is, in turn, simply an integer of bounded power-of-two size.
58 | Interpreting these bit vectors as humans, and turning human understandable
59 | values into their corresponding bit vectors, can both be a bit of a pain. The
60 | functions below do not create any hardware but rather help in the process of
61 | reasoning about bit vector representations of human understandable values.
62 |
63 | .. autofunction:: pyrtl.helperfuncs.val_to_signed_integer
64 | .. autofunction:: pyrtl.helperfuncs.val_to_formatted_str
65 | .. autofunction:: pyrtl.helperfuncs.formatted_str_to_val
66 | .. autoclass:: pyrtl.helperfuncs.ValueBitwidthTuple
67 | :members: value, bitwidth
68 | .. autofunction:: pyrtl.helperfuncs.infer_val_and_bitwidth
69 | .. autofunction:: pyrtl.helperfuncs.log2
70 |
71 | Debugging
72 | ---------
73 |
74 | .. autofunction:: pyrtl.core.set_debug_mode
75 | .. autofunction:: pyrtl.helperfuncs.probe
76 | .. autofunction:: pyrtl.helperfuncs.rtl_assert
77 | .. autofunction:: pyrtl.helperfuncs.check_rtl_assertions
78 |
79 | Reductions
80 | ----------
81 |
82 | .. autofunction:: pyrtl.corecircuits.and_all_bits
83 | .. autofunction:: pyrtl.corecircuits.or_all_bits
84 | .. autofunction:: pyrtl.corecircuits.xor_all_bits
85 | .. autofunction:: pyrtl.corecircuits.parity
86 | .. autofunction:: pyrtl.corecircuits.rtl_any
87 | .. autofunction:: pyrtl.corecircuits.rtl_all
88 |
89 | Extended Logic and Arithmetic
90 | -----------------------------
91 |
92 | The functions below provide ways of comparing and arithmetically combining
93 | :class:`WireVectors<.WireVector>` in ways that are often useful in hardware
94 | design. The functions below extend those member functions of the
95 | :class:`.WireVector` class itself (which provides support for unsigned
96 | addition, subtraction, multiplication, comparison, and many others).
97 |
98 | .. autofunction:: pyrtl.corecircuits.signed_add
99 | .. autofunction:: pyrtl.corecircuits.signed_sub
100 | .. autofunction:: pyrtl.corecircuits.signed_mult
101 | .. autofunction:: pyrtl.corecircuits.signed_lt
102 | .. autofunction:: pyrtl.corecircuits.signed_le
103 | .. autofunction:: pyrtl.corecircuits.signed_gt
104 | .. autofunction:: pyrtl.corecircuits.signed_ge
105 | .. autofunction:: pyrtl.corecircuits.shift_left_arithmetic
106 | .. autofunction:: pyrtl.corecircuits.shift_right_arithmetic
107 | .. autofunction:: pyrtl.corecircuits.shift_left_logical
108 | .. autofunction:: pyrtl.corecircuits.shift_right_logical
109 |
110 | Encoders and Decoders
111 | ---------------------
112 |
113 | .. autofunction:: pyrtl.helperfuncs.one_hot_to_binary
114 | .. autofunction:: pyrtl.helperfuncs.binary_to_one_hot
115 |
116 |
--------------------------------------------------------------------------------
/docs/images/gcd-graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSBarchlab/PyRTL/d5f8dbe53f54e61e1d54722449e4894b885243c7/docs/images/gcd-graph.png
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | =====
2 | PYRTL
3 | =====
4 |
5 | A collection of classes providing simple RTL specification, simulation,
6 | tracing, and testing suitable for teaching and research. Simplicity, usability,
7 | clarity, and extensibility rather than performance or optimization is the
8 | overarching goal. With PyRTL you can use the full power of Python to describe
9 | complex synthesizable digital designs, simulate and test them, and export them
10 | to Verilog.
11 |
12 | Quick links
13 | ===========
14 | * Get an overview from the `PyRTL Project Webpage `_
15 | * Read through `Example PyRTL Code `_
16 | * File a `Bug or Issue Report `_
17 | * Contribute to project on `GitHub `_
18 |
19 | Installation
20 | ============
21 |
22 | **Automatic installation**::
23 |
24 | pip install pyrtl
25 |
26 | PyRTL is listed in `PyPI `_ and can be
27 | installed with :program:`pip` or :program:`pip3`. If the above command fails
28 | due to insufficient permissions, you may need to do ``sudo pip install pyrtl``
29 | (to install as superuser) or ``pip install --user pyrtl`` (to install as a
30 | normal user).
31 |
32 | PyRTL is tested to work with Python 3.8+.
33 |
34 | Design, Simulate, and Inspect in 15 lines
35 | =========================================
36 |
37 | .. code-block::
38 | :linenos:
39 |
40 | import pyrtl
41 |
42 | a = pyrtl.Input(8,'a') # input "pins"
43 | b = pyrtl.Input(8,'b')
44 | q = pyrtl.Output(8,'q') # output "pins"
45 | gt5 = pyrtl.Output(1,'gt5')
46 |
47 | result = a + b # makes an 8-bit adder
48 | q <<= result # assigns output of adder to out pin
49 | gt5 <<= result > 5 # does a comparison, assigns that to different pin
50 |
51 | # simulate and output the resulting waveform to the terminal
52 | sim = pyrtl.Simulation()
53 | sim.step_multiple({'a':[0,1,2,3,4], 'b':[2,2,3,3,4]})
54 | sim.tracer.render_trace()
55 |
56 | After you have PyRTL installed, you should be able to cut and paste the above
57 | into a file and run it with Python. The result you should see, drawn right
58 | into the terminal, is the output of the simulation. While a great deal of work
59 | has gone into making hardware design in PyRTL as friendly as possible, please
60 | don't mistake that for a lack of depth. You can just as easily export to
61 | Verilog or other hardware formats, view results with your favorite waveform
62 | viewer, build hardware transformation passes, run JIT-accelerated simulations,
63 | design, test, and even verify hugely complex digital systems, and much more.
64 | Most critically of all it is easy to extend with your own approaches to digital
65 | hardware development as you find necessary.
66 |
67 |
68 | Overview of PyRTL
69 | =================
70 |
71 | If you are brand new to PyRTL we recommend that you start with the `PyRTL Code
72 | Examples `_
73 | which will show you most of the core functionality in the context of a complete
74 | design.
75 |
76 | PyRTL Classes:
77 | --------------
78 |
79 | Perhaps the most important class to understand is :class:`.WireVector`, which
80 | is the basic type from which you build all hardware. If you are coming to
81 | PyRTL from Verilog, a :class:`.WireVector` is closest to a multi-bit `wire`.
82 | Every new :class:`.WireVector` builds a set of wires which you can then connect
83 | with other :class:`.WireVector` through overloaded operations such as
84 | `addition` or `bitwise or`. A bunch of other related classes, including
85 | :class:`.Input`, :class:`.Output`, :class:`.Const`, and :class:`.Register` are
86 | all derived from :class:`.WireVector`. Coupled with :class:`.MemBlock` (and
87 | :class:`.RomBlock`), this is all a user needs to create a functional hardware
88 | design.
89 |
90 | .. inheritance-diagram:: pyrtl.wire.WireVector
91 | pyrtl.wire.Input
92 | pyrtl.wire.Output
93 | pyrtl.wire.Const
94 | pyrtl.wire.Register
95 | :parts: 1
96 |
97 | After specifying a hardware design, there are then options to simulate your
98 | design right in PyRTL, synthesize it down to primitive 1-bit operations,
99 | optimize it, and export it to Verilog (along with a testbench).
100 |
101 | Simulation
102 | ^^^^^^^^^^
103 |
104 | PyRTL provides tools for simulation and viewing simulation traces. Simulation
105 | is how your hardware is "executed" for the purposes of testing, and three
106 | different classes help you do that: :class:`.Simulation`,
107 | :class:`.FastSimulation` and :class:`.CompiledSimulation`. All three have
108 | `almost` the same interface and, except for a few debugging cases, can be used
109 | interchangeably. Typically one starts with :class:`.Simulation` and then moves
110 | up to :class:`.FastSimulation` when performance begins to matter.
111 |
112 | Both :class:`.Simulation` and :class:`.FastSimulation` take an instance of
113 | :class:`.SimulationTrace` as an argument (or makes an empty
114 | :class:`.SimulationTrace` by default), which stores a list of the signals as
115 | they are simulated. This trace can then be rendered to the terminal with
116 | :class:`.WaveRenderer`, although unless there are some problems with the
117 | default configurations, most end users should not need to even be aware of
118 | :class:`.WaveRenderer`. The examples describe other ways that the trace may be
119 | handled, including extraction as a test bench and export to a VCD file.
120 |
121 | Optimization
122 | ^^^^^^^^^^^^
123 |
124 | :class:`.WireVector` and :class:`.MemBlock` are just "sugar" over a core set of
125 | primitives, and the final design is built up incrementally as a graph of these
126 | primitives. :class:`WireVectors<.WireVector>` connects these "primitives",
127 | which connect to other :class:`WireVectors<.WireVector>`. Each primitive is a
128 | :class:`.LogicNet`, and a :class:`.Block` is a graph of
129 | :class:`LogicNets<.LogicNet>`. Typically a full design is stored in a single
130 | :class:`.Block`. The function :func:`.working_block()` returns the block on
131 | which we are implicitly working. Hardware transforms may make a new
132 | :class:`.Block` from an old one. For example, see :class:`.PostSynthBlock`.
133 |
134 | Errors
135 | ^^^^^^
136 |
137 | Finally, when things go wrong you may hit on one of two ``Exceptions``, neither
138 | of which is likely recoverable automatically (which is why we limited them to
139 | only two). The intention is that ``PyrtlError`` is intended to capture end
140 | user errors such as invalid constant strings and mis-matched bitwidths. In
141 | contrast, ``PyrtlInternalError`` captures internal invariants and assertions
142 | over the core logic graph which should never be hit when constructing designs
143 | in the normal ways. If you hit a confusing ``PyrtlError`` or any
144 | ``PyrtlInternalError`` feel free to file an issue.
145 |
146 |
147 | Reference Guide
148 | ===============
149 | .. toctree::
150 | :maxdepth: 2
151 |
152 | basic
153 | regmem
154 | simtest
155 | blocks
156 | helpers
157 | analysis
158 | export
159 | rtllib
160 |
161 | Index
162 | =====
163 | * :ref:`genindex`
164 |
--------------------------------------------------------------------------------
/docs/regmem.rst:
--------------------------------------------------------------------------------
1 | Registers and Memories
2 | ======================
3 |
4 | Registers
5 | ---------
6 |
7 | .. autoclass:: pyrtl.wire.Register
8 | :members:
9 | :show-inheritance:
10 | :special-members: __init__
11 |
12 | Memories
13 | --------
14 |
15 | .. autoclass:: pyrtl.memory.MemBlock
16 | :members:
17 | :special-members: __init__, __getitem__, __setitem__
18 |
19 | ROMs
20 | ----
21 |
22 | .. autoclass:: pyrtl.memory.RomBlock
23 | :members:
24 | :show-inheritance:
25 | :special-members: __init__, __getitem__
26 |
--------------------------------------------------------------------------------
/docs/release/README.md:
--------------------------------------------------------------------------------
1 | # PyRTL release process documentation
2 |
3 | See [Packaging Python
4 | Projects](https://packaging.python.org/en/latest/tutorials/packaging-projects/)
5 | for an overview of Python packaging. PyRTL's `pyproject.toml` configuration is
6 | based on this tutorial.
7 |
8 | See [Publishing package distribution releases using GitHub
9 | Actions](https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/)
10 | for an overview of distributing Python packages with GitHub Actions. PyRTL's
11 | `python-release.yml` configuration is based on this tutorial.
12 |
13 | ## PyRTL versioning
14 |
15 | PyRTL uses [semantic versioning](https://semver.org/). All version numbers are
16 | of the form `MAJOR.MINOR.PATCH`, with an optional `rcN` suffix for release
17 | candidates, starting from `rc0`. Valid examples include `0.11.1` and
18 | `0.11.0rc0`. Never reuse a version number.
19 |
20 | Releases with a `rc` suffix are Release Candidates. Use release candidates for
21 | pre-releases, and to test the release process. `pip` will not install release
22 | candidates by default. Use:
23 | ```shell
24 | $ pip install --pre pyrtl
25 | ```
26 | to install the latest release candidate.
27 |
28 | The rest of this document assumes that the new PyRTL version number is
29 | `$NEW_VERSION`.
30 |
31 | See [PEP 440](https://peps.python.org/pep-0440/) for more details on version
32 | numbers.
33 |
34 | ## Building a new release
35 |
36 | 1. Clone the PyRTL repository:
37 | ```shell
38 | $ git clone git@github.com:UCSBarchlab/PyRTL.git pyrtl
39 | $ cd pyrtl
40 | ```
41 | 2. Tag the new version:
42 | ```shell
43 | $ git tag $NEW_VERSION
44 | ```
45 | 3. Push this change to GitHub. Tags are not pushed by default, so use:
46 | ```shell
47 | $ git push origin $NEW_VERSION
48 | ```
49 |
50 | The `python-release.yml` GitHub workflow should detect the new tag, build a new
51 | release, and upload the new release to TestPyPI. Check workflow status at
52 | https://github.com/UCSBarchlab/PyRTL/actions
53 |
54 | ### Testing a new TestPyPI release
55 |
56 | Check the [TestPyPI release
57 | history](https://test.pypi.org/project/pyrtl/#history). You should see your
58 | `$NEW_VERSION` at the top of the page.
59 |
60 | Test this TestPyPI release by creating a new Python virtual environment
61 | (`venv`):
62 | ```shell
63 | $ python3 -m venv pyrtl-release-test
64 | $ . pyrtl-release-test/bin/activate
65 | ```
66 |
67 | Then install and test the new release from TestPyPI:
68 | ```shell
69 | $ pip install --index-url https://test.pypi.org/simple/ --no-deps pyrtl
70 | ```
71 | If you created a `rc` release candidate, don't forget to add the `--pre` flag:
72 | ```shell
73 | $ pip install --index-url https://test.pypi.org/simple/ --no-deps --pre pyrtl
74 | ```
75 |
76 | ### Deploying a new PyPI release
77 |
78 | If the new release looks good, approve the 'Publish distribution archives on
79 | PyPI' workflow on GitHub to deploy the new release to PyPI.
80 |
81 | ### Testing a new PyPI release
82 |
83 | Check the [PyPI release history](https://pypi.org/project/pyrtl/#history). You
84 | should see your `$NEW_VERSION` at the top of the page.
85 |
86 | Test this PyPI release by installing and testing the new release from PyPI:
87 | ```shell
88 | $ pip install pyrtl
89 | ```
90 | If you created a `rc` release candidate, don't forget to add the `--pre` flag:
91 | ```shell
92 | $ pip install --pre pyrtl
93 | ```
94 |
95 | ## Read the Docs Versioning
96 |
97 | Read the Docs builds documentation for each PyRTL release. Available versions
98 | can be seen on [PyRTL's
99 | dashboard](https://readthedocs.org/projects/pyrtl/versions/), or in the bottom
100 | right [flyout
101 | menu](https://docs.readthedocs.io/en/stable/glossary.html#term-flyout-menu) on
102 | each documentation page.
103 |
104 | After building a new release, check the new release's documentation on [PyRTL's
105 | Read the Docs dashboard](https://readthedocs.org/projects/pyrtl/versions/).
106 |
107 | Versioned documentation builds are triggered by the creation of git tags, and
108 | versions for new releases are automatically activated by the Read the Docs
109 | "Activate new version" [automation
110 | rule](https://docs.readthedocs.io/en/stable/automation-rules.html). The "Hide
111 | release candidates" automation rule hides release candidates from the bottom
112 | right flyout menu.
113 |
114 | After a full release (not a release candidate), deactivate the documentation
115 | for any corresponding release candidates on the dashboard.
116 |
117 | ## Manually building and publishing a new release
118 |
119 | The following manual steps should be automated by
120 | `.github/workflows/python-release.yml`. Manual release instructions are
121 | provided below in case a release needs to be built without GitHub workflow
122 | automation. If the automation is working, you shouldn't need to run these
123 | commands.
124 |
125 | ### Manual build
126 |
127 | 1. Update build tools:
128 | ```shell
129 | $ pip install --upgrade -r release/requirements.txt
130 | ```
131 | 2. Build distribution archive:
132 | ```shell
133 | $ python3 -m build
134 | ```
135 | This produces two files in `dist/`: a `.whl` file and a `.tar.gz` file.
136 |
137 | ### Manual publish on TestPyPI
138 |
139 | 1. If necessary, create a TestPyPI API token, by going to
140 | https://test.pypi.org/manage/account/ and clicking 'Add API token'.
141 | 2. Upload distribution archive to TestPyPI:
142 | ```shell
143 | $ twine upload --repository testpypi dist/*
144 | ```
145 | 3. Enter your API token when prompted.
146 | 4. Check the new release's status at https://test.pypi.org/project/pyrtl/#history
147 | 5. Install and test the new release from TestPyPI by following the [Testing a
148 | new TestPyPI release](#testing-a-new-testpypi-release) instructions above.
149 |
150 | ### Manual publish on PyPI
151 |
152 | > :warning: The next steps update the official PyRTL release on PyPI, which
153 | > affects anyone running `pip install pyrtl`. Proceed with caution!
154 |
155 | 1. If necessary, create a PyPI API token, by going to
156 | https://pypi.org/manage/account/ and clicking 'Add API token'.
157 | 2. Upload distribution archive to PyPI:
158 | ```shell
159 | $ twine upload dist/*
160 | ```
161 | 3. Enter your API token when prompted.
162 | 4. Check the new release's status at https://pypi.org/project/pyrtl/#history
163 | 5. Install and test the new release from PyPI by following the [Testing a new
164 | PyPI release](#testing-a-new-pypi-release) instructions above.
165 |
166 | ## Fixing Mistakes
167 |
168 | First assess the magnitude of the problem. If the new release is unusable or is
169 | unsafe to use, yank the bad release, then publish a fixed release. If the new
170 | release has a smaller problem, and is mostly usable, just publish a fixed
171 | release.
172 |
173 | ### Yanking A Bad Release
174 |
175 | If the new release is unusable, [yank the
176 | release](https://pypi.org/help/#yanked). This can be done by a project owner on
177 | PyPI, by clicking 'Manage project' then 'Options', then Yank'. When a release
178 | is yanked, it remains available to anyone requesting exactly the yanked
179 | version, so a project with a pinned requirement on PyRTL won't break. A yanked
180 | version will not be installed in any other case, so a user running `pip install
181 | pyrtl` will never receive a yanked version.
182 |
183 | ### Publishing A Fixed Release
184 |
185 | Fix the problem in the code, then publish a new release with a new version
186 | number by following the instructions above. Do not attempt to reuse an existing
187 | version number.
188 |
--------------------------------------------------------------------------------
/docs/release/requirements.txt:
--------------------------------------------------------------------------------
1 | build
2 | twine
3 |
--------------------------------------------------------------------------------
/docs/requirements.in:
--------------------------------------------------------------------------------
1 | furo
2 | sphinx
3 | sphinx-autodoc-typehints
4 | sphinx-copybutton
5 |
--------------------------------------------------------------------------------
/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | #
2 | # This file is autogenerated by pip-compile with Python 3.13
3 | # by the following command:
4 | #
5 | # pip-compile requirements.in
6 | #
7 | alabaster==1.0.0
8 | # via sphinx
9 | babel==2.16.0
10 | # via sphinx
11 | beautifulsoup4==4.12.3
12 | # via furo
13 | certifi==2024.12.14
14 | # via requests
15 | charset-normalizer==3.4.1
16 | # via requests
17 | docutils==0.21.2
18 | # via sphinx
19 | furo==2024.8.6
20 | # via -r requirements.in
21 | idna==3.10
22 | # via requests
23 | imagesize==1.4.1
24 | # via sphinx
25 | jinja2==3.1.5
26 | # via sphinx
27 | markupsafe==3.0.2
28 | # via jinja2
29 | packaging==24.2
30 | # via sphinx
31 | pygments==2.19.1
32 | # via
33 | # furo
34 | # sphinx
35 | requests==2.32.3
36 | # via sphinx
37 | snowballstemmer==2.2.0
38 | # via sphinx
39 | soupsieve==2.6
40 | # via beautifulsoup4
41 | sphinx==8.1.3
42 | # via
43 | # -r requirements.in
44 | # furo
45 | # sphinx-autodoc-typehints
46 | # sphinx-basic-ng
47 | # sphinx-copybutton
48 | sphinx-autodoc-typehints==3.0.0
49 | # via -r requirements.in
50 | sphinx-basic-ng==1.0.0b2
51 | # via furo
52 | sphinx-copybutton==0.5.2
53 | # via -r requirements.in
54 | sphinxcontrib-applehelp==2.0.0
55 | # via sphinx
56 | sphinxcontrib-devhelp==2.0.0
57 | # via sphinx
58 | sphinxcontrib-htmlhelp==2.1.0
59 | # via sphinx
60 | sphinxcontrib-jsmath==1.0.1
61 | # via sphinx
62 | sphinxcontrib-qthelp==2.0.0
63 | # via sphinx
64 | sphinxcontrib-serializinghtml==2.0.0
65 | # via sphinx
66 | urllib3==2.3.0
67 | # via requests
68 |
--------------------------------------------------------------------------------
/docs/rtllib.rst:
--------------------------------------------------------------------------------
1 | .. PyRTL rtllib master file
2 |
3 | .. default-domain:: pyrtl.rtllib
4 |
5 | ===========
6 | RTL Library
7 | ===========
8 |
9 | Useful circuits, functions, and testing utilities.
10 |
11 | Adders
12 | ------
13 |
14 | .. automodule:: pyrtl.rtllib.adders
15 | :members:
16 | :special-members:
17 | :undoc-members:
18 | :exclude-members: __dict__,__weakref__,__module__
19 |
20 | AES-128
21 | -------
22 |
23 | .. automodule:: pyrtl.rtllib.aes
24 | :members:
25 | :undoc-members:
26 | :exclude-members: __dict__,__weakref__,__module__
27 |
28 | Barrel
29 | ------
30 |
31 | .. automodule:: pyrtl.rtllib.barrel
32 | :members:
33 | :special-members:
34 | :undoc-members:
35 | :exclude-members: __dict__,__weakref__,__module__
36 |
37 | Library Utilities
38 | -----------------
39 |
40 | .. automodule:: pyrtl.rtllib.libutils
41 | :members:
42 | :special-members:
43 | :undoc-members:
44 | :exclude-members: __dict__,__weakref__,__module__
45 |
46 | Multipliers
47 | -----------
48 |
49 | .. automodule:: pyrtl.rtllib.multipliers
50 | :members:
51 | :special-members:
52 | :undoc-members:
53 | :exclude-members: __dict__,__weakref__,__module__
54 |
55 | Muxes
56 | -----
57 |
58 | .. automodule:: pyrtl.rtllib.muxes
59 | :members:
60 | :special-members:
61 | :undoc-members:
62 | :exclude-members: __dict__,__weakref__,__module__,__exit__
63 |
64 | Matrix
65 | ------
66 |
67 | .. automodule:: pyrtl.rtllib.matrix
68 | :members:
69 | :special-members: __init__
70 | :undoc-members:
71 | :exclude-members: __dict__,__weakref__,__module__
72 |
73 | Testing Utilities
74 | -----------------
75 |
76 | .. automodule:: pyrtl.rtllib.testingutils
77 | :members:
78 | :special-members:
79 | :exclude-members: __dict__,__weakref__,__module__
80 |
--------------------------------------------------------------------------------
/docs/screenshots/pyrtl-counter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSBarchlab/PyRTL/d5f8dbe53f54e61e1d54722449e4894b885243c7/docs/screenshots/pyrtl-counter.png
--------------------------------------------------------------------------------
/docs/screenshots/pyrtl-renderer-demo-ascii.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSBarchlab/PyRTL/d5f8dbe53f54e61e1d54722449e4894b885243c7/docs/screenshots/pyrtl-renderer-demo-ascii.png
--------------------------------------------------------------------------------
/docs/screenshots/pyrtl-renderer-demo-cp437.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSBarchlab/PyRTL/d5f8dbe53f54e61e1d54722449e4894b885243c7/docs/screenshots/pyrtl-renderer-demo-cp437.png
--------------------------------------------------------------------------------
/docs/screenshots/pyrtl-renderer-demo-powerline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSBarchlab/PyRTL/d5f8dbe53f54e61e1d54722449e4894b885243c7/docs/screenshots/pyrtl-renderer-demo-powerline.png
--------------------------------------------------------------------------------
/docs/screenshots/pyrtl-renderer-demo-utf-8-alt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSBarchlab/PyRTL/d5f8dbe53f54e61e1d54722449e4894b885243c7/docs/screenshots/pyrtl-renderer-demo-utf-8-alt.png
--------------------------------------------------------------------------------
/docs/screenshots/pyrtl-renderer-demo-utf-8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSBarchlab/PyRTL/d5f8dbe53f54e61e1d54722449e4894b885243c7/docs/screenshots/pyrtl-renderer-demo-utf-8.png
--------------------------------------------------------------------------------
/docs/screenshots/pyrtl-statemachine.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSBarchlab/PyRTL/d5f8dbe53f54e61e1d54722449e4894b885243c7/docs/screenshots/pyrtl-statemachine.png
--------------------------------------------------------------------------------
/docs/simtest.rst:
--------------------------------------------------------------------------------
1 | Simulation and Testing
2 | ======================
3 |
4 | Simulation
5 | ----------
6 |
7 | .. autoclass:: pyrtl.simulation.Simulation
8 | :members:
9 | :special-members: __init__
10 |
11 | Fast (JIT to Python) Simulation
12 | -------------------------------
13 |
14 | .. autoclass:: pyrtl.simulation.FastSimulation
15 | :members:
16 | :special-members: __init__
17 |
18 | Compiled (JIT to C) Simulation
19 | ------------------------------
20 |
21 | .. autoclass:: pyrtl.compilesim.CompiledSimulation
22 | :members:
23 | :special-members: __init__
24 |
25 | Simulation Trace
26 | ----------------
27 |
28 | .. autoclass:: pyrtl.simulation.SimulationTrace
29 | :members:
30 | :special-members: __init__
31 |
32 | Wave Renderer
33 | -------------
34 |
35 | .. autoclass:: pyrtl.simulation.WaveRenderer
36 | :members:
37 | :special-members: __init__
38 | :exclude-members: render_ruler_segment, render_val, val_to_str
39 | .. autofunction:: pyrtl.simulation.enum_name
40 | .. autoclass:: pyrtl.simulation.PowerlineRendererConstants
41 | :show-inheritance:
42 | .. autoclass:: pyrtl.simulation.Utf8RendererConstants
43 | :show-inheritance:
44 | .. autoclass:: pyrtl.simulation.Utf8AltRendererConstants
45 | :show-inheritance:
46 | .. autoclass:: pyrtl.simulation.Cp437RendererConstants
47 | :show-inheritance:
48 | .. autoclass:: pyrtl.simulation.AsciiRendererConstants
49 | :show-inheritance:
50 |
--------------------------------------------------------------------------------
/examples/example0-minimum-viable-hardware.py:
--------------------------------------------------------------------------------
1 | import pyrtl
2 |
3 | # "pin" input/outputs
4 | a = pyrtl.Input(8, 'a')
5 | b = pyrtl.Input(8, 'b')
6 | q = pyrtl.Output(8, 'q')
7 | gt5 = pyrtl.Output(1, 'gt5')
8 |
9 | sum = a + b # makes an 8-bit adder
10 | q <<= sum # assigns output of adder to out pin
11 | gt5 <<= sum > 5 # does a comparison, assigns that to different pin
12 |
13 | # the simulation and waveform output
14 | sim = pyrtl.Simulation()
15 | sim.step_multiple({'a': [0, 1, 2, 3, 4], 'b': [2, 2, 3, 3, 4]})
16 | sim.tracer.render_trace()
17 |
--------------------------------------------------------------------------------
/examples/example1-combologic.py:
--------------------------------------------------------------------------------
1 | """ Example 1: A simple combination logic block example.
2 |
3 | This example declares a block of hardware with three one-bit inputs,
4 | (a,b,c) and two one-bit outputs (sum, cout). The logic declared is a
5 | simple one-bit adder and the definition uses some of the most common
6 | parts of PyRTL. The adder is then simulated on random data, the
7 | wave form is printed to the screen, and the resulting trace is
8 | compared to a "correct" addition. If the result is correct then a 0
9 | is returned, else 1.
10 | """
11 |
12 | import random
13 | import pyrtl
14 |
15 | # The basic idea of PyRTL is to specify the component of a some hardware block
16 | # through the declaration of wires and operations on those wires. The current
17 | # working block, an instance of a class devilishly named "Block", is implicit
18 | # in all of the below code -- it is easiest to start with the way wires work.
19 |
20 | # --- Step 1: Define Logic -------------------------------------------------
21 |
22 | # One of the most fundamental types in PyRTL is the "WireVector" which acts
23 | # very much like a Python list of 1-bit wires. Unlike a normal list, though, the
24 | # number of bits is explicitly declared.
25 | temp1 = pyrtl.WireVector(bitwidth=1, name='temp1')
26 |
27 | # Both arguments are in fact optional and default to a bitwidth of 1 and a unique
28 | # name generated by PyRTL starting with 'tmp'
29 | temp2 = pyrtl.WireVector()
30 |
31 | # Two special types of WireVectors are Input and Output, which are used to specify
32 | # an interface to the hardware block.
33 | a, b, c = pyrtl.Input(1, 'a'), pyrtl.Input(1, 'b'), pyrtl.Input(1, 'c')
34 | sum, carry_out = pyrtl.Output(1, 'sum'), pyrtl.Output(1, 'carry_out')
35 |
36 | # Okay, let's build a one-bit adder. To do this we need to use the assignment
37 | # operator, which is '<<='. This takes an already declared wire and "connects"
38 | # it to some other already declared wire. Let's start with the sum bit, which is
39 | # of course just the xor of the three inputs
40 | sum <<= a ^ b ^ c
41 |
42 | # The carry_out bit would just be "carry_out <<= a & b | a & c | b & c" but let's break
43 | # than down a bit to see what is really happening. What if we want to give names
44 | # to the partial signals in the middle of that computation? When you take
45 | # "a & b" in PyRTL, what that really means is "make an AND gate, connect one input
46 | # to 'a' and the other to 'b' and return the result of the gate". The result of
47 | # that AND gate can then be assigned to temp1 or it can be used like any other
48 | # Python variable.
49 |
50 | temp1 <<= a & b # connect the result of a & b to the pre-allocated wirevector
51 | temp2 <<= a & c
52 | temp3 = b & c # temp3 IS the result of b & c (this is the first mention of temp3)
53 | carry_out <<= temp1 | temp2 | temp3
54 |
55 | # You can access the working block through pyrtl.working_block(), and for most
56 | # things one block is all you will need. Example 2 discusses this in more detail,
57 | # but for now we can just print the block to see that in fact it looks like the
58 | # hardware we described. The format is a bit weird, but roughly translates to
59 | # a list of gates (the 'w' gates are just wires). The ins and outs of the gates
60 | # are printed 'name'/'bitwidth''WireVectorType'
61 |
62 | print('--- One Bit Adder Implementation ---')
63 | print(pyrtl.working_block())
64 | print()
65 |
66 | # --- Step 2: Simulate Design -----------------------------------------------
67 |
68 | # Okay, let's simulate our one-bit adder. To keep track of the output of
69 | # the simulation we need to make a new "SimulationTrace" and a "Simulation"
70 | # that then uses that trace.
71 |
72 | sim_trace = pyrtl.SimulationTrace()
73 | sim = pyrtl.Simulation(tracer=sim_trace)
74 |
75 | # Now all we need to do is call "sim.step" to simulate each clock cycle of our
76 | # design. We just need to pass in some input each cycle, which is a dictionary
77 | # mapping inputs (the *names* of the inputs, not the actual Input instances)
78 | # to their value for that signal each cycle. In this simple example, we
79 | # can just specify a random value of 0 or 1 with Python's random module. We
80 | # call step 15 times to simulate 15 cycles.
81 |
82 | for cycle in range(15):
83 | sim.step({
84 | 'a': random.choice([0, 1]),
85 | 'b': random.choice([0, 1]),
86 | 'c': random.choice([0, 1])
87 | })
88 |
89 | # Now all we need to do is print the trace results to the screen. Here we use
90 | # "render_trace" with some size information.
91 | print('--- One Bit Adder Simulation ---')
92 | sim_trace.render_trace(symbol_len=2)
93 |
94 | a_value = sim.inspect(a)
95 | print("The latest value of 'a' was: " + str(a_value))
96 |
97 | # --- Step 3: Verification of Simulated Design ---------------------------------------
98 |
99 | # Now finally, let's check the trace to make sure that sum and carry_out are actually
100 | # the right values when compared to Python's addition operation. Note that
101 | # all the simulation is done at this point and we are just checking the waveform,
102 | # but there is no reason you could not do this at simulation time if you had a
103 | # really long-running design.
104 |
105 | for cycle in range(15):
106 | # Note that we are doing all arithmetic on values, NOT wirevectors, here.
107 | # We can add the inputs together to get a value for the result
108 | add_result = (sim_trace.trace['a'][cycle]
109 | + sim_trace.trace['b'][cycle]
110 | + sim_trace.trace['c'][cycle])
111 | # We can select off the bits and compare
112 | python_sum = add_result & 0x1
113 | python_cout = (add_result >> 1) & 0x1
114 | if (python_sum != sim_trace.trace['sum'][cycle]
115 | or python_cout != sim_trace.trace['carry_out'][cycle]):
116 | print('This Example is Broken!!!')
117 | exit(1)
118 |
--------------------------------------------------------------------------------
/examples/example1.1-signed-numbers.py:
--------------------------------------------------------------------------------
1 | """Example 1.1: Working with signed integers.
2 |
3 | This example demonstrates:
4 | • Correct addition of signed integers with `signed_add`.
5 | • Displaying signed integers in traces with `val_to_signed_integer`.
6 |
7 | Signed integers are represented in two's complement.
8 | https://en.wikipedia.org/wiki/Two%27s_complement
9 |
10 | """
11 |
12 | import pyrtl
13 |
14 | # Let's start with unsigned addition.
15 | # ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
16 | # Add all combinations of two unsigned 2-bit inputs.
17 | a = pyrtl.Input(bitwidth=2, name='a')
18 | b = pyrtl.Input(bitwidth=2, name='b')
19 |
20 | unsigned_sum = pyrtl.Output(bitwidth=3, name='unsigned_sum')
21 | unsigned_sum <<= a + b
22 |
23 | # Try all combinations of {0, 1, 2, 3} + {0, 1, 2, 3}.
24 | unsigned_inputs = {
25 | 'a': [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3],
26 | 'b': [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3]
27 | }
28 |
29 | sim = pyrtl.Simulation()
30 | sim.step_multiple(provided_inputs=unsigned_inputs)
31 | # In this trace, `unsigned_sum` is the sum of all combinations of
32 | # {0, 1, 2, 3} + {0, 1, 2, 3}. For example:
33 | # • cycle 0 shows 0 + 0 = 0
34 | # • cycle 1 shows 0 + 1 = 1
35 | # • cycle 15 shows 3 + 3 = 6
36 | print('Unsigned addition. Each cycle adds a different combination of '
37 | 'numbers.\nunsigned_sum == a + b')
38 | sim.tracer.render_trace(repr_func=int)
39 |
40 | # Re-interpreting `a`, `b`, and `unsigned_sum` as signed is incorrect.
41 | # ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
42 | # Use `val_to_signed_integer` to re-interpret the previous simulation results
43 | # as signed integers. But the results are INCORRECT, because `unsigned_sum`
44 | # performed unsigned addition with `+`. For example:
45 | #
46 | # • cycle 2 shows 0 + -2 = 2
47 | # • cycle 13 shows -1 + 1 = -4
48 | #
49 | # `unsigned_sum` is incorrect because PyRTL must extend `a` and `b` to the
50 | # sum's bitwidth before adding, but PyRTL zero-extends by default, instead of
51 | # sign-extending. Zero-extending is correct when `a` and `b` are unsigned, but
52 | # sign-extending is correct when `a` and `b` are signed. Use `signed_add` to
53 | # sign-extend `a` and `b`, as demonstrated next.
54 | print('\nUse `val_to_signed_integer` to re-interpret the previous simulation '
55 | 'results as\nsigned integers. But re-interpreting the previous trace as '
56 | 'signed integers \nproduces INCORRECT RESULTS!')
57 | sim.tracer.render_trace(repr_func=pyrtl.val_to_signed_integer)
58 |
59 | # Use `signed_add` to correctly add signed integers.
60 | # ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
61 | # `signed_add` works by sign-extending its inputs to the sum's bitwidth before
62 | # adding. There are many `signed_*` functions for signed operations, like
63 | # `signed_sub`, `signed_mul`, `signed_lt`, and so on.
64 | pyrtl.reset_working_block()
65 |
66 | a = pyrtl.Input(bitwidth=2, name='a')
67 | b = pyrtl.Input(bitwidth=2, name='b')
68 |
69 | signed_sum = pyrtl.Output(bitwidth=3, name='signed_sum')
70 | signed_sum <<= pyrtl.signed_add(a, b)
71 |
72 | # Try all combinations of {-2, -1, 0, 1} + {-2, -1, 0, 1}.
73 | signed_inputs = {
74 | 'a': [-2, -2, -2, -2, -1, -1, -1, -1, 0, 0, 0, 0, 1, 1, 1, 1],
75 | 'b': [-2, -1, 0, 1, -2, -1, 0, 1, -2, -1, 0, 1, -2, -1, 0, 1]
76 | }
77 | sim = pyrtl.Simulation()
78 | sim.step_multiple(provided_inputs=signed_inputs)
79 |
80 | # In this trace, `signed_sum` is the sum of all combinations of
81 | # {-2, -1, 0, 1} + {-2, -1, 0, 1}. For example:
82 | # • cycle 0 shows -2 + -2 = -4
83 | # • cycle 1 shows -2 + -1 = -3
84 | # • cycle 15 shows 1 + 1 = 2
85 | print('\nReset the simulation and use `signed_add` to correctly add signed '
86 | 'integers.\nsigned_sum == signed_add(a, b)')
87 | sim.tracer.render_trace(repr_func=pyrtl.val_to_signed_integer)
88 |
89 | # Manually sign-extend inputs to correctly add signed integers with `+`.
90 | # ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
91 | # Instead of using `signed_add`, we can manually sign-extend the inputs and
92 | # truncate the output to correctly add signed integers with `+`. Use this trick
93 | # to implement other signed arithmetic operations.
94 | pyrtl.reset_working_block()
95 |
96 | a = pyrtl.Input(bitwidth=2, name='a')
97 | b = pyrtl.Input(bitwidth=2, name='b')
98 |
99 | # Using `+` produces the correct result for signed addition here because we
100 | # manually extend `a` and `b` to 3 bits. The result of `a.sign_extended(3) +
101 | # b.sign_extended(3)` is now 4 bits, but we truncate it to 3 bits. This
102 | # truncation is subtle, but important! The addition's full 4 bit result would
103 | # not be correct.
104 | sign_extended_sum = pyrtl.Output(bitwidth=3, name='sign_extended_sum')
105 | extended_a = a.sign_extended(bitwidth=3)
106 | extended_b = b.sign_extended(bitwidth=3)
107 | sign_extended_sum <<= (extended_a + extended_b).truncate(bitwidth=3)
108 |
109 | # Try all combinations of {-2, -1, 0, 1} + {-2, -1, 0, 1}.
110 | signed_inputs = {
111 | 'a': [-2, -2, -2, -2, -1, -1, -1, -1, 0, 0, 0, 0, 1, 1, 1, 1],
112 | 'b': [-2, -1, 0, 1, -2, -1, 0, 1, -2, -1, 0, 1, -2, -1, 0, 1]
113 | }
114 | sim = pyrtl.Simulation()
115 | sim.step_multiple(provided_inputs=signed_inputs)
116 |
117 | print('\nInstead of using `signed_add`, we can also manually sign extend the '
118 | 'inputs to\ncorrectly add signed integers with `+`.\n'
119 | 'sign_extended_sum == (a.sign_extended(3) + b.sign_extended(3))'
120 | '.truncate(3)')
121 | sim.tracer.render_trace(repr_func=pyrtl.val_to_signed_integer)
122 |
--------------------------------------------------------------------------------
/examples/example1.2-wire-struct.py:
--------------------------------------------------------------------------------
1 | """Example 1.2: wire_struct and wire_matrix.
2 |
3 | This example demonstrates named slicing with wire_struct and wire_matrix:
4 | • wire_struct: Named WireVector slices. Names are alphanumeric, like a Python
5 | namedtuple.
6 | • wire_matrix: Indexed WireVector slices. Indices are integers, like a Python list.
7 |
8 | """
9 |
10 | import inspect
11 | import pyrtl
12 |
13 | # Motivation
14 | # ▔▔▔▔▔▔▔▔▔▔
15 | # wire_struct and wire_matrix are syntactic sugar. They make PyRTL code easier to read
16 | # and write, but they do not introduce any new functionality. We start with a motivating
17 | # example that does not use wire_struct or wire_matrix, point out some common problems,
18 | # then rewrite the example with wire_struct to address those problems.
19 | #
20 | # This example deals with 24-bit pixel data. These 24 bits are composed of three
21 | # concatenated components:
22 | #
23 | # pixel:
24 | # ┌───────┬───────┬───────┐
25 | # │ red │ green │ blue │
26 | # └┬─────┬┴┬─────┬┴┬─────┬┘
27 | # 23 16 15 8 7 0
28 | #
29 | # In other words:
30 | # • The `red` component is in pixel[16:24]
31 | # • The `green` component is in pixel[8:16]
32 | # • And the `blue` component is in pixel[0:8]
33 |
34 | # Accept a 24-bit pixel as a PyRTL Input:
35 | pixel_in = pyrtl.Input(name="pixel_in", bitwidth=24)
36 |
37 | # Split the pixel data into `red`, `green`, and `blue` components. The indices are
38 | # manually calculated and hardcoded below. These calculations are prone to off-by-one
39 | # errors. If we wanted to change the pixel data format (for example, adding an 8-bit
40 | # `alpha` channel), we may need to update all these indices.
41 | red_in = pixel_in[16:24]
42 | red_in.name = "red_in"
43 | green_in = pixel_in[8:16]
44 | green_in.name = "green_in"
45 | blue_in = pixel_in[0:8]
46 | blue_in.name = "blue_in"
47 |
48 | # Do some calculations on the individual components:
49 | red_out = red_in + 0x11
50 | green_out = green_in + 0x22
51 | blue_out = blue_in + 0x33
52 |
53 | # red_out, green_out, and blue_out are 9 bits wide, so they must be truncated back down
54 | # to 8 bits before concatenating.
55 | assert red_out.bitwidth == 9
56 | assert green_out.bitwidth == 9
57 | assert blue_out.bitwidth == 9
58 |
59 | # Now concatenate the processed components into a new output pixel. The components must
60 | # be specified in the correct order, and we must truncate them to the correct bitwidth.
61 | pixel_out = pyrtl.concat(
62 | red_out.truncate(8), green_out.truncate(8), blue_out.truncate(8)
63 | )
64 | assert pixel_out.bitwidth == 24
65 | pixel_out.name = "pixel_out"
66 |
67 | # Test this slicing and concatenation logic.
68 | print("Pixel slicing and concatenation without wire_struct:")
69 | sim = pyrtl.Simulation()
70 | sim.step_multiple(provided_inputs={"pixel_in": [0x112233, 0xAABBCC]})
71 | sim.tracer.render_trace(
72 | trace_list=["pixel_in", "red_in", "green_in", "blue_in", "pixel_out"]
73 | )
74 |
75 | # Introducing wire_struct
76 | # ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
77 | # Start over, and rebuild the example with wire_struct.
78 | pyrtl.reset_working_block()
79 |
80 |
81 | # This Pixel class is the single source of truth that specifies how the `red`, `green`,
82 | # and `blue` components are packed into 24 bits. A Pixel instance can do one of two
83 | # things:
84 | # (1). Slice a 24-bit value into `red`, `green`, and `blue` components, OR
85 | # (2). Pack `red`, `green`, and `blue` components into a 24-bit value.
86 | #
87 | # Because this definition is the single source of truth, we can change the pixel data
88 | # format just by changing this class definition. For example, try making the `red`
89 | # component the last component, instead of the first component.
90 | @pyrtl.wire_struct
91 | class Pixel:
92 | # The most significant bits are specified first, so the "red" component is the 8
93 | # most significant bits, indices 16..23
94 | red: 8
95 | # The "green" component is the 8 middle bits, indices 8..15
96 | green: 8
97 | # The "blue" component is the 8 least significant bits, indices 0..7
98 | blue: 8
99 |
100 |
101 | # pixel_in is a Pixel constructed from a 24-bit Input, option (1) above. The 24-bit
102 | # input will be sliced into 8-bit components, and those components can be manipulated as
103 | # pixel_in.red, pixel_in.green, and pixel_in.blue in the Python code below.
104 | #
105 | # Because this Pixel has a name, the 8-bit components will be assigned the names
106 | # "pixel_in.red", "pixel_in.green", and "pixel_in.blue". These component names are
107 | # included in the `trace_list` below.
108 | pixel_in = Pixel(name="pixel_in", concatenated_type=pyrtl.Input)
109 | assert pixel_in.bitwidth == 24
110 | assert pixel_in.red.bitwidth == 8
111 | assert pixel_in.green.bitwidth == 8
112 | assert pixel_in.blue.bitwidth == 8
113 |
114 | # Repeat our calculations with the named components. Components are accessed with "dot"
115 | # notation (.green), like a Python namedtuple. There are no more hardcoded indices like
116 | # [8:16] in this example.
117 | red_out = pixel_in.red + 0x11
118 | green_out = pixel_in.green + 0x22
119 | blue_out = pixel_in.blue + 0x33
120 |
121 | # pixel_out is a Pixel constructed from components, option (2) above. Components with
122 | # more than 8 bits are implicitly truncated to 8 bits.
123 | #
124 | # Python keyword arguments are used for `red`, `green`, and `blue` here, so the order
125 | # does not matter. We arbitrarily provide the value of the `green` component first.
126 | pixel_out = Pixel(name="pixel_out", green=green_out, red=red_out, blue=blue_out)
127 | assert pixel_out.bitwidth == 24
128 |
129 | # Test this new version of our slicing and concatenation logic.
130 | print("\nPixel slicing and concatenation with wire_struct:")
131 | sim = pyrtl.Simulation()
132 | sim.step_multiple(provided_inputs={"pixel_in": [0x112233, 0xAABBCC]})
133 | sim.tracer.render_trace(
134 | trace_list=[
135 | "pixel_in",
136 | "pixel_in.red",
137 | "pixel_in.green",
138 | "pixel_in.blue",
139 | "pixel_out",
140 | ]
141 | )
142 |
143 | # Introducing wire_matrix
144 | # ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
145 | # wire_matrix is like wire_struct, except the components are indexed by integers, like
146 | # an array.
147 |
148 | # Start over.
149 | pyrtl.reset_working_block()
150 |
151 | # PixelPair is a pair of two Pixels. This also shows how wire_matrix and wire_struct
152 | # work together - PixelPair is a wire_struct nested in a wire_matrix.
153 | PixelPair = pyrtl.wire_matrix(component_schema=Pixel, size=2)
154 |
155 | # wire_matrix() returns a class!
156 | assert inspect.isclass(PixelPair)
157 |
158 | # Create a PixelPair that decomposes a 48-bit input into two 24-bit Pixels. Each 24-bit
159 | # Pixel can be further decomposed into 8-bit components. This PixelPair has been
160 | # assigned a name, so all its components are named, as seen in the simulation trace
161 | # below.
162 | pixel_pair_in = PixelPair(name="pixel_pair_in", concatenated_type=pyrtl.Input)
163 | assert pixel_pair_in.bitwidth == 48
164 | assert pixel_pair_in[0].bitwidth == 24
165 | assert pixel_pair_in[0].red.bitwidth == 8
166 |
167 | # Do some calculations on the Pixel components. Components are accessed with
168 | # square-bracket notation ([1]), like a Python list.
169 | pixel_pair_out_values = [
170 | Pixel(
171 | red=pixel_pair_in[0].red + 0x10,
172 | green=pixel_pair_in[0].green,
173 | blue=pixel_pair_in[0].blue,
174 | ),
175 | pixel_pair_in[1] + 1,
176 | ]
177 |
178 | # Pack the new component values into a new PixelPair.
179 | pixel_pair_out = PixelPair(name="pixel_pair_out", values=pixel_pair_out_values)
180 |
181 | # Test out PixelPair.
182 | print("\nPixelPair slicing and concatenating with wire_matrix:")
183 | sim = pyrtl.Simulation()
184 | sim.step_multiple(provided_inputs={"pixel_pair_in": [0x112233AABBCC, 0x445566778899]})
185 | sim.tracer.render_trace(
186 | trace_list=[
187 | "pixel_pair_in",
188 | "pixel_pair_in[0]",
189 | "pixel_pair_in[0].red",
190 | "pixel_pair_in[0].green",
191 | "pixel_pair_in[0].blue",
192 | "pixel_pair_in[1]",
193 | "pixel_pair_in[1].red",
194 | "pixel_pair_in[1].green",
195 | "pixel_pair_in[1].blue",
196 | "pixel_pair_out",
197 | ]
198 | )
199 |
200 | # These examples hopefully show how wire_matrix and wire_struct result in code that's
201 | # easier to write correctly and easier to read.
202 | #
203 | # wire_struct and wire_matrix have many more features, but this example should
204 | # demonstrate the main ideas. See the documentation for more details:
205 | #
206 | # https://pyrtl.readthedocs.io/en/latest/helpers.html#pyrtl.helperfuncs.wire_struct
207 | # https://pyrtl.readthedocs.io/en/latest/helpers.html#pyrtl.helperfuncs.wire_matrix
208 |
--------------------------------------------------------------------------------
/examples/example2-counter.py:
--------------------------------------------------------------------------------
1 | """ Example 2: A Counter with Ripple Carry Adder.
2 |
3 | This next example shows how you make stateful things with registers
4 | and more complex hardware structures with functions. We generate
5 | a 3-bit ripple carry adder building off of the 1-bit adder from
6 | the prior example, and then hook it to a register to count up modulo 8.
7 | """
8 |
9 | import pyrtl
10 |
11 |
12 | # Let's just dive right in.
13 |
14 | def one_bit_add(a, b, carry_in):
15 | assert len(a) == len(b) == 1 # len returns the bitwidth
16 | sum = a ^ b ^ carry_in
17 | carry_out = a & b | a & carry_in | b & carry_in
18 | return sum, carry_out
19 |
20 | # A function in PyRTL is nothing special -- it just so happens that the statements
21 | # it encapsulate tell PyRTL to build some hardware. If we call "one_bit_add"
22 | # above with the arguments "x", "y", and "z", it will make a one-bit adder to add
23 | # those values together and return the wires for sum and carry_out as applied to "x",
24 | # "y", and "z". If I call it again on "i", "j", and "k" it will build a new one-bit
25 | # adder for those inputs and return the resulting sum and carry_out for that adder.
26 |
27 |
28 | # While PyRTL actually provides an "+" operator for WireVectors which generates
29 | # adders, a ripple carry adder is something people can understand easily but has
30 | # enough structure to be mildly interesting. Let's define an adder of arbitrary
31 | # length recursively and (hopefully) Pythonically. More comments after the code.
32 |
33 | def ripple_add(a, b, carry_in=0):
34 | a, b = pyrtl.match_bitwidth(a, b)
35 | # This function is a function that allows us to match the bitwidth of multiple
36 | # different wires. By default, it zero extends the shorter bits
37 | if len(a) == 1:
38 | sumbits, carry_out = one_bit_add(a, b, carry_in)
39 | else:
40 | lsbit, ripplecarry = one_bit_add(a[0], b[0], carry_in)
41 | msbits, carry_out = ripple_add(a[1:], b[1:], ripplecarry)
42 | sumbits = pyrtl.concat(msbits, lsbit)
43 | return sumbits, carry_out
44 |
45 | # The above code breaks down into two cases. 1) If the size of the inputs
46 | # is one-bit just do one_bit_add. 2) If they are more than one bit, do
47 | # a one-bit add on the least significant bits, a ripple carry on the rest,
48 | # and then stick the results back together into one WireVector. A couple
49 | # interesting features of PyRTL can be seen here: WireVectors can be indexed
50 | # like lists, with [0] accessing the least significant bit and [1:] being an
51 | # example of the use of Python slicing syntax. While you can add two lists
52 | # together in Python, a WireVector + Wirevector means "make an adder", so to
53 | # concatenate the bits of two vectors one needs to use "concat". Finally,
54 | # if we look at "carry_in" it seems to have a default value of the integer "0" but
55 | # is a WireVector at other times. Python supports polymorphism throughout
56 | # and PyRTL will cast integers and some other types to WireVectors when it can.
57 |
58 |
59 | # Now let's build a 3-bit counter from our N-bit ripple carry adder.
60 |
61 | counter = pyrtl.Register(bitwidth=3, name='counter')
62 | sum, carry_out = ripple_add(counter, pyrtl.Const("1'b1"))
63 | counter.next <<= sum
64 |
65 | # A couple new things in the above code. The two remaining types of basic
66 | # WireVectors, Const and Register, both appear. Const, unsurprisingly, is just for
67 | # holding constants (such as the 0 in ripple_add), but here we create one directly
68 | # from a Verilog-like string which includes both the value and the bitwidth.
69 | # Registers are just like wires, except their updates are delayed to the next
70 | # clock cycle. This is made explicit in the syntax through the property '.next'
71 | # which should always be set for registers. In this simple example, we make the
72 | # counter's value on the next cycle equal to the counter's value this cycle plus one.
73 |
74 | # Now let's run the bugger. No need for inputs, as it doesn't have any.
75 | # Finally we'll print the trace to the screen and check that it counts up correctly.
76 |
77 | sim_trace = pyrtl.SimulationTrace()
78 | sim = pyrtl.Simulation(tracer=sim_trace)
79 | for cycle in range(15):
80 | sim.step()
81 | assert sim.value[counter] == cycle % 8
82 | sim_trace.render_trace()
83 |
--------------------------------------------------------------------------------
/examples/example3-statemachine.py:
--------------------------------------------------------------------------------
1 | """Example 3: A State Machine built with conditional_assignment
2 |
3 | In this example we describe how conditional_assignment works in the context
4 | of a vending machine that will dispense an item when it has received 4
5 | tokens. If a refund is requested, it returns the tokens.
6 |
7 | """
8 |
9 | import enum
10 | import pyrtl
11 |
12 | token_in = pyrtl.Input(1, 'token_in')
13 | req_refund = pyrtl.Input(1, 'req_refund')
14 | dispense = pyrtl.Output(1, 'dispense')
15 | refund = pyrtl.Output(1, 'refund')
16 | state = pyrtl.Register(3, 'state')
17 |
18 |
19 | # First new step, let's enumerate a set of constants to serve as our states
20 | class State(enum.IntEnum):
21 | WAIT = 0 # Waiting for first token.
22 | TOK1 = 1 # Received first token, waiting for second token.
23 | TOK2 = 2 # Received second token, waiting for third token.
24 | TOK3 = 3 # Received third token, waiting for fourth token.
25 | DISP = 4 # Received fourth token, dispense item.
26 | RFND = 5 # Issue refund.
27 |
28 |
29 | # Now we could build a state machine using just the registers and logic
30 | # discussed in the earlier examples, but doing operations *conditionally* on
31 | # some input is a pretty fundamental operation in hardware design. PyRTL
32 | # provides an instance called "conditional_assignment" to provide a predicated
33 | # update to a registers, wires, and memories.
34 | #
35 | # Conditional assignments are specified with a "|=" instead of a "<<="
36 | # operator. The conditional assignment is only valid in the context of a
37 | # condition, and updates to those values only happens when that condition is
38 | # true. In hardware this is implemented with a simple mux -- for people coming
39 | # from software it is important to remember that this is describing a big logic
40 | # function, **NOT** an "if-then-else" clause. All of these things will execute
41 | # straight through when "build_everything" is called. More comments after the
42 | # code.
43 | #
44 | # One more thing: conditional_assignment might not always be the best solution.
45 | # If the update is simple, a regular 'mux(sel_wire, falsecase=f_wire,
46 | # truecase=t_wire)' can be sufficient.
47 | with pyrtl.conditional_assignment:
48 | with req_refund: # signal of highest precedence
49 | state.next |= State.RFND
50 | with token_in: # if token received, advance state in counter sequence
51 | with state == State.WAIT:
52 | state.next |= State.TOK1
53 | with state == State.TOK1:
54 | state.next |= State.TOK2
55 | with state == State.TOK2:
56 | state.next |= State.TOK3
57 | with state == State.TOK3:
58 | state.next |= State.DISP # 4th token received, go to dispense
59 | with pyrtl.otherwise: # token received in unsupported state
60 | state.next |= State.RFND
61 | # unconditional transition from these two states back to wait state
62 |
63 | # NOTE: the parens are needed because in Python the "|" operator is lower
64 | # precedence than the "==" operator!
65 | with (state == State.DISP) | (state == State.RFND):
66 | state.next |= State.WAIT
67 |
68 | dispense <<= state == State.DISP
69 | refund <<= state == State.RFND
70 |
71 | # A few more notes:
72 | #
73 | # 1) A condition can be nested within another condition and the implied
74 | # hardware is that the left-hand-side should only get that value if ALL of
75 | # the encompassing conditions are satisfied.
76 | # 2) Only one conditional at each level can be true meaning that all conditions
77 | # are implicitly also saying that none of the prior conditions at the same
78 | # level also have been true. The highest priority condition is listed first,
79 | # and in a sense you can think about each other condition as an "elif".
80 | # 3) If not every condition is enumerated, the default value for the register
81 | # under those cases will be the same as it was the prior cycle ("state.next
82 | # |= state" in this example). The default for a wirevector is 0.
83 | # 4) There is a way to specify something like an "else" instead of "elif" and
84 | # that is with an "otherwise" (as seen on the line above "state.next <<=
85 | # State.RFND"). This condition will be true if none of the other conditions
86 | # at the same level were also true (for this example specifically,
87 | # state.next will get RFND when req_refund==0, token_in==1, and state is not
88 | # in TOK1, TOK2, TOK3, or DISP.
89 | # 5) Not shown here, but you can update multiple different registers, wires,
90 | # and memories all under the same set of conditionals.
91 |
92 | # A more artificial example might make it even more clear how these rules interact:
93 | # with a:
94 | # r.next |= 1 <-- when a is true
95 | # with d:
96 | # r2.next |= 2 <-- when a and d are true
97 | # with otherwise:
98 | # r2.next |= 3 <-- when a is true and d is false
99 | # with b == c:
100 | # r.next |= 0 <-- when a is not true and b==c is true
101 |
102 | # Now let's build and test our state machine.
103 |
104 | sim_trace = pyrtl.SimulationTrace()
105 | sim = pyrtl.Simulation(tracer=sim_trace)
106 |
107 | # Rather than just give some random inputs, let's specify some specific 1-bit
108 | # values. To make it easier to simulate it over several steps, we'll use
109 | # sim.step_multiple, which takes in a dictionary mapping each input to its
110 | # value on each step.
111 |
112 | sim_inputs = {
113 | 'token_in': '0010100111010000',
114 | 'req_refund': '1100010000000000'
115 | }
116 |
117 | sim.step_multiple(sim_inputs)
118 |
119 | # Also, to make our input/output easy to reason about let's specify an order to
120 | # the traces. We also use `enum_name` to display the state names (WAIT, TOK1,
121 | # ...) rather than their numbers (0, 1, ...).
122 | sim_trace.render_trace(
123 | trace_list=['token_in', 'req_refund', 'state', 'dispense', 'refund'],
124 | repr_per_name={'state': pyrtl.enum_name(State)})
125 |
126 | # Finally, suppose you want to simulate your design and verify its output
127 | # matches your expectations. sim.step_multiple also accepts as a second
128 | # argument a dictionary mapping output wires to their expected value on each
129 | # step. If during the simulation the actual and expected values differ, it will
130 | # be reported to you! This might be useful if you have a working design which,
131 | # after some tweaks, you'd like to test for functional equivalence, or as a
132 | # basic sanity check.
133 |
134 | sim_outputs = {
135 | 'dispense': '0000000000001000',
136 | 'refund': '0111001000000000'
137 | }
138 |
139 | # Note that you don't need to explicitly supply a tracer to Simulation(); it
140 | # will create one internally for you if needed.
141 | sim = pyrtl.Simulation()
142 |
143 | sim.step_multiple(sim_inputs, sim_outputs)
144 |
--------------------------------------------------------------------------------
/examples/example4-debuggingtools.py:
--------------------------------------------------------------------------------
1 | """ Example 4: Debugging
2 |
3 | Debugging is half the coding process in software, and in PyRTL, it's no
4 | different. PyRTL provides some additional challenges when it comes to
5 | debugging, as a problem may surface long after the error was made. Fortunately,
6 | PyRTL comes with various features to help you find mistakes.
7 | """
8 |
9 | import random
10 | import io
11 | from pyrtl.rtllib import adders, multipliers
12 | import pyrtl
13 |
14 | random.seed(93729473) # used to make random calls deterministic for this example
15 |
16 |
17 | # This example covers debugging strategies for PyRTL. For general Python debugging,
18 | # we recommend healthy use of the "assert" statement, and use of "pdb" for
19 | # tracking down bugs. However, PyRTL introduces some new complexities because
20 | # the place where functionality is defined (when you construct and operate
21 | # on PyRTL classes) is separate in time from where that functionality is executed
22 | # (i.e. during simulation). Thus, sometimes it hard to track down where a wire
23 | # might have come from, or what exactly it is doing.
24 |
25 | # In this example specifically, we will be building a circuit that adds up three values.
26 | # However, instead of building an add function ourselves or using the
27 | # built-in "+" function in PyRTL, we will instead use the Kogge-Stone adders
28 | # in RtlLib, the standard library for PyRTL.
29 |
30 | # Building three inputs
31 | in1, in2, in3 = (pyrtl.Input(8, "in" + str(x)) for x in range(1, 4))
32 | out = pyrtl.Output(10, "out")
33 |
34 | add1_out = adders.kogge_stone(in1, in2)
35 | add2_out = adders.kogge_stone(add1_out, in2)
36 | out <<= add2_out
37 |
38 | # The most basic way of debugging PyRTL is to connect a value to an output wire
39 | # and use the simulation to trace the output. A simple "print" statement doesn't work
40 | # because the values in the wires are not populated during *creation* time
41 |
42 | # If we want to check the result of the first addition, we can connect an output wire
43 | # to the result wire of the first adder
44 |
45 | debug_out = pyrtl.Output(9, "debug_out")
46 | debug_out <<= add1_out
47 |
48 | # Now simulate the circuit. Let's create some random inputs to feed our adder.
49 |
50 | vals1 = [int(2**random.uniform(1, 8) - 2) for _ in range(20)]
51 | vals2 = [int(2**random.uniform(1, 8) - 2) for _ in range(20)]
52 | vals3 = [int(2**random.uniform(1, 8) - 2) for _ in range(20)]
53 |
54 | sim_trace = pyrtl.SimulationTrace()
55 | sim = pyrtl.Simulation(tracer=sim_trace)
56 | sim.step_multiple({
57 | 'in1': vals1,
58 | 'in2': vals2,
59 | 'in3': vals3
60 | })
61 |
62 | # In order to get the result data, you do not need to print a waveform of the trace.
63 | # You always have the option to just pull the data out of the tracer directly
64 | print("---- Inputs and debug_out ----")
65 | print("in1: ", str(sim_trace.trace['in1']))
66 | print("in2: ", str(sim_trace.trace['in2']))
67 | print("debug_out: ", str(sim_trace.trace['debug_out']))
68 | print('\n')
69 |
70 | # Below, I am using the ability to directly retrieve the trace data to
71 | # verify the correctness of the first adder
72 |
73 | for i in range(len(vals1)):
74 | assert sim_trace.trace['debug_out'][i] == sim_trace.trace['in1'][i] + sim_trace.trace['in2'][i]
75 |
76 |
77 | # --- Probe ----
78 |
79 | # Now that we have built some stuff, let's clear it so we can try again in a
80 | # different way. We can start by clearing all of the hardware from the current working
81 | # block. The working block is a global structure that keeps track of all the
82 | # hardware you have built thus far. A "reset" will clear it so we can start fresh.
83 | pyrtl.reset_working_block()
84 | print("---- Using Probes ----")
85 |
86 | # In this example, we will be multiplying two numbers using tree_multiplier()
87 | # Again, create the two inputs and an output
88 | in1, in2 = (pyrtl.Input(8, "in" + str(x)) for x in range(1, 3))
89 | out1, out2 = (pyrtl.Output(8, "out" + str(x)) for x in range(1, 3))
90 |
91 | multout = multipliers.tree_multiplier(in1, in2)
92 |
93 | # The following line will create a probe named 'std_probe" for later use, like an output.
94 | pyrtl.probe(multout, 'std_probe')
95 |
96 | # We could also do the same thing during assignment. The next command will
97 | # create a probe (named 'stdout_probe') that refers to multout (returns the wire multout).
98 | # This achieves virtually the same thing as 4 lines above, but it is done during assignment,
99 | # so we skip a step by probing the wire before the multiplication.
100 | # The probe returns multout, the original wire, and out will be assigned multout * 2
101 | out1 <<= pyrtl.probe(multout, 'stdout_probe') * 2
102 |
103 | # probe can also be used with other operations like this:
104 | pyrtl.probe(multout + 32, 'adder_probe')
105 |
106 | # or this:
107 | pyrtl.probe(multout[2:7], 'select_probe')
108 |
109 | # or, similarly:
110 | # (this will create a probe of multout while passing multout[2:16] to out2)
111 | out2 <<= pyrtl.probe(multout)[2:16] # notice probe names are not absolutely necessary
112 |
113 | # As one can see, probe can be used on any wire any time,
114 | # such as before or during its operation, assignment, etc.
115 |
116 | # Now on to the simulation...
117 | # For variation, we'll recreate the random inputs:
118 | vals1 = [int(2**random.uniform(1, 8) - 2) for _ in range(10)]
119 | vals2 = [int(2**random.uniform(1, 8) - 2) for _ in range(10)]
120 |
121 | sim_trace = pyrtl.SimulationTrace()
122 | sim = pyrtl.Simulation(tracer=sim_trace)
123 | sim.step_multiple({
124 | 'in1': vals1,
125 | 'in2': vals2,
126 | })
127 |
128 | # Now we will show the values of the inputs and probes
129 | # and look at that, we didn't need to make any outputs!
130 | # (although we did, to demonstrate the power and convenience of probes)
131 | sim_trace.render_trace()
132 | sim_trace.print_trace()
133 |
134 | print("--- Probe w/ debugging: ---")
135 | # Say we wanted to have gotten more information about
136 | # one of those probes above at declaration.
137 | # We could have used pyrtl.set_debug_mode() before their creation, like so:
138 | pyrtl.set_debug_mode()
139 | pyrtl.probe(multout - 16, 'debugsubtr_probe')
140 | pyrtl.set_debug_mode(debug=False)
141 |
142 |
143 | # ---- WireVector Stack Trace ----
144 |
145 | # Another case that might arise is that a certain wire is causing an error to occur
146 | # in your program. WireVector Stack Traces allow you to find out more about where a particular
147 | # WireVector was made in your code. With this enabled the WireVector will
148 | # store exactly were it was created, which should help with issues where
149 | # there is a problem with an identified wire.
150 |
151 | # Like above, just add the following line before the relevant WireVector
152 | # might be made or at the beginning of the program.
153 |
154 | pyrtl.set_debug_mode()
155 |
156 | print("---- Stack Trace ----")
157 | test_out = pyrtl.Output(9, "test_out")
158 | test_out <<= adders.kogge_stone(in1, in2)
159 |
160 | # Now to retrieve information
161 | wire_trace = test_out.init_call_stack
162 |
163 | # This data is generated using the traceback.format_stack() call from the Python
164 | # standard library's Traceback module (look at the Python standard library docs for
165 | # details on the function). Therefore, the stack traces are stored as a list with the
166 | # outermost call first.
167 |
168 | for frame in wire_trace:
169 | print(frame)
170 |
171 | # Storage of Additional Debug Data
172 |
173 | # ------------------------------------
174 | # WARNING: the debug information generated by the following two processes are
175 | # not guaranteed to be preserved when functions (eg. pyrtl.synthesize() ) are
176 | # done over the block.
177 | # ------------------------------------
178 |
179 | # However, if the stack trace does not give you enough information about the
180 | # WireVector, you can also embed additional information into the wire itself
181 | # Two ways of doing so is either through manipulating the name of the
182 | # WireVector, or by adding your own custom metadata to the WireVector.
183 |
184 |
185 | # So far, each input and output WireVector have been given their own names, but
186 | # normal WireVectors can also be given names by supplying the name argument to
187 | # the constructor
188 |
189 | dummy_wv = pyrtl.WireVector(1, name="blah")
190 |
191 | # Also, because of the flexible nature of Python, you can also add custom
192 | # properties to the WireVector.
193 |
194 | dummy_wv.my_custom_property_name = "John Clow is great"
195 | dummy_wv.custom_value_028493 = 13
196 |
197 | # removing the WireVector from the block to prevent problems with the rest of
198 | # this example
199 | pyrtl.working_block().remove_wirevector(dummy_wv)
200 |
201 | # ---- Trivial Graph Format ----
202 |
203 | # Finally, there is a handy way to view your hardware creations as a graph.
204 | # The function output_to_trivialgraph will render your hardware in a format that
205 | # you can then open with the free software "yEd"
206 | # (http://en.wikipedia.org/wiki/YEd). There are options under the
207 | # "hierarchical" rendering to draw something that looks quite like a circuit.
208 |
209 | pyrtl.working_block().sanity_check()
210 | pyrtl.passes._remove_unused_wires(pyrtl.working_block()) # so that trivial_graph() will work
211 |
212 | print("--- Trivial Graph Format ---")
213 | with io.StringIO() as tgf:
214 | pyrtl.output_to_trivialgraph(tgf)
215 | print(tgf.getvalue())
216 |
--------------------------------------------------------------------------------
/examples/example5-introspection.py:
--------------------------------------------------------------------------------
1 | """ Example 5: Making use of PyRTL and Introspection. """
2 |
3 | import pyrtl
4 |
5 |
6 | # The following example shows how PyRTL can be used to make some interesting
7 | # hardware structures using Python introspection. In particular, this example
8 | # makes a N-stage pipeline structure. Any specific pipeline is then a derived
9 | # class of SimplePipeline where methods with names starting with "stage" are
10 | # stages, and new members with names not starting with "_" are to be registered
11 | # for the next stage.
12 |
13 | class SimplePipeline(object):
14 | """ Pipeline builder with auto generation of pipeline registers. """
15 |
16 | def __init__(self):
17 | self._pipeline_register_map = {}
18 | self._current_stage_num = 0
19 | stage_list = [method for method in dir(self) if method.startswith('stage')]
20 | for stage in sorted(stage_list):
21 | stage_method = getattr(self, stage)
22 | stage_method()
23 | self._current_stage_num += 1
24 |
25 | def __getattr__(self, name):
26 | try:
27 | return self._pipeline_register_map[self._current_stage_num][name]
28 | except KeyError:
29 | raise pyrtl.PyrtlError(
30 | 'error, no pipeline register "%s" defined for stage %d'
31 | % (name, self._current_stage_num))
32 |
33 | def __setattr__(self, name, value):
34 | if name.startswith('_'):
35 | # do not do anything tricky with variables starting with '_'
36 | object.__setattr__(self, name, value)
37 | else:
38 | next_stage = self._current_stage_num + 1
39 | pipereg_id = str(self._current_stage_num) + 'to' + str(next_stage)
40 | rname = 'pipereg_' + pipereg_id + '_' + name
41 | new_pipereg = pyrtl.Register(bitwidth=len(value), name=rname)
42 | if next_stage not in self._pipeline_register_map:
43 | self._pipeline_register_map[next_stage] = {}
44 | self._pipeline_register_map[next_stage][name] = new_pipereg
45 | new_pipereg.next <<= value
46 |
47 |
48 | class SimplePipelineExample(SimplePipeline):
49 | """ A very simple pipeline to show how registers are inferred. """
50 |
51 | def __init__(self):
52 | self._loopback = pyrtl.WireVector(1, 'loopback')
53 | super(SimplePipelineExample, self).__init__()
54 |
55 | def stage0(self):
56 | self.n = ~ self._loopback
57 |
58 | def stage1(self):
59 | self.n = self.n
60 |
61 | def stage2(self):
62 | self.n = self.n
63 |
64 | def stage3(self):
65 | self.n = self.n
66 |
67 | def stage4(self):
68 | self._loopback <<= self.n
69 |
70 |
71 | simplepipeline = SimplePipelineExample()
72 | print(pyrtl.working_block())
73 | # Simulation of the core
74 | sim_trace = pyrtl.SimulationTrace()
75 | sim = pyrtl.Simulation(tracer=sim_trace)
76 | sim.step_multiple({}, nsteps=15)
77 | sim_trace.render_trace()
78 |
--------------------------------------------------------------------------------
/examples/example6-memory.py:
--------------------------------------------------------------------------------
1 | """ Example 6: Memories in PyRTL
2 |
3 | One important part of many circuits is the ability to have data in
4 | locations that are persistent over clock cycles. In previous examples,
5 | we have shown the Register WireVector, which is great for storing
6 | a small amount of data for a single clock cycle. However, PyRTL also
7 | has other ways to store data, namely memories and ROMs.
8 | """
9 |
10 | import random
11 |
12 | import pyrtl
13 | from pyrtl import *
14 |
15 | # --- Part 1: Memories -------------------------------------------------------
16 |
17 | # Memories are a way to store multiple sets of data for extended periods of
18 | # time. Below we will make two instances of the same memory to test
19 | # that the same thing happens to two different memories using the same
20 | # inputs
21 |
22 | mem1 = MemBlock(bitwidth=32, addrwidth=3, name='mem')
23 | mem2 = MemBlock(32, 3, 'mem')
24 |
25 | # One memory will receive the write address from an input, the other, a register
26 | waddr = Input(3, 'waddr')
27 | count = Register(3, 'count')
28 |
29 | # In order to make sure that the two memories take the same inputs,
30 | # we will use same write data, write enable, and read addr values
31 | wdata = Input(32, 'wdata')
32 | we = Input(1, 'we')
33 | raddr = Input(3, 'raddr')
34 |
35 | # We will be grabbing data from each of the two memory blocks so we need
36 | # two different output wires to see the results
37 |
38 | rdata1 = Output(32, 'rdata1')
39 | rdata2 = Output(32, 'rdata2')
40 |
41 | # Ports
42 | # The way of sending data to and from a memory block is through the
43 | # use of a port. There are two types of ports: read ports and write ports.
44 | # Each memory can have multiple read and write ports, but it doesn't make
45 | # sense for one to have either 0 read ports or 0 write ports. Below, we
46 | # will make one read port for each of the two memories
47 |
48 | rdata1 <<= mem1[raddr]
49 | rdata2 <<= mem2[raddr]
50 |
51 | # Write Enable Bit
52 | # For the write ports, we will do something different. Sometimes you don't
53 | # want the memories to always accept the data and address on the write port.
54 | # The write enable bit allows us to disable the write port as long as the
55 | # value is zero, giving us complete control over whether to accept the data.
56 |
57 | WE = MemBlock.EnabledWrite
58 | mem1[waddr] <<= WE(wdata, we) # Uses input wire
59 | mem2[count] <<= WE(wdata, we) # Uses count register
60 |
61 | # Now we will finish up the circuit
62 | # We will increment count register on each write
63 |
64 | count.next <<= select(we, truecase=count + 1, falsecase=count)
65 |
66 | # We will also verify that the two write addresses are always the same
67 |
68 | validate = Output(1, 'validate')
69 | validate <<= waddr == count
70 |
71 | # Now it is time to simulate the circuit. First we will set up the values
72 | # for all of the inputs.
73 | # Write 1 through 8 into the eight registers, then read back out
74 | simvals = {
75 | 'we': "00111111110000000000000000",
76 | 'waddr': "00012345670000000000000000",
77 | 'wdata': "00123456789990000000000000",
78 | 'raddr': "00000000000000000123456777"
79 | }
80 |
81 | # For simulation purposes, we can give the spots in memory an initial value.
82 | # Note that in the actual circuit, the values are initially undefined.
83 | # Below, we are building the data with which to initialize memory.
84 | mem1_init = {addr: 9 for addr in range(8)}
85 | mem2_init = {addr: 9 for addr in range(8)}
86 |
87 | # The simulation only recognizes initial values of memories when they are in a
88 | # dictionary composed of memory : mem_values pairs.
89 | memvals = {mem1: mem1_init, mem2: mem2_init}
90 |
91 | # Now run the simulation like before. Note the adding of the memory
92 | # value map.
93 | print("---------memories----------")
94 | print(pyrtl.working_block())
95 | sim_trace = pyrtl.SimulationTrace()
96 | sim = pyrtl.Simulation(tracer=sim_trace, memory_value_map=memvals)
97 | sim.step_multiple(simvals)
98 | sim_trace.render_trace()
99 |
100 | # Cleanup in preparation for the ROM example
101 | pyrtl.reset_working_block()
102 |
103 | # --- Part 2: ROMs -----------------------------------------------------------
104 |
105 | # ROMs are another type of memory. Unlike normal memories, ROMs are read-only
106 | # and therefore only have read ports. They are used to store predefined data.
107 |
108 | # There are two different ways to define the data stored in the ROMs,
109 | # either through passing a function or though a list or tuple.
110 |
111 |
112 | def rom_data_func(address):
113 | return 31 - 2 * address
114 |
115 |
116 | rom_data_array = [rom_data_func(a) for a in range(16)]
117 |
118 | # Now we will make the ROM blocks. ROM blocks are similar to memory blocks,
119 | # but because they are read-only, they also need to be passed a set of
120 | # data to be initialized as.
121 |
122 | # FIXME: rework how memassigns work to account for more read ports
123 | rom1 = RomBlock(bitwidth=5, addrwidth=4, romdata=rom_data_func, max_read_ports=10)
124 | rom2 = RomBlock(5, 4, rom_data_array, max_read_ports=10)
125 |
126 | rom_add_1, rom_add_2 = Input(4, "rom_in"), Input(4, "rom_in_2")
127 |
128 | rom_out_1, rom_out_2 = Output(5, "rom_out_1"), Output(5, "rom_out_2")
129 | rom_out_3, cmp_out = Output(5, "rom_out_3"), Output(1, "cmp_out")
130 |
131 | # Because Output WireVectors cannot be used as the source for other nets,
132 | # in order to use the ROM outputs in two different places, we must instead
133 | # assign them to a temporary variable.
134 |
135 | temp1 = rom1[rom_add_1]
136 | temp2 = rom2[rom_add_1]
137 |
138 | rom_out_3 <<= rom2[rom_add_2]
139 |
140 | # now we will connect the rest of the outputs together
141 |
142 | rom_out_1 <<= temp1
143 | rom_out_2 <<= temp2
144 |
145 | cmp_out <<= temp1 == temp2
146 |
147 | # One of the things that is useful to have is repeatability, However, we
148 | # also don't want the hassle of typing out a set of values to test. One
149 | # solution in this case is to seed random and then pulling out 'random'
150 | # numbers from it.
151 | print("---------roms----------")
152 | print(pyrtl.working_block())
153 | random.seed(4839483)
154 |
155 | # Now we will create a new set of simulation values. In this case, since we
156 | # want to use simulation values that are larger than 9 we cannot use the
157 | # trick used in previous examples to parse values. The two ways we are doing
158 | # it below are both valid ways of making larger values
159 |
160 | simvals = {
161 | 'rom_in': [1, 11, 4, 2, 7, 8, 2, 4, 5, 13, 15, 3, 4, 4, 4, 8, 12, 13, 2, 1],
162 | 'rom_in_2': [random.randrange(0, 16) for i in range(20)]
163 | }
164 |
165 | # Now run the simulation like before. Note that for ROMs, we do not
166 | # supply a memory value map because ROMs are defined with the values
167 | # predefined.
168 |
169 | sim_trace = pyrtl.SimulationTrace()
170 | sim = pyrtl.Simulation(tracer=sim_trace)
171 | sim.step_multiple(simvals)
172 | sim_trace.render_trace()
173 |
--------------------------------------------------------------------------------
/examples/example7-synth-timing.py:
--------------------------------------------------------------------------------
1 | """ Example 7: Reduction and Speed Analysis
2 |
3 | After building a circuit, one might want to do some stuff to reduce the
4 | hardware into simpler nets as well as analyze various metrics of the
5 | hardware. This functionality is provided in the Passes part of PyRTL
6 | and will be demonstrated here.
7 | """
8 |
9 |
10 | import pyrtl
11 |
12 | # --- Part 1: Timing Analysis ------------------------------------------------
13 |
14 | # Timing and area usage are key considerations of any hardware block that one
15 | # makes. PyRTL provides functions to do these operations.
16 |
17 | # Creating a sample hardware block
18 | pyrtl.reset_working_block()
19 | const_wire = pyrtl.Const(6, bitwidth=4)
20 | in_wire2 = pyrtl.Input(bitwidth=4, name="input2")
21 | out_wire = pyrtl.Output(bitwidth=5, name="output")
22 | out_wire <<= const_wire + in_wire2
23 |
24 |
25 | # Now we will do the timing analysis as well as print out the critical path
26 |
27 | # Generating timing analysis information
28 | print("Pre Synthesis:")
29 | timing = pyrtl.TimingAnalysis()
30 | timing.print_max_length()
31 |
32 | # We are also able to print out the critical paths as well as get them
33 | # back as an array.
34 | critical_path_info = timing.critical_path()
35 |
36 | # --- Part 2: Area Analysis --------------------------------------------------
37 |
38 | # PyRTL also provides estimates for the area that would be used up if the
39 | # circuit was printed as an ASIC.
40 |
41 | logic_area, mem_area = pyrtl.area_estimation(tech_in_nm=65)
42 | est_area = logic_area + mem_area
43 | print("Estimated Area of block", est_area, "sq mm")
44 | print()
45 |
46 |
47 | # --- Part 3: Synthesis ------------------------------------------------------
48 |
49 | # Synthesis is the operation of reducing the circuit into simpler components.
50 | # The base synthesis function breaks down the more complex logic operations
51 | # into logic gates (keeping registers and memories intact) as well as reduces
52 | # all combinatorial logic into operations that only use 1-bitwidth wires.
53 | #
54 | # This synthesis allows for PyRTL to make optimizations to the net structure
55 | # as well as prepares it for further transformations on the PyRTL toolchain.
56 |
57 | pyrtl.synthesize()
58 |
59 | print("Pre Optimization:")
60 | timing = pyrtl.TimingAnalysis()
61 | timing.print_max_length()
62 | for net in pyrtl.working_block().logic:
63 | print(str(net))
64 | print()
65 |
66 |
67 | # --- Part 4: Optimization ----------------------------------------------------
68 |
69 | # PyRTL has functions built-in to eliminate unnecessary logic from the
70 | # circuit. These functions are all done with a simple call:
71 | pyrtl.optimize()
72 |
73 | # Now to see the difference
74 | print("Post Optimization:")
75 | timing = pyrtl.TimingAnalysis()
76 | timing.print_max_length()
77 |
78 | for net in pyrtl.working_block().logic:
79 | print(str(net))
80 |
81 | # As we can see, the number of nets in the circuit was drastically reduced by
82 | # the optimization algorithm.
83 |
--------------------------------------------------------------------------------
/examples/example8-verilog.py:
--------------------------------------------------------------------------------
1 | """ Example 8: Interfacing with Verilog.
2 |
3 | While there is much more about PyRTL design to discuss, at some point somebody
4 | might ask you to do something with your code other than have it print
5 | pretty things out to the terminal. We provide import from and export to
6 | Verilog of designs, export of waveforms to VCD, and a set of transforms
7 | that make doing netlist-level transforms and analysis directly in PyRTL easy.
8 | """
9 |
10 | import random
11 | import io
12 | import pyrtl
13 |
14 | # ---- Importing From Verilog ----
15 |
16 | # Sometimes it is useful to pull in components written in Verilog to be used
17 | # as subcomponents of PyRTL designs or to be subject to analysis written over
18 | # the PyRTL core. One standard format supported by PyRTL is "blif" format:
19 | # https://www.ece.cmu.edu/~ee760/760docs/blif.pdf
20 |
21 | # Many tools supoprt outputting hardware designs to this format, including the
22 | # free open source project "Yosys". Blif files can then be imported either
23 | # as a string or directly from a file name by the function input_from_blif.
24 | # Here is a simple example of a 1-bit full adder imported and then simulated
25 | # from this blif format.
26 |
27 | full_adder_blif = """
28 | # Generated by Yosys 0.3.0+ (git sha1 7e758d5, clang 3.4-1ubuntu3 -fPIC -Os)
29 | .model full_adder
30 | .inputs x y cin
31 | .outputs sum cout
32 | .names $false
33 | .names $true
34 | 1
35 | .names y $not$FA.v:12$3_Y
36 | 0 1
37 | .names x $not$FA.v:11$1_Y
38 | 0 1
39 | .names cin $not$FA.v:15$6_Y
40 | 0 1
41 | .names ind3 ind4 sum
42 | 1- 1
43 | -1 1
44 | .names $not$FA.v:15$6_Y ind2 ind3
45 | 11 1
46 | .names x $not$FA.v:12$3_Y ind1
47 | 11 1
48 | .names ind2 $not$FA.v:16$8_Y
49 | 0 1
50 | .names cin $not$FA.v:16$8_Y ind4
51 | 11 1
52 | .names x y $and$FA.v:19$11_Y
53 | 11 1
54 | .names ind0 ind1 ind2
55 | 1- 1
56 | -1 1
57 | .names cin ind2 $and$FA.v:19$12_Y
58 | 11 1
59 | .names $and$FA.v:19$11_Y $and$FA.v:19$12_Y cout
60 | 1- 1
61 | -1 1
62 | .names $not$FA.v:11$1_Y y ind0
63 | 11 1
64 | .end
65 | """
66 |
67 | pyrtl.input_from_blif(full_adder_blif)
68 | # Have to find the actual wire vectors generated from the names in the blif file
69 | x, y, cin = [pyrtl.working_block().get_wirevector_by_name(s) for s in ['x', 'y', 'cin']]
70 | io_vectors = pyrtl.working_block().wirevector_subset((pyrtl.Input, pyrtl.Output))
71 |
72 | # We are only going to trace the input and output vectors for clarity
73 | sim_trace = pyrtl.SimulationTrace(wires_to_track=io_vectors)
74 | # Now simulate the logic with some random inputs
75 | sim = pyrtl.Simulation(tracer=sim_trace)
76 | for i in range(15):
77 | # here we actually generate random booleans for the inputs
78 | sim.step({
79 | 'x': random.choice([0, 1]),
80 | 'y': random.choice([0, 1]),
81 | 'cin': random.choice([0, 1])
82 | })
83 | sim_trace.render_trace(symbol_len=2)
84 |
85 |
86 | # ---- Exporting to Verilog ----
87 |
88 | # However, not only do we want to have a method to import from Verilog, we also
89 | # want a way to export it back out to Verilog as well. To demonstrate PyRTL's
90 | # ability to export in Verilog, we will create a sample 3-bit counter. However
91 | # unlike the example in example2, we extend it to be synchronously resetting.
92 |
93 | pyrtl.reset_working_block()
94 |
95 | zero = pyrtl.Input(1, 'zero')
96 | counter_output = pyrtl.Output(3, 'counter_output')
97 | counter = pyrtl.Register(3, 'counter')
98 | counter.next <<= pyrtl.mux(zero, counter + 1, 0)
99 | counter_output <<= counter
100 |
101 | # The counter gets 0 in the next cycle if the "zero" signal goes high, otherwise just
102 | # counter + 1. Note that both "0" and "1" are bit extended to the proper length and
103 | # here we are making use of that native add operation. Let's dump this bad boy out
104 | # to a Verilog file and see what is looks like (here we are using StringIO just to
105 | # print it to a string for demo purposes; most likely you will want to pass a normal
106 | # open file).
107 |
108 | print("--- PyRTL Representation ---")
109 | print(pyrtl.working_block())
110 | print()
111 |
112 | print("--- Verilog for the Counter ---")
113 | with io.StringIO() as vfile:
114 | pyrtl.output_to_verilog(vfile)
115 | print(vfile.getvalue())
116 |
117 | print("--- Simulation Results ---")
118 | sim_trace = pyrtl.SimulationTrace([counter_output, zero])
119 | sim = pyrtl.Simulation(tracer=sim_trace)
120 | for cycle in range(15):
121 | sim.step({'zero': random.choice([0, 0, 0, 1])})
122 | sim_trace.render_trace()
123 |
124 | # We already did the "hard" work of generating a test input for this simulation, so
125 | # we might want to reuse that work when we take this design through a Verilog toolchain.
126 | # The class OutputVerilogTestbench grabs the inputs used in the simulation trace
127 | # and sets them up in a standard verilog testbench.
128 |
129 | print("--- Verilog for the TestBench ---")
130 | with io.StringIO() as tbfile:
131 | pyrtl.output_verilog_testbench(dest_file=tbfile, simulation_trace=sim_trace)
132 | print(tbfile.getvalue())
133 |
134 |
135 | # Now let's talk about transformations of the hardware block. Many times when you are
136 | # doing some hardware-level analysis you might wish to ignore higher level things like
137 | # multi-bit wirevectors, adds, concatenation, etc. and just think about wires and basic
138 | # gates. PyRTL supports "lowering" of designs into this more restricted set of functionality
139 | # though the function "synthesize". Once we lower a design to this form we can then apply
140 | # basic optimizations like constant propagation and dead wire elimination as well. By
141 | # printing it out to Verilog we can see exactly how the design changed.
142 |
143 | print("--- Optimized Single-bit Verilog for the Counter ---")
144 | pyrtl.synthesize()
145 | pyrtl.optimize()
146 |
147 | with io.StringIO() as vfile:
148 | pyrtl.output_to_verilog(vfile)
149 | print(vfile.getvalue())
150 |
--------------------------------------------------------------------------------
/examples/example9-transforms-draft.py:
--------------------------------------------------------------------------------
1 |
2 | import pyrtl
3 | from pyrtl import transform
4 |
5 |
6 | def insert_random_inversions(rate=0.5):
7 | import random
8 |
9 | def randomly_replace(wire):
10 | if random.random() < rate:
11 | new_src, new_dst = transform.clone_wire(wire), transform.clone_wire(wire)
12 | new_dst <<= ~new_src
13 | return new_src, new_dst
14 | return wire, wire
15 |
16 | transform.wire_transform(randomly_replace)
17 |
18 |
19 | def probe_wire_if(condition_func):
20 | """
21 |
22 | :param condition_func: (logic net) -> bool
23 | :return:
24 | """
25 |
26 | def add_probe_if(wire):
27 | if condition_func(wire):
28 | pyrtl.probe(wire)
29 | return wire, wire
30 |
31 | transform.wire_transform(add_probe_if)
32 |
--------------------------------------------------------------------------------
/examples/introduction-to-hardware.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | """ Introduction to Hardware Design
3 |
4 | This code works through the hardware design process with the
5 | audience of software developers more in mind. We start with the simple
6 | problem of designing a Fibonacci sequence calculator (http://oeis.org/A000045).
7 | """
8 |
9 | import pyrtl
10 |
11 |
12 | def software_fibonacci(n):
13 | """ A normal old Python function to return the Nth Fibonacci number. """
14 | a, b = 0, 1
15 | for i in range(n):
16 | a, b = b, a + b
17 | return a
18 |
19 | # Iterative implementation of Fibonacci, just iteratively adds a and b to
20 | # calculate the nth number in the sequence.
21 | # >> [software_fibonacci(x) for x in range(10)]
22 | # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
23 |
24 |
25 | # So let's convert this into some hardware that computes the same thing.
26 | # Our first go will be to just replace the 0 and 1 with WireVectors to see
27 | # what happens.
28 |
29 | def attempt1_hardware_fibonacci(n, bitwidth):
30 | a = pyrtl.Const(0)
31 | b = pyrtl.Const(1)
32 | for i in range(n):
33 | a, b = b, a + b
34 | return a
35 |
36 | # The above looks really nice but does not really represent a hardware implementation
37 | # of Fibonacci. Let's reason through the code, line by line, to figure out what
38 | # it would actually build.
39 | # a = pyrtl.Const(0) -- This makes a WireVector of bitwidth=1 that is driven by
40 | # a zero. Thus "a" is a WireVector. Seems good.
41 | # b = pyrtl.Const(1) -- Just like above, "b" is a WireVector driven by 1
42 | # for i in range(n): -- Okay, here is where things start to go off the rails a bit.
43 | # This says to perform the following code 'n' times, but the
44 | # value 'n' is passed as an input and is not something that is
45 | # evaluated in the hardware; it is evaluated when you run the
46 | # PyRTL program which generates (or more specifically elaborates)
47 | # the hardware. Thus the hardware we are building will have
48 | # the value of 'n' built into the hardware and won't actually
49 | # be a run-time parameter. Loops are really useful for building
50 | # large repetitive hardware structures, but they CAN'T be used
51 | # to represent hardware that should do a computation iteratively.
52 | # Instead we are going to need to use some registers to build a
53 | # state machine.
54 | # a, b = b, a + b -- Let's break this apart. In the first cycle b is Const(1) and
55 | # (a + b) builds an adder with a (Const(0)) and b (Const(1)) as
56 | # inputs. Thus (b, a + b) in the first iteration is:
57 | # ( Const(1), result_of_adding( Const(0), Const(1) )
58 | # At the end of the first iteration "a" and "b" refer to those
59 | # two constant values. In each following iteration more
60 | # adders are built and the names "a" and "b" are bound to larger
61 | # and larger trees of adders but all the inputs are constants!
62 | # return a -- The final thing that is returned then is the last output from
63 | # this tree of adders which all have Consts as inputs. Thus
64 | # this hardware is hard-wired to find only and exactly the value
65 | # of Fibonacci of the value N specified at design time! Probably
66 | # not what you are intending.
67 |
68 |
69 | # So let's try a different approach. Let's specify two registers ("a" and "b") and then we
70 | # can update those values as we iteratively compute Fibonacci of N cycle by cycle.
71 |
72 | def attempt2_hardware_fibonacci(n, bitwidth):
73 | a = pyrtl.Register(bitwidth, 'a')
74 | b = pyrtl.Register(bitwidth, 'b')
75 |
76 | a.next <<= b
77 | b.next <<= a + b
78 |
79 | return a
80 |
81 | # This is looking much better. Two registers, "a" and "b", store the values from which we
82 | # can compute the series. The line "a.next <<= b" means that the value of "a" in the next
83 | # cycle should be simply be "b" from the current cycle. The line "b.next <<= a + b" says
84 | # to build an adder with inputs of "a" and "b" from the current cycle and assign the value
85 | # to "b" in the next cycle. A visual representation of the hardware built is as such:
86 | #
87 | # ┌─────┐ ┌─────┐
88 | # │ │ │ │
89 | # ▼ │ ▼ │
90 | # ▕▔▔▔▔▔▏ │ ▕▔▔▔▔▔▏ │
91 | # ▕ a ▏ │ ▕ b ▏ │
92 | # ▕▁▁▁▁▁▏ │ ▕▁▁▁▁▁▏ │
93 | # │ │ │ │
94 | # │ └─────┤ │
95 | # │ │ │
96 | # ▼ ▼ │
97 | # ╲▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔╱ │
98 | # ╲ adder ╱ │
99 | # ╲▁▁▁▁▁▁▁▁▁▁▁╱ │
100 | # │ │
101 | # └───────────┘
102 | #
103 | # Note that in the picture the register "a" and "b" each have a WireVector which is
104 | # the current value (shown flowing out of the bottom of the register) and an "input"
105 | # which is giving the value that should be the value of the register in the following
106 | # cycle (shown flowing into the top of the register) which are "b" and "a + b" respectively.
107 | # When we say "return a" what we are returning is a reference to the register "a" in
108 | # the picture above.
109 |
110 |
111 | # Of course one problem is that we don't know when we are done. How do we know we
112 | # reached the "Nth" number in the sequence? Well, we need to add a register to
113 | # count up and see if we are done.
114 |
115 | def attempt3_hardware_fibonacci(n, bitwidth):
116 | a = pyrtl.Register(bitwidth, 'a')
117 | b = pyrtl.Register(bitwidth, 'b')
118 | i = pyrtl.Register(bitwidth, 'i')
119 |
120 | i.next <<= i + 1
121 | a.next <<= b
122 | b.next <<= a + b
123 |
124 | return a, i == n
125 |
126 | # This is very similar to the example before, except that now we have a register "i"
127 | # which keeps track of the iteration that we are on (i.next <<= i + 1). The function
128 | # now returns two values, a reference to the register "a" and a reference to a single
129 | # bit that tells us if we are done. That bit is calculated by comparing "i" to the
130 | # to a wirevector "n" that is passed in to see if they are the same.
131 |
132 |
133 | # Finally, we need a way to indicate that we want a new Fibonacci number.
134 | # We'll add another input, "req", which when high sets our "local_n" register and
135 | # resets the others. Now our ending condition occurs when the current iteration "i" is
136 | # equal to the locally stored "local_n".
137 |
138 | def attempt4_hardware_fibonacci(n, req, bitwidth):
139 | a = pyrtl.Register(bitwidth, 'a')
140 | b = pyrtl.Register(bitwidth, 'b')
141 | i = pyrtl.Register(bitwidth, 'i')
142 | local_n = pyrtl.Register(bitwidth, 'local_n')
143 | done = pyrtl.WireVector(bitwidth=1, name='done')
144 |
145 | with pyrtl.conditional_assignment:
146 | with req:
147 | local_n.next |= n
148 | i.next |= 0
149 | a.next |= 0
150 | b.next |= 1
151 | with pyrtl.otherwise:
152 | i.next |= i + 1
153 | a.next |= b
154 | b.next |= a + b
155 | done <<= i == local_n
156 | return a, done
157 |
158 |
159 | # This is now far enough along that we can simulate the design and see what happens.
160 | # We begin by connecting our input and output wires to the implementation,
161 | # stepping once with the 'req' signal high to signify we're beginning a
162 | # a new request for a value, and then continuing to step until 'done' is emitted.
163 | # Note that although the Fibonacci implementation only uses the value of 'n'
164 | # when 'req' is high, we must still provide a value for 'n' (and all other inputs
165 | # tracked by the simulator) for each step.
166 |
167 | BITWIDTH = 8
168 |
169 | n_in = pyrtl.Input(BITWIDTH, 'n_in')
170 | req_in = pyrtl.Input(1, 'req_in')
171 | fib_out = pyrtl.Output(BITWIDTH, 'fib_out')
172 | done_out = pyrtl.Output(1, 'done_out')
173 |
174 | output = attempt4_hardware_fibonacci(n_in, req_in, len(n_in))
175 | fib_out <<= output[0]
176 | done_out <<= output[1]
177 |
178 | sim_trace = pyrtl.SimulationTrace()
179 | sim = pyrtl.Simulation(tracer=sim_trace)
180 |
181 | sim.step({'n_in': 7, 'req_in': 1})
182 |
183 | sim.step({'n_in': 0, 'req_in': 0})
184 | while not sim.inspect('done_out'):
185 | sim.step({'n_in': 0, 'req_in': 0})
186 |
187 | sim_trace.render_trace(
188 | trace_list=['n_in', 'req_in', 'i', 'fib_out', 'done_out'], repr_func=int)
189 |
--------------------------------------------------------------------------------
/examples/renderer-demo.py:
--------------------------------------------------------------------------------
1 | import pyrtl
2 |
3 | """Render traces with various WaveRenderer options.
4 |
5 | Run this demo to see which options work well in your terminal.
6 |
7 | """
8 |
9 |
10 | def make_clock(period: int):
11 | """Make a clock signal that inverts every `period` cycles."""
12 | assert period > 0
13 |
14 | # Build a chain of registers.
15 | first_reg = pyrtl.Register(bitwidth=1, name=f'clock_0_{period}',
16 | reset_value=1)
17 | last_reg = first_reg
18 | for offset in range(1, period):
19 | reg = pyrtl.Register(bitwidth=1, name=f'clock_{offset}_{period}')
20 | reg.next <<= last_reg
21 | last_reg = reg
22 |
23 | # The first register's input is the inverse of the last register's output.
24 | first_reg.next <<= ~last_reg
25 | return last_reg
26 |
27 |
28 | def make_counter(period: int, bitwidth=2):
29 | """Make a counter that increments every `period` cycles."""
30 | assert period > 0
31 |
32 | # Build a chain of registers.
33 | first_reg = pyrtl.Register(bitwidth=bitwidth, name=f'counter_0_{period}')
34 | last_reg = first_reg
35 | for offset in range(1, period):
36 | reg = pyrtl.Register(bitwidth=bitwidth,
37 | name=f'counter_{offset}_{period}')
38 | reg.next <<= last_reg
39 | last_reg = reg
40 |
41 | # The first register's input is the last register's output plus 1.
42 | first_reg.next <<= last_reg + pyrtl.Const(1)
43 | return last_reg
44 |
45 |
46 | make_clock(period=1)
47 | make_clock(period=2)
48 | make_counter(period=1)
49 | make_counter(period=2)
50 |
51 | # Simulate 20 cycles.
52 | sim = pyrtl.Simulation()
53 | sim.step_multiple(nsteps=20)
54 |
55 | # Render the trace with a variety of rendering options.
56 | renderers = {
57 | 'powerline': (pyrtl.simulation.PowerlineRendererConstants(),
58 | 'Requires a font with powerline glyphs'),
59 | 'utf-8': (pyrtl.simulation.Utf8RendererConstants(),
60 | 'Unicode, default non-Windows renderer'),
61 | 'utf-8-alt': (pyrtl.simulation.Utf8AltRendererConstants(),
62 | 'Unicode, alternate display option'),
63 | 'cp437': (pyrtl.simulation.Cp437RendererConstants(),
64 | 'Code page 437 (8-bit ASCII), default Windows renderer'),
65 | 'ascii': (pyrtl.simulation.AsciiRendererConstants(),
66 | 'Basic 7-bit ASCII renderer'),
67 | }
68 |
69 | for i, name in enumerate(renderers):
70 | constants, notes = renderers[name]
71 | print(f'# {notes}')
72 | print(f'export PYRTL_RENDERER={name}\n')
73 | sim.tracer.render_trace(
74 | renderer=pyrtl.simulation.WaveRenderer(constants),
75 | repr_func=int)
76 | print()
77 |
--------------------------------------------------------------------------------
/ipynb-examples/example2-counter.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Example 2: A Counter with Ripple Carry Adder."
8 | ]
9 | },
10 | {
11 | "cell_type": "markdown",
12 | "metadata": {},
13 | "source": [
14 | "This next example shows how you make stateful things with registers\n",
15 | "and more complex hardware structures with functions. We generate\n",
16 | "a **3-bit ripple carry adder** building off of the 1-bit adder from\n",
17 | "the prior example, and then hook it to a register to count up modulo 8."
18 | ]
19 | },
20 | {
21 | "cell_type": "code",
22 | "execution_count": null,
23 | "metadata": {
24 | "collapsed": true
25 | },
26 | "outputs": [],
27 | "source": [
28 | "import pyrtl"
29 | ]
30 | },
31 | {
32 | "cell_type": "code",
33 | "execution_count": null,
34 | "metadata": {
35 | "collapsed": true
36 | },
37 | "outputs": [],
38 | "source": [
39 | "pyrtl.reset_working_block()"
40 | ]
41 | },
42 | {
43 | "cell_type": "markdown",
44 | "metadata": {},
45 | "source": [
46 | "A **function in PyRTL** is nothing special -- it just so happens that the statements\n",
47 | "it encapsulate tell PyRTL to build some hardware."
48 | ]
49 | },
50 | {
51 | "cell_type": "code",
52 | "execution_count": null,
53 | "metadata": {
54 | "collapsed": true
55 | },
56 | "outputs": [],
57 | "source": [
58 | "def one_bit_add(a, b, carry_in):\n",
59 | " assert len(a) == len(b) == 1 # len returns the bitwidth\n",
60 | " sum = a ^ b ^ carry_in\n",
61 | " carry_out = a & b | a & carry_in | b & carry_in\n",
62 | " return sum, carry_out"
63 | ]
64 | },
65 | {
66 | "cell_type": "markdown",
67 | "metadata": {},
68 | "source": [
69 | "If we call *one_bit_add*\n",
70 | "above with the arguments *x*, *y*, and *z* it will make a **one-bit adder to add\n",
71 | "those values together** and return the wires for sum and carry_out as applied to *x*,\n",
72 | "*y*, and *z*. If I call it again on *i*, *j*, and *k* it will build a **new one-bit\n",
73 | "adder** for those inputs and return the resulting sum and carry_out for that adder."
74 | ]
75 | },
76 | {
77 | "cell_type": "markdown",
78 | "metadata": {},
79 | "source": [
80 | "While PyRTL actually provides an \"+\" operator for wirevectors which generates\n",
81 | "adders, a **ripple carry adder** is something people can understand easily but has\n",
82 | "enough structure to be mildly interesting. Let's **define an adder of arbitrary\n",
83 | "length** recursively and (hopefully) pythonically. More comments after the code."
84 | ]
85 | },
86 | {
87 | "cell_type": "code",
88 | "execution_count": null,
89 | "metadata": {
90 | "collapsed": true
91 | },
92 | "outputs": [],
93 | "source": [
94 | "def ripple_add(a, b, carry_in=0):\n",
95 | " a, b = pyrtl.match_bitwidth(a, b)\n",
96 | " # this function is a function that allows us to match the bitwidth of multiple\n",
97 | " # different wires. By default, it zero extends the shorter bits\n",
98 | " if len(a) == 1:\n",
99 | " sumbits, carry_out = one_bit_add(a, b, carry_in)\n",
100 | " else:\n",
101 | " lsbit, ripplecarry = one_bit_add(a[0], b[0], carry_in)\n",
102 | " msbits, carry_out = ripple_add(a[1:], b[1:], ripplecarry)\n",
103 | " sumbits = pyrtl.concat(msbits, lsbit)\n",
104 | " return sumbits, carry_out"
105 | ]
106 | },
107 | {
108 | "cell_type": "markdown",
109 | "metadata": {},
110 | "source": [
111 | "#### The above code breaks down into two cases:\n",
112 | "* If the size of the inputs is one-bit just do one_bit_add.\n",
113 | "* if they are more than one bit, do a one-bit add on the least significant bits, a ripple carry on the rest, and then stick the results back together into one WireVector."
114 | ]
115 | },
116 | {
117 | "cell_type": "markdown",
118 | "metadata": {},
119 | "source": [
120 | "#### A couple interesting features of PyRTL can be seen here:\n",
121 | "* WireVectors can be indexed like lists, with [0] accessing the least significant bit and [1:] being an example of the use of Python slicing syntax.\n",
122 | "* While you can add two lists together in python a WireVector + Wirevector means \"make an adder\" so to concatenate the bits of two vectors one need to use \"concat\".\n",
123 | "* If we look at \"cin\" it seems to have a default value of the integer \"0\" but is a WireVector at other times.Python supports polymorphism throughout and PyRTL will cast integers and some other types to WireVectors when it can."
124 | ]
125 | },
126 | {
127 | "cell_type": "markdown",
128 | "metadata": {},
129 | "source": [
130 | "Now let's **build a 3-bit counter** from our N-bit ripple carry adder."
131 | ]
132 | },
133 | {
134 | "cell_type": "code",
135 | "execution_count": null,
136 | "metadata": {
137 | "collapsed": true
138 | },
139 | "outputs": [],
140 | "source": [
141 | "counter = pyrtl.Register(bitwidth=3, name='counter')\n",
142 | "sum, carry_out = ripple_add(counter, pyrtl.Const(\"1'b1\"))\n",
143 | "counter.next <<= sum"
144 | ]
145 | },
146 | {
147 | "cell_type": "markdown",
148 | "metadata": {},
149 | "source": [
150 | "#### A couple new things in the above code:\n",
151 | "* 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 directly from a Verilog-like string which includes both the value and the bitwidth.\n",
152 | "* Registers are just like wires, 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.\n",
153 | "* In this simple example, we take counter next cycle equal to counter this cycle plus one."
154 | ]
155 | },
156 | {
157 | "cell_type": "markdown",
158 | "metadata": {},
159 | "source": [
160 | "Now let's **run the bugger**. No need for inputs, it doesn't have any, but let's\n",
161 | "**throw in an assert** to check that it really counts up modulo 8. Finally we'll\n",
162 | "**print the trace** to the screen."
163 | ]
164 | },
165 | {
166 | "cell_type": "code",
167 | "execution_count": null,
168 | "metadata": {
169 | "collapsed": true
170 | },
171 | "outputs": [],
172 | "source": [
173 | "sim_trace = pyrtl.SimulationTrace()\n",
174 | "sim = pyrtl.Simulation(tracer=sim_trace)\n",
175 | "for cycle in range(15):\n",
176 | " sim.step({})\n",
177 | " assert sim.value[counter] == cycle % 8\n",
178 | "sim_trace.render_trace()"
179 | ]
180 | }
181 | ],
182 | "metadata": {
183 | "kernelspec": {
184 | "display_name": "Python 3",
185 | "language": "python",
186 | "name": "python3"
187 | },
188 | "language_info": {
189 | "codemirror_mode": {
190 | "name": "ipython",
191 | "version": 3
192 | },
193 | "file_extension": ".py",
194 | "mimetype": "text/x-python",
195 | "name": "python",
196 | "nbconvert_exporter": "python",
197 | "pygments_lexer": "ipython3",
198 | "version": "3.6.3"
199 | }
200 | },
201 | "nbformat": 4,
202 | "nbformat_minor": 2
203 | }
204 |
--------------------------------------------------------------------------------
/ipynb-examples/example3-statemachine.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Example 3: A State Machine built with ConditionalUpdate"
8 | ]
9 | },
10 | {
11 | "cell_type": "markdown",
12 | "metadata": {},
13 | "source": [
14 | "In this example we describe how **ConditionalUpdate** works in the context of\n",
15 | "a vending machine that will dispense an item when it has received 4 tokens.\n",
16 | "If a refund is requested, it returns the tokens."
17 | ]
18 | },
19 | {
20 | "cell_type": "code",
21 | "execution_count": null,
22 | "metadata": {
23 | "collapsed": true
24 | },
25 | "outputs": [],
26 | "source": [
27 | "import pyrtl\n",
28 | "pyrtl.reset_working_block()"
29 | ]
30 | },
31 | {
32 | "cell_type": "code",
33 | "execution_count": null,
34 | "metadata": {
35 | "collapsed": true
36 | },
37 | "outputs": [],
38 | "source": [
39 | "token_in = pyrtl.Input(1, 'token_in')\n",
40 | "req_refund = pyrtl.Input(1, 'req_refund')\n",
41 | "dispense = pyrtl.Output(1, 'dispense')\n",
42 | "refund = pyrtl.Output(1, 'refund')\n",
43 | "state = pyrtl.Register(3, 'state')"
44 | ]
45 | },
46 | {
47 | "cell_type": "markdown",
48 | "metadata": {},
49 | "source": [
50 | "First new step, let's **enumerate a set of constant to serve as our states**"
51 | ]
52 | },
53 | {
54 | "cell_type": "code",
55 | "execution_count": null,
56 | "metadata": {
57 | "collapsed": true
58 | },
59 | "outputs": [],
60 | "source": [
61 | "WAIT, TOK1, TOK2, TOK3, DISPENSE, REFUND = [pyrtl.Const(x, bitwidth=3) for x in range(6)]"
62 | ]
63 | },
64 | {
65 | "cell_type": "markdown",
66 | "metadata": {},
67 | "source": [
68 | "Now we could build a state machine using just the registers and logic discussed\n",
69 | "in the earlier examples, but doing operations *conditional* on some input is a pretty\n",
70 | "fundamental operation in hardware design. **PyRTL provides a class \"ConditionalUpdate\"**\n",
71 | "to provide a predicated update to a registers, wires, and memories.\n",
72 | "\n",
73 | "**Conditional assignments** are specified with a *\"|=\"* instead of a *\"<<=\"* operator. The\n",
74 | "conditional assignment is only value in the context of a condition, and update to those\n",
75 | "values only happens when that condition is true. In hardware this is implemented\n",
76 | "with a simple mux -- for people coming from software it is important to remember that this\n",
77 | "is describing a big logic function NOT an \"if-then-else\" clause. All of these things will\n",
78 | "execute straight through when *build_everything* is called. More comments after the code.\n",
79 | "\n",
80 | "**One more thing:** ConditionalUpdate might not always be the best item to use.\n",
81 | "if the update is simple, a regular mux(sel_wire, falsecase=f_wire, truecase=t_wire)\n",
82 | "can be sufficient."
83 | ]
84 | },
85 | {
86 | "cell_type": "code",
87 | "execution_count": null,
88 | "metadata": {
89 | "collapsed": true
90 | },
91 | "outputs": [],
92 | "source": [
93 | "with pyrtl.conditional_assignment:\n",
94 | " with req_refund: # signal of highest precedence\n",
95 | " state.next |= REFUND\n",
96 | " with token_in: # if token received, advance state in counter sequence\n",
97 | " with state == WAIT:\n",
98 | " state.next |= TOK1\n",
99 | " with state == TOK1:\n",
100 | " state.next |= TOK2\n",
101 | " with state == TOK2:\n",
102 | " state.next |= TOK3\n",
103 | " with state == TOK3:\n",
104 | " state.next |= DISPENSE # 4th token received, go to dispense\n",
105 | " with pyrtl.otherwise: # token received but in state where we can't handle it\n",
106 | " state.next |= REFUND\n",
107 | " # unconditional transition from these two states back to wait state\n",
108 | " # NOTE: the parens are needed because in Python the \"|\" operator is lower precedence\n",
109 | " # than the \"==\" operator!\n",
110 | " with (state == DISPENSE) | (state == REFUND):\n",
111 | " state.next |= WAIT"
112 | ]
113 | },
114 | {
115 | "cell_type": "code",
116 | "execution_count": null,
117 | "metadata": {
118 | "collapsed": true
119 | },
120 | "outputs": [],
121 | "source": [
122 | "dispense <<= state == DISPENSE\n",
123 | "refund <<= state == REFUND"
124 | ]
125 | },
126 | {
127 | "cell_type": "markdown",
128 | "metadata": {},
129 | "source": [
130 | "#### A couple of other things to note:\n",
131 | "* A condition can be nested within another condition and the implied hardware is that the left-hand-side should only get that value if ALL of the encompassing conditions are satisfied.\n",
132 | "* Only one conditional at each level can be true meaning that all conditions are implicitly also saying that none of the prior conditions at the same level also have been true. The highest priority condition is listed first, and in a sense you can think about each other condition as an \"elif\".\n",
133 | "* If not every condition is enumerated, the default value for the register under those cases will be the same as it was the prior cycle (\"state.next |= state\" in this example). The default for a wirevector is 0.\n",
134 | "* There is a way to specify something like an \"else\" instead of \"elif\" and that is with an \"otherwise\" (as seen on the line above \"state.next <<= REFUND\"). This condition will be true if none of the other conditions at the same level were also true (for this example specifically state.next will get REFUND when req_refund==0, token_in==1, and state is not in TOK1, TOK2, TOK3, or DISPENSE.\n",
135 | "* Not shown here, you can update multiple different registers, wires, and memories all under the same set of conditionals."
136 | ]
137 | },
138 | {
139 | "cell_type": "markdown",
140 | "metadata": {},
141 | "source": [
142 | "#### A more artificial example might make it even more clear how these rules interact:\n",
143 | "```python \n",
144 | "with a:\n",
145 | " r.next |= 1 <-- when a is true\n",
146 | " with d:\n",
147 | " r2.next |= 2 <-- when a and d are true\n",
148 | " with otherwise:\n",
149 | " r2.next |= 3 <-- when a is true and d is false\n",
150 | "with b == c:\n",
151 | " r.next |= 0 <-- when a is not true and b == c is true\n",
152 | "```"
153 | ]
154 | },
155 | {
156 | "cell_type": "markdown",
157 | "metadata": {},
158 | "source": [
159 | "Now let's **build and test our state machine**."
160 | ]
161 | },
162 | {
163 | "cell_type": "code",
164 | "execution_count": null,
165 | "metadata": {
166 | "collapsed": true
167 | },
168 | "outputs": [],
169 | "source": [
170 | "sim_trace = pyrtl.SimulationTrace()\n",
171 | "sim = pyrtl.Simulation(tracer=sim_trace)"
172 | ]
173 | },
174 | {
175 | "cell_type": "markdown",
176 | "metadata": {},
177 | "source": [
178 | "Rather than just give some random inputs, let's **specify some specific 1 bit values**. Recall\n",
179 | "that the sim.step method takes a dictionary mapping inputs to their values. We could just\n",
180 | "specify the input set directly as a dictionary but it gets pretty ugly -- let's use some python\n",
181 | "to parse them up."
182 | ]
183 | },
184 | {
185 | "cell_type": "code",
186 | "execution_count": null,
187 | "metadata": {
188 | "collapsed": true
189 | },
190 | "outputs": [],
191 | "source": [
192 | "sim_inputs = {\n",
193 | " 'token_in': '0010100111010000',\n",
194 | " 'req_refund': '1100010000000000'\n",
195 | " }\n",
196 | "\n",
197 | "for cycle in range(len(sim_inputs['token_in'])):\n",
198 | " sim.step({w: int(v[cycle]) for w, v in sim_inputs.items()})"
199 | ]
200 | },
201 | {
202 | "cell_type": "markdown",
203 | "metadata": {},
204 | "source": [
205 | "Also, to make our input/output easy to reason about let's **specify an order to the traces**\n"
206 | ]
207 | },
208 | {
209 | "cell_type": "code",
210 | "execution_count": null,
211 | "metadata": {
212 | "collapsed": true
213 | },
214 | "outputs": [],
215 | "source": [
216 | "sim_trace.render_trace(trace_list=['token_in', 'req_refund', 'state', 'dispense', 'refund'])"
217 | ]
218 | }
219 | ],
220 | "metadata": {
221 | "kernelspec": {
222 | "display_name": "Python 3",
223 | "language": "python",
224 | "name": "python3"
225 | },
226 | "language_info": {
227 | "codemirror_mode": {
228 | "name": "ipython",
229 | "version": 3
230 | },
231 | "file_extension": ".py",
232 | "mimetype": "text/x-python",
233 | "name": "python",
234 | "nbconvert_exporter": "python",
235 | "pygments_lexer": "ipython3",
236 | "version": "3.6.3"
237 | }
238 | },
239 | "nbformat": 4,
240 | "nbformat_minor": 2
241 | }
242 |
--------------------------------------------------------------------------------
/ipynb-examples/example5-introspection.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Example 5: Making use of PyRTL and Introspection."
8 | ]
9 | },
10 | {
11 | "cell_type": "markdown",
12 | "metadata": {},
13 | "source": [
14 | "The following example shows how pyrtl can be used to make some interesting\n",
15 | "hardware structures using **python introspection**. In particular, this example\n",
16 | "makes a **N-stage pipeline structure**. Any specific pipeline is then a derived\n",
17 | "class of *SimplePipeline* where methods with names starting with *\"stage\"* are\n",
18 | "stages, and new members with names not starting with \"\\_\" are to be registered\n",
19 | "for the next stage."
20 | ]
21 | },
22 | {
23 | "cell_type": "markdown",
24 | "metadata": {},
25 | "source": [
26 | "### Pipeline builder with auto generation of pipeline registers."
27 | ]
28 | },
29 | {
30 | "cell_type": "code",
31 | "execution_count": null,
32 | "metadata": {
33 | "collapsed": true
34 | },
35 | "outputs": [],
36 | "source": [
37 | "import pyrtl\n",
38 | "\n",
39 | "pyrtl.reset_working_block()\n",
40 | "\n",
41 | "class SimplePipeline(object):\n",
42 | " def __init__(self):\n",
43 | " self._pipeline_register_map = {}\n",
44 | " self._current_stage_num = 0\n",
45 | " stage_list = [method for method in dir(self) if method.startswith('stage')]\n",
46 | " for stage in sorted(stage_list):\n",
47 | " stage_method = getattr(self, stage)\n",
48 | " stage_method()\n",
49 | " self._current_stage_num += 1\n",
50 | " \n",
51 | " def __getattr__(self, name):\n",
52 | " try:\n",
53 | " return self._pipeline_register_map[self._current_stage_num][name]\n",
54 | " except KeyError:\n",
55 | " raise pyrtl.PyrtlError(\n",
56 | " 'error, no pipeline register \"%s\" defined for stage %d'\n",
57 | " % (name, self._current_stage_num))\n",
58 | "\n",
59 | " def __setattr__(self, name, value):\n",
60 | " if name.startswith('_'):\n",
61 | " # do not do anything tricky with variables starting with '_'\n",
62 | " object.__setattr__(self, name, value)\n",
63 | " else:\n",
64 | " next_stage = self._current_stage_num + 1\n",
65 | " pipereg_id = str(self._current_stage_num) + 'to' + str(next_stage)\n",
66 | " rname = 'pipereg_' + pipereg_id + '_' + name\n",
67 | " new_pipereg = pyrtl.Register(bitwidth=len(value), name=rname)\n",
68 | " if next_stage not in self._pipeline_register_map:\n",
69 | " self._pipeline_register_map[next_stage] = {}\n",
70 | " self._pipeline_register_map[next_stage][name] = new_pipereg\n",
71 | " new_pipereg.next <<= value "
72 | ]
73 | },
74 | {
75 | "cell_type": "markdown",
76 | "metadata": {},
77 | "source": [
78 | "### A very simple pipeline to show how registers are inferred."
79 | ]
80 | },
81 | {
82 | "cell_type": "code",
83 | "execution_count": null,
84 | "metadata": {
85 | "collapsed": true
86 | },
87 | "outputs": [],
88 | "source": [
89 | "class SimplePipelineExample(SimplePipeline):\n",
90 | " def __init__(self):\n",
91 | " self._loopback = pyrtl.WireVector(1, 'loopback')\n",
92 | " super(SimplePipelineExample, self).__init__()\n",
93 | "\n",
94 | " def stage0(self):\n",
95 | " self.n = ~ self._loopback\n",
96 | "\n",
97 | " def stage1(self):\n",
98 | " self.n = self.n\n",
99 | "\n",
100 | " def stage2(self):\n",
101 | " self.n = self.n\n",
102 | "\n",
103 | " def stage3(self):\n",
104 | " self.n = self.n\n",
105 | "\n",
106 | " def stage4(self):\n",
107 | " self._loopback <<= self.n"
108 | ]
109 | },
110 | {
111 | "cell_type": "markdown",
112 | "metadata": {},
113 | "source": [
114 | "### Simulation of the core"
115 | ]
116 | },
117 | {
118 | "cell_type": "code",
119 | "execution_count": null,
120 | "metadata": {},
121 | "outputs": [],
122 | "source": [
123 | "simplepipeline = SimplePipelineExample()\n",
124 | "\n",
125 | "sim_trace = pyrtl.SimulationTrace()\n",
126 | "sim = pyrtl.Simulation(tracer=sim_trace)\n",
127 | "\n",
128 | "for cycle in range(15):\n",
129 | " sim.step({})\n",
130 | "sim_trace.render_trace()"
131 | ]
132 | }
133 | ],
134 | "metadata": {
135 | "kernelspec": {
136 | "display_name": "Python 3",
137 | "language": "python",
138 | "name": "python3"
139 | },
140 | "language_info": {
141 | "codemirror_mode": {
142 | "name": "ipython",
143 | "version": 3
144 | },
145 | "file_extension": ".py",
146 | "mimetype": "text/x-python",
147 | "name": "python",
148 | "nbconvert_exporter": "python",
149 | "pygments_lexer": "ipython3",
150 | "version": "3.6.3"
151 | }
152 | },
153 | "nbformat": 4,
154 | "nbformat_minor": 2
155 | }
156 |
--------------------------------------------------------------------------------
/ipynb-examples/example7-synth-timing.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Example 7: Reduction and Speed Analysis"
8 | ]
9 | },
10 | {
11 | "cell_type": "markdown",
12 | "metadata": {},
13 | "source": [
14 | "After building a circuit, one might want to do some stuff to **reduce the\n",
15 | "hardware into simpler nets** as well as **analyze various metrics of the\n",
16 | "hardware**. This functionality is provided in the Passes part of PyRTL\n",
17 | "and will demonstrated here."
18 | ]
19 | },
20 | {
21 | "cell_type": "code",
22 | "execution_count": null,
23 | "metadata": {
24 | "collapsed": true
25 | },
26 | "outputs": [],
27 | "source": [
28 | "import pyrtl"
29 | ]
30 | },
31 | {
32 | "cell_type": "markdown",
33 | "metadata": {},
34 | "source": [
35 | "## Part 1: Timing Analysis"
36 | ]
37 | },
38 | {
39 | "cell_type": "markdown",
40 | "metadata": {},
41 | "source": [
42 | "**Timing and area usage** are key considerations of any hardware block that one\n",
43 | "makes.\n",
44 | "\n",
45 | "PyRTL provides functions to do these opertions"
46 | ]
47 | },
48 | {
49 | "cell_type": "code",
50 | "execution_count": null,
51 | "metadata": {
52 | "collapsed": true
53 | },
54 | "outputs": [],
55 | "source": [
56 | "# Creating a sample harware block\n",
57 | "pyrtl.reset_working_block()\n",
58 | "const_wire = pyrtl.Const(6, bitwidth=4)\n",
59 | "in_wire2 = pyrtl.Input(bitwidth=4, name=\"input2\")\n",
60 | "out_wire = pyrtl.Output(bitwidth=5, name=\"output\")\n",
61 | "out_wire <<= const_wire + in_wire2"
62 | ]
63 | },
64 | {
65 | "cell_type": "markdown",
66 | "metadata": {},
67 | "source": [
68 | "### Now we will do the timing analysis as well as print out the critical path"
69 | ]
70 | },
71 | {
72 | "cell_type": "code",
73 | "execution_count": null,
74 | "metadata": {},
75 | "outputs": [],
76 | "source": [
77 | "# Generating timing analysis information\n",
78 | "print(\"Pre Synthesis:\")\n",
79 | "timing = pyrtl.TimingAnalysis()\n",
80 | "timing.print_max_length()"
81 | ]
82 | },
83 | {
84 | "cell_type": "markdown",
85 | "metadata": {},
86 | "source": [
87 | "We are also able to **print out the critical paths** as well as get them\n",
88 | "back as an array."
89 | ]
90 | },
91 | {
92 | "cell_type": "code",
93 | "execution_count": null,
94 | "metadata": {},
95 | "outputs": [],
96 | "source": [
97 | "critical_path_info = timing.critical_path()"
98 | ]
99 | },
100 | {
101 | "cell_type": "markdown",
102 | "metadata": {},
103 | "source": [
104 | "## Part 2: Area Analysis"
105 | ]
106 | },
107 | {
108 | "cell_type": "markdown",
109 | "metadata": {},
110 | "source": [
111 | "PyRTL also provides **estimates for the area** that would be used up if the\n",
112 | "circuit was printed as an **ASIC**"
113 | ]
114 | },
115 | {
116 | "cell_type": "code",
117 | "execution_count": null,
118 | "metadata": {},
119 | "outputs": [],
120 | "source": [
121 | "logic_area, mem_area = pyrtl.area_estimation(tech_in_nm=65)\n",
122 | "est_area = logic_area + mem_area\n",
123 | "print(\"Estimated Area of block\", est_area, \"sq mm\")\n",
124 | "print()"
125 | ]
126 | },
127 | {
128 | "cell_type": "markdown",
129 | "metadata": {},
130 | "source": [
131 | "## Part 3: Synthesis"
132 | ]
133 | },
134 | {
135 | "cell_type": "markdown",
136 | "metadata": {},
137 | "source": [
138 | "Synthesis is the operation of **reducing the circuit into simpler components**\n",
139 | "The base synthesis function breaks down the more complex logic operations\n",
140 | "into logic gates (keeps registers and memories intact) as well as **reduces\n",
141 | "all combinatorial logic into ops that only use one bitwidth wires**\n",
142 | "\n",
143 | "This synthesis allows for PyRTL to make **optimizations to the net structure**\n",
144 | "as well as prepares it for further transformations on the PyRTL Toolchain"
145 | ]
146 | },
147 | {
148 | "cell_type": "code",
149 | "execution_count": null,
150 | "metadata": {},
151 | "outputs": [],
152 | "source": [
153 | "pyrtl.synthesize()\n",
154 | "\n",
155 | "print(\"Pre Optimization:\")\n",
156 | "timing = pyrtl.TimingAnalysis()\n",
157 | "timing.print_max_length()\n",
158 | "for net in pyrtl.working_block().logic:\n",
159 | " print(str(net))\n",
160 | "print()"
161 | ]
162 | },
163 | {
164 | "cell_type": "markdown",
165 | "metadata": {},
166 | "source": [
167 | "## Part 4: Optimization"
168 | ]
169 | },
170 | {
171 | "cell_type": "markdown",
172 | "metadata": {},
173 | "source": [
174 | "PyRTL has functions built in to **eliminate unnecessary logic** from the\n",
175 | "circuit.\n",
176 | "\n",
177 | "These functions are all done with a simple call:"
178 | ]
179 | },
180 | {
181 | "cell_type": "code",
182 | "execution_count": null,
183 | "metadata": {},
184 | "outputs": [],
185 | "source": [
186 | "pyrtl.optimize()"
187 | ]
188 | },
189 | {
190 | "cell_type": "markdown",
191 | "metadata": {},
192 | "source": [
193 | "### Now to see the difference"
194 | ]
195 | },
196 | {
197 | "cell_type": "code",
198 | "execution_count": null,
199 | "metadata": {},
200 | "outputs": [],
201 | "source": [
202 | "print(\"Post Optimization:\")\n",
203 | "timing = pyrtl.TimingAnalysis()\n",
204 | "timing.print_max_length()"
205 | ]
206 | },
207 | {
208 | "cell_type": "code",
209 | "execution_count": null,
210 | "metadata": {},
211 | "outputs": [],
212 | "source": [
213 | "for net in pyrtl.working_block().logic:\n",
214 | " print(str(net))"
215 | ]
216 | },
217 | {
218 | "cell_type": "markdown",
219 | "metadata": {},
220 | "source": [
221 | "### As we can see, the number of nets in the circuit were drastically reduced by the optimization algorithm."
222 | ]
223 | }
224 | ],
225 | "metadata": {
226 | "kernelspec": {
227 | "display_name": "Python 3",
228 | "language": "python",
229 | "name": "python3"
230 | },
231 | "language_info": {
232 | "codemirror_mode": {
233 | "name": "ipython",
234 | "version": 3
235 | },
236 | "file_extension": ".py",
237 | "mimetype": "text/x-python",
238 | "name": "python",
239 | "nbconvert_exporter": "python",
240 | "pygments_lexer": "ipython3",
241 | "version": "3.6.3"
242 | }
243 | },
244 | "nbformat": 4,
245 | "nbformat_minor": 2
246 | }
247 |
--------------------------------------------------------------------------------
/ipynb-examples/example8-verilog.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Example 8: Interfacing with Verilog."
8 | ]
9 | },
10 | {
11 | "cell_type": "markdown",
12 | "metadata": {},
13 | "source": [
14 | "While there is much more about PyRTL design to discuss, at some point somebody\n",
15 | "might ask you to do something with your code other than have it print\n",
16 | "pretty things out to the terminal. We provide **import from and export to\n",
17 | "Verilog of designs**, export of **waveforms to VCD**, and a set of **transforms\n",
18 | "that make doing netlist-level transforms and analyis directly in pyrtl easy.**"
19 | ]
20 | },
21 | {
22 | "cell_type": "code",
23 | "execution_count": null,
24 | "metadata": {
25 | "collapsed": true
26 | },
27 | "outputs": [],
28 | "source": [
29 | "import random\n",
30 | "import io\n",
31 | "import pyrtl"
32 | ]
33 | },
34 | {
35 | "cell_type": "code",
36 | "execution_count": null,
37 | "metadata": {
38 | "collapsed": true
39 | },
40 | "outputs": [],
41 | "source": [
42 | "pyrtl.reset_working_block()"
43 | ]
44 | },
45 | {
46 | "cell_type": "markdown",
47 | "metadata": {},
48 | "source": [
49 | "## Importing From Verilog"
50 | ]
51 | },
52 | {
53 | "cell_type": "markdown",
54 | "metadata": {},
55 | "source": [
56 | "Sometimes it is useful to pull in components written in Verilog to be used\n",
57 | "as subcomponents of PyRTL designs or to be subject to analysis written over\n",
58 | "the PyRTL core. One standard format supported by PyRTL is **\"blif\" format:**\n",
59 | "https://www.ece.cmu.edu/~ee760/760docs/blif.pdf\n",
60 | "\n",
61 | "Many tools support outputing hardware designs to this format, including the\n",
62 | "free open source project \"Yosys\". Blif files can then be imported either\n",
63 | "as a string or directly from a file name by the function input_from_blif.\n",
64 | "Here is a simple example of a **1 bit full adder imported and then simulated**\n",
65 | "from this blif format."
66 | ]
67 | },
68 | {
69 | "cell_type": "code",
70 | "execution_count": null,
71 | "metadata": {
72 | "collapsed": true
73 | },
74 | "outputs": [],
75 | "source": [
76 | "full_adder_blif = \"\"\"\n",
77 | "# Generated by Yosys 0.3.0+ (git sha1 7e758d5, clang 3.4-1ubuntu3 -fPIC -Os)\n",
78 | ".model full_adder\n",
79 | ".inputs x y cin\n",
80 | ".outputs sum cout\n",
81 | ".names $false\n",
82 | ".names $true\n",
83 | "1\n",
84 | ".names y $not$FA.v:12$3_Y\n",
85 | "0 1\n",
86 | ".names x $not$FA.v:11$1_Y\n",
87 | "0 1\n",
88 | ".names cin $not$FA.v:15$6_Y\n",
89 | "0 1\n",
90 | ".names ind3 ind4 sum\n",
91 | "1- 1\n",
92 | "-1 1\n",
93 | ".names $not$FA.v:15$6_Y ind2 ind3\n",
94 | "11 1\n",
95 | ".names x $not$FA.v:12$3_Y ind1\n",
96 | "11 1\n",
97 | ".names ind2 $not$FA.v:16$8_Y\n",
98 | "0 1\n",
99 | ".names cin $not$FA.v:16$8_Y ind4\n",
100 | "11 1\n",
101 | ".names x y $and$FA.v:19$11_Y\n",
102 | "11 1\n",
103 | ".names ind0 ind1 ind2\n",
104 | "1- 1\n",
105 | "-1 1\n",
106 | ".names cin ind2 $and$FA.v:19$12_Y\n",
107 | "11 1\n",
108 | ".names $and$FA.v:19$11_Y $and$FA.v:19$12_Y cout\n",
109 | "1- 1\n",
110 | "-1 1\n",
111 | ".names $not$FA.v:11$1_Y y ind0\n",
112 | "11 1\n",
113 | ".end\n",
114 | "\"\"\""
115 | ]
116 | },
117 | {
118 | "cell_type": "code",
119 | "execution_count": null,
120 | "metadata": {
121 | "collapsed": true
122 | },
123 | "outputs": [],
124 | "source": [
125 | "pyrtl.input_from_blif(full_adder_blif)\n",
126 | "# have to find the actual wire vectors generated from the names in the blif file\n",
127 | "x, y, cin = [pyrtl.working_block().get_wirevector_by_name(s) for s in ['x', 'y', 'cin']]\n",
128 | "io_vectors = pyrtl.working_block().wirevector_subset((pyrtl.Input, pyrtl.Output))"
129 | ]
130 | },
131 | {
132 | "cell_type": "code",
133 | "execution_count": null,
134 | "metadata": {
135 | "collapsed": true
136 | },
137 | "outputs": [],
138 | "source": [
139 | "# we are only going to trace the input and output vectors for clarity\n",
140 | "sim_trace = pyrtl.SimulationTrace(wires_to_track=io_vectors)"
141 | ]
142 | },
143 | {
144 | "cell_type": "markdown",
145 | "metadata": {},
146 | "source": [
147 | "### Now simulate the logic with some random inputs"
148 | ]
149 | },
150 | {
151 | "cell_type": "code",
152 | "execution_count": null,
153 | "metadata": {},
154 | "outputs": [],
155 | "source": [
156 | "sim = pyrtl.Simulation(tracer=sim_trace)\n",
157 | "for i in range(15):\n",
158 | " # here we actually generate random booleans for the inputs\n",
159 | " sim.step({\n",
160 | " 'x': random.choice([0, 1]),\n",
161 | " 'y': random.choice([0, 1]),\n",
162 | " 'cin': random.choice([0, 1])\n",
163 | " })\n",
164 | "sim_trace.render_trace(symbol_len=5, segment_size=5)"
165 | ]
166 | },
167 | {
168 | "cell_type": "markdown",
169 | "metadata": {},
170 | "source": [
171 | "## Exporting to Verilog\n",
172 | "\n",
173 | "However, not only do we want to have a method to import from Verilog, we also\n",
174 | "want a way to **export it back out to Verilog** as well. To demonstrate PyRTL's\n",
175 | "ability to export in Verilog, we will create a **sample 3-bit counter**. However\n",
176 | "unlike the example in example2, we extend it to be **synchronously resetting**."
177 | ]
178 | },
179 | {
180 | "cell_type": "code",
181 | "execution_count": null,
182 | "metadata": {
183 | "collapsed": true
184 | },
185 | "outputs": [],
186 | "source": [
187 | "pyrtl.reset_working_block()\n",
188 | "\n",
189 | "zero = pyrtl.Input(1, 'zero')\n",
190 | "counter_output = pyrtl.Output(3, 'counter_output')\n",
191 | "counter = pyrtl.Register(3, 'counter')\n",
192 | "counter.next <<= pyrtl.mux(zero, counter + 1, 0)\n",
193 | "counter_output <<= counter"
194 | ]
195 | },
196 | {
197 | "cell_type": "markdown",
198 | "metadata": {},
199 | "source": [
200 | "The counter **gets 0 in the next cycle if the \"zero\" signal goes high, otherwise just\n",
201 | "counter + 1.** Note that both \"0\" and \"1\" are bit extended to the proper length and\n",
202 | "here we are making use of that native add operation.\n",
203 | "\n",
204 | "Let's dump this bad boy out\n",
205 | "to a verilog file and see what is looks like (here we are using StringIO just to\n",
206 | "print it to a string for demo purposes, most likely you will want to pass a normal\n",
207 | "open file)."
208 | ]
209 | },
210 | {
211 | "cell_type": "code",
212 | "execution_count": null,
213 | "metadata": {
214 | "collapsed": true
215 | },
216 | "outputs": [],
217 | "source": [
218 | "print(\"--- PyRTL Representation ---\")\n",
219 | "print(pyrtl.working_block())\n",
220 | "print()\n",
221 | "\n",
222 | "print(\"--- Verilog for the Counter ---\")\n",
223 | "with io.StringIO() as vfile:\n",
224 | " pyrtl.OutputToVerilog(vfile)\n",
225 | " print(vfile.getvalue())\n",
226 | "\n",
227 | "print(\"--- Simulation Results ---\")\n",
228 | "sim_trace = pyrtl.SimulationTrace([counter_output, zero])\n",
229 | "sim = pyrtl.Simulation(tracer=sim_trace)\n",
230 | "for cycle in range(15):\n",
231 | " sim.step({'zero': random.choice([0, 0, 0, 1])})\n",
232 | "sim_trace.render_trace()"
233 | ]
234 | },
235 | {
236 | "cell_type": "markdown",
237 | "metadata": {},
238 | "source": [
239 | "We already did the *\"hard\" work* of generating a test input for this simulation so\n",
240 | "we might want to reuse that work when we take this design through a verilog toolchain.\n",
241 | "The class *__OutputVerilogTestbench__ grabs the inputs used in the simulation trace\n",
242 | "and sets them up in a standard verilog testbench.*"
243 | ]
244 | },
245 | {
246 | "cell_type": "code",
247 | "execution_count": null,
248 | "metadata": {
249 | "collapsed": true
250 | },
251 | "outputs": [],
252 | "source": [
253 | "print(\"--- Verilog for the TestBench ---\")\n",
254 | "with io.StringIO() as tbfile:\n",
255 | " pyrtl.output_verilog_testbench(dest_file=tbfile, simulation_trace=sim_trace)\n",
256 | " print(tbfile.getvalue())"
257 | ]
258 | },
259 | {
260 | "cell_type": "markdown",
261 | "metadata": {},
262 | "source": [
263 | "### Now let's talk about transformations of the hardware block. \n",
264 | "Many times when you are\n",
265 | "doing some hardware-level analysis you might wish to ignore higher level things like\n",
266 | "multi-bit wirevectors, adds, concatination, etc. and just thing about wires and basic\n",
267 | "gates. **PyRTL supports \"lowering\" of designs** into this more restricted set of functionality\n",
268 | "though the function *\"synthesize\".* Once we lower a design to this form we can then **apply\n",
269 | "basic optimizations** like constant propgation and dead wire elimination as well. By\n",
270 | "printing it out to verilog we can see exactly how the design changed."
271 | ]
272 | },
273 | {
274 | "cell_type": "code",
275 | "execution_count": null,
276 | "metadata": {
277 | "collapsed": true
278 | },
279 | "outputs": [],
280 | "source": [
281 | "print(\"--- Optimized Single-bit Verilog for the Counter ---\")\n",
282 | "pyrtl.synthesize()\n",
283 | "pyrtl.optimize()\n",
284 | "\n",
285 | "with io.StringIO() as vfile:\n",
286 | " pyrtl.OutputToVerilog(vfile)\n",
287 | " print(vfile.getvalue())"
288 | ]
289 | }
290 | ],
291 | "metadata": {
292 | "kernelspec": {
293 | "display_name": "Python 3",
294 | "language": "python",
295 | "name": "python3"
296 | },
297 | "language_info": {
298 | "codemirror_mode": {
299 | "name": "ipython",
300 | "version": 3
301 | },
302 | "file_extension": ".py",
303 | "mimetype": "text/x-python",
304 | "name": "python",
305 | "nbconvert_exporter": "python",
306 | "pygments_lexer": "ipython3",
307 | "version": "3.6.3"
308 | }
309 | },
310 | "nbformat": 4,
311 | "nbformat_minor": 2
312 | }
313 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | # See PyRTL's release documentation in docs/release/README.md
2 | #
3 | # This file configures PyRTL's distribution archives, which are built and
4 | # uploaded to PyPI with GitHub actions.
5 | #
6 | # This configuration is roughly based on the "Packaging Python Projects"
7 | # tutorial at
8 | # https://packaging.python.org/en/latest/tutorials/packaging-projects/
9 |
10 | [build-system]
11 | requires = ["hatchling", "hatch-vcs"]
12 | build-backend = "hatchling.build"
13 |
14 | [project]
15 | name = "pyrtl"
16 | # hatch-vcs determines the version number from the latest git tag.
17 | dynamic = ["version"]
18 | authors = [
19 | {name="Timothy Sherwood", email="sherwood@cs.ucsb.edu"},
20 | {name="John Clow"},
21 | {name="UCSBarchlab"},
22 | ]
23 | description = "RTL-level Hardware Design and Simulation Toolkit"
24 | readme = "README.md"
25 | license = {file = "LICENSE.md"}
26 | requires-python = ">=3.8"
27 | classifiers = [
28 | "Development Status :: 4 - Beta",
29 | "Environment :: Console",
30 | "Intended Audience :: Developers",
31 | "Intended Audience :: Education",
32 | "Intended Audience :: Science/Research",
33 | "License :: OSI Approved :: BSD License",
34 | "Natural Language :: English",
35 | "Operating System :: OS Independent",
36 | "Programming Language :: Python :: 3",
37 | "Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)",
38 | "Topic :: System :: Hardware",
39 | ]
40 |
41 | [project.optional-dependencies]
42 | # Required by `input_from_blif`.
43 | blif = ["pyparsing"]
44 | # Required by `block_to_svg`.
45 | svg = ["graphviz"]
46 |
47 | [project.urls]
48 | Homepage = "http://ucsbarchlab.github.io/PyRTL/"
49 | GitHub = "https://github.com/UCSBarchlab/PyRTL"
50 | Documentation = "https://pyrtl.readthedocs.io/"
51 | Changelog = "https://github.com/UCSBarchlab/PyRTL/blob/development/CHANGELOG.md"
52 |
53 | [tool.hatch.version]
54 | source = "vcs"
55 |
--------------------------------------------------------------------------------
/pyrtl/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | # error types thrown
3 | from .pyrtlexceptions import PyrtlError
4 | from .pyrtlexceptions import PyrtlInternalError
5 |
6 | # core rtl constructs
7 | from .core import LogicNet
8 | from .core import Block
9 | from .core import PostSynthBlock
10 | from .core import working_block
11 | from .core import reset_working_block
12 | from .core import set_working_block
13 | from .core import temp_working_block
14 | from .core import set_debug_mode
15 |
16 | # convenience classes for building hardware
17 | from .wire import WireVector
18 | from .wire import Input, Output
19 | from .wire import Const
20 | from .wire import Register
21 |
22 | # helper functions
23 | from .helperfuncs import input_list
24 | from .helperfuncs import output_list
25 | from .helperfuncs import register_list
26 | from .helperfuncs import wirevector_list
27 | from .helperfuncs import log2
28 | from .helperfuncs import truncate
29 | from .helperfuncs import match_bitpattern
30 | from .helperfuncs import bitpattern_to_val
31 | from .helperfuncs import chop
32 | from .helperfuncs import val_to_signed_integer
33 | from .helperfuncs import val_to_formatted_str
34 | from .helperfuncs import formatted_str_to_val
35 | from .helperfuncs import infer_val_and_bitwidth
36 | from .helperfuncs import probe
37 | from .helperfuncs import rtl_assert
38 | from .helperfuncs import check_rtl_assertions
39 | from .helperfuncs import find_loop
40 | from .helperfuncs import find_and_print_loop
41 | from .helperfuncs import wire_struct
42 | from .helperfuncs import wire_matrix
43 | from .helperfuncs import one_hot_to_binary
44 | from .helperfuncs import binary_to_one_hot
45 |
46 | from .corecircuits import and_all_bits
47 | from .corecircuits import or_all_bits
48 | from .corecircuits import xor_all_bits
49 | from .corecircuits import rtl_any
50 | from .corecircuits import rtl_all
51 | from .corecircuits import mux
52 | from .corecircuits import select
53 | from .corecircuits import concat
54 | from .corecircuits import concat_list
55 | from .corecircuits import parity
56 | from .corecircuits import tree_reduce
57 | from .corecircuits import as_wires
58 | from .corecircuits import match_bitwidth
59 | from .corecircuits import enum_mux
60 | from .corecircuits import bitfield_update
61 | from .corecircuits import bitfield_update_set
62 | from .corecircuits import signed_add
63 | from .corecircuits import signed_sub
64 | from .corecircuits import signed_mult
65 | from .corecircuits import signed_lt
66 | from .corecircuits import signed_le
67 | from .corecircuits import signed_gt
68 | from .corecircuits import signed_ge
69 | from .corecircuits import shift_left_arithmetic
70 | from .corecircuits import shift_right_arithmetic
71 | from .corecircuits import shift_left_logical
72 | from .corecircuits import shift_right_logical
73 |
74 | # memory blocks
75 | from .memory import MemBlock
76 | from .memory import RomBlock
77 |
78 | # conditional updates
79 | from .conditional import conditional_assignment
80 | from .conditional import otherwise
81 | from .conditional import currently_under_condition
82 |
83 | # block simulation support
84 | from .simulation import Simulation
85 | from .simulation import FastSimulation
86 | from .simulation import SimulationTrace
87 | from .simulation import enum_name
88 | from .compilesim import CompiledSimulation
89 |
90 | # block visualization output formats
91 | from .visualization import output_to_trivialgraph
92 | from .visualization import graphviz_detailed_namer
93 | from .visualization import output_to_graphviz
94 | from .visualization import output_to_svg
95 | from .visualization import block_to_graphviz_string
96 | from .visualization import block_to_svg
97 | from .visualization import trace_to_html
98 | from .visualization import net_graph
99 |
100 | # import from and export to file format routines
101 | from .importexport import input_from_verilog
102 | from .importexport import output_to_verilog
103 | from .importexport import OutputToVerilog
104 | from .importexport import output_verilog_testbench
105 | from .importexport import input_from_blif
106 | from .importexport import output_to_firrtl
107 | from .importexport import input_from_iscas_bench
108 |
109 | # different transform passes
110 | from .passes import common_subexp_elimination
111 | from .passes import constant_propagation
112 | from .passes import synthesize
113 | from .passes import nand_synth
114 | from .passes import and_inverter_synth
115 | from .passes import optimize
116 | from .passes import one_bit_selects
117 | from .passes import two_way_concat
118 | from .passes import direct_connect_outputs
119 | from .passes import two_way_fanout
120 |
121 | from .transform import net_transform
122 | from .transform import wire_transform
123 | from .transform import copy_block
124 | from .transform import clone_wire
125 | from .transform import replace_wires
126 | from .transform import replace_wire_fast
127 |
128 | # analysis and estimation functions
129 | from .analysis import area_estimation
130 | from .analysis import TimingAnalysis
131 | from .analysis import yosys_area_delay
132 | from .analysis import paths
133 | from .analysis import distance
134 | from .analysis import fanout
135 |
--------------------------------------------------------------------------------
/pyrtl/pyrtlexceptions.py:
--------------------------------------------------------------------------------
1 | """ The set of error types thrown by PyRTL. """
2 |
3 | # -----------------------------------------------------------------
4 | # ___ __ __ __ __ ___ __ ___ __
5 | # |__ |__) |__) / \ |__) | \ / |__) |__ /__`
6 | # |___ | \ | \ \__/ | \ | | | |___ .__/
7 | #
8 |
9 |
10 | class PyrtlError(Exception):
11 | """ Raised on any user-facing error in this module. """
12 | pass
13 |
14 |
15 | class PyrtlInternalError(Exception):
16 | """ Raised on any PyRTL internal failure. """
17 | pass
18 |
--------------------------------------------------------------------------------
/pyrtl/rtllib/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSBarchlab/PyRTL/d5f8dbe53f54e61e1d54722449e4894b885243c7/pyrtl/rtllib/__init__.py
--------------------------------------------------------------------------------
/pyrtl/rtllib/barrel.py:
--------------------------------------------------------------------------------
1 | import pyrtl
2 | import math
3 |
4 |
5 | def barrel_shifter(bits_to_shift, bit_in, direction, shift_dist, wrap_around=0):
6 | """ Create a barrel shifter that operates on data based on the wire width.
7 |
8 | :param bits_to_shift: the input wire
9 | :param bit_in: the 1-bit wire giving the value to shift in
10 | :param direction: a one bit WireVector representing shift direction
11 | (0 = shift down, 1 = shift up)
12 | :param shift_dist: WireVector representing offset to shift
13 | :param wrap_around: ****currently not implemented****
14 | :return: shifted WireVector
15 | """
16 | from pyrtl import concat, select # just for readability
17 |
18 | if wrap_around != 0:
19 | raise NotImplementedError
20 |
21 | # Implement with logN stages pyrtl.muxing between shifted and un-shifted values
22 | final_width = len(bits_to_shift)
23 | val = bits_to_shift
24 | append_val = bit_in
25 |
26 | for i in range(len(shift_dist)):
27 | shift_amt = pow(2, i) # stages shift 1,2,4,8,...
28 | if shift_amt < final_width:
29 | newval = select(direction,
30 | concat(val[:-shift_amt], append_val), # shift up
31 | concat(append_val, val[shift_amt:])) # shift down
32 | val = select(shift_dist[i],
33 | truecase=newval, # if bit of shift is 1, do the shift
34 | falsecase=val) # otherwise, don't
35 | # the value to append grows exponentially, but is capped at full width
36 | append_val = concat(append_val, append_val)[:final_width]
37 | else:
38 | # if we are shifting this much, all the data is gone
39 | val = select(shift_dist[i],
40 | truecase=append_val, # if bit of shift is 1, do the shift
41 | falsecase=val) # otherwise, don't
42 |
43 | return val
44 |
--------------------------------------------------------------------------------
/pyrtl/rtllib/libutils.py:
--------------------------------------------------------------------------------
1 | import pyrtl
2 |
3 |
4 | def match_bitwidth(*args):
5 | # TODO: allow for custom bit extension functions
6 | """ Matches the bitwidth of all of the input arguments.
7 |
8 | :param WireVector args: input arguments
9 | :return: tuple of `args` in order with extended bits
10 | """
11 | return pyrtl.match_bitwidth(*args)
12 |
13 |
14 | def partition_wire(wire, partition_size):
15 | """ Partitions a wire into a list of N wires of size `partition_size`.
16 |
17 | :param wire: Wire to partition
18 | :param partition_size: Integer representing size of each partition
19 |
20 | The `wire`'s bitwidth must be evenly divisible by `parition_size`.
21 | """
22 | if len(wire) % partition_size != 0:
23 | raise pyrtl.PyrtlError("Wire {} cannot be evenly partitioned into items of size {}"
24 | .format(wire, partition_size))
25 | return [wire[offset:offset + partition_size] for offset in range(0, len(wire), partition_size)]
26 |
27 |
28 | def str_to_int_array(string, base=16):
29 | """
30 | Converts a string to an array of integer values according to the
31 | base specified (int numbers must be whitespace delimited).\n
32 | Example: ``"13 a3 3c" => [0x13, 0xa3, 0x3c]``
33 |
34 | :return: [int]
35 | """
36 |
37 | int_strings = string.split()
38 | return [int(int_str, base) for int_str in int_strings]
39 |
40 |
41 | def twos_comp_repr(val, bitwidth):
42 | """Converts a value to its two's-complement (positive) integer
43 | representation using a given bitwidth (only converts the value if it is
44 | negative).
45 |
46 | :param val: Integer literal to convert to two's complement
47 | :param bitwidth: Size of val in bits
48 |
49 | For use with :py:meth:`.Simulation.step` etc. in passing negative numbers,
50 | which it does not accept.
51 |
52 | """
53 | correctbw = abs(val).bit_length() + 1
54 | if bitwidth < correctbw:
55 | raise pyrtl.PyrtlError("please choose a larger target bitwidth")
56 | if val >= 0:
57 | return val
58 | else:
59 | return (~abs(val) & (2 ** bitwidth - 1)) + 1 # flip the bits and add one
60 |
61 |
62 | def rev_twos_comp_repr(val, bitwidth):
63 | """
64 | Takes a two's-complement represented value and
65 | converts it to a signed integer based on the provided `bitwidth`.
66 | For use with :py:meth:`.Simulation.inspect` etc. when expecting negative numbers,
67 | which it does not recognize
68 | """
69 | valbl = val.bit_length()
70 | if bitwidth < val.bit_length() or val == 2 ** (bitwidth - 1):
71 | raise pyrtl.PyrtlError("please choose a larger target bitwidth")
72 | if bitwidth == valbl: # MSB is a 1, value is negative
73 | return -((~val & (2 ** bitwidth - 1)) + 1) # flip the bits, add one, and make negative
74 | else:
75 | return val
76 |
77 |
78 | def _shifted_reg_next(reg, direct, num=1):
79 | """
80 | Creates a shifted 'next' property for shifted (left or right) register.\n
81 | Use: `myReg.next = shifted_reg_next(myReg, 'l', 4)`
82 |
83 | :param string direct: direction of shift, either 'l' or 'r'
84 | :param int num: number of shifts
85 | :return: Register containing reg's (shifted) next state
86 | """
87 | if direct == 'l':
88 | if num >= len(reg):
89 | return 0
90 | else:
91 | return pyrtl.concat(reg, pyrtl.Const(0, num))
92 | elif direct == 'r':
93 | if num >= len(reg):
94 | return 0
95 | else:
96 | return reg[num:]
97 | else:
98 | raise pyrtl.PyrtlError("direction must be specified with 'direct'"
99 | "parameter as either 'l' or 'r'")
100 |
--------------------------------------------------------------------------------
/pyrtl/rtllib/muxes.py:
--------------------------------------------------------------------------------
1 | import pyrtl
2 |
3 |
4 | def prioritized_mux(selects, vals):
5 | """ Returns the value in the first wire for which its select bit is 1
6 |
7 | :param [WireVector] selects: a list of WireVectors signaling whether
8 | a wire should be chosen
9 | :param [WireVector] vals: values to return when the corresponding select
10 | value is 1
11 | :return: WireVector
12 |
13 | If none of the `selects` are high, the last `val` is returned
14 | """
15 | if len(selects) != len(vals):
16 | raise pyrtl.PyrtlError("Number of select and val signals must match")
17 | if len(vals) == 0:
18 | raise pyrtl.PyrtlError("Must have a signal to mux")
19 | if len(vals) == 1:
20 | return vals[0]
21 | else:
22 | half = len(vals) // 2
23 | return pyrtl.select(pyrtl.rtl_any(*selects[:half]),
24 | truecase=prioritized_mux(selects[:half], vals[:half]),
25 | falsecase=prioritized_mux(selects[half:], vals[half:]))
26 |
27 |
28 | def _is_equivalent(w1, w2):
29 | if isinstance(w1, pyrtl.Const) & isinstance(w2, pyrtl.Const):
30 | return (w1.val == w2.val) & (w1.bitwidth == w2.bitwidth)
31 | return w1 is w2
32 |
33 |
34 | SparseDefault = "default"
35 |
36 |
37 | def sparse_mux(sel, vals):
38 | """ Mux that avoids instantiating unnecessary mux_2s when possible.
39 |
40 | :param WireVector sel: Select wire, determines what is selected on a given cycle
41 | :param dict[int, WireVector] vals: dictionary of values at mux inputs
42 | :return: WireVector that signifies the change
43 |
44 | This mux supports not having a full specification. Indices that are not
45 | specified are treated as don't-cares
46 |
47 | It also supports a specified default value, SparseDefault
48 | """
49 | import numbers
50 |
51 | max_val = 2**len(sel) - 1
52 | if SparseDefault in vals:
53 | default_val = vals[SparseDefault]
54 | del vals[SparseDefault]
55 | for i in range(max_val + 1):
56 | if i not in vals:
57 | vals[i] = default_val
58 |
59 | for key in vals.keys():
60 | if not isinstance(key, numbers.Integral):
61 | raise pyrtl.PyrtlError("value %s nust be either an integer or 'default'" % str(key))
62 | if key < 0 or key > max_val:
63 | raise pyrtl.PyrtlError("value %s is out of range of the sel wire" % str(key))
64 |
65 | return _sparse_mux(sel, vals)
66 |
67 |
68 | def _sparse_mux(sel, vals):
69 | """ Mux that avoids instantiating unnecessary mux_2s when possible.
70 |
71 | :param WireVector sel: Select wire, determines what is selected on a given cycle
72 | :param {int: WireVector} vals: dictionary to store the values that are
73 | :return: Wirevector that signifies the change
74 |
75 | This mux supports not having a full specification. indices that are not
76 | specified are treated as Don't Cares
77 | """
78 | items = list(vals.values())
79 | if len(vals) <= 1:
80 | if len(vals) == 0:
81 | raise pyrtl.PyrtlError("Needs at least one parameter for val")
82 | return items[0]
83 |
84 | if len(sel) == 1:
85 | try:
86 | false_result = vals[0]
87 | true_result = vals[1]
88 | except KeyError:
89 | raise pyrtl.PyrtlError("Failed to retrieve values for smartmux. "
90 | "The length of sel might be wrong")
91 | else:
92 | half = 2**(len(sel) - 1)
93 |
94 | first_dict = {indx: wire for indx, wire in vals.items() if indx < half}
95 | second_dict = {indx - half: wire for indx, wire in vals.items() if indx >= half}
96 | if not len(first_dict):
97 | return sparse_mux(sel[:-1], second_dict)
98 | if not len(second_dict):
99 | return sparse_mux(sel[:-1], first_dict)
100 |
101 | false_result = sparse_mux(sel[:-1], first_dict)
102 | true_result = sparse_mux(sel[:-1], second_dict)
103 | if _is_equivalent(false_result, true_result):
104 | return true_result
105 | return pyrtl.select(sel[-1], falsecase=false_result, truecase=true_result)
106 |
107 |
108 | class MultiSelector(object):
109 | """ The MultiSelector allows you to specify multiple wire value results
110 | for a single select wire.
111 |
112 | Useful for processors, finite state machines and other places where the
113 | result of many wire values are determined by a common wire signal
114 | (such as a 'state' wire).
115 |
116 | Example::
117 |
118 | with muxes.MultiSelector(select, res0, res1, res2, ...) as ms:
119 | ms.option(val1, data0, data1, data2, ...)
120 | ms.option(val2, data0_2, data1_2, data2_2, ...)
121 |
122 | This means that when the ``select`` wire equals the ``val1`` wire
123 | the results will have the values in ``data0, data1, data2, ...``
124 | (all ints are converted to wires)
125 | """
126 | def __init__(self, signal_wire, *dest_wires):
127 | self._final = False
128 | self.dest_wires = dest_wires
129 | self.signal_wire = signal_wire
130 | self.instructions = []
131 | self.dest_instrs_info = {dest_w: [] for dest_w in dest_wires}
132 |
133 | def __enter__(self):
134 | """ For compatibility with `with` statements, which is the recommended
135 | method of using a MultiSelector.
136 | """
137 | return self
138 |
139 | def __exit__(self, exc_type, exc_val, exc_tb):
140 | if exc_type is None:
141 | self.finalize()
142 | else:
143 | print("The MultiSelector was not finalized due to uncaught exception")
144 |
145 | def _check_finalized(self):
146 | if self._final:
147 | raise pyrtl.PyrtlError("Cannot change InstrConnector, already finalized")
148 |
149 | def option(self, select_val, *data_signals):
150 | self._check_finalized()
151 | instr, ib = pyrtl.infer_val_and_bitwidth(select_val, self.signal_wire.bitwidth)
152 | if instr in self.instructions:
153 | raise pyrtl.PyrtlError("instruction %s already exists" % str(select_val))
154 | self.instructions.append(instr)
155 | self._add_signal(data_signals)
156 |
157 | def default(self, *data_signals):
158 | self._check_finalized()
159 | self.instructions.append(SparseDefault)
160 | self._add_signal(data_signals)
161 |
162 | def _add_signal(self, data_signals):
163 | self._check_finalized()
164 | if len(data_signals) != len(self.dest_wires):
165 | raise pyrtl.PyrtlError("Incorrect number of data_signals for "
166 | "instruction received {} , expected {}"
167 | .format(len(data_signals), len(self.dest_wires)))
168 |
169 | for dw, sig in zip(self.dest_wires, data_signals):
170 | data_signal = pyrtl.as_wires(sig, dw.bitwidth)
171 | self.dest_instrs_info[dw].append(data_signal)
172 |
173 | def finalize(self):
174 | """ Connects the wires.
175 | """
176 | self._check_finalized()
177 | self._final = True
178 |
179 | for dest_w, values in self.dest_instrs_info.items():
180 | mux_vals = dict(zip(self.instructions, values))
181 | dest_w <<= sparse_mux(self.signal_wire, mux_vals)
182 |
183 |
184 | def demux(select):
185 | """ Demultiplexes a wire of arbitrary bitwidth
186 |
187 | :param WireVector select: indicates which wire to set on
188 | :return (WireVector, ...): a tuple of wires corresponding to each demultiplexed wire
189 | """
190 | if len(select) == 1:
191 | return _demux_2(select)
192 |
193 | wires = demux(select[:-1])
194 | sel = select[-1]
195 | not_select = ~sel
196 | zero_wires = tuple(not_select & w for w in wires)
197 | one_wires = tuple(sel & w for w in wires)
198 | return zero_wires + one_wires
199 |
200 |
201 | def _demux_2(select):
202 | assert len(select) == 1
203 | return ~select, select
204 |
--------------------------------------------------------------------------------
/pyrtl/rtllib/testingutils.py:
--------------------------------------------------------------------------------
1 | import pyrtl
2 | import random
3 |
4 | """ testcase_utils
5 |
6 | This file (intentionally misspelled) is created to store common utility
7 | functions used for the test cases.
8 |
9 | I am documenting this rather well because users have
10 | a good reason to look at it - John Clow
11 | """
12 |
13 |
14 | def calcuate_max_and_min_bitwidths(max_bitwidth=None, exact_bitwidth=None):
15 | if max_bitwidth is not None:
16 | min_bitwidth = 1
17 | elif exact_bitwidth is not None:
18 | min_bitwidth = max_bitwidth = exact_bitwidth
19 | else:
20 | raise pyrtl.PyrtlError("A max or exact bitwidth must be specified")
21 | return min_bitwidth, max_bitwidth
22 |
23 |
24 | def inverse_power_dist(bitwidth):
25 | # Note that this is not uniformly distributed
26 | return int(2**random.uniform(0, bitwidth) - 1)
27 |
28 |
29 | def uniform_dist(bitwidth):
30 | return random.randrange(2**bitwidth)
31 |
32 |
33 | def make_inputs_and_values(num_wires, max_bitwidth=None, exact_bitwidth=None,
34 | dist=uniform_dist, test_vals=20):
35 | """ Generates multiple input wires and sets of test values for
36 | testing purposes
37 |
38 | :param function dist: function to generate the random values
39 | :return: wires; list of values for the wires
40 |
41 | The list of values is a list of lists. The interior lists represent the
42 | values of a single wire for all of the simulation cycles
43 |
44 | """
45 | min_bitwidth, max_bitwidth = calcuate_max_and_min_bitwidths(max_bitwidth, exact_bitwidth)
46 | wires, vals = list(zip(*(
47 | an_input_and_vals(random.randrange(min_bitwidth, max_bitwidth + 1), test_vals,
48 | random_dist=dist) for i in range(num_wires))))
49 | return wires, vals
50 |
51 |
52 | def an_input_and_vals(bitwidth, test_vals=20, name='',
53 | random_dist=uniform_dist):
54 | """ Generates an input wire and a set of test values for
55 | testing purposes
56 |
57 | :param bitwidth: The bitwidth of the value to be generated
58 | :param int test_vals: number of values to generate per wire
59 | :param name: name for the input wire to be generated
60 | :return: tuple of `input_wire`, `test_values`
61 | """
62 | input_wire = pyrtl.Input(bitwidth, name=name) # Creating a new input wire
63 | test_vals = [random_dist(bitwidth) for i in range(test_vals)]
64 | return input_wire, test_vals
65 |
66 |
67 | # deprecated name
68 | generate_in_wire_and_values = an_input_and_vals
69 |
70 |
71 | def make_consts(num_wires, max_bitwidth=None, exact_bitwidth=None, random_dist=inverse_power_dist):
72 | """
73 | :return: [Const_wires]; [Const_vals]
74 | """
75 | min_bitwidth, max_bitwidth = calcuate_max_and_min_bitwidths(max_bitwidth, exact_bitwidth)
76 | bitwidths = [random.randrange(min_bitwidth, max_bitwidth + 1) for i in range(num_wires)]
77 | wires = [pyrtl.Const(random_dist(b), b) for b in bitwidths]
78 | vals = [w.val for w in wires]
79 | return wires, vals
80 |
81 |
82 | def sim_and_ret_out(outwire, inwires, invals):
83 | """ Simulates the net using `inwires` and `invals`, and returns the output array.
84 | Used for rapid test development.
85 |
86 | :param outwire: The wire to return the output of
87 | :param inwires: a list of wires to read in from (`[Input, ...]`)
88 | :param invals: a list of input value lists (`[ [int, ...], ...]`)
89 | :return: a list of values from the output wire simulation result
90 | """
91 | # Pulling the value of outwire straight from the log
92 | return sim_and_ret_outws(inwires, invals)[outwire.name]
93 |
94 |
95 | def sim_and_ret_outws(inwires, invals):
96 | """ Simulates the net using `inwires` and `invals`, and returns the output array.
97 | Used for rapid test development.
98 |
99 | :param inwires: a list of wires to read in from (`[Input, ...]`)
100 | :param invals: a list of input value lists (`[[int, ...], ...]`)
101 | :return: a list of values from the output wire simulation result
102 | """
103 | sim_trace = pyrtl.SimulationTrace() # Creating a logger for the simulator
104 | sim = pyrtl.Simulation(tracer=sim_trace) # Creating the simulation
105 | for cycle in range(len(invals[0])):
106 | sim.step({wire.name: val[cycle] for wire, val in zip(inwires, invals)})
107 |
108 | return sim_trace.trace # Pulling the value of wires straight from the trace
109 |
110 |
111 | def sim_multicycle(in_dict, hold_dict, hold_cycles, sim=None):
112 | # TODO: write param and return descriptions
113 | """ Simulation of a circuit that takes multiple cycles to complete.
114 |
115 | :param in_dict:
116 | :param hold_dict:
117 | :param hold_cycles:
118 | :param sim:
119 | :return:
120 | """
121 | if sim is None:
122 | sim = pyrtl.Simulation(tracer=pyrtl.SimulationTrace())
123 | sim.step(in_dict)
124 | for i in range(hold_cycles):
125 | sim.step(hold_dict)
126 | return sim.tracer.trace[-1]
127 |
128 |
129 | def multi_sim_multicycle(in_dict, hold_dict, hold_cycles, sim=None):
130 | # TODO: write param and return descriptions
131 | """ Simulates a circuit that takes multiple cycles to complete multiple times.
132 |
133 | :param in_dict: `{in_wire: [in_values, ...], ...}`
134 | :param hold_dict: `{hold_wire: hold_value}` The hold values for the
135 | :param hold_cycles:
136 | :param sim:
137 | :return:
138 | """
139 | if sim is None:
140 | sim = pyrtl.Simulation(tracer=pyrtl.SimulationTrace())
141 | cycles = len(list(in_dict.values())[0])
142 | for cycle in range(cycles):
143 | current_dict = {wire: values[cycle] for wire, values in in_dict}
144 | cur_result = sim_multicycle(current_dict, hold_dict, hold_cycles, sim)
145 | if cycle == 0:
146 | results = {wire: [result_val] for wire, result_val in cur_result}
147 | else:
148 | for wire, result_val in cur_result:
149 | results[wire].append(result_val)
150 | return results
151 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | pycodestyle
2 | pylint
3 | pyparsing
4 | pytest
5 | pytest-cov
6 | pytest-xdist
7 | tox
8 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSBarchlab/PyRTL/d5f8dbe53f54e61e1d54722449e4894b885243c7/tests/__init__.py
--------------------------------------------------------------------------------
/tests/rtllib/test_adders.py:
--------------------------------------------------------------------------------
1 | import random
2 | import unittest
3 |
4 | import pyrtl
5 | import pyrtl.rtllib.testingutils as utils
6 | from pyrtl.rtllib import adders
7 |
8 |
9 | class TestAdders(unittest.TestCase):
10 |
11 | @classmethod
12 | def setUpClass(cls):
13 | random.seed(8492049)
14 |
15 | def setUp(self):
16 | pyrtl.reset_working_block()
17 |
18 | def tearDown(self):
19 | pyrtl.reset_working_block()
20 |
21 | def adder2_t_base_1(self, adder_func):
22 | self.adder_t_base(adder_func, max_bitwidth=34, num_wires=2)
23 |
24 | def adder_t_base(self, adder_func, **kwargs):
25 | wires, vals = utils.make_inputs_and_values(dist=utils.inverse_power_dist, **kwargs)
26 | outwire = pyrtl.Output(name="test")
27 | outwire <<= adder_func(*wires)
28 |
29 | out_vals = utils.sim_and_ret_out(outwire, wires, vals)
30 | true_result = [sum(cycle_vals) for cycle_vals in zip(*vals)]
31 | self.assertEqual(out_vals, true_result)
32 |
33 | def test_kogge_stone_1(self):
34 | self.adder2_t_base_1(adders.kogge_stone)
35 |
36 | def test_ripple_1(self):
37 | self.adder2_t_base_1(adders.ripple_add)
38 |
39 | def test_carrylookahead_1(self):
40 | self.adder2_t_base_1(adders.cla_adder)
41 |
42 | def test_carry_save_1(self):
43 | self.adder_t_base(adders.carrysave_adder, exact_bitwidth=32, num_wires=3)
44 |
45 | def test_fast_group_adder_1(self):
46 | wires, vals = utils.make_inputs_and_values(max_bitwidth=12, num_wires=7,
47 | dist=utils.inverse_power_dist)
48 | outwire = pyrtl.Output(name="test")
49 | outwire <<= adders.fast_group_adder(wires)
50 |
51 | out_vals = utils.sim_and_ret_out(outwire, wires, vals)
52 | true_result = [sum(cycle_vals) for cycle_vals in zip(*vals)]
53 | self.assertEqual(out_vals, true_result)
54 |
--------------------------------------------------------------------------------
/tests/rtllib/test_barrel.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import random
3 |
4 | import pyrtl
5 | from pyrtl.rtllib import barrel
6 |
7 |
8 | class TestBarrel(unittest.TestCase):
9 | # @classmethod
10 | # def setUpClass(cls):
11 | # # this is to ensure reproducibility
12 | # random.seed(777906374)
13 |
14 | def setUp(self):
15 | pyrtl.reset_working_block()
16 | self.inp_val = pyrtl.Input(8, 'inp_val')
17 | self.inp_shift = pyrtl.Input(2, 'inp_shift')
18 | self.out_zeros = pyrtl.Output(18, 'out_zeros')
19 | self.out_ones = pyrtl.Output(18, 'out_ones')
20 |
21 | def test_shift_left(self):
22 | random.seed(777906373)
23 | zero = pyrtl.Const(0, 1)
24 | one = pyrtl.Const(1, 1)
25 | self.out_zeros <<= barrel.barrel_shifter(self.inp_val, zero, one, self.inp_shift)
26 | self.out_ones <<= barrel.barrel_shifter(self.inp_val, one, one, self.inp_shift)
27 | sim_trace = pyrtl.SimulationTrace()
28 | sim = pyrtl.Simulation(tracer=sim_trace)
29 | vals = [random.randint(0, 20) for v in range(20)]
30 | shifts = [random.randint(0, 3) for s in range(20)]
31 | for i in range(len(vals)):
32 | sim.step({
33 | self.inp_val: vals[i],
34 | self.inp_shift: shifts[i]
35 | })
36 | base_sum = vals[i] * pow(2, shifts[i])
37 | self.assertEqual(sim.inspect(self.out_zeros), base_sum)
38 | self.assertEqual(sim.inspect(self.out_ones), base_sum + pow(2, shifts[i]) - 1)
39 |
40 | def test_shift_right(self):
41 | random.seed(777906374)
42 | zero = pyrtl.Const(0, 1)
43 | one = pyrtl.Const(1, 1)
44 | self.out_zeros <<= barrel.barrel_shifter(self.inp_val, zero, zero, self.inp_shift)
45 | self.out_ones <<= barrel.barrel_shifter(self.inp_val, one, zero, self.inp_shift)
46 | sim_trace = pyrtl.SimulationTrace()
47 | sim = pyrtl.Simulation(tracer=sim_trace)
48 | vals = [random.randint(0, 20) for v in range(20)]
49 | shifts = [random.randint(0, 3) for s in range(20)]
50 | for i in range(len(vals)):
51 | sim.step({
52 | self.inp_val: vals[i],
53 | self.inp_shift: shifts[i]
54 | })
55 | base_sum = int(vals[i] / pow(2, shifts[i]))
56 | self.assertEqual(sim.inspect(self.out_zeros), base_sum, "failed on value %d" % vals[i])
57 | extra_sum = sum([pow(2, len(self.inp_val) - b - 1) for b in range(shifts[i])])
58 | self.assertEqual(sim.inspect(self.out_ones), base_sum + extra_sum,
59 | "failed on value %d" % vals[i])
60 |
--------------------------------------------------------------------------------
/tests/rtllib/test_libutils.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import pyrtl
4 | import pyrtl.rtllib.testingutils as utils
5 | from pyrtl.rtllib import libutils
6 |
7 |
8 | class TestPartitionWire(unittest.TestCase):
9 |
10 | def test_successful_partition(self):
11 | w = pyrtl.WireVector(24)
12 | partitioned_w = libutils.partition_wire(w, 4)
13 | self.assertEqual(len(partitioned_w), 6)
14 | for wire in partitioned_w:
15 | self.assertIsInstance(wire, pyrtl.WireVector)
16 |
17 | def test_failing_partition(self):
18 | w = pyrtl.WireVector(14)
19 | with self.assertRaises(pyrtl.PyrtlError):
20 | partitioned_w = libutils.partition_wire(w, 4)
21 |
22 | def test_partition_sim(self):
23 | pyrtl.reset_working_block()
24 | wires, vals = utils.make_inputs_and_values(exact_bitwidth=32, num_wires=1)
25 | out_wires = [pyrtl.Output(8, 'o' + str(i)) for i in range(4)]
26 | partitioned_w = libutils.partition_wire(wires[0], 8)
27 | for p_wire, o_wire in zip(partitioned_w, out_wires):
28 | o_wire <<= p_wire
29 |
30 | out_vals = utils.sim_and_ret_outws(wires, vals)
31 | partitioned_vals = [[(val >> i) & 0xff for i in (0, 8, 16, 24)] for val in vals[0]]
32 | true_vals = tuple(zip(*partitioned_vals))
33 | for index, wire in enumerate(out_wires):
34 | self.assertEqual(tuple(out_vals[wire.name]), true_vals[index])
35 |
36 |
37 | class TestStringConversion(unittest.TestCase):
38 |
39 | def test_simple_conversion(self):
40 | self.assertEqual([0xa7, 0x23], libutils.str_to_int_array("a7 23"))
41 |
42 | def test_binary_conversion(self):
43 | result = libutils.str_to_int_array("0100 0110 010", base=2)
44 | self.assertEqual(result, [4, 6, 2])
45 |
46 | def test_empty(self):
47 | result = libutils.str_to_int_array("")
48 | self.assertEqual(result, [])
49 |
50 | def test_multiline(self):
51 | text = """
52 | 374 1c
53 | a
54 | 34 76"""
55 | result = libutils.str_to_int_array(text)
56 | self.assertEqual([0x374, 0x1c, 0xa, 0x34, 0x76], result)
57 |
58 | def test_invalid_str(self):
59 | with self.assertRaises(ValueError):
60 | libutils.str_to_int_array("hello")
61 |
62 | def test_invalid_bin_str(self):
63 | with self.assertRaises(ValueError):
64 | libutils.str_to_int_array("0313", 2)
65 |
66 | def test_no_override(self):
67 | with self.assertRaises(ValueError):
68 | libutils.str_to_int_array("0x0313", 2)
69 |
70 |
71 | class TestTwosComp(unittest.TestCase):
72 | def setUp(self):
73 | pyrtl.reset_working_block()
74 | self.in1, self.in2 = (pyrtl.Input(8, "in" + str(i)) for i in range(1, 3))
75 | self.out = pyrtl.Output(9, "out")
76 |
77 | def test_inverse_functionality(self):
78 | for i in range(20):
79 | self.assertEqual(i * 3, libutils.rev_twos_comp_repr(
80 | libutils.twos_comp_repr(i * 3, 16), 16))
81 |
82 | def test_low_bw_error(self):
83 | with self.assertRaises(pyrtl.PyrtlError):
84 | libutils.twos_comp_repr(-40, 6)
85 | with self.assertRaises(pyrtl.PyrtlError):
86 | libutils.rev_twos_comp_repr(88, 6)
87 | with self.assertRaises(pyrtl.PyrtlError):
88 | libutils.rev_twos_comp_repr(8, 4)
89 |
90 | def test_twos_comp_sim(self):
91 | self.out <<= self.in1 + self.in2
92 | sim_trace = pyrtl.SimulationTrace()
93 | sim = pyrtl.Simulation(tracer=sim_trace)
94 | for i in range(10):
95 | sim.step({
96 | 'in1': i,
97 | 'in2': libutils.twos_comp_repr(-2 * i, 8)
98 | })
99 | self.assertEqual(-i, libutils.rev_twos_comp_repr(sim.inspect('out'), 8))
100 |
--------------------------------------------------------------------------------
/tests/rtllib/test_multipliers.py:
--------------------------------------------------------------------------------
1 | import random
2 | import unittest
3 |
4 | import pyrtl
5 | import pyrtl.rtllib.testingutils as utils
6 | from pyrtl.rtllib import multipliers, adders, libutils
7 |
8 |
9 | class TestSimpleMult(unittest.TestCase):
10 |
11 | def setUp(self):
12 | pyrtl.reset_working_block()
13 |
14 | def test_trivial_case(self):
15 | self.mult_t_base(1, 5)
16 |
17 | def test_trivial_case_2(self):
18 | self.mult_t_base(2, 1)
19 |
20 | def test_trivial_case_3(self):
21 | self.mult_t_base(1, 1)
22 |
23 | def test_simple_mult_1(self):
24 | self.mult_t_base(5, 7)
25 |
26 | def test_simple_mult_2(self):
27 | self.mult_t_base(2, 9)
28 |
29 | def mult_t_base(self, len_a, len_b):
30 | a, b, reset = pyrtl.Input(len_a, "a"), pyrtl.Input(len_b, "b"), pyrtl.Input(1, 'reset')
31 | product, done = pyrtl.Output(name="product"), pyrtl.Output(name="done")
32 | m_prod, m_done = multipliers.simple_mult(a, b, reset)
33 | product <<= m_prod
34 | done <<= m_done
35 | self.assertEqual(len(product), len_a + len_b)
36 |
37 | xvals = [int(random.uniform(0, 2 ** len_a - 1)) for i in range(20)]
38 | yvals = [int(random.uniform(0, 2 ** len_b - 1)) for i in range(20)]
39 | true_result = [i * j for i, j in zip(xvals, yvals)]
40 | mult_results = []
41 |
42 | for x_val, y_val, true_res in zip(xvals, yvals, true_result):
43 | sim_trace = pyrtl.SimulationTrace()
44 | sim = pyrtl.Simulation(tracer=sim_trace)
45 | sim.step({a: x_val, b: y_val, reset: 1})
46 | for cycle in range(len(a) + 1):
47 | sim.step({a: 0, b: 0, reset: 0})
48 |
49 | # Extracting the values and verifying correctness
50 | mult_results.append(sim.inspect("product"))
51 | self.assertEqual(sim.inspect("done"), 1)
52 | self.assertEqual(mult_results, true_result)
53 |
54 |
55 | class TestComplexMult(unittest.TestCase):
56 |
57 | def setUp(self):
58 | pyrtl.reset_working_block()
59 |
60 | def test_trivial_case(self):
61 | with self.assertRaises(pyrtl.PyrtlError):
62 | self.mult_t_base(1, 5, 2)
63 |
64 | def test_trivial_case_2(self):
65 | with self.assertRaises(pyrtl.PyrtlError):
66 | self.mult_t_base(2, 1, 5)
67 |
68 | def test_trivial_case_3(self):
69 | self.mult_t_base(1, 1, 1)
70 |
71 | def test_complex_mult_1(self):
72 | self.mult_t_base(5, 7, 3)
73 |
74 | def test_complex_mult_2(self):
75 | self.mult_t_base(10, 12, 3)
76 |
77 | def test_complex_mult_3(self):
78 | with self.assertRaises(pyrtl.PyrtlError):
79 | self.mult_t_base(2, 9, 4)
80 |
81 | def test_complex_mult_4(self):
82 | with self.assertRaises(pyrtl.PyrtlError):
83 | self.mult_t_base(8, 4, 6)
84 |
85 | def mult_t_base(self, len_a, len_b, shifts):
86 | a, b = pyrtl.Input(len_a, 'a'), pyrtl.Input(len_b, 'b')
87 | reset = pyrtl.Input(1, 'reset')
88 | product, done = pyrtl.Output(name='product'), pyrtl.Output(name='done')
89 | m_prod, m_done = multipliers.complex_mult(a, b, shifts, reset)
90 | product <<= m_prod
91 | done <<= m_done
92 | self.assertEqual(len(product), len_a + len_b)
93 |
94 | xvals = [int(random.uniform(0, 2 ** len_a - 1)) for i in range(20)]
95 | yvals = [int(random.uniform(0, 2 ** len_b - 1)) for i in range(20)]
96 | true_result = [i * j for i, j in zip(xvals, yvals)]
97 | mult_results = []
98 |
99 | for x_val, y_val, true_res in zip(xvals, yvals, true_result):
100 | sim_trace = pyrtl.SimulationTrace()
101 | sim = pyrtl.Simulation(tracer=sim_trace)
102 | sim.step({a: x_val, b: y_val, reset: 1})
103 | if shifts <= len_a:
104 | length = len_a // shifts + (1 if len_a % shifts == 0 else 2)
105 | else:
106 | length = len_a + 1
107 | for cycle in range(length):
108 | sim.step({a: 0, b: 0, reset: 0})
109 |
110 | # Extracting the values and verifying correctness
111 | mult_results.append(sim.inspect('product'))
112 | self.assertEqual(sim.inspect('done'), 1)
113 | self.assertEqual(mult_results, true_result)
114 |
115 |
116 | class TestWallace(unittest.TestCase):
117 |
118 | @classmethod
119 | def setUpClass(cls):
120 | # this is to ensure reproducibility
121 | random.seed(777906376)
122 |
123 | def setUp(self):
124 | pyrtl.reset_working_block()
125 |
126 | def mult_t_base(self, len_a, len_b, **mult_args):
127 | # Creating the logic nets
128 | a, b = pyrtl.Input(len_a, "a"), pyrtl.Input(len_b, "b")
129 | product = pyrtl.Output(name="product")
130 | product <<= multipliers.tree_multiplier(a, b, **mult_args)
131 |
132 | self.assertEqual(len(product), len_a + len_b)
133 |
134 | # creating the testing values and the correct results
135 | xvals = [int(random.uniform(0, 2 ** len_a - 1)) for i in range(20)]
136 | yvals = [int(random.uniform(0, 2 ** len_b - 1)) for i in range(20)]
137 | true_result = [i * j for i, j in zip(xvals, yvals)]
138 |
139 | # Setting up and running the tests
140 | sim_trace = pyrtl.SimulationTrace()
141 | sim = pyrtl.Simulation(tracer=sim_trace)
142 | for cycle in range(len(xvals)):
143 | sim.step({a: xvals[cycle], b: yvals[cycle]})
144 |
145 | # Extracting the values and verifying correctness
146 | multiplier_result = sim_trace.trace[product.name]
147 | self.assertEqual(multiplier_result, true_result)
148 |
149 | def test_trivial_case(self):
150 | self.mult_t_base(1, 5)
151 |
152 | def test_trivial_case_2(self):
153 | self.mult_t_base(2, 1)
154 |
155 | def test_trivial_case_3(self):
156 | self.mult_t_base(1, 1)
157 |
158 | def test_wallace_tree_1(self):
159 | self.mult_t_base(5, 7)
160 |
161 | def test_wallace_tree_2(self):
162 | self.mult_t_base(2, 9)
163 |
164 | def test_dada_tree(self):
165 | self.mult_t_base(5, 10, reducer=adders.dada_reducer)
166 |
167 | def test_fma_1(self):
168 | wires, vals = utils.make_inputs_and_values(exact_bitwidth=10, num_wires=3,
169 | dist=utils.inverse_power_dist)
170 | test_w = multipliers.fused_multiply_adder(wires[0], wires[1], wires[2], False,
171 | reducer=adders.dada_reducer,
172 | adder_func=adders.ripple_add)
173 | self.assertEqual(len(test_w), 20)
174 | outwire = pyrtl.Output(21, "test")
175 | outwire <<= test_w
176 |
177 | out_vals = utils.sim_and_ret_out(outwire, wires, vals)
178 | true_result = [vals[0][cycle] * vals[1][cycle] + vals[2][cycle]
179 | for cycle in range(len(vals[0]))]
180 | self.assertEqual(out_vals, true_result)
181 |
182 | def test_gen_fma_1(self):
183 | wires, vals = utils.make_inputs_and_values(max_bitwidth=8, num_wires=8,
184 | dist=utils.inverse_power_dist)
185 | # mixing tuples and lists solely for readability purposes
186 | mult_pairs = [(wires[0], wires[1]), (wires[2], wires[3]), (wires[4], wires[5])]
187 | add_wires = (wires[6], wires[7])
188 |
189 | outwire = pyrtl.Output(name="test")
190 | outwire <<= multipliers.generalized_fma(mult_pairs, add_wires, signed=False)
191 |
192 | out_vals = utils.sim_and_ret_out(outwire, wires, vals)
193 | true_result = [vals[0][cycle] * vals[1][cycle] + vals[2][cycle] * vals[3][cycle]
194 | + vals[4][cycle] * vals[5][cycle] + vals[6][cycle] + vals[7][cycle]
195 | for cycle in range(len(vals[0]))]
196 | self.assertEqual(out_vals, true_result)
197 |
198 |
199 | class TestSignedTreeMult(unittest.TestCase):
200 | @classmethod
201 | def setUpClass(cls):
202 | # this is to ensure reproducibility
203 | random.seed(777906375)
204 |
205 | def setUp(self):
206 | pyrtl.reset_working_block()
207 |
208 | def mult_t_base(self, len_a, len_b, **mult_args):
209 | # Creating the logic nets
210 | a, b = pyrtl.Input(len_a, "a"), pyrtl.Input(len_b, "b")
211 | product = pyrtl.Output(name="product")
212 | product <<= multipliers.signed_tree_multiplier(a, b, **mult_args)
213 |
214 | self.assertEqual(len(product), len_a + len_b)
215 |
216 | # creating the testing values and the correct results
217 | bound_a = 2 ** (len_a - 1) - 1
218 | bound_b = 2 ** (len_b - 1) - 1
219 | xvals = [int(random.uniform(-bound_a, bound_a)) for i in range(20)]
220 | yvals = [int(random.uniform(-bound_b, bound_b)) for i in range(20)]
221 | true_result = [i * j for i, j in zip(xvals, yvals)]
222 |
223 | # Setting up and running the tests
224 | sim_trace = pyrtl.SimulationTrace()
225 | sim = pyrtl.Simulation(tracer=sim_trace)
226 | for cycle in range(len(xvals)):
227 | sim.step({
228 | a: libutils.twos_comp_repr(xvals[cycle], len_a),
229 | b: libutils.twos_comp_repr(yvals[cycle], len_b)
230 | })
231 |
232 | # Extracting the values and verifying correctness
233 | multiplier_result = [libutils.rev_twos_comp_repr(p, len(product))
234 | for p in sim_trace.trace[product.name]]
235 | self.assertEqual(multiplier_result, true_result)
236 |
237 | def test_small_bitwidth_error(self):
238 | with self.assertRaises(pyrtl.PyrtlError):
239 | self.mult_t_base(1, 1)
240 |
241 | def test_trivial_case(self):
242 | self.mult_t_base(2, 3)
243 |
244 | def test_trivial_case_2(self):
245 | self.mult_t_base(4, 4)
246 |
247 | def test_trivial_case_3(self):
248 | self.mult_t_base(3, 4)
249 |
250 | def test_wallace_tree_1(self):
251 | self.mult_t_base(10, 3)
252 |
253 | def test_wallace_tree_2(self):
254 | self.mult_t_base(8, 8)
255 |
256 | def test_dada_tree(self):
257 | self.mult_t_base(5, 10, reducer=adders.dada_reducer)
258 |
--------------------------------------------------------------------------------
/tests/rtllib/test_prngs.py:
--------------------------------------------------------------------------------
1 | import random
2 | import unittest
3 | from itertools import islice
4 |
5 | import pyrtl
6 | from pyrtl.rtllib import prngs
7 |
8 |
9 | class TestPrngs(unittest.TestCase):
10 |
11 | def setUp(self):
12 | pyrtl.reset_working_block()
13 |
14 | @staticmethod
15 | def fibonacci_lfsr(seed):
16 | lfsr = seed
17 | while True:
18 | bit = (lfsr >> 126 ^ lfsr >> 125) & 1
19 | lfsr = lfsr << 1 | bit
20 | yield bit
21 |
22 | @staticmethod
23 | def xoroshiro128plus(seed):
24 | mask = 2**64 - 1
25 | s = [seed & mask, seed >> 64]
26 | while True:
27 | s0 = s[0]
28 | s1 = s[1]
29 | word = (s1 + s0) & mask
30 | s1 ^= s0
31 | s[0] = (s0 << 55 | s0 >> 9) ^ s1 ^ s1 << 14
32 | s[1] = s1 << 36 | s1 >> 28
33 | yield word
34 |
35 | def test_prng_lfsr(self):
36 | seed = pyrtl.Input(127, 'seed')
37 | load, req = pyrtl.Input(1, 'load'), pyrtl.Input(1, 'req')
38 | rand = pyrtl.Output(128, 'rand')
39 | rand <<= prngs.prng_lfsr(128, load, req, seed)
40 | sim_trace = pyrtl.SimulationTrace()
41 | sim = pyrtl.Simulation(tracer=sim_trace)
42 | in_vals = [random.randrange(1, 2**127) for i in range(5)]
43 |
44 | for trial in range(5):
45 | true_val = 0
46 | for bit in islice(TestPrngs.fibonacci_lfsr(in_vals[trial]), 128):
47 | true_val = true_val << 1 | bit
48 | sim.step({'load': 1, 'req': 0, 'seed': in_vals[trial]})
49 | sim.step({'load': 0, 'req': 1, 'seed': 0x0})
50 | sim.step({'load': 0, 'req': 0, 'seed': 0x0})
51 | circuit_out = sim.inspect(rand)
52 | self.assertEqual(circuit_out, true_val,
53 | "\nAssertion failed on trial {}\nExpected value: {}\nGotten value: {}"
54 | .format(trial, hex(true_val), hex(circuit_out)))
55 |
56 | def test_prng_xoroshiro128(self):
57 | seed = pyrtl.Input(128, 'seed')
58 | load, req = pyrtl.Input(1, 'load'), pyrtl.Input(1, 'req')
59 | ready = pyrtl.Output(1, 'ready')
60 | rand = pyrtl.Output(128, 'rand')
61 | ready_out, rand_out = prngs.prng_xoroshiro128(128, load, req, seed)
62 | ready <<= ready_out
63 | rand <<= rand_out
64 | sim_trace = pyrtl.SimulationTrace()
65 | sim = pyrtl.Simulation(tracer=sim_trace)
66 | in_vals = [random.randrange(1, 2**128) for i in range(5)]
67 |
68 | for trial in range(5):
69 | true_val = 0
70 | for word in islice(TestPrngs.xoroshiro128plus(in_vals[trial]), 2):
71 | true_val = true_val << 64 | word
72 | sim.step({'load': 1, 'req': 0, 'seed': in_vals[trial]})
73 | sim.step({'load': 0, 'req': 1, 'seed': 0x0})
74 | for cycle in range(2, 4):
75 | sim.step({'load': 0, 'req': 0, 'seed': 0x0})
76 | circuit_out = sim.inspect(rand)
77 | self.assertEqual(circuit_out, true_val,
78 | "\nAssertion failed on trial {}\nExpected value: {}\nGotten value: {}"
79 | .format(trial, hex(true_val), hex(circuit_out)))
80 |
81 | for ready_signal in sim_trace.trace['ready'][:3]:
82 | self.assertEqual(ready_signal, 0)
83 | self.assertEqual(sim_trace.trace['ready'][3], 1)
84 | self.assertEqual(sim_trace.trace['ready'][4], 0)
85 |
86 | def test_csprng_trivium(self):
87 | """
88 | Trivium test vectors retrived from:
89 | https://www.sheffield.ac.uk/polopoly_fs/1.12164!/file/eSCARGOt_full_datasheet_v1.3.pdf
90 | bit ordering is modified to adapt to the Pyrtl implementation
91 | """
92 | in_vector = pyrtl.Input(160, 'in_vector')
93 | load, req = pyrtl.Input(1, 'load'), pyrtl.Input(1, 'req')
94 | ready = pyrtl.Output(1, 'ready')
95 | out_vector = pyrtl.Output(128, 'out_vector')
96 | ready_out, rand_out = prngs.csprng_trivium(128, load, req, in_vector)
97 | ready <<= ready_out
98 | out_vector <<= rand_out
99 | sim_trace = pyrtl.SimulationTrace()
100 | sim = pyrtl.Simulation(tracer=sim_trace)
101 |
102 | in_vals = [0x0100000000000000000000000000000000000000,
103 | 0x0a09080706050403020100000000000000000000,
104 | 0xfffefdfcfbfaf9f8f7f600000000000000000000,
105 | 0xfaa75401ae5b08b5620fc760f9922bc45df68f28,
106 | 0xf5a24ffca95603b05d0abe57f08922bb54ed861f, ]
107 |
108 | true_vals = [0x1cd761ffceb05e39f5b18f5c22042ab0,
109 | 0x372e6b86524afa71b5fee86d5cebb07d,
110 | 0xc100baca274287277ff49b9fb512af1c,
111 | 0xcb5996fcff373a953fc169e899e02f46,
112 | 0xf142d1df4b36c7652cba2e4a22ee51a0, ]
113 |
114 | for trial in range(5):
115 | sim.step({'load': 1, 'req': 0, 'in_vector': in_vals[trial]})
116 | for cycle in range(1, 20):
117 | sim.step({'load': 0, 'req': 0, 'in_vector': 0x0})
118 | sim.step({'load': 0, 'req': 1, 'in_vector': 0x0})
119 | for cycle in range(21, 23):
120 | sim.step({'load': 0, 'req': 0, 'in_vector': 0x0})
121 | circuit_out = sim.inspect(out_vector)
122 | self.assertEqual(circuit_out, true_vals[trial],
123 | "\nAssertion failed on trial {}\nExpected value: {}\nGotten value: {}"
124 | .format(trial, hex(true_vals[trial]), hex(circuit_out)))
125 |
126 | for ready_signal in sim_trace.trace['ready'][:19]:
127 | self.assertEqual(ready_signal, 0)
128 | self.assertEqual(sim_trace.trace['ready'][19], 1)
129 |
130 | for ready_signal in sim_trace.trace['ready'][20:22]:
131 | self.assertEqual(ready_signal, 0)
132 | self.assertEqual(sim_trace.trace['ready'][22], 1)
133 | self.assertEqual(sim_trace.trace['ready'][23], 0)
134 |
--------------------------------------------------------------------------------
/tests/test_examples.py:
--------------------------------------------------------------------------------
1 | import glob
2 | import os
3 | import subprocess
4 | import pyrtl
5 | import pytest
6 |
7 |
8 | """
9 | Tests all of the files in the example folder
10 |
11 | Note that this file is structure dependent, so don't forget to change it if the
12 | relative location of the examples changes
13 | """
14 |
15 |
16 | @pytest.mark.parametrize(
17 | 'file',
18 | glob.iglob(os.path.dirname(__file__) + "/../examples/*.py"))
19 | def test_all_examples(file):
20 | pyrtl.reset_working_block()
21 | try:
22 | output = subprocess.check_output(['python', file])
23 | except subprocess.CalledProcessError as e:
24 | raise e
25 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | # When changing the following line, be sure to update 'python-version' in
3 | # .github/workflows/python-test.yml
4 | envlist = py{3.9,3.13}-{test}, pycodestyle
5 |
6 | [gh-actions]
7 | python =
8 | 3.9: py3.9
9 | 3.13: py3.13, pycodestyle
10 |
11 | [testenv]
12 | deps = test: -rrequirements.txt
13 | pycodestyle: pycodestyle
14 |
15 | envdir =
16 | py3.9: {toxworkdir}/3.9
17 | py3.13: {toxworkdir}/3.13
18 | pycodestyle: {toxworkdir}/pycodestyle
19 |
20 | setenv =
21 | PYTHONPATH = {toxinidir}
22 |
23 | commands =
24 | test: pytest --cov=pyrtl --cov-report=xml -n auto {posargs}
25 | test: pylint -E pyrtl/
26 | pycodestyle: pycodestyle --max-line-length=100 --ignore=W503 pyrtl/
27 | pycodestyle: pycodestyle --max-line-length=100 --ignore=W503 examples/
28 | pycodestyle: pycodestyle --max-line-length=100 --ignore=W503 tests/
29 |
--------------------------------------------------------------------------------