├── amaranth_yosys ├── __init__.py └── __main__.py ├── dependencies.txt ├── .gitmodules ├── .gitignore ├── LICENSE.txt ├── pyproject.toml ├── setup.py ├── tests └── inverter.il ├── .github └── workflows │ ├── track-wasmtime.yml │ └── package.yml └── README.rst /amaranth_yosys/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dependencies.txt: -------------------------------------------------------------------------------- 1 | importlib_resources>=1.4; python_version < "3.9" 2 | wasmtime<40,>=1 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "yosys-src"] 2 | path = yosys-src 3 | url = https://github.com/YosysHQ/yosys 4 | branch = master 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python 2 | __pycache__/ 3 | /*.egg-info 4 | /.eggs 5 | /build 6 | /dist 7 | 8 | # WASM 9 | /wasi-sdk-* 10 | *.wasm 11 | 12 | # Yosys 13 | /amaranth_yosys/share 14 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (C) 2020-2023 whitequark 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools~=78.0", "setuptools_scm[toml]~=7.0"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | dynamic = ["version", "dependencies"] 7 | 8 | name = "amaranth-yosys" 9 | description = "Specialized WebAssembly build of Yosys used by Amaranth HDL" 10 | readme = "README.rst" 11 | authors = [{name = "whitequark", email = "whitequark@whitequark.org"}] 12 | license = "ISC" 13 | 14 | requires-python = "~=3.8" 15 | 16 | [project.urls] 17 | "Source Code" = "https://github.com/amaranth-lang/amaranth-yosys" 18 | "Bug Tracker" = "https://github.com/amaranth-lang/amaranth-yosys/issues" 19 | 20 | [tool.setuptools] 21 | packages = ["amaranth_yosys"] 22 | 23 | [tool.setuptools.package-data] 24 | amaranth_yosys = ["yosys.wasm", "share/**/**/**/*"] 25 | 26 | [tool.setuptools.dynamic] 27 | dependencies = {file = "dependencies.txt"} 28 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import re 2 | import subprocess 3 | from setuptools import setup 4 | from setuptools_scm.git import parse as parse_git 5 | 6 | 7 | def version(): 8 | yosys_version_raw = subprocess.check_output([ 9 | "make", "-s", "-C", "yosys-src", "echo-yosys-ver" 10 | ], encoding="utf-8").strip() 11 | 12 | # Yosys can't figure out if it should have a patch version or not. 13 | # Match one, and add one below in our version just in case. 14 | yosys_version = re.match(r"^(\d+)\.(\d+)(?:\.(\d+))?(?:\+(\d+))?$", yosys_version_raw) 15 | 16 | yosys_major = yosys_version[1] 17 | yosys_minor = yosys_version[2] 18 | yosys_patch = yosys_version[3] or "0" 19 | yosys_node = yosys_version[4] or "0" 20 | git_distance = parse_git(".").format_with("post{distance}") 21 | 22 | return ".".join([yosys_major, yosys_minor, yosys_patch, yosys_node, git_distance]) 23 | 24 | 25 | setup( 26 | version=version(), 27 | ) 28 | -------------------------------------------------------------------------------- /amaranth_yosys/__main__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import wasmtime 4 | try: 5 | from importlib import resources as importlib_resources 6 | importlib_resources.files # py3.9+ stdlib 7 | except (ImportError, AttributeError): 8 | import importlib_resources # py3.8- shim 9 | 10 | 11 | wasm_cfg = wasmtime.Config() 12 | wasm_cfg.cache = True 13 | 14 | wasi_cfg = wasmtime.WasiConfig() 15 | wasi_cfg.argv = ("amaranth-yosys", *sys.argv[1:]) 16 | wasi_cfg.preopen_dir(".", ".") 17 | wasi_cfg.inherit_stdin() 18 | wasi_cfg.inherit_stdout() 19 | wasi_cfg.inherit_stderr() 20 | 21 | engine = wasmtime.Engine(wasm_cfg) 22 | linker = wasmtime.Linker(engine) 23 | linker.define_wasi() 24 | store = wasmtime.Store(engine) 25 | store.set_wasi(wasi_cfg) 26 | yosys = linker.instantiate(store, wasmtime.Module(engine, 27 | (importlib_resources.files(__package__) / "yosys.wasm").read_bytes())) 28 | try: 29 | yosys.exports(store)["_start"](store) 30 | except wasmtime.ExitTrap as trap: 31 | sys.exit(trap.code) 32 | -------------------------------------------------------------------------------- /tests/inverter.il: -------------------------------------------------------------------------------- 1 | attribute \generator "Amaranth" 2 | attribute \top 1 3 | attribute \amaranth.hierarchy "top" 4 | module \top 5 | attribute \src ":1" 6 | wire width 1 input 0 \i 7 | attribute \init 1'0 8 | attribute \src ":1" 9 | wire width 1 output 1 \o 10 | attribute \src ":1" 11 | wire width 1 \o$next 12 | attribute \src "/home/whitequark/Projects/amaranth/amaranth/hdl/ir.py:527" 13 | wire width 1 input 2 \clk 14 | attribute \src "/home/whitequark/Projects/amaranth/amaranth/hdl/ir.py:527" 15 | wire width 1 input 3 \rst 16 | attribute \src ":1" 17 | wire width 1 $1 18 | attribute \src ":1" 19 | cell $not $2 20 | parameter \A_SIGNED 0 21 | parameter \A_WIDTH 1 22 | parameter \Y_WIDTH 1 23 | connect \A \i 24 | connect \Y $1 25 | end 26 | process $group_0 27 | assign \o$next \o 28 | assign \o$next $1 29 | attribute \src "/home/whitequark/Projects/amaranth/amaranth/hdl/xfrm.py:516" 30 | switch \rst 31 | case 1'1 32 | assign \o$next 1'0 33 | end 34 | end 35 | cell $dff $3 36 | parameter \CLK_POLARITY 1 37 | parameter \WIDTH 1 38 | connect \CLK \clk 39 | connect \D \o$next 40 | connect \Q \o 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /.github/workflows/track-wasmtime.yml: -------------------------------------------------------------------------------- 1 | on: 2 | schedule: 3 | - cron: '0 0 * * *' 4 | name: Track wasmtime releases 5 | jobs: 6 | track-wasmtime: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Check out source code 10 | uses: actions/checkout@v4 11 | with: 12 | token: ${{ secrets.PUSH_TOKEN }} 13 | submodules: recursive 14 | - name: Set up Python 15 | uses: actions/setup-python@v5 16 | with: 17 | python-version: '3.x' 18 | - name: Check for a new release and update version requirement 19 | id: track-version 20 | run: | 21 | pip install git+https://github.com/whitequark/track-pypi-dependency-version.git 22 | track-pypi-dependency-version --status $GITHUB_OUTPUT -r dependencies.txt wasmtime 23 | - name: Install dependencies 24 | if: ${{ steps.track-version.outputs.status == 'stale' }} 25 | run: | 26 | python -m pip install setuptools_scm~=7.0 # for setup.py --version 27 | sudo apt-get install flex bison ccache 28 | - name: Set up caching 29 | if: ${{ steps.track-version.outputs.status == 'stale' }} 30 | uses: actions/cache@v4 31 | with: 32 | path: ~/.cache/ccache 33 | key: ${{ runner.os }}- 34 | - name: Build WASM binaries 35 | if: ${{ steps.track-version.outputs.status == 'stale' }} 36 | run: | 37 | ./build.sh 38 | - name: Test against updated version requirement 39 | if: ${{ steps.track-version.outputs.status == 'stale' }} 40 | run: | 41 | pip install . 42 | python -m amaranth_yosys --help 43 | - name: Push updated version requirement 44 | if: ${{ steps.track-version.outputs.status == 'stale' }} 45 | uses: stefanzweifel/git-auto-commit-action@v4 46 | with: 47 | commit_message: | 48 | [autorelease] Update wasmtime version requirement from ${{ steps.track-version.outputs.old-requirement }} to ${{ steps.track-version.outputs.new-requirement }}. 49 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Amaranth Yosys distribution 2 | ########################### 3 | 4 | `Amaranth `_ is a Python-based hardware description language that uses `Yosys `_ as a backend to emit Verilog. 5 | 6 | The Amaranth HDL Yosys wheels provide a specialized `WebAssembly `_ based build of Yosys that runs via `wasmtime-py `_ if there is no system-wide Yosys installation, or if that installation is too old. This build is aggressively optimized for binary size and startup latency, and only includes features required by Amaranth's Verilog and CXXRTL backends; it is not useful for any other purpose. 7 | 8 | Although this package is platform-independent, it depends on wasmtime-py wheels, which are currently available only for x86_64 Windows, Linux, and macOS. This is expected to improve in the future. 9 | 10 | Versioning 11 | ========== 12 | 13 | The version of this package (which is derived from the upstream Yosys package version) is comprised of five parts in a ``X.Y.Z.N.postM`` format: 14 | 15 | 1. ``X``: Yosys major version 16 | 2. ``Y``: Yosys minor version 17 | 3. ``Z``: Yosys patch version; only present for some Yosys releases, zero if not present 18 | 4. ``N``: Yosys node version; only present for unreleased Yosys snapshots (where it matches the ``N`` in the ``X.Y+N`` upstream version), zero for releases 19 | 5. ``postM``: package version, monotonically incrementing from the initial commit 20 | 21 | Building 22 | ======== 23 | 24 | The primary build environment for this repository is the ``ubuntu-latest`` GitHub CI runner; packages are built on every push and automatically published from the ``release`` branch to PyPI. 25 | 26 | To reduce maintenance overhead, the only development environment we will support for this repository is x86_64 Linux. 27 | 28 | License 29 | ======= 30 | 31 | This package is covered by the `ISC license `_, which is the same as the `Yosys license `_. 32 | -------------------------------------------------------------------------------- /.github/workflows/package.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | name: Build & publish 3 | jobs: 4 | build: 5 | if: ${{ !contains(github.event.head_commit.message, 'skip ci') }} 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Check out source code 9 | uses: actions/checkout@v4 10 | with: 11 | fetch-depth: 0 12 | submodules: recursive 13 | - name: Set up Python 14 | uses: actions/setup-python@v5 15 | with: 16 | python-version: '>=3.7' 17 | - name: Install dependencies 18 | run: | 19 | python -m pip install setuptools_scm~=7.0 # for setup.py --version 20 | sudo apt-get install flex bison ccache 21 | - name: Set up caching 22 | uses: actions/cache@v4 23 | with: 24 | path: ~/.cache/ccache 25 | key: ${{ runner.os }}- 26 | - name: Set up ccache 27 | run: | 28 | ccache --max-size=2G -z 29 | - name: Build WASM binaries 30 | run: | 31 | ./build.sh 32 | - name: Build binary wheels 33 | run: | 34 | pip wheel . -w dist/ 35 | - name: Upload binary wheel artifact 36 | uses: actions/upload-artifact@v4 37 | with: 38 | name: wheel 39 | path: dist/amaranth_yosys-*.whl 40 | - name: Test binary wheels 41 | run: | 42 | pip install dist/amaranth_yosys-*.whl 43 | python -m amaranth_yosys --help 44 | python -m amaranth_yosys \ 45 | -p "read_rtlil tests/inverter.il" \ 46 | -p "proc -nomux" \ 47 | -p "memory_collect" \ 48 | -p "write_rtlil" \ 49 | -p "write_verilog" \ 50 | -p "proc" \ 51 | -p "write_json" 52 | - name: Print ccache statistics 53 | run: | 54 | ccache -s 55 | publish: 56 | needs: build 57 | runs-on: ubuntu-latest 58 | environment: publish 59 | permissions: 60 | id-token: write 61 | steps: 62 | - uses: actions/download-artifact@v4 63 | with: 64 | name: wheel 65 | path: dist/ 66 | - name: Publish wheels to Test PyPI 67 | if: ${{ github.event_name == 'push' && github.event.ref == 'refs/heads/develop' }} 68 | uses: pypa/gh-action-pypi-publish@release/v1 69 | with: 70 | repository-url: https://test.pypi.org/legacy/ 71 | - name: Publish wheels to PyPI 72 | if: ${{ github.event_name == 'push' && github.event.ref == 'refs/heads/release' }} 73 | uses: pypa/gh-action-pypi-publish@release/v1 74 | release: 75 | needs: build 76 | runs-on: ubuntu-latest 77 | if: ${{ contains(github.event.head_commit.message, 'autorelease') && github.event_name == 'push' && github.ref == 'refs/heads/develop' }} 78 | steps: 79 | - name: Check out source code 80 | uses: actions/checkout@v4 81 | with: 82 | fetch-depth: 0 83 | token: ${{ secrets.PUSH_TOKEN }} 84 | - name: Update release branch 85 | run: | 86 | git push origin develop:release 87 | --------------------------------------------------------------------------------