├── .codecov.yml ├── .coveragerc ├── .editorconfig ├── .env.toolchain ├── .gitattributes ├── .github ├── CODEOWNERS └── workflows │ └── main.yaml ├── .gitignore ├── .mailmap ├── CONTRIBUTING.txt ├── LICENSE.txt ├── README.md ├── amaranth ├── __init__.py ├── _toolchain │ ├── __init__.py │ └── yosys.py ├── _unused.py ├── _utils.py ├── asserts.py ├── back │ ├── __init__.py │ ├── cxxrtl.py │ ├── rtlil.py │ └── verilog.py ├── build │ ├── __init__.py │ ├── dsl.py │ ├── plat.py │ ├── res.py │ └── run.py ├── cli.py ├── hdl │ ├── __init__.py │ ├── _ast.py │ ├── _cd.py │ ├── _dsl.py │ ├── _ir.py │ ├── _mem.py │ ├── _nir.py │ ├── _time.py │ └── _xfrm.py ├── lib │ ├── __init__.py │ ├── cdc.py │ ├── crc │ │ ├── __init__.py │ │ └── catalog.py │ ├── data.py │ ├── enum.py │ ├── fifo.py │ ├── io.py │ ├── memory.py │ ├── meta.py │ ├── stream.py │ └── wiring.py ├── rpc.py ├── sim │ ├── __init__.py │ ├── _async.py │ ├── _base.py │ ├── _pyclock.py │ ├── _pycoro.py │ ├── _pyeval.py │ ├── _pyrtl.py │ ├── core.py │ └── pysim.py ├── tracer.py ├── utils.py └── vendor │ ├── __init__.py │ ├── _altera.py │ ├── _gowin.py │ ├── _lattice.py │ ├── _quicklogic.py │ ├── _siliconblue.py │ └── _xilinx.py ├── docs ├── .gitignore ├── _code │ ├── led_blinker.py │ ├── up_counter.py │ └── up_counter.v ├── _static │ ├── custom.css │ └── logo.png ├── changes.rst ├── conf.py ├── contrib.rst ├── cover.rst ├── guide.rst ├── index.rst ├── install.rst ├── intro.rst ├── platform.rst ├── platform │ ├── altera.rst │ ├── gowin.rst │ ├── lattice.rst │ ├── quicklogic.rst │ ├── siliconblue.rst │ └── xilinx.rst ├── reference.rst ├── simulator.rst ├── start.rst ├── stdlib.rst ├── stdlib │ ├── _images │ │ └── stream_pipeline.png │ ├── cdc.rst │ ├── crc.rst │ ├── crc │ │ └── catalog.rst │ ├── data.rst │ ├── enum.rst │ ├── fifo.rst │ ├── io.rst │ ├── memory.rst │ ├── meta.rst │ ├── stream.rst │ └── wiring.rst └── tutorial.rst ├── examples ├── basic │ ├── alu.py │ ├── alu_hier.py │ ├── arst.py │ ├── cdc.py │ ├── ctr.py │ ├── ctr_en.py │ ├── fsm.py │ ├── inst.py │ ├── mem.py │ ├── pmux.py │ ├── por.py │ └── uart.py └── board │ ├── 01_blinky.py │ └── 02_domain.py ├── pdm_build.py ├── pyproject.toml └── tests ├── __init__.py ├── test_back_rtlil.py ├── test_build_dsl.py ├── test_build_plat.py ├── test_build_res.py ├── test_examples.py ├── test_hdl_ast.py ├── test_hdl_cd.py ├── test_hdl_dsl.py ├── test_hdl_ir.py ├── test_hdl_mem.py ├── test_hdl_time.py ├── test_hdl_xfrm.py ├── test_lib_cdc.py ├── test_lib_crc.py ├── test_lib_data.py ├── test_lib_enum.py ├── test_lib_fifo.py ├── test_lib_io.py ├── test_lib_memory.py ├── test_lib_meta.py ├── test_lib_stream.py ├── test_lib_wiring.py ├── test_sim.py ├── test_tracer.py ├── test_utils.py └── utils.py /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: false 5 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | include = 4 | amaranth/* 5 | omit = 6 | tests/* 7 | 8 | [report] 9 | exclude_lines = 10 | :nocov: 11 | partial_branches = 12 | :nobr: 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | indent_style = space 6 | indent_size = 4 7 | max_line_length = 100 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.yaml] 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.env.toolchain: -------------------------------------------------------------------------------- 1 | AMARANTH_USE_YOSYS=system 2 | YOSYS=yowasp-yosys 3 | SBY=yowasp-sby 4 | SMTBMC=yowasp-yosys-smtbmc 5 | # examples 6 | NEXTPNR_ICE40=yowasp-nextpnr-ice40 7 | ICEPACK=yowasp-icepack -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | /amaranth/vendor/* -linguist-vendored 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @whitequark 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python 2 | __pycache__/ 3 | *.egg-info 4 | /dist 5 | 6 | # pdm 7 | /.pdm-plugins 8 | /.pdm-python 9 | /.venv 10 | /pdm.lock 11 | 12 | # metadata schemas 13 | /schema 14 | 15 | # coverage 16 | /.coverage 17 | /htmlcov 18 | /coverage.xml 19 | 20 | # tests 21 | /tests/spec_*/ 22 | *.vcd 23 | *.gtkw 24 | 25 | # misc user-created 26 | /*.il 27 | /*.v 28 | /build 29 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Wanda Phinode 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.txt: -------------------------------------------------------------------------------- 1 | CONTRIBUTIONS and CODE REVIEWS 2 | 3 | We love contributions from the community. Please send pull requests and raise 4 | issues! All code submissions require review. We use GitHub pull requests for 5 | this purpose. 6 | 7 | See the "Contributing" section of the documentation manual for more details 8 | about contributing to Amaranth. 9 | 10 | 11 | USE OF LARGE LANGUAGE MODELS / "GENERATIVE AI" TOOLS 12 | 13 | Contributions created using large language models or "generative AI" tools are 14 | not accepted. 15 | 16 | The burden of ensuring the appropriateness of a contribution lies on its 17 | author, with code review being a tool to assist this process, and even partial 18 | shift of this responsibility to reviewers that occurs when these tools are 19 | used is both unethical and unsustainable. 20 | 21 | 22 | COPYRIGHTS and LICENSE 23 | 24 | Amaranth HDL is licensed under the 2-clause BSD license, which is contained in 25 | the LICENSE.txt file. 26 | 27 | All authors retain copyright ownership of their contributions. 28 | 29 | Authors should note that all contributions are licensed under the 2-clause BSD 30 | license. 31 | 32 | 33 | CONTRIBUTORS 34 | 35 | The complete list of contributors is contained within the git repository. 36 | Tools such as the git log command can be used to see all contributions and 37 | contributors. 38 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2019-2023 Amaranth HDL contributors 2 | 3 | Redistribution and use in source and binary forms, with or without modification, 4 | are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright notice, 9 | this list of conditions and the following disclaimer in the documentation 10 | and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 15 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 16 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 19 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Amaranth HDL (previously nMigen) 2 | 3 | The Amaranth project provides an open-source toolchain for developing hardware based on synchronous digital logic using the Python programming language, as well as [evaluation board definitions][amaranth-boards], a [System on Chip toolkit][amaranth-soc], and more. It aims to be easy to learn and use, reduce or eliminate common coding mistakes, and simplify the design of complex hardware with reusable components. 4 | 5 | The Amaranth toolchain consists of the Amaranth hardware definition language, the standard library, the simulator, and the build system, covering all steps of a typical FPGA development workflow. At the same time, it does not restrict the designer’s choice of tools: existing industry-standard (System)Verilog or VHDL code can be integrated into an Amaranth-based design flow, or, conversely, Amaranth code can be integrated into an existing Verilog-based design flow. 6 | 7 | [amaranth-boards]: https://github.com/amaranth-lang/amaranth-boards 8 | [amaranth-soc]: https://github.com/amaranth-lang/amaranth-soc 9 | 10 | The development of Amaranth has been supported by [LambdaConcept][], [ChipEleven][], and [Chipflow][]. 11 | 12 | [yosys]: https://yosyshq.net/yosys/ 13 | [lambdaconcept]: http://lambdaconcept.com/ 14 | [chipeleven]: https://chipeleven.com/ 15 | [chipflow]: https://chipflow.io/ 16 | 17 | ## Introduction 18 | 19 | See the [Introduction](https://amaranth-lang.org/docs/amaranth/latest/intro.html) section of the documentation. 20 | 21 | ## Installation 22 | 23 | See the [Installation](https://amaranth-lang.org/docs/amaranth/latest/install.html) section of the documentation. 24 | 25 | ## Usage 26 | 27 | See the [Language guide](https://amaranth-lang.org/docs/amaranth/latest/guide.html) section of the documentation. 28 | 29 | ## Platform support 30 | 31 | Amaranth can be used to target any FPGA or ASIC process that accepts behavioral Verilog-2001 as input. It also offers extended support for many FPGA families, providing toolchain integration, abstractions for device-specific primitives, and more. Specifically: 32 | 33 | * Lattice iCE40 (toolchains: **Yosys+nextpnr**, LSE-iCECube2, Synplify-iCECube2); 34 | * Lattice MachXO2, MachXO3L (toolchains: **Yosys+nextpnr**, Diamond); 35 | * Lattice ECP5 (toolchains: **Yosys+nextpnr**, Diamond); 36 | * Lattice Nexus (toolchains: **Yosys+nextpnr**, Radiant); 37 | * AMD Virtex, Virtex E, Spartan 2, Spartan 2E (toolchains: ISE); 38 | * AMD Virtex II, Virtex II Pro (toolchains: ISE); 39 | * AMD Spartan 3, Spartan 3E, Spartan 3A, Spartan 3AN, Spartan 3A DSP (toolchains: ISE); 40 | * AMD Virtex 4, Virtex 5, Virtex 6 (toolchains: ISE); 41 | * AMD Spartan 6 (toolchains: ISE); 42 | * AMD 7-series (toolchains: Vivado, ISE); 43 | * AMD UltraScale, UltraScale+ (toolchains: Vivado); 44 | * Altera (toolchains: Quartus); 45 | * Quicklogic EOS S3 (toolchains: **Yosys+VPR**). 46 | 47 | FOSS toolchains are listed in **bold**. 48 | 49 | ## Community 50 | 51 | Amaranth has a dedicated IRC channel, [#amaranth-lang at libera.chat](https://web.libera.chat/#amaranth-lang), which is _bridged_[^1] to Matrix at [#amaranth-lang:matrix.org](https://matrix.to/#/#amaranth-lang:matrix.org). Feel free to join to ask questions about using Amaranth or discuss ongoing development of Amaranth and its related projects. 52 | 53 | [^1]: The same messages appear on IRC and on Matrix, and one can participate in the discussion equally using either communication system. 54 | 55 | ## License 56 | 57 | Amaranth is released under the [two-clause BSD license](LICENSE.txt). You are permitted to use Amaranth for open-source and proprietary designs provided that the copyright notice in the license file is reproduced. 58 | -------------------------------------------------------------------------------- /amaranth/__init__.py: -------------------------------------------------------------------------------- 1 | # Extract version for this package from the environment package metadata. This used to be a lot 2 | # more difficult in earlier Python versions, and the `__version__` field is a legacy of that time. 3 | import importlib.metadata 4 | try: 5 | __version__ = importlib.metadata.version(__package__) 6 | except importlib.metadata.PackageNotFoundError: 7 | # No importlib metadata for this package. This shouldn't normally happen, but some people 8 | # prefer not installing packages via pip at all. Although not recommended we still support it. 9 | __version__ = "unknown" # :nocov: 10 | del importlib 11 | 12 | 13 | from .hdl import * 14 | 15 | # must be kept in sync with docs/reference.rst! 16 | __all__ = [ 17 | "Shape", "unsigned", "signed", 18 | "Value", "Const", "C", "Mux", "Cat", "Array", "Signal", "ClockSignal", "ResetSignal", 19 | "Format", "Print", "Assert", 20 | "Module", 21 | "ClockDomain", 22 | "Elaboratable", "Fragment", "Instance", 23 | "DomainRenamer", "ResetInserter", "EnableInserter", 24 | ] 25 | -------------------------------------------------------------------------------- /amaranth/_toolchain/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | 4 | 5 | __all__ = ["ToolNotFound", "tool_env_var", "has_tool", "require_tool"] 6 | 7 | 8 | class ToolNotFound(Exception): 9 | pass 10 | 11 | 12 | def tool_env_var(name): 13 | return name.upper().replace("-", "_").replace("+", "X") 14 | 15 | 16 | def _get_tool(name): 17 | return os.environ.get(tool_env_var(name), name) 18 | 19 | 20 | def has_tool(name): 21 | return shutil.which(_get_tool(name)) is not None 22 | 23 | 24 | def require_tool(name): 25 | env_var = tool_env_var(name) 26 | path = _get_tool(name) 27 | if shutil.which(path) is None: 28 | if env_var in os.environ: 29 | raise ToolNotFound("Could not find required tool {} in {} as " 30 | "specified via the {} environment variable". 31 | format(name, path, env_var)) 32 | else: 33 | raise ToolNotFound("Could not find required tool {} in PATH. Place " 34 | "it directly in PATH or specify path explicitly " 35 | "via the {} environment variable". 36 | format(name, env_var)) 37 | return path 38 | -------------------------------------------------------------------------------- /amaranth/_toolchain/yosys.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import re 4 | import subprocess 5 | import warnings 6 | import pathlib 7 | import importlib.metadata 8 | import importlib.resources 9 | 10 | from . import has_tool, require_tool 11 | 12 | 13 | __all__ = ["YosysError", "YosysBinary", "find_yosys"] 14 | 15 | 16 | class YosysError(Exception): 17 | pass 18 | 19 | 20 | class YosysWarning(Warning): 21 | pass 22 | 23 | 24 | class YosysBinary: 25 | @classmethod 26 | def available(cls): 27 | """Check for Yosys availability. 28 | 29 | Returns 30 | ------- 31 | available : bool 32 | ``True`` if Yosys is installed, ``False`` otherwise. Installed binary may still not 33 | be runnable, or might be too old to be useful. 34 | """ 35 | raise NotImplementedError 36 | 37 | @classmethod 38 | def version(cls): 39 | """Get Yosys version. 40 | 41 | Returns 42 | ------- 43 | ``None`` if version number could not be determined, or a 3-tuple ``(major, minor, distance)`` if it could. 44 | 45 | major : int 46 | Major version. 47 | minor : int 48 | Minor version. 49 | distance : int 50 | Distance to last tag per ``git describe``. May not be exact for system Yosys. 51 | """ 52 | raise NotImplementedError 53 | 54 | @classmethod 55 | def data_dir(cls): 56 | """Get Yosys data directory. 57 | 58 | Returns 59 | ------- 60 | data_dir : pathlib.Path 61 | Yosys data directory (also known as "datdir"). 62 | """ 63 | raise NotImplementedError 64 | 65 | @classmethod 66 | def run(cls, args, stdin=""): 67 | """Run Yosys process. 68 | 69 | Parameters 70 | ---------- 71 | args : list of str 72 | Arguments, not including the program name. 73 | stdin : str 74 | Standard input. 75 | 76 | Returns 77 | ------- 78 | stdout : str 79 | Standard output. 80 | 81 | Exceptions 82 | ---------- 83 | YosysError 84 | Raised if Yosys returns a non-zero code. The exception message is the standard error 85 | output. 86 | """ 87 | raise NotImplementedError 88 | 89 | @classmethod 90 | def _process_result(cls, returncode, stdout, stderr, ignore_warnings, src_loc_at): 91 | if returncode: 92 | raise YosysError(stderr.strip()) 93 | if not ignore_warnings: 94 | for match in re.finditer(r"(?ms:^Warning: (.+)\n$)", stderr): 95 | message = match.group(1).replace("\n", " ") 96 | warnings.warn(message, YosysWarning, stacklevel=3 + src_loc_at) 97 | return stdout 98 | 99 | 100 | class _BuiltinYosys(YosysBinary): 101 | YOSYS_PACKAGE = "amaranth_yosys" 102 | 103 | @classmethod 104 | def available(cls): 105 | try: 106 | importlib.metadata.version(cls.YOSYS_PACKAGE) 107 | return True 108 | except importlib.metadata.PackageNotFoundError: 109 | return False 110 | 111 | @classmethod 112 | def version(cls): 113 | version = importlib.metadata.version(cls.YOSYS_PACKAGE) 114 | match = re.match(r"^(\d+)\.(\d+)\.(?:\d+)(?:\.(\d+))?(?:\.post(\d+))?", version) 115 | return (int(match[1]), int(match[2]), int(match[3] or 0), int(match[4] or 0)) 116 | 117 | @classmethod 118 | def data_dir(cls): 119 | return importlib.resources.files(cls.YOSYS_PACKAGE) / "share" 120 | 121 | @classmethod 122 | def run(cls, args, stdin="", *, ignore_warnings=False, src_loc_at=0): 123 | popen = subprocess.Popen([sys.executable, "-m", cls.YOSYS_PACKAGE, *args], 124 | stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, 125 | encoding="utf-8") 126 | stdout, stderr = popen.communicate(stdin) 127 | return cls._process_result(popen.returncode, stdout, stderr, ignore_warnings, src_loc_at) 128 | 129 | 130 | class _SystemYosys(YosysBinary): 131 | YOSYS_BINARY = "yosys" 132 | 133 | @classmethod 134 | def available(cls): 135 | return has_tool(cls.YOSYS_BINARY) 136 | 137 | @classmethod 138 | def version(cls): 139 | version = cls.run(["-V"]) 140 | match = re.match(r"^Yosys (\d+)\.(\d+)(?:\+(\d+))?", version) 141 | if match: 142 | return (int(match[1]), int(match[2]), int(match[3] or 0)) 143 | else: 144 | return None 145 | 146 | @classmethod 147 | def data_dir(cls): 148 | popen = subprocess.Popen([require_tool(cls.YOSYS_BINARY) + "-config", "--datdir"], 149 | stdout=subprocess.PIPE, stderr=subprocess.PIPE, 150 | encoding="utf-8") 151 | stdout, stderr = popen.communicate() 152 | if popen.returncode: 153 | raise YosysError(stderr.strip()) 154 | return pathlib.Path(stdout.strip()) 155 | 156 | @classmethod 157 | def run(cls, args, stdin="", *, ignore_warnings=False, src_loc_at=0): 158 | popen = subprocess.Popen([require_tool(cls.YOSYS_BINARY), *args], 159 | stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, 160 | encoding="utf-8") 161 | stdout, stderr = popen.communicate(stdin) 162 | # If Yosys is built with an evaluation version of Verific, then Verific license 163 | # information is printed first. It consists of empty lines and lines starting with `--`, 164 | # which are not normally a part of Yosys output, and can be fairly safely removed. 165 | # 166 | # This is not ideal, but Verific license conditions rule out any other solution. 167 | stdout = re.sub(r"\A(-- .+\n|\n)*", "", stdout) 168 | return cls._process_result(popen.returncode, stdout, stderr, ignore_warnings, src_loc_at) 169 | 170 | 171 | class _JavaScriptYosys(YosysBinary): 172 | """ 173 | This toolchain proxy is compatible with Pyodide_. The JavaScript environment must include 174 | the following function: 175 | 176 | .. code:: 177 | 178 | runAmaranthYosys(args: string[], stdin: string): (exit_code: int, stdout: string, stderr: string); 179 | 180 | .. _Pyodide: https://pyodide.org/ 181 | """ 182 | 183 | @classmethod 184 | def available(cls): 185 | try: 186 | return hasattr(__import__("js"), "runAmaranthYosys") 187 | except ImportError: 188 | return False 189 | 190 | @classmethod 191 | def version(cls): 192 | version = cls.run(["-V"]) 193 | match = re.match(r"^Yosys (\d+)\.(\d+)(?:\+(\d+))?", version) 194 | if match: 195 | return (int(match[1]), int(match[2]), int(match[3] or 0)) 196 | else: 197 | return None 198 | 199 | @classmethod 200 | def data_dir(cls): 201 | # Not yet clear how this could work in a design with Wasm components. Most likely, 202 | # the component would have to export its filesystem wholesale, and this method would 203 | # return some kind of non-filesystem path-like object. 204 | raise NotImplementedError 205 | 206 | @classmethod 207 | def run(cls, args, stdin="", *, ignore_warnings=False, src_loc_at=0): 208 | exit_code, stdout, stderr = __import__("js").runAmaranthYosys(args, stdin) 209 | return cls._process_result(exit_code, stdout, stderr, ignore_warnings, src_loc_at) 210 | 211 | 212 | def find_yosys(requirement): 213 | """Find an available Yosys executable of required version. 214 | 215 | Parameters 216 | ---------- 217 | requirement : function 218 | Version check. Should return ``True`` if the version is acceptable, ``False`` otherwise. 219 | 220 | Returns 221 | ------- 222 | yosys_binary : subclass of YosysBinary 223 | Proxy for running the requested version of Yosys. 224 | 225 | Exceptions 226 | ---------- 227 | YosysError 228 | Raised if required Yosys version is not found. 229 | """ 230 | proxies = [] 231 | clauses = os.environ.get("AMARANTH_USE_YOSYS", "system,builtin").split(",") 232 | for clause in clauses: 233 | if clause == "builtin": 234 | proxies.append(_BuiltinYosys) 235 | elif clause == "system": 236 | proxies.append(_SystemYosys) 237 | elif clause == "javascript": 238 | proxies.append(_JavaScriptYosys) 239 | else: 240 | raise YosysError("The AMARANTH_USE_YOSYS environment variable contains " 241 | "an unrecognized clause {!r}" 242 | .format(clause)) 243 | for proxy in proxies: 244 | if proxy.available(): 245 | version = proxy.version() 246 | if version is not None and requirement(version): 247 | return proxy 248 | else: 249 | if "AMARANTH_USE_YOSYS" in os.environ: 250 | raise YosysError("Could not find an acceptable Yosys binary. Searched: {}" 251 | .format(", ".join(clauses))) 252 | else: 253 | raise YosysError("Could not find an acceptable Yosys binary. The `amaranth-yosys` PyPI " 254 | "package, if available for this platform, can be used as fallback") 255 | -------------------------------------------------------------------------------- /amaranth/_unused.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import warnings 3 | 4 | from ._utils import get_linter_option 5 | 6 | 7 | __all__ = ["UnusedMustUse", "MustUse"] 8 | 9 | 10 | class UnusedMustUse(Warning): 11 | pass 12 | 13 | 14 | class MustUse: 15 | _MustUse__silence = False 16 | _MustUse__warning = UnusedMustUse 17 | 18 | def __new__(cls, *args, src_loc_at=0, **kwargs): 19 | frame = sys._getframe(1 + src_loc_at) 20 | self = super().__new__(cls) 21 | self._MustUse__used = False 22 | self._MustUse__context = dict( 23 | filename=frame.f_code.co_filename, 24 | lineno=frame.f_lineno, 25 | source=self) 26 | return self 27 | 28 | def __del__(self): 29 | if self._MustUse__silence: 30 | return 31 | if getattr(self._MustUse__warning, "_MustUse__silence", False): 32 | return 33 | if hasattr(self, "_MustUse__used") and not self._MustUse__used: 34 | if get_linter_option(self._MustUse__context["filename"], 35 | self._MustUse__warning.__qualname__, bool, True): 36 | warnings.warn_explicit( 37 | f"{self!r} created but never used", self._MustUse__warning, 38 | **self._MustUse__context) 39 | 40 | 41 | _old_excepthook = sys.excepthook 42 | def _silence_elaboratable(type, value, traceback): 43 | # Don't show anything if the interpreter crashed; that'd just obscure the exception 44 | # traceback instead of helping. 45 | MustUse._MustUse__silence = True 46 | _old_excepthook(type, value, traceback) 47 | sys.excepthook = _silence_elaboratable 48 | -------------------------------------------------------------------------------- /amaranth/_utils.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import functools 3 | import warnings 4 | import linecache 5 | import operator 6 | import re 7 | from collections.abc import Iterable 8 | 9 | 10 | __all__ = ["to_binary", "flatten", "union", "final", "deprecated", "get_linter_options", 11 | "get_linter_option"] 12 | 13 | 14 | def to_binary(n: int, width: int) -> str: 15 | """Formats ``n`` as exactly ``width`` binary digits, including when ``width`` is 0""" 16 | n = operator.index(n) 17 | width = operator.index(width) 18 | if n not in range(1 << width): 19 | raise ValueError(f"{n} does not fit in {width} bits") 20 | if width == 0: 21 | return "" 22 | return f"{n:0{width}b}" 23 | 24 | 25 | def flatten(i): 26 | for e in i: 27 | if isinstance(e, str) or not isinstance(e, Iterable): 28 | yield e 29 | else: 30 | yield from flatten(e) 31 | 32 | 33 | def union(i, start=None): 34 | r = start 35 | for e in i: 36 | if r is None: 37 | r = e 38 | else: 39 | r |= e 40 | return r 41 | 42 | 43 | def final(cls): 44 | def init_subclass(): 45 | raise TypeError("Subclassing {}.{} is not supported" 46 | .format(cls.__module__, cls.__qualname__)) 47 | cls.__init_subclass__ = init_subclass 48 | return cls 49 | 50 | 51 | def deprecated(message, stacklevel=2): 52 | def decorator(f): 53 | @functools.wraps(f) 54 | def wrapper(*args, **kwargs): 55 | warnings.warn(message, DeprecationWarning, stacklevel=stacklevel) 56 | return f(*args, **kwargs) 57 | return wrapper 58 | return decorator 59 | 60 | 61 | def _ignore_deprecated(f=None): 62 | if f is None: 63 | @contextlib.contextmanager 64 | def context_like(): 65 | with warnings.catch_warnings(): 66 | warnings.filterwarnings(action="ignore", category=DeprecationWarning) 67 | yield 68 | return context_like() 69 | else: 70 | @functools.wraps(f) 71 | def decorator_like(*args, **kwargs): 72 | with warnings.catch_warnings(): 73 | warnings.filterwarnings(action="ignore", category=DeprecationWarning) 74 | return f(*args, **kwargs) 75 | return decorator_like 76 | 77 | 78 | def get_linter_options(filename): 79 | first_line = linecache.getline(filename, 1) 80 | if first_line: 81 | match = re.match(r"^#\s*amaranth:\s*((?:\w+=\w+\s*)(?:,\s*\w+=\w+\s*)*)\n$", first_line) 82 | if match: 83 | return dict(map(lambda s: s.strip().split("=", 2), match.group(1).split(","))) 84 | return dict() 85 | 86 | 87 | def get_linter_option(filename, name, type, default): 88 | options = get_linter_options(filename) 89 | if name not in options: 90 | return default 91 | 92 | option = options[name] 93 | if type is bool: 94 | if option in ("1", "yes", "enable"): 95 | return True 96 | if option in ("0", "no", "disable"): 97 | return False 98 | return default 99 | if type is int: 100 | try: 101 | return int(option, 0) 102 | except ValueError: 103 | return default 104 | assert False 105 | -------------------------------------------------------------------------------- /amaranth/asserts.py: -------------------------------------------------------------------------------- 1 | from .hdl._ast import AnyConst, AnySeq, Initial 2 | from . import hdl as __hdl 3 | 4 | 5 | __all__ = ["AnyConst", "AnySeq", "Initial", "Assert", "Assume", "Cover"] 6 | 7 | 8 | def __getattr__(name): 9 | import warnings 10 | if name in __hdl.__dict__ and name in __all__: 11 | if not (name.startswith("__") and name.endswith("__")): 12 | warnings.warn(f"instead of `{__name__}.{name}`, use `{__hdl.__name__}.{name}`", 13 | DeprecationWarning, stacklevel=2) 14 | return getattr(__hdl, name) 15 | raise AttributeError(f"module {__name__!r} has no attribute {name!r}") -------------------------------------------------------------------------------- /amaranth/back/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amaranth-lang/amaranth/916791022cb9cf96a552756d0e00efe2a4b16aba/amaranth/back/__init__.py -------------------------------------------------------------------------------- /amaranth/back/cxxrtl.py: -------------------------------------------------------------------------------- 1 | from .._toolchain.yosys import * 2 | from . import rtlil 3 | 4 | 5 | __all__ = ["YosysError", "convert", "convert_fragment"] 6 | 7 | 8 | def _convert_rtlil_text(rtlil_text, black_boxes, *, src_loc_at=0): 9 | if black_boxes is not None: 10 | if not isinstance(black_boxes, dict): 11 | raise TypeError("CXXRTL black boxes must be a dictionary, not {!r}" 12 | .format(black_boxes)) 13 | for box_name, box_source in black_boxes.items(): 14 | if not isinstance(box_name, str): 15 | raise TypeError("CXXRTL black box name must be a string, not {!r}" 16 | .format(box_name)) 17 | if not isinstance(box_source, str): 18 | raise TypeError("CXXRTL black box source code must be a string, not {!r}" 19 | .format(box_source)) 20 | 21 | yosys = find_yosys(lambda ver: ver >= (0, 10)) 22 | 23 | script = [] 24 | if black_boxes is not None: 25 | for box_name, box_source in black_boxes.items(): 26 | script.append(f"read_rtlil <= (0, 40)) 13 | 14 | script = [] 15 | script.append(f"read_rtlil < 1: 33 | raise TypeError("Period accepts at most one argument") 34 | 35 | (unit, value), = kwargs.items() 36 | 37 | if not isinstance(value, numbers.Real): 38 | raise TypeError(f"{unit} value must be a real number") 39 | 40 | if unit in _TIME_UNITS: 41 | self._femtoseconds = round(value * _TIME_UNITS[unit]) 42 | 43 | elif unit in _FREQUENCY_UNITS: 44 | if value == 0: 45 | raise ZeroDivisionError("Frequency can't be zero") 46 | elif value < 0: 47 | raise ValueError("Frequency can't be negative") 48 | 49 | self._femtoseconds = round(_FREQUENCY_UNITS[unit] / value) 50 | 51 | else: 52 | raise TypeError(f"{unit} is not a valid unit") 53 | 54 | @property 55 | def seconds(self): 56 | return self._femtoseconds / 1_000_000_000_000_000 57 | 58 | @property 59 | def milliseconds(self): 60 | return self._femtoseconds / 1_000_000_000_000 61 | 62 | @property 63 | def microseconds(self): 64 | return self._femtoseconds / 1_000_000_000 65 | 66 | @property 67 | def nanoseconds(self): 68 | return self._femtoseconds / 1_000_000 69 | 70 | @property 71 | def picoseconds(self): 72 | return self._femtoseconds / 1_000 73 | 74 | @property 75 | def femtoseconds(self): 76 | return self._femtoseconds 77 | 78 | def _check_reciprocal(self): 79 | if self._femtoseconds == 0: 80 | raise ZeroDivisionError("Can't calculate the frequency of a zero period") 81 | elif self._femtoseconds < 0: 82 | raise ValueError("Can't calculate the frequency of a negative period") 83 | 84 | @property 85 | def hertz(self): 86 | self._check_reciprocal() 87 | return 1_000_000_000_000_000 / self._femtoseconds 88 | 89 | @property 90 | def kilohertz(self): 91 | self._check_reciprocal() 92 | return 1_000_000_000_000 / self._femtoseconds 93 | 94 | @property 95 | def megahertz(self): 96 | self._check_reciprocal() 97 | return 1_000_000_000 / self._femtoseconds 98 | 99 | @property 100 | def gigahertz(self): 101 | self._check_reciprocal() 102 | return 1_000_000 / self._femtoseconds 103 | 104 | def __lt__(self, other): 105 | if not isinstance(other, Period): 106 | return NotImplemented 107 | return self._femtoseconds < other._femtoseconds 108 | 109 | def __le__(self, other): 110 | if not isinstance(other, Period): 111 | return NotImplemented 112 | return self._femtoseconds <= other._femtoseconds 113 | 114 | def __eq__(self, other): 115 | if not isinstance(other, Period): 116 | return NotImplemented 117 | return self._femtoseconds == other._femtoseconds 118 | 119 | def __ne__(self, other): 120 | if not isinstance(other, Period): 121 | return NotImplemented 122 | return self._femtoseconds != other._femtoseconds 123 | 124 | def __gt__(self, other): 125 | if not isinstance(other, Period): 126 | return NotImplemented 127 | return self._femtoseconds > other._femtoseconds 128 | 129 | def __ge__(self, other): 130 | if not isinstance(other, Period): 131 | return NotImplemented 132 | return self._femtoseconds >= other._femtoseconds 133 | 134 | def __hash__(self): 135 | return hash(self._femtoseconds) 136 | 137 | def __bool__(self): 138 | return bool(self._femtoseconds) 139 | 140 | def __neg__(self): 141 | return Period(fs=-self._femtoseconds) 142 | 143 | def __pos__(self): 144 | return self 145 | 146 | def __abs__(self): 147 | return Period(fs=abs(self._femtoseconds)) 148 | 149 | def __add__(self, other): 150 | if not isinstance(other, Period): 151 | return NotImplemented 152 | return Period(fs=self._femtoseconds + other._femtoseconds) 153 | 154 | def __sub__(self, other): 155 | if not isinstance(other, Period): 156 | return NotImplemented 157 | return Period(fs=self._femtoseconds - other._femtoseconds) 158 | 159 | def __mul__(self, other): 160 | if not isinstance(other, numbers.Real): 161 | return NotImplemented 162 | return Period(fs=self._femtoseconds * other) 163 | 164 | __rmul__ = __mul__ 165 | 166 | def __truediv__(self, other): 167 | if isinstance(other, Period): 168 | return self._femtoseconds / other._femtoseconds 169 | elif isinstance(other, numbers.Real): 170 | return Period(fs=self._femtoseconds / other) 171 | else: 172 | return NotImplemented 173 | 174 | def __floordiv__(self, other): 175 | if not isinstance(other, Period): 176 | return NotImplemented 177 | return self._femtoseconds // other._femtoseconds 178 | 179 | def __mod__(self, other): 180 | if not isinstance(other, Period): 181 | return NotImplemented 182 | return Period(fs=self._femtoseconds % other._femtoseconds) 183 | 184 | def __str__(self): 185 | return self.__format__("") 186 | 187 | def __format__(self, format_spec): 188 | m = re.match(r"^([1-9]\d*)?(\.\d+)?( ?)(([munpf]?)s|([kMG]?)Hz)?$", format_spec) 189 | 190 | if m is None: 191 | raise ValueError(f"Invalid format specifier '{format_spec}' for object of type 'Period'") 192 | 193 | width, precision, space, unit, s_unit, hz_unit = m.groups() 194 | 195 | if unit is None: 196 | if abs(self._femtoseconds) >= 1_000_000_000_000_000: 197 | s_unit = "" 198 | elif abs(self._femtoseconds) >= 1_000_000_000_000: 199 | s_unit = "m" 200 | elif abs(self._femtoseconds) >= 1_000_000_000: 201 | s_unit = "u" 202 | elif abs(self._femtoseconds) >= 1_000_000: 203 | s_unit = "n" 204 | elif abs(self._femtoseconds) >= 1_000: 205 | s_unit = "p" 206 | else: 207 | s_unit = "f" 208 | 209 | unit = f"{s_unit}s" 210 | 211 | if s_unit is not None: 212 | div, digits = { 213 | "": (1_000_000_000_000_000, 15), 214 | "m": (1_000_000_000_000, 12), 215 | "u": (1_000_000_000, 9), 216 | "n": (1_000_000, 6), 217 | "p": (1_000, 3), 218 | "f": (1, 0), 219 | }[s_unit] 220 | integer, decimal = divmod(self._femtoseconds, div) 221 | 222 | if precision: 223 | precision = int(precision[1:]) 224 | decimal = round(decimal * 10**(precision - digits)) 225 | digits = precision 226 | 227 | value = f"{integer}.{decimal:0{digits}}" 228 | 229 | if not precision: 230 | value = value.rstrip('0') 231 | value = value.rstrip('.') 232 | 233 | else: 234 | if hz_unit == "": 235 | value = f"{self.hertz:{precision or ''}f}" 236 | elif hz_unit == "k": 237 | value = f"{self.kilohertz:{precision or ''}f}" 238 | elif hz_unit == "M": 239 | value = f"{self.megahertz:{precision or ''}f}" 240 | elif hz_unit == "G": 241 | value = f"{self.gigahertz:{precision or ''}f}" 242 | 243 | str = f"{value}{space}{unit}" 244 | if width: 245 | str = f"{str:>{width}}" 246 | 247 | return str 248 | 249 | def __repr__(self): 250 | for unit, div in _TIME_UNITS.items(): 251 | if self._femtoseconds % div == 0: 252 | return f"Period({unit}={self._femtoseconds // div})" 253 | -------------------------------------------------------------------------------- /amaranth/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amaranth-lang/amaranth/916791022cb9cf96a552756d0e00efe2a4b16aba/amaranth/lib/__init__.py -------------------------------------------------------------------------------- /amaranth/lib/meta.py: -------------------------------------------------------------------------------- 1 | import jschon 2 | import pprint 3 | import warnings 4 | from abc import abstractmethod, ABCMeta 5 | 6 | 7 | __all__ = ["InvalidSchema", "InvalidAnnotation", "Annotation"] 8 | 9 | 10 | class InvalidSchema(Exception): 11 | """Exception raised when a subclass of :class:`Annotation` is defined with a non-conformant 12 | :data:`~Annotation.schema`.""" 13 | 14 | 15 | class InvalidAnnotation(Exception): 16 | """Exception raised by :meth:`Annotation.validate` when the JSON representation of 17 | an annotation does not conform to its schema.""" 18 | 19 | 20 | class Annotation(metaclass=ABCMeta): 21 | """Interface annotation. 22 | 23 | Annotations are containers for metadata that can be retrieved from an interface object using 24 | the :meth:`Signature.annotations <.wiring.Signature.annotations>` method. 25 | 26 | Annotations have a JSON representation whose structure is defined by the `JSON Schema`_ 27 | language. 28 | """ 29 | 30 | #: :class:`dict`: Schema of this annotation, expressed in the `JSON Schema`_ language. 31 | #: 32 | #: Subclasses of :class:`Annotation` must define this class attribute. 33 | schema = {} 34 | 35 | @classmethod 36 | def __jschon_schema(cls): 37 | catalog = jschon.create_catalog("2020-12") 38 | return jschon.JSONSchema(cls.schema, catalog=catalog) 39 | 40 | def __init_subclass__(cls, **kwargs): 41 | """ 42 | Defining a subclass of :class:`Annotation` causes its :data:`schema` to be validated. 43 | 44 | Raises 45 | ------ 46 | :exc:`InvalidSchema` 47 | If :data:`schema` doesn't conform to the `2020-12` draft of `JSON Schema`_. 48 | :exc:`InvalidSchema` 49 | If :data:`schema` doesn't have a `"$id" keyword`_ at its root. This requirement is 50 | specific to :class:`Annotation` schemas. 51 | """ 52 | super().__init_subclass__(**kwargs) 53 | 54 | if not isinstance(cls.schema, dict): 55 | raise TypeError(f"Annotation schema must be a dict, not {cls.schema!r}") 56 | 57 | if "$id" not in cls.schema: 58 | raise InvalidSchema(f"'$id' keyword is missing from Annotation schema: {cls.schema}") 59 | 60 | try: 61 | # TODO: Remove this. Ignore a deprecation warning from jschon's rfc3986 dependency. 62 | with warnings.catch_warnings(): 63 | warnings.filterwarnings("ignore", category=DeprecationWarning) 64 | result = cls.__jschon_schema().validate() 65 | except jschon.JSONSchemaError as e: 66 | raise InvalidSchema(e) from e 67 | 68 | if not result.valid: 69 | raise InvalidSchema("Invalid Annotation schema:\n" + 70 | pprint.pformat(result.output("basic")["errors"], 71 | sort_dicts=False)) 72 | 73 | @property 74 | @abstractmethod 75 | def origin(self): 76 | """Python object described by this :class:`Annotation` instance. 77 | 78 | Subclasses of :class:`Annotation` must implement this property. 79 | """ 80 | pass # :nocov: 81 | 82 | @abstractmethod 83 | def as_json(self): 84 | """Convert to a JSON representation. 85 | 86 | Subclasses of :class:`Annotation` must implement this method. 87 | 88 | JSON representation returned by this method must adhere to :data:`schema` and pass 89 | validation by :meth:`validate`. 90 | 91 | Returns 92 | ------- 93 | :class:`dict` 94 | JSON representation of this annotation, expressed in Python primitive types 95 | (:class:`dict`, :class:`list`, :class:`str`, :class:`int`, :class:`bool`). 96 | """ 97 | pass # :nocov: 98 | 99 | @classmethod 100 | def validate(cls, instance): 101 | """Validate a JSON representation against :attr:`schema`. 102 | 103 | Arguments 104 | --------- 105 | instance : :class:`dict` 106 | JSON representation to validate, either previously returned by :meth:`as_json` 107 | or retrieved from an external source. 108 | 109 | Raises 110 | ------ 111 | :exc:`InvalidAnnotation` 112 | If :py:`instance` doesn't conform to :attr:`schema`. 113 | """ 114 | # TODO: Remove this. Ignore a deprecation warning from jschon's rfc3986 dependency. 115 | with warnings.catch_warnings(): 116 | warnings.filterwarnings("ignore", category=DeprecationWarning) 117 | result = cls.__jschon_schema().evaluate(jschon.JSON(instance)) 118 | 119 | if not result.valid: 120 | raise InvalidAnnotation("Invalid instance:\n" + 121 | pprint.pformat(result.output("basic")["errors"], 122 | sort_dicts=False)) 123 | 124 | def __repr__(self): 125 | return f"<{type(self).__module__}.{type(self).__qualname__} for {self.origin!r}>" 126 | 127 | 128 | # For internal use only; we may consider exporting this function in the future. 129 | def _extract_schemas(package, *, base_uri, path="schema/"): 130 | import sys 131 | import json 132 | import pathlib 133 | from importlib.metadata import distribution 134 | 135 | entry_points = distribution(package).entry_points 136 | for entry_point in entry_points.select(group="amaranth.lib.meta"): 137 | schema = entry_point.load().schema 138 | relative_path = entry_point.name # "0.5/component.json" 139 | schema_filename = pathlib.Path(path) / relative_path 140 | assert schema["$id"] == f"{base_uri}/{relative_path}", \ 141 | f"Schema $id {schema['$id']} must be {base_uri}/{relative_path}" 142 | 143 | schema_filename.parent.mkdir(parents=True, exist_ok=True) 144 | with open(pathlib.Path(path) / relative_path, "w") as schema_file: 145 | json.dump(schema, schema_file, indent=2) 146 | print(f"Extracted {schema['$id']} to {schema_filename}") 147 | -------------------------------------------------------------------------------- /amaranth/lib/stream.py: -------------------------------------------------------------------------------- 1 | from ..hdl import * 2 | from .._utils import final 3 | from . import wiring 4 | from .wiring import In, Out 5 | 6 | 7 | @final 8 | class Signature(wiring.Signature): 9 | """Signature of a unidirectional data stream. 10 | 11 | .. note:: 12 | 13 | "Minimal streams" as defined in `RFC 61`_ lack support for complex payloads, such as 14 | multiple lanes or packetization, as well as introspection of the payload. This limitation 15 | will be lifted in a later release. 16 | 17 | .. _RFC 61: https://amaranth-lang.org/rfcs/0061-minimal-streams.html 18 | 19 | Parameters 20 | ---------- 21 | payload_shape : :class:`~.hdl.ShapeLike` 22 | Shape of the payload member. 23 | payload_init : :ref:`constant-castable ` object 24 | Initial value of the payload member. 25 | always_valid : :class:`bool` 26 | Whether the stream has a payload available each cycle. 27 | always_ready : :class:`bool` 28 | Whether the stream has its payload accepted whenever it is available (i.e. whether it lacks 29 | support for backpressure). 30 | 31 | Members 32 | ------- 33 | payload : :py:`Out(payload_shape)` 34 | Payload. 35 | valid : :py:`Out(1)` 36 | Whether a payload is available. If the stream is :py:`always_valid`, :py:`Const(1)`. 37 | ready : :py:`In(1)` 38 | Whether a payload is accepted. If the stream is :py:`always_ready`, :py:`Const(1)`. 39 | """ 40 | def __init__(self, payload_shape: ShapeLike, *, payload_init=None, 41 | always_valid=False, always_ready=False): 42 | Shape.cast(payload_shape) 43 | self._payload_shape = payload_shape 44 | self._always_valid = bool(always_valid) 45 | self._always_ready = bool(always_ready) 46 | 47 | super().__init__({ 48 | "payload": Out(payload_shape, init=payload_init), 49 | "valid": Out(1), 50 | "ready": In(1) 51 | }) 52 | 53 | # payload_shape intentionally not introspectable (for now) 54 | 55 | @property 56 | def always_valid(self): 57 | return self._always_valid 58 | 59 | @property 60 | def always_ready(self): 61 | return self._always_ready 62 | 63 | def __eq__(self, other): 64 | return (type(other) is type(self) and 65 | other._payload_shape == self._payload_shape and 66 | other.always_valid == self.always_valid and 67 | other.always_ready == self.always_ready) 68 | 69 | def create(self, *, path=None, src_loc_at=0): 70 | return Interface(self, path=path, src_loc_at=1 + src_loc_at) 71 | 72 | def __repr__(self): 73 | always_valid_repr = "" if not self._always_valid else ", always_valid=True" 74 | always_ready_repr = "" if not self._always_ready else ", always_ready=True" 75 | return f"stream.Signature({self._payload_shape!r}{always_valid_repr}{always_ready_repr})" 76 | 77 | 78 | @final 79 | class Interface: 80 | """A unidirectional data stream. 81 | 82 | Attributes 83 | ---------- 84 | signature : :class:`Signature` 85 | Signature of this data stream. 86 | """ 87 | 88 | payload: Signal 89 | valid: 'Signal | Const' 90 | ready: 'Signal | Const' 91 | 92 | def __init__(self, signature: Signature, *, path=None, src_loc_at=0): 93 | if not isinstance(signature, Signature): 94 | raise TypeError(f"Signature of stream.Interface must be a stream.Signature, not " 95 | f"{signature!r}") 96 | self._signature = signature 97 | self.__dict__.update(signature.members.create(path=path, src_loc_at=1 + src_loc_at)) 98 | if signature.always_valid: 99 | self.valid = Const(1) 100 | if signature.always_ready: 101 | self.ready = Const(1) 102 | 103 | @property 104 | def signature(self): 105 | return self._signature 106 | 107 | @property 108 | def p(self): 109 | """Shortcut for :py:`self.payload`. 110 | 111 | This shortcut reduces repetition when manipulating the payload, for example: 112 | 113 | .. code:: 114 | 115 | m.d.comb += [ 116 | self.o_stream.p.result.eq(self.i_stream.p.first + self.i_stream.p.second), 117 | self.o_stream.valid.eq(self.i_stream.valid), 118 | self.i_stream.ready.eq(self.o_stream.ready), 119 | ] 120 | """ 121 | return self.payload 122 | 123 | def __repr__(self): 124 | return (f"stream.Interface(payload={self.payload!r}, valid={self.valid!r}, " 125 | f"ready={self.ready!r})") 126 | -------------------------------------------------------------------------------- /amaranth/rpc.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | import argparse 4 | import importlib 5 | 6 | from amaranth.lib.wiring import Signature 7 | 8 | from .hdl import Signal, Elaboratable 9 | from .back import rtlil 10 | 11 | 12 | __all__ = ["main"] 13 | 14 | 15 | def _collect_modules(names): 16 | modules = {} 17 | for name in names: 18 | py_module_name, py_class_name = name.rsplit(".", 1) 19 | py_module = importlib.import_module(py_module_name) 20 | if py_class_name == "*": 21 | for py_class_name in py_module.__all__: 22 | py_class = py_module.__dict__[py_class_name] 23 | if not issubclass(py_class, Elaboratable): 24 | continue 25 | modules[f"{py_module_name}.{py_class_name}"] = py_class 26 | else: 27 | py_class = py_module.__dict__[py_class_name] 28 | if not isinstance(py_class, type) or not issubclass(py_class, Elaboratable): 29 | raise TypeError("{}.{} is not a class inheriting from Elaboratable" 30 | .format(py_module_name, py_class_name)) 31 | modules[name] = py_class 32 | return modules 33 | 34 | 35 | def _serve_yosys(modules): 36 | while True: 37 | request_json = sys.stdin.readline() 38 | if not request_json: break 39 | request = json.loads(request_json) 40 | 41 | if request["method"] == "modules": 42 | response = {"modules": list(modules.keys())} 43 | 44 | elif request["method"] == "derive": 45 | module_name = request["module"] 46 | 47 | args, kwargs = [], {} 48 | for parameter_name, parameter in request["parameters"].items(): 49 | if parameter["type"] == "unsigned": 50 | parameter_value = int(parameter["value"], 2) 51 | elif parameter["type"] == "signed": 52 | width = len(parameter["value"]) 53 | parameter_value = int(parameter["value"], 2) 54 | if parameter_value & (1 << (width - 1)): 55 | parameter_value = -((1 << width) - parameter_value) 56 | elif parameter["type"] == "string": 57 | parameter_value = parameter["value"] 58 | elif parameter["type"] == "real": 59 | parameter_value = float(parameter["value"]) 60 | else: 61 | raise NotImplementedError("Unrecognized parameter type {}" 62 | .format(parameter_name)) 63 | if parameter_name.startswith("$"): 64 | index = int(parameter_name[1:]) 65 | while len(args) < index: 66 | args.append(None) 67 | args[index] = parameter_value 68 | if parameter_name.startswith("\\"): 69 | kwargs[parameter_name[1:]] = parameter_value 70 | 71 | try: 72 | elaboratable = modules[module_name](*args, **kwargs) 73 | ports = None 74 | if not (hasattr(elaboratable, "signature") and isinstance(elaboratable.signature, Signature)): 75 | ports = [] 76 | # By convention, any public attribute that is a Signal is considered a port. 77 | for port_name, port in vars(elaboratable).items(): 78 | if not port_name.startswith("_") and isinstance(port, Signal): 79 | ports += port._lhs_signals() 80 | rtlil_text = rtlil.convert(elaboratable, name=module_name, ports=ports) 81 | response = {"frontend": "rtlil", "source": rtlil_text} 82 | except Exception as error: 83 | response = {"error": f"{type(error).__qualname__}: {str(error)}"} 84 | 85 | else: 86 | return {"error": "Unrecognized method {!r}".format(request["method"])} 87 | 88 | sys.stdout.write(json.dumps(response)) 89 | sys.stdout.write("\n") 90 | sys.stdout.flush() 91 | 92 | 93 | def main(): 94 | parser = argparse.ArgumentParser(description=r""" 95 | The Amaranth RPC server allows a HDL synthesis program to request an Amaranth module to 96 | be elaborated on demand using the parameters it provides. For example, using Yosys together 97 | with the Amaranth RPC server allows instantiating parametric Amaranth modules directly 98 | from Verilog. 99 | """) 100 | def add_modules_arg(parser): 101 | parser.add_argument("modules", metavar="MODULE", type=str, nargs="+", 102 | help="import and provide MODULES") 103 | protocols = parser.add_subparsers(metavar="PROTOCOL", dest="protocol", required=True) 104 | protocol_yosys = protocols.add_parser("yosys", help="use Yosys JSON-based RPC protocol") 105 | add_modules_arg(protocol_yosys) 106 | 107 | args = parser.parse_args() 108 | modules = _collect_modules(args.modules) 109 | if args.protocol == "yosys": 110 | _serve_yosys(modules) 111 | 112 | 113 | if __name__ == "__main__": 114 | main() 115 | -------------------------------------------------------------------------------- /amaranth/sim/__init__.py: -------------------------------------------------------------------------------- 1 | from .core import Simulator 2 | from ._async import DomainReset, BrokenTrigger, SimulatorContext, TickTrigger, TriggerCombination 3 | from ._pycoro import Settle, Delay, Tick, Passive, Active 4 | from ..hdl import Period 5 | 6 | 7 | __all__ = [ 8 | "DomainReset", "BrokenTrigger", 9 | "SimulatorContext", "Simulator", "TickTrigger", "TriggerCombination", 10 | "Period", 11 | # deprecated 12 | "Settle", "Delay", "Tick", "Passive", "Active", 13 | ] 14 | -------------------------------------------------------------------------------- /amaranth/sim/_base.py: -------------------------------------------------------------------------------- 1 | __all__ = ["BaseProcess", "BaseSignalState", "BaseMemoryState", "BaseEngineState", "BaseEngine"] 2 | 3 | 4 | class BaseProcess: 5 | __slots__ = () 6 | 7 | runnable = False 8 | critical = False 9 | 10 | def reset(self): 11 | raise NotImplementedError # :nocov: 12 | 13 | def run(self): 14 | raise NotImplementedError # :nocov: 15 | 16 | 17 | class BaseSignalState: 18 | __slots__ = () 19 | 20 | signal = NotImplemented 21 | is_comb = NotImplemented 22 | 23 | curr = NotImplemented 24 | next = NotImplemented 25 | 26 | def update(self, value, mask=~0): 27 | raise NotImplementedError # :nocov: 28 | 29 | 30 | class BaseMemoryState: 31 | __slots__ = () 32 | 33 | memory = NotImplemented 34 | 35 | def read(self, addr): 36 | raise NotImplementedError # :nocov: 37 | 38 | def write(self, addr, value, mask=None): 39 | raise NotImplementedError # :nocov: 40 | 41 | 42 | class BaseEngineState: 43 | def reset(self): 44 | raise NotImplementedError # :nocov: 45 | 46 | def get_signal(self, signal): 47 | raise NotImplementedError # :nocov: 48 | 49 | def get_memory(self, memory): 50 | raise NotImplementedError # :nocov: 51 | 52 | slots = NotImplemented 53 | 54 | def set_delay_waker(self, interval, waker): 55 | raise NotImplementedError # :nocov: 56 | 57 | def add_signal_waker(self, signal, waker): 58 | raise NotImplementedError # :nocov: 59 | 60 | def add_memory_waker(self, memory, waker): 61 | raise NotImplementedError # :nocov: 62 | 63 | 64 | class BaseEngine: 65 | @property 66 | def state(self) -> BaseEngineState: 67 | raise NotImplementedError # :nocov: 68 | 69 | @property 70 | def now(self): 71 | raise NotImplementedError # :nocov: 72 | 73 | def reset(self): 74 | raise NotImplementedError # :nocov: 75 | 76 | def add_clock_process(self, clock, *, phase, period): 77 | raise NotImplementedError # :nocov: 78 | 79 | def add_async_process(self, simulator, process): 80 | raise NotImplementedError # :nocov: 81 | 82 | def add_async_testbench(self, simulator, process, *, background): 83 | raise NotImplementedError # :nocov: 84 | 85 | def add_trigger_combination(self, combination, *, oneshot): 86 | raise NotImplementedError # :nocov: 87 | 88 | def get_value(self, expr): 89 | raise NotImplementedError # :nocov: 90 | 91 | def set_value(self, expr, value): 92 | raise NotImplementedError # :nocov: 93 | 94 | def step_design(self): 95 | raise NotImplementedError # :nocov: 96 | 97 | def advance(self): 98 | raise NotImplementedError # :nocov: 99 | 100 | def write_vcd(self, *, vcd_file, gtkw_file, traces, fs_per_delta): 101 | raise NotImplementedError # :nocov: 102 | -------------------------------------------------------------------------------- /amaranth/sim/_pyclock.py: -------------------------------------------------------------------------------- 1 | from ._base import BaseProcess 2 | 3 | 4 | __all__ = ["PyClockProcess"] 5 | 6 | 7 | class PyClockProcess(BaseProcess): 8 | def __init__(self, state, signal, *, phase, period): 9 | assert len(signal) == 1 10 | 11 | self.state = state 12 | self.slot = self.state.get_signal(signal) 13 | self.phase = phase 14 | self.period = period 15 | 16 | self.reset() 17 | 18 | def reset(self): 19 | self.runnable = True 20 | self.critical = False 21 | 22 | self.initial = True 23 | 24 | def run(self): 25 | self.runnable = False 26 | 27 | def waker(): 28 | self.runnable = True 29 | 30 | if self.initial: 31 | self.initial = False 32 | self.state.set_delay_waker(self.phase, waker) 33 | 34 | else: 35 | clk_state = self.state.slots[self.slot] 36 | clk_state.update(not clk_state.curr) 37 | self.state.set_delay_waker(self.period // 2, waker) 38 | -------------------------------------------------------------------------------- /amaranth/sim/_pycoro.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | 3 | from .._utils import deprecated 4 | from ..hdl import * 5 | from ..hdl._ast import Assign, ValueCastable 6 | 7 | 8 | __all__ = ["Command", "Settle", "Delay", "Tick", "Passive", "Active", "PyCoroProcess"] 9 | 10 | 11 | class Command: 12 | pass 13 | 14 | 15 | class Settle(Command): 16 | @deprecated("The `Settle` command is deprecated per RFC 27. Use `add_testbench` to write " 17 | "testbenches; there, an equivalent of `yield Settle()` is performed " 18 | "automatically after each `ctx.set()`.") 19 | def __init__(self): 20 | pass 21 | 22 | def __repr__(self): 23 | return "(settle)" 24 | 25 | 26 | class Delay(Command): 27 | def __init__(self, interval=None): 28 | self.interval = None if interval is None else float(interval) 29 | 30 | def __repr__(self): 31 | if self.interval is None: 32 | return "(delay ε)" 33 | else: 34 | return f"(delay {self.interval * 1e6:.3}us)" 35 | 36 | 37 | class Tick(Command): 38 | def __init__(self, domain="sync"): 39 | if not isinstance(domain, (str, ClockDomain)): 40 | raise TypeError(f"Domain must be a string or a ClockDomain instance, not {domain!r}") 41 | assert domain != "comb" 42 | self.domain = domain 43 | 44 | def __repr__(self): 45 | return f"(tick {self.domain})" 46 | 47 | 48 | class Passive(Command): 49 | def __repr__(self): 50 | return "(passive)" 51 | 52 | 53 | class Active(Command): 54 | def __repr__(self): 55 | return "(active)" 56 | 57 | 58 | def coro_wrapper(process, *, testbench, default_cmd=None): 59 | async def inner(context): 60 | def src_loc(coroutine): 61 | if coroutine is None: 62 | return None 63 | while coroutine.gi_yieldfrom is not None and inspect.isgenerator(coroutine.gi_yieldfrom): 64 | coroutine = coroutine.gi_yieldfrom 65 | if inspect.isgenerator(coroutine): 66 | frame = coroutine.gi_frame 67 | if inspect.iscoroutine(coroutine): 68 | frame = coroutine.cr_frame 69 | return f"{inspect.getfile(frame)}:{inspect.getlineno(frame)}" 70 | 71 | coroutine = process() 72 | 73 | response = None 74 | exception = None 75 | while True: 76 | try: 77 | if exception is None: 78 | command = coroutine.send(response) 79 | else: 80 | command = coroutine.throw(exception) 81 | except StopIteration: 82 | return 83 | 84 | try: 85 | if command is None: 86 | command = default_cmd 87 | response = None 88 | exception = None 89 | 90 | if isinstance(command, ValueCastable): 91 | command = Value.cast(command) 92 | if isinstance(command, Value): 93 | response = context._engine.get_value(command) 94 | 95 | elif isinstance(command, Assign): 96 | context.set(command.lhs, context._engine.get_value(command.rhs)) 97 | 98 | elif type(command) is Tick: 99 | await context.tick(command.domain) 100 | 101 | elif testbench and (command is None or isinstance(command, Settle)): 102 | raise TypeError(f"Command {command!r} is not allowed in testbenches") 103 | 104 | elif type(command) is Settle: 105 | await context.delay(Period()) 106 | 107 | elif type(command) is Delay: 108 | await context.delay(Period(s=command.interval or 0)) 109 | 110 | elif type(command) is Passive: 111 | context._process.critical = False 112 | 113 | elif type(command) is Active: 114 | context._process.critical = True 115 | 116 | elif command is None: # only possible if self.default_cmd is None 117 | raise TypeError("Received default command from process {!r} that was added " 118 | "with add_process(); did you mean to use Tick() instead?" 119 | .format(src_loc(coroutine))) 120 | 121 | else: 122 | raise TypeError("Received unsupported command {!r} from process {!r}" 123 | .format(command, src_loc(coroutine))) 124 | 125 | except Exception as exn: 126 | response = None 127 | exception = exn 128 | 129 | return inner 130 | -------------------------------------------------------------------------------- /amaranth/sim/_pyeval.py: -------------------------------------------------------------------------------- 1 | from amaranth.hdl._ast import * 2 | from amaranth.hdl._mem import MemoryData 3 | from amaranth.hdl._ir import DriverConflict 4 | 5 | 6 | __all__ = ["eval_value", "eval_format", "eval_assign"] 7 | 8 | 9 | def _eval_matches(test, patterns): 10 | if patterns is None: 11 | return True 12 | for pattern in patterns: 13 | if isinstance(pattern, str): 14 | mask = int("".join("0" if b == "-" else "1" for b in pattern), 2) 15 | value = int("".join("0" if b == "-" else b for b in pattern), 2) 16 | if value == (mask & test): 17 | return True 18 | else: 19 | if pattern == test: 20 | return True 21 | return False 22 | 23 | 24 | def eval_value(sim, value): 25 | if isinstance(value, Const): 26 | return value.value 27 | elif isinstance(value, Operator): 28 | if len(value.operands) == 1: 29 | op_a = eval_value(sim, value.operands[0]) 30 | if value.operator in ("u", "s"): 31 | width = value.shape().width 32 | res = op_a 33 | res &= (1 << width) - 1 34 | if value.operator == "s" and res & (1 << (width - 1)): 35 | res |= -1 << (width - 1) 36 | return res 37 | elif value.operator == "-": 38 | return -op_a 39 | elif value.operator == "~": 40 | shape = value.shape() 41 | if shape.signed: 42 | return ~op_a 43 | else: 44 | return ~op_a & ((1 << shape.width) - 1) 45 | elif value.operator in ("b", "r|"): 46 | return int(op_a != 0) 47 | elif value.operator == "r&": 48 | width = value.operands[0].shape().width 49 | mask = (1 << width) - 1 50 | return int((op_a & mask) == mask) 51 | elif value.operator == "r^": 52 | width = value.operands[0].shape().width 53 | mask = (1 << width) - 1 54 | # Believe it or not, this is the fastest way to compute a sideways XOR in Python. 55 | return format(op_a & mask, 'b').count('1') % 2 56 | elif len(value.operands) == 2: 57 | op_a = eval_value(sim, value.operands[0]) 58 | op_b = eval_value(sim, value.operands[1]) 59 | if value.operator == "|": 60 | return op_a | op_b 61 | elif value.operator == "&": 62 | return op_a & op_b 63 | elif value.operator == "^": 64 | return op_a ^ op_b 65 | elif value.operator == "+": 66 | return op_a + op_b 67 | elif value.operator == "-": 68 | return op_a - op_b 69 | elif value.operator == "*": 70 | return op_a * op_b 71 | elif value.operator == "//": 72 | if op_b == 0: 73 | return 0 74 | return op_a // op_b 75 | elif value.operator == "%": 76 | if op_b == 0: 77 | return 0 78 | return op_a % op_b 79 | elif value.operator == "<<": 80 | return op_a << op_b 81 | elif value.operator == ">>": 82 | return op_a >> op_b 83 | elif value.operator == "==": 84 | return int(op_a == op_b) 85 | elif value.operator == "!=": 86 | return int(op_a != op_b) 87 | elif value.operator == "<": 88 | return int(op_a < op_b) 89 | elif value.operator == "<=": 90 | return int(op_a <= op_b) 91 | elif value.operator == ">": 92 | return int(op_a > op_b) 93 | elif value.operator == ">=": 94 | return int(op_a >= op_b) 95 | assert False # :nocov: 96 | elif isinstance(value, Slice): 97 | res = eval_value(sim, value.value) 98 | res >>= value.start 99 | width = value.stop - value.start 100 | return res & ((1 << width) - 1) 101 | elif isinstance(value, Part): 102 | res = eval_value(sim, value.value) 103 | offset = eval_value(sim, value.offset) 104 | offset *= value.stride 105 | res >>= offset 106 | return res & ((1 << value.width) - 1) 107 | elif isinstance(value, Concat): 108 | res = 0 109 | pos = 0 110 | for part in value.parts: 111 | width = len(part) 112 | part = eval_value(sim, part) 113 | part &= (1 << width) - 1 114 | res |= part << pos 115 | pos += width 116 | return res 117 | elif isinstance(value, SwitchValue): 118 | test = eval_value(sim, value.test) 119 | for patterns, val in value.cases: 120 | if _eval_matches(test, patterns): 121 | return eval_value(sim, val) 122 | return 0 123 | elif isinstance(value, Signal): 124 | slot = sim.get_signal(value) 125 | return sim.slots[slot].curr 126 | elif isinstance(value, MemoryData._Row): 127 | slot = sim.get_memory(value._memory) 128 | return sim.slots[slot].read(value._index) 129 | elif isinstance(value, (ResetSignal, ClockSignal, AnyValue, Initial)): 130 | raise ValueError(f"Value {value!r} cannot be used in simulation") 131 | else: 132 | assert False # :nocov: 133 | 134 | 135 | def value_to_string(value): 136 | """Unpack a Verilog-like (but LSB-first) string of unknown width from an integer.""" 137 | msg = bytearray() 138 | while value: 139 | byte = value & 0xff 140 | value >>= 8 141 | if byte: 142 | msg.append(byte) 143 | return msg.decode() 144 | 145 | 146 | def eval_format(sim, fmt): 147 | fmt = Format("{}", fmt) 148 | chunks = [] 149 | for chunk in fmt._chunks: 150 | if isinstance(chunk, str): 151 | chunks.append(chunk) 152 | else: 153 | value, spec = chunk 154 | value = eval_value(sim, value) 155 | if spec.endswith("s"): 156 | chunks.append(format(value_to_string(value), spec[:-1])) 157 | else: 158 | chunks.append(format(value, spec)) 159 | return "".join(chunks) 160 | 161 | 162 | def _eval_assign_inner(sim, lhs, lhs_start, rhs, rhs_len): 163 | if isinstance(lhs, Operator) and lhs.operator in ("u", "s"): 164 | _eval_assign_inner(sim, lhs.operands[0], lhs_start, rhs, rhs_len) 165 | elif isinstance(lhs, Signal): 166 | lhs_stop = lhs_start + rhs_len 167 | if lhs_stop > len(lhs): 168 | lhs_stop = len(lhs) 169 | if lhs_start >= len(lhs): 170 | return 171 | slot = sim.get_signal(lhs) 172 | if sim.slots[slot].is_comb: 173 | raise DriverConflict("Combinationally driven signals cannot be overriden by testbenches") 174 | value = sim.slots[slot].next 175 | mask = (1 << lhs_stop) - (1 << lhs_start) 176 | value &= ~mask 177 | value |= (rhs << lhs_start) & mask 178 | value &= (1 << len(lhs)) - 1 179 | if lhs._signed and (value & (1 << (len(lhs) - 1))): 180 | value |= -1 << (len(lhs) - 1) 181 | sim.slots[slot].update(value) 182 | elif isinstance(lhs, MemoryData._Row): 183 | lhs_stop = lhs_start + rhs_len 184 | if lhs_stop > len(lhs): 185 | lhs_stop = len(lhs) 186 | if lhs_start >= len(lhs): 187 | return 188 | slot = sim.get_memory(lhs._memory) 189 | mask = (1 << lhs_stop) - (1 << lhs_start) 190 | sim.slots[slot].write(lhs._index, rhs << lhs_start, mask) 191 | elif isinstance(lhs, Slice): 192 | _eval_assign_inner(sim, lhs.value, lhs_start + lhs.start, rhs, rhs_len) 193 | elif isinstance(lhs, Concat): 194 | part_stop = 0 195 | for part in lhs.parts: 196 | part_start = part_stop 197 | part_len = len(part) 198 | part_stop = part_start + part_len 199 | if lhs_start >= part_stop: 200 | continue 201 | if lhs_start + rhs_len <= part_start: 202 | continue 203 | if lhs_start < part_start: 204 | part_lhs_start = 0 205 | part_rhs_start = part_start - lhs_start 206 | else: 207 | part_lhs_start = lhs_start - part_start 208 | part_rhs_start = 0 209 | if lhs_start + rhs_len >= part_stop: 210 | part_rhs_len = part_stop - lhs_start - part_rhs_start 211 | else: 212 | part_rhs_len = rhs_len - part_rhs_start 213 | part_rhs = rhs >> part_rhs_start 214 | part_rhs &= (1 << part_rhs_len) - 1 215 | _eval_assign_inner(sim, part, part_lhs_start, part_rhs, part_rhs_len) 216 | elif isinstance(lhs, Part): 217 | offset = eval_value(sim, lhs.offset) 218 | offset *= lhs.stride 219 | _eval_assign_inner(sim, lhs.value, lhs_start + offset, rhs, rhs_len) 220 | elif isinstance(lhs, SwitchValue): 221 | test = eval_value(sim, lhs.test) 222 | for patterns, val in lhs.cases: 223 | if _eval_matches(test, patterns): 224 | _eval_assign_inner(sim, val, lhs_start, rhs, rhs_len) 225 | return 226 | else: 227 | raise ValueError(f"Value {lhs!r} cannot be assigned") 228 | 229 | 230 | def eval_assign(sim, lhs, value): 231 | _eval_assign_inner(sim, lhs, 0, value, len(lhs)) 232 | -------------------------------------------------------------------------------- /amaranth/tracer.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import platform 3 | from opcode import opname 4 | 5 | 6 | __all__ = ["NameNotFound", "get_var_name", "get_src_loc"] 7 | 8 | 9 | class NameNotFound(Exception): 10 | pass 11 | 12 | 13 | _raise_exception = object() 14 | 15 | 16 | def get_var_name(depth=2, default=_raise_exception): 17 | frame = sys._getframe(depth) 18 | code = frame.f_code 19 | call_index = frame.f_lasti 20 | while call_index > 0 and opname[code.co_code[call_index]] == "CACHE": 21 | call_index -= 2 22 | while True: 23 | call_opc = opname[code.co_code[call_index]] 24 | if call_opc in ("EXTENDED_ARG",): 25 | call_index += 2 26 | else: 27 | break 28 | if call_opc not in ("CALL_FUNCTION", "CALL_FUNCTION_KW", "CALL_FUNCTION_EX", 29 | "CALL_METHOD", "CALL_METHOD_KW", "CALL", "CALL_KW"): 30 | if default is _raise_exception: 31 | raise NameNotFound 32 | else: 33 | return default 34 | 35 | index = call_index + 2 36 | imm = 0 37 | while True: 38 | opc = opname[code.co_code[index]] 39 | if opc == 'EXTENDED_ARG': 40 | imm |= int(code.co_code[index + 1]) 41 | imm <<= 8 42 | index += 2 43 | elif opc in ("STORE_NAME", "STORE_ATTR"): 44 | imm |= int(code.co_code[index + 1]) 45 | return code.co_names[imm] 46 | elif opc == "STORE_FAST": 47 | imm |= int(code.co_code[index + 1]) 48 | if sys.version_info >= (3, 11) and platform.python_implementation() == 'CPython': 49 | return code._varname_from_oparg(imm) 50 | else: 51 | return code.co_varnames[imm] 52 | elif opc == "STORE_DEREF": 53 | imm |= int(code.co_code[index + 1]) 54 | if sys.version_info >= (3, 11) and platform.python_implementation() == 'CPython': 55 | return code._varname_from_oparg(imm) 56 | else: 57 | if imm < len(code.co_cellvars): 58 | return code.co_cellvars[imm] 59 | else: 60 | return code.co_freevars[imm - len(code.co_cellvars)] 61 | elif opc in ("LOAD_GLOBAL", "LOAD_NAME", "LOAD_ATTR", "LOAD_FAST", "LOAD_DEREF", 62 | "DUP_TOP", "BUILD_LIST", "CACHE", "COPY"): 63 | imm = 0 64 | index += 2 65 | else: 66 | if default is _raise_exception: 67 | raise NameNotFound 68 | else: 69 | return default 70 | 71 | 72 | def get_src_loc(src_loc_at=0): 73 | # n-th frame: get_src_loc() 74 | # n-1th frame: caller of get_src_loc() (usually constructor) 75 | # n-2th frame: caller of caller (usually user code) 76 | frame = sys._getframe(2 + src_loc_at) 77 | return (frame.f_code.co_filename, frame.f_lineno) 78 | -------------------------------------------------------------------------------- /amaranth/utils.py: -------------------------------------------------------------------------------- 1 | import operator 2 | 3 | 4 | __all__ = ["ceil_log2", "exact_log2", "bits_for"] 5 | 6 | 7 | def ceil_log2(n): 8 | """Returns the integer log2 of the smallest power-of-2 greater than or equal to ``n``. 9 | 10 | Raises a ``ValueError`` for negative inputs. 11 | """ 12 | n = operator.index(n) 13 | if n < 0: 14 | raise ValueError(f"{n} is negative") 15 | if n == 0: 16 | return 0 17 | return (n - 1).bit_length() 18 | 19 | 20 | def exact_log2(n): 21 | """Returns the integer log2 of ``n``, which must be an exact power of two. 22 | 23 | Raises a ``ValueError`` if ``n`` is not a power of two. 24 | """ 25 | n = operator.index(n) 26 | if n <= 0 or (n & (n - 1)): 27 | raise ValueError(f"{n} is not a power of 2") 28 | return (n - 1).bit_length() 29 | 30 | 31 | def bits_for(n, require_sign_bit=False): 32 | n = operator.index(n) 33 | if n > 0: 34 | r = ceil_log2(n + 1) 35 | else: 36 | require_sign_bit = True 37 | r = ceil_log2(-n) 38 | if require_sign_bit: 39 | r += 1 40 | return r 41 | -------------------------------------------------------------------------------- /amaranth/vendor/__init__.py: -------------------------------------------------------------------------------- 1 | # The machinery in this module is PEP 562 compliant. 2 | # See https://peps.python.org/pep-0562/ for details. 3 | 4 | 5 | # Keep this list sorted alphabetically. 6 | __all__ = [ 7 | "AlteraPlatform", 8 | "AMDPlatform", 9 | "GowinPlatform", 10 | "IntelPlatform", 11 | "LatticeECP5Platform", 12 | "LatticeICE40Platform", 13 | "LatticeMachXO2Platform", 14 | "LatticeMachXO3LPlatform", 15 | "LatticePlatform", 16 | "QuicklogicPlatform", 17 | "SiliconBluePlatform", 18 | "XilinxPlatform", 19 | ] 20 | 21 | 22 | def __dir__(): 23 | return list({*globals(), *__all__}) 24 | 25 | 26 | def __getattr__(name): 27 | if name in ("AlteraPlatform", "IntelPlatform"): 28 | from ._altera import AlteraPlatform 29 | return AlteraPlatform 30 | if name == "GowinPlatform": 31 | from ._gowin import GowinPlatform 32 | return GowinPlatform 33 | if name in ("LatticePlatform", "LatticeECP5Platform", "LatticeMachXO2Platform", 34 | "LatticeMachXO3LPlatform"): 35 | from ._lattice import LatticePlatform 36 | return LatticePlatform 37 | if name == "QuicklogicPlatform": 38 | from ._quicklogic import QuicklogicPlatform 39 | return QuicklogicPlatform 40 | if name in ("SiliconBluePlatform", "LatticeICE40Platform"): 41 | from ._siliconblue import SiliconBluePlatform 42 | return SiliconBluePlatform 43 | if name in ("XilinxPlatform", "AMDPlatform"): 44 | from ._xilinx import XilinxPlatform 45 | return XilinxPlatform 46 | raise AttributeError(f"module {__name__!r} has no attribute {name!r}") 47 | -------------------------------------------------------------------------------- /amaranth/vendor/_quicklogic.py: -------------------------------------------------------------------------------- 1 | from abc import abstractmethod 2 | 3 | from ..hdl import * 4 | from ..lib import io 5 | from ..lib.cdc import ResetSynchronizer 6 | from ..build import * 7 | 8 | 9 | __all__ = ["QuicklogicPlatform"] 10 | 11 | 12 | class QuicklogicPlatform(TemplatedPlatform): 13 | """ 14 | .. rubric:: Symbiflow toolchain 15 | 16 | Required tools: 17 | * ``symbiflow_synth`` 18 | * ``symbiflow_pack`` 19 | * ``symbiflow_place`` 20 | * ``symbiflow_route`` 21 | * ``symbiflow_write_fasm`` 22 | * ``symbiflow_write_bitstream`` 23 | 24 | The environment is populated by running the script specified in the environment variable 25 | ``AMARANTH_ENV_QLSYMBIFLOW``, if present. 26 | 27 | Available overrides: 28 | * ``add_constraints``: inserts commands in XDC file. 29 | """ 30 | 31 | device = property(abstractmethod(lambda: None)) 32 | package = property(abstractmethod(lambda: None)) 33 | 34 | # Since the QuickLogic version of SymbiFlow toolchain is not upstreamed yet 35 | # we should distinguish the QuickLogic version from mainline one. 36 | # QuickLogic toolchain: https://github.com/QuickLogic-Corp/quicklogic-fpga-toolchain/releases 37 | toolchain = "QLSymbiflow" 38 | 39 | required_tools = [ 40 | "symbiflow_synth", 41 | "symbiflow_pack", 42 | "symbiflow_place", 43 | "symbiflow_route", 44 | "symbiflow_write_fasm", 45 | "symbiflow_write_bitstream", 46 | "symbiflow_write_openocd", 47 | ] 48 | file_templates = { 49 | **TemplatedPlatform.build_script_templates, 50 | "{{name}}.v": r""" 51 | /* {{autogenerated}} */ 52 | {{emit_verilog()}} 53 | """, 54 | "{{name}}.debug.v": r""" 55 | /* {{autogenerated}} */ 56 | {{emit_debug_verilog()}} 57 | """, 58 | "{{name}}.pcf": r""" 59 | # {{autogenerated}} 60 | {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%} 61 | set_io {{port_name}} {{pin_name}} 62 | {% endfor %} 63 | """, 64 | "{{name}}.xdc": r""" 65 | # {{autogenerated}} 66 | {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%} 67 | {% for attr_name, attr_value in attrs.items() -%} 68 | set_property {{attr_name}} {{attr_value}} [get_ports {{port_name|tcl_quote}} }] 69 | {% endfor %} 70 | {% endfor %} 71 | {{get_override("add_constraints")|default("# (add_constraints placeholder)")}} 72 | """, 73 | "{{name}}.sdc": r""" 74 | # {{autogenerated}} 75 | {% for signal, frequency in platform.iter_signal_clock_constraints() -%} 76 | create_clock -period {{100000000/frequency}} {{signal.name|ascii_escape}} 77 | {% endfor %} 78 | {% for port, frequency in platform.iter_port_clock_constraints() -%} 79 | create_clock -period {{100000000/frequency}} {{port.name|ascii_escape}} 80 | {% endfor %} 81 | """ 82 | } 83 | command_templates = [ 84 | r""" 85 | {{invoke_tool("symbiflow_synth")}} 86 | -t {{name}} 87 | -v {% for file in platform.iter_files(".v", ".sv", ".vhd", ".vhdl") -%} {{file}} {% endfor %} {{name}}.v 88 | -d {{platform.device}} 89 | -p {{name}}.pcf 90 | -P {{platform.package}} 91 | -x {{name}}.xdc 92 | """, 93 | r""" 94 | {{invoke_tool("symbiflow_pack")}} 95 | -e {{name}}.eblif 96 | -d {{platform.device}} 97 | -s {{name}}.sdc 98 | """, 99 | r""" 100 | {{invoke_tool("symbiflow_place")}} 101 | -e {{name}}.eblif 102 | -d {{platform.device}} 103 | -p {{name}}.pcf 104 | -n {{name}}.net 105 | -P {{platform.package}} 106 | -s {{name}}.sdc 107 | """, 108 | r""" 109 | {{invoke_tool("symbiflow_route")}} 110 | -e {{name}}.eblif 111 | -d {{platform.device}} 112 | -s {{name}}.sdc 113 | """, 114 | r""" 115 | {{invoke_tool("symbiflow_write_fasm")}} 116 | -e {{name}}.eblif 117 | -d {{platform.device}} 118 | -s {{name}}.sdc 119 | """, 120 | r""" 121 | {{invoke_tool("symbiflow_write_bitstream")}} 122 | -f {{name}}.fasm 123 | -d {{platform.device}} 124 | -P {{platform.package}} 125 | -b {{name}}.bit 126 | """, 127 | # This should be `invoke_tool("symbiflow_write_openocd")`, but isn't because of a bug in 128 | # the QLSymbiflow v1.3.0 toolchain release. 129 | r""" 130 | python3 -m quicklogic_fasm.bitstream_to_openocd 131 | {{name}}.bit 132 | {{name}}.openocd 133 | --osc-freq {{platform.osc_freq}} 134 | --fpga-clk-divider {{platform.osc_div}} 135 | """, 136 | ] 137 | 138 | # Common logic 139 | 140 | @property 141 | def default_clk_constraint(self): 142 | if self.default_clk == "sys_clk0": 143 | return Clock(Period(Hz=self.osc_freq / self.osc_div)) 144 | return super().default_clk_constraint 145 | 146 | def add_clock_constraint(self, clock, period=None, frequency=None): 147 | # TODO(amaranth-0.7): remove frequency argument 148 | super().add_clock_constraint(clock, period, frequency) 149 | clock.attrs["keep"] = "TRUE" 150 | 151 | def create_missing_domain(self, name): 152 | if name == "sync" and self.default_clk is not None: 153 | m = Module() 154 | if self.default_clk == "sys_clk0": 155 | if not hasattr(self, "osc_div"): 156 | raise ValueError("OSC divider (osc_div) must be an integer between 2 " 157 | "and 512") 158 | if not isinstance(self.osc_div, int) or self.osc_div < 2 or self.osc_div > 512: 159 | raise ValueError("OSC divider (osc_div) must be an integer between 2 " 160 | "and 512, not {!r}" 161 | .format(self.osc_div)) 162 | if not hasattr(self, "osc_freq"): 163 | raise ValueError("OSC frequency (osc_freq) must be an integer between 2100000 " 164 | "and 80000000") 165 | if not isinstance(self.osc_freq, int) or self.osc_freq < 2100000 or self.osc_freq > 80000000: 166 | raise ValueError("OSC frequency (osc_freq) must be an integer between 2100000 " 167 | "and 80000000, not {!r}" 168 | .format(self.osc_freq)) 169 | clk_i = Signal() 170 | sys_clk0 = Signal() 171 | m.submodules += Instance("qlal4s3b_cell_macro", 172 | o_Sys_Clk0=sys_clk0) 173 | m.submodules += Instance("gclkbuff", 174 | o_A=sys_clk0, 175 | o_Z=clk_i) 176 | else: 177 | clk_io = self.request(self.default_clk, dir="-") 178 | m.submodules.clk_buf = clk_buf = io.Buffer("i", clk_io) 179 | clk_i = clk_buf.i 180 | 181 | if self.default_rst is not None: 182 | rst_io = self.request(self.default_rst, dir="-") 183 | m.submodules.rst_buf = rst_buf = io.Buffer("i", rst_io) 184 | rst_i = rst_buf.i 185 | else: 186 | rst_i = Const(0) 187 | 188 | m.domains += ClockDomain("sync") 189 | m.d.comb += ClockSignal("sync").eq(clk_i) 190 | m.submodules.reset_sync = ResetSynchronizer(rst_i, domain="sync") 191 | return m 192 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _build/ 2 | _linkcheck/ 3 | -------------------------------------------------------------------------------- /docs/_code/led_blinker.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | 3 | 4 | class LEDBlinker(Elaboratable): 5 | def elaborate(self, platform): 6 | m = Module() 7 | 8 | led = platform.request("led") 9 | 10 | half_freq = int(platform.default_clk_period.hertz // 2) 11 | timer = Signal(range(half_freq + 1)) 12 | 13 | with m.If(timer == half_freq): 14 | m.d.sync += led.o.eq(~led.o) 15 | m.d.sync += timer.eq(0) 16 | with m.Else(): 17 | m.d.sync += timer.eq(timer + 1) 18 | 19 | return m 20 | # --- BUILD --- 21 | from amaranth_boards.icestick import ICEStickPlatform 22 | 23 | 24 | ICEStickPlatform().build(LEDBlinker(), do_program=True) 25 | -------------------------------------------------------------------------------- /docs/_code/up_counter.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.lib import wiring 3 | from amaranth.lib.wiring import In, Out 4 | 5 | 6 | class UpCounter(wiring.Component): 7 | """ 8 | A 16-bit up counter with a fixed limit. 9 | 10 | Parameters 11 | ---------- 12 | limit : int 13 | The value at which the counter overflows. 14 | 15 | Attributes 16 | ---------- 17 | en : Signal, in 18 | The counter is incremented if ``en`` is asserted, and retains 19 | its value otherwise. 20 | ovf : Signal, out 21 | ``ovf`` is asserted when the counter reaches its limit. 22 | """ 23 | 24 | en: In(1) 25 | ovf: Out(1) 26 | 27 | def __init__(self, limit): 28 | self.limit = limit 29 | self.count = Signal(16) 30 | 31 | super().__init__() 32 | 33 | def elaborate(self, platform): 34 | m = Module() 35 | 36 | m.d.comb += self.ovf.eq(self.count == self.limit) 37 | 38 | with m.If(self.en): 39 | with m.If(self.ovf): 40 | m.d.sync += self.count.eq(0) 41 | with m.Else(): 42 | m.d.sync += self.count.eq(self.count + 1) 43 | 44 | return m 45 | # --- TEST --- 46 | from amaranth.sim import Simulator, Period 47 | 48 | 49 | dut = UpCounter(25) 50 | async def bench(ctx): 51 | # Disabled counter should not overflow. 52 | ctx.set(dut.en, 0) 53 | for _ in range(30): 54 | await ctx.tick() 55 | assert not ctx.get(dut.ovf) 56 | 57 | # Once enabled, the counter should overflow in 25 cycles. 58 | ctx.set(dut.en, 1) 59 | for _ in range(24): 60 | await ctx.tick() 61 | assert not ctx.get(dut.ovf) 62 | await ctx.tick() 63 | assert ctx.get(dut.ovf) 64 | 65 | # The overflow should clear in one cycle. 66 | await ctx.tick() 67 | assert not ctx.get(dut.ovf) 68 | 69 | 70 | sim = Simulator(dut) 71 | sim.add_clock(Period(MHz=1)) 72 | sim.add_testbench(bench) 73 | with sim.write_vcd("up_counter.vcd"): 74 | sim.run() 75 | # --- CONVERT --- 76 | from amaranth.back import verilog 77 | 78 | 79 | top = UpCounter(25) 80 | with open("up_counter.v", "w") as f: 81 | f.write(verilog.convert(top)) 82 | -------------------------------------------------------------------------------- /docs/_code/up_counter.v: -------------------------------------------------------------------------------- 1 | (* generator = "Amaranth" *) 2 | module top(ovf, clk, rst, en); 3 | reg \$auto$verilog_backend.cc:2255:dump_module$1 = 0; 4 | (* src = "up_counter.py:36" *) 5 | wire \$1 ; 6 | (* src = "up_counter.py:42" *) 7 | wire [16:0] \$3 ; 8 | (* src = "up_counter.py:42" *) 9 | wire [16:0] \$4 ; 10 | (* src = "/amaranth/hdl/ir.py:509" *) 11 | input clk; 12 | wire clk; 13 | (* src = "up_counter.py:29" *) 14 | reg [15:0] count = 16'h0000; 15 | (* src = "up_counter.py:29" *) 16 | reg [15:0] \count$next ; 17 | (* src = "/amaranth/lib/wiring.py:1647" *) 18 | input en; 19 | wire en; 20 | (* src = "/amaranth/lib/wiring.py:1647" *) 21 | output ovf; 22 | wire ovf; 23 | (* src = "/amaranth/hdl/ir.py:509" *) 24 | input rst; 25 | wire rst; 26 | assign \$1 = count == (* src = "up_counter.py:36" *) 5'h19; 27 | assign \$4 = count + (* src = "up_counter.py:42" *) 1'h1; 28 | always @(posedge clk) 29 | count <= \count$next ; 30 | always @* begin 31 | if (\$auto$verilog_backend.cc:2255:dump_module$1 ) begin end 32 | \count$next = count; 33 | (* src = "up_counter.py:38" *) 34 | if (en) begin 35 | (* full_case = 32'd1 *) 36 | (* src = "up_counter.py:39" *) 37 | if (ovf) begin 38 | \count$next = 16'h0000; 39 | end else begin 40 | \count$next = \$4 [15:0]; 41 | end 42 | end 43 | (* src = "/amaranth/hdl/xfrm.py:534" *) 44 | if (rst) begin 45 | \count$next = 16'h0000; 46 | end 47 | end 48 | assign \$3 = \$4 ; 49 | assign ovf = \$1 ; 50 | endmodule 51 | -------------------------------------------------------------------------------- /docs/_static/custom.css: -------------------------------------------------------------------------------- 1 | /* Links in text should be underlined. */ 2 | a { text-decoration: underline; } 3 | .wy-menu-vertical a, .wy-side-nav-search > a { text-decoration: none; } 4 | 5 | /* Match the logo colors in the background. */ 6 | .wy-nav-top, .wy-side-nav-search { background-color: #784b9a; } 7 | 8 | /* Make the logo more reasonably sized. */ 9 | .wy-side-nav-search > a img.logo { width: 160px; } 10 | 11 | /* Some of our section titles are looong */ 12 | @media screen and (min-width:769px) { 13 | .wy-nav-side, .wy-side-scroll, .wy-menu-vertical { width: 340px; } 14 | .wy-side-nav-search { width: 340px; margin-bottom: .0em; } 15 | .wy-nav-content-wrap { margin-left: 340px; } 16 | } 17 | 18 | /* We don't have a version picker widget */ 19 | .wy-nav-side { padding-bottom: 0; } 20 | 21 | /* Many of our diagnostics are even longer */ 22 | .rst-content pre.literal-block, .rst-content div[class^="highlight"] pre, .rst-content .linenodiv pre { white-space: pre-wrap; } 23 | 24 | /* Work around https://github.com/readthedocs/sphinx_rtd_theme/issues/1301 */ 25 | .py.property { display: block !important; } 26 | 27 | /* Avoid excessively tiny font in the sidebar */ 28 | .wy-menu-vertical li.toctree-l2, .wy-menu-vertical li.toctree-l3, .wy-menu-vertical li.toctree-l4 { font-size: 0.97em; } 29 | /* For some cursed reason the RTD theme was decreasing the font size twice! */ 30 | .wy-menu-vertical a { font-size: 100%; } 31 | 32 | /* Work around images in docstrings being glued to the paragraph underneath */ 33 | .rst-content section dd>img { margin-bottom: 24px; } 34 | 35 | /* No switchable color schemes */ 36 | img { color-scheme: light; } 37 | -------------------------------------------------------------------------------- /docs/_static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amaranth-lang/amaranth/916791022cb9cf96a552756d0e00efe2a4b16aba/docs/_static/logo.png -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | import os, sys 2 | sys.path.insert(0, os.path.abspath(".")) 3 | 4 | import time 5 | from importlib.metadata import version as package_version 6 | 7 | 8 | project = "Amaranth language & toolchain" 9 | version = package_version('amaranth').replace(".editable", "") 10 | release = version.split("+")[0] 11 | copyright = time.strftime("2020—%Y, Amaranth project contributors") 12 | 13 | extensions = [ 14 | "sphinx.ext.intersphinx", 15 | "sphinx.ext.doctest", 16 | "sphinx.ext.todo", 17 | "sphinx.ext.autodoc", 18 | "sphinx.ext.napoleon", 19 | "sphinx_rtd_theme", 20 | "sphinxcontrib.platformpicker", 21 | "sphinxcontrib.yowasp_wavedrom", 22 | ] 23 | 24 | with open(".gitignore") as f: 25 | exclude_patterns = [line.strip() for line in f.readlines()] 26 | 27 | root_doc = "cover" 28 | 29 | intersphinx_mapping = { 30 | "python": ("https://docs.python.org/3", None), 31 | } 32 | 33 | todo_include_todos = True 34 | 35 | autodoc_member_order = "bysource" 36 | autodoc_default_options = { 37 | "members": True 38 | } 39 | autodoc_preserve_defaults = True 40 | autodoc_inherit_docstrings = False 41 | 42 | # Amaranth mostly does not include typehints, and showing them in some places but not others is 43 | # worse than not showing them at all. 44 | autodoc_typehints = "none" 45 | 46 | napoleon_google_docstring = False 47 | napoleon_numpy_docstring = True 48 | napoleon_use_ivar = True 49 | napoleon_include_init_with_doc = True 50 | napoleon_include_special_with_doc = True 51 | napoleon_custom_sections = [ 52 | ("Attributes", "params_style"), # by default displays as "Variables", which is confusing 53 | ("Members", "params_style"), # `lib.wiring` signature members 54 | "Platform overrides" 55 | ] 56 | 57 | html_theme = "sphinx_rtd_theme" 58 | html_static_path = ["_static"] 59 | html_css_files = ["custom.css"] 60 | html_logo = "_static/logo.png" 61 | 62 | rst_prolog = """ 63 | .. role:: py(code) 64 | :language: python 65 | """ 66 | 67 | linkcheck_ignore = [ 68 | r"^http://127\.0\.0\.1:8000$", 69 | # Picked up automatically by ReST and doesn't have an index. 70 | r"^https://amaranth-lang\.org/schema/$", 71 | ] 72 | 73 | linkcheck_anchors_ignore_for_url = [ 74 | r"^https://matrix\.to/", 75 | r"^https://web\.libera\.chat/", 76 | # React page with README content included as a JSON payload. 77 | r"^https://github\.com/[^/]+/[^/]+/$", 78 | ] 79 | 80 | 81 | # Silence the warnings globally; otherwise they may fire on object destruction and crash completely 82 | # unrelated tests. 83 | import amaranth._unused 84 | amaranth._unused.MustUse._MustUse__silence = True 85 | -------------------------------------------------------------------------------- /docs/cover.rst: -------------------------------------------------------------------------------- 1 | .. This page is loaded if you click on the Amaranth logo. 2 | 3 | Amaranth project documentation 4 | ############################## 5 | 6 | .. toctree:: 7 | :maxdepth: 1 8 | 9 | index 10 | Standard I/O components 11 | System on Chip toolkit 12 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Language & toolchain 2 | #################### 3 | 4 | .. warning:: 5 | 6 | This manual is a work in progress and is seriously incomplete! 7 | 8 | .. toctree:: 9 | :maxdepth: 2 10 | 11 | intro 12 | install 13 | start 14 | tutorial 15 | guide 16 | reference 17 | stdlib 18 | simulator 19 | platform 20 | changes 21 | contrib 22 | -------------------------------------------------------------------------------- /docs/intro.rst: -------------------------------------------------------------------------------- 1 | .. TODO: this introduction is written for people well familiar with HDLs; we likely need 2 | another one for people who will use Amaranth as their first HDL 3 | 4 | Introduction 5 | ############ 6 | 7 | The Amaranth project provides an open-source toolchain for developing hardware based on synchronous digital logic using the Python programming language. It aims to be easy to learn and use, reduce or eliminate common coding mistakes, and simplify the design of complex hardware with reusable components. 8 | 9 | The Amaranth toolchain consists of the :ref:`Amaranth language `, the :ref:`standard library `, the :ref:`simulator `, and the :ref:`build system `, covering all steps of a typical FPGA development workflow. At the same time, it does not restrict the designer's choice of tools: existing industry-standard (System)Verilog or VHDL code can be integrated into an Amaranth-based design flow, or, conversely, Amaranth code can be integrated into an existing Verilog-based design flow. 10 | 11 | .. TODO: add links to connect_rpc docs once they exist 12 | 13 | 14 | .. _intro-lang: 15 | 16 | The Amaranth language 17 | ===================== 18 | 19 | The :doc:`Amaranth hardware description language ` is a Python library for register transfer level modeling of synchronous logic. Ordinary Python code is used to construct a netlist of a digital circuit, which can be simulated, directly synthesized via Yosys_, or converted to human-readable Verilog code for use with industry-standard toolchains. 20 | 21 | By relying on the flexibility, rich functionality and widespread adoption of the Python language, the Amaranth language is focused on a single task: modeling digital logic well. It has first-class support for building blocks like clock domains and finite state machines, and uses simple rules for arithmetic operations that closely match the Python semantics. Python classes, functions, loops and conditionals can be used to build organized and flexible designs; Python libraries can be seamlessly used with Amaranth during design or verification; and Python development tools can process Amaranth code. 22 | 23 | A core design principle of the Amaranth language is to be not only easy to use, but also hard to accidentally misuse. Some HDLs provide functionality that has unexpected and undesirable behavior in synthesis, often with expensive consequences, and require a significant effort in learning a "safe" coding style and adopting third-party linting tools. Amaranth lacks non-synthesizable constructs and avoids error-prone inference in favor of explicit instantiation. It has many diagnostics (and regularly adds new ones) highlighting potential design issues. Most importantly, all usability issues are considered `reportable bugs`_. 24 | 25 | .. _Yosys: https://yosyshq.net/yosys/ 26 | .. _reportable bugs: https://github.com/amaranth-lang/amaranth/issues 27 | 28 | 29 | .. _intro-stdlib: 30 | 31 | The Amaranth standard library 32 | ============================= 33 | 34 | The Amaranth language comes with a standard library---a collection of essential digital design components and interfaces. It includes clock domain crossing primitives, synchronous and asynchronous FIFOs, a flexible I/O buffer interface, and more. By providing reliable building blocks out of the box, Amaranth allows the designer to focus on their application and avoids subtle differences in behavior between different designs. 35 | 36 | .. TODO: link to stdlib here 37 | 38 | Clock domain crossing often requires special treatment, such as using vendor-defined attributes or instantiating device-specific primitives. The CDC primitives in the Amaranth standard library can be overridden by the platform integration, and every platform integration included with Amaranth follows the vendor recommendations for CDC. 39 | 40 | High-speed designs usually require the use of registered (and sometimes, geared) I/O buffers. The Amaranth standard library provides a common interface to be used between I/O buffers and peripheral implementations. The Amaranth build system, if used, can instantiate I/O buffers for every platform integration included with Amaranth. 41 | 42 | While many designs will use at least some vendor-specific functionality, the components provided by the Amaranth standard library reduce the amount of code that needs to be changed when migrating between FPGA families, and the common interfaces simplify peripherals, test benches and simulations. 43 | 44 | The Amaranth standard library is optional: the Amaranth language can be used without it. Conversely, it is possible to use the Amaranth standard library components in Verilog or VHDL code, with some limitations. 45 | 46 | .. TODO: link to connect_rpc docs here *again* 47 | 48 | 49 | .. _intro-sim: 50 | 51 | The Amaranth simulator 52 | ====================== 53 | 54 | The Amaranth project includes an advanced simulator for Amaranth code implemented in Python with no system dependencies; in this simulator, test benches are written as Python generator functions. Of course, it is always possible to convert an Amaranth design to Verilog for use with well-known tool like `Icarus Verilog`_ or Verilator_. 55 | 56 | The Amaranth simulator is event-driven and can simulate designs with multiple clocks or asynchronous resets. Although it is slower than `Icarus Verilog`_, it compiles the netlist to Python code ahead of time, achieving remarkably high performance for a pure Python implementation---especially when running on PyPy_. 57 | 58 | Although Amaranth does not support native code simulation or co-simulation at the moment, such support will be added in near future. 59 | 60 | .. _Icarus Verilog: https://steveicarus.github.io/iverilog/ 61 | .. _Verilator: https://www.veripool.org/verilator/ 62 | .. _GTKWave: http://gtkwave.sourceforge.net/ 63 | .. _PyPy: https://www.pypy.org/ 64 | 65 | 66 | .. _intro-build: 67 | 68 | The Amaranth build system 69 | ========================= 70 | 71 | To achieve an end-to-end FPGA development workflow, the Amaranth project integrates with all major FPGA toolchains and provides definitions for many common development boards. 72 | 73 | .. TODO: link to vendor docs and board docs here 74 | 75 | 76 | FPGA toolchain integration 77 | -------------------------- 78 | 79 | Each FPGA family requires the use of synthesis and place & route tools specific for that device family. The Amaranth build system directly integrates with every major open-source and commercial FPGA toolchain, and can be easily extended to cover others. 80 | 81 | Through this integration, Amaranth can specialize the CDC primitives and I/O buffers for a particular device and toolchain; generate I/O and clock constraints from board definition files; synchronize the power-on reset in single-clock designs; include (System)Verilog and VHDL files in the design (if supported by the toolchain); and finally, generate a script running synthesis, placement, routing, and timing analysis. The generated code can be customized to insert additional options, commands, constraints, and so on. 82 | 83 | The Amaranth build system produces self-contained, portable build trees that require only the toolchain to be present in the environment. This makes builds easier to reproduce, or to run on a remote machine. The generated build scripts are always provided for both \*nix and Windows. 84 | 85 | 86 | Development board definitions 87 | ----------------------------- 88 | 89 | Getting started with a new FPGA development board often requires going through a laborous and error-prone process of deriving toolchain configuration and constraint files from the supplied documentation. The Amaranth project includes a community-maintained repository of definitions for many open-source and commercial FPGA development boards. 90 | 91 | These board definitions contain everything that is necessary to start using the board: FPGA family and model, clocks and resets, descriptions of on-board peripherals (including pin direction and attributes such as I/O standard), connector pinouts, and for boards with a built-in debug probe, the steps required to program the board. It takes a single Python invocation to generate, build, and download a test design that shows whether the board, toolchain, and programmer are working correctly. 92 | 93 | Amaranth establishes a pin naming convention for many common peripherals (such as 7-segment displays, SPI flashes and SDRAM memories), enabling the reuse of unmodified interface code with many different boards. Further, the polarity of all control signals is unified to be active high, eliminating accidental polarity inversions and making simulation traces easier to follow; active low signals are inverted during I/O buffer instantiation. 94 | -------------------------------------------------------------------------------- /docs/platform.rst: -------------------------------------------------------------------------------- 1 | .. _platform: 2 | 3 | Platform integration 4 | #################### 5 | 6 | .. todo:: 7 | 8 | Write this section. 9 | 10 | .. toctree:: 11 | :maxdepth: 2 12 | 13 | platform/altera 14 | platform/gowin 15 | platform/lattice 16 | platform/quicklogic 17 | platform/siliconblue 18 | platform/xilinx 19 | -------------------------------------------------------------------------------- /docs/platform/altera.rst: -------------------------------------------------------------------------------- 1 | Altera 2 | ###### 3 | 4 | .. currentmodule:: amaranth.vendor 5 | 6 | The :class:`AlteraPlatform` class provides a base platform to support Altera toolchains. 7 | 8 | The Quartus and Mistral toolchains are supported. 9 | 10 | .. autoclass:: AlteraPlatform 11 | -------------------------------------------------------------------------------- /docs/platform/gowin.rst: -------------------------------------------------------------------------------- 1 | Gowin 2 | ##### 3 | 4 | .. currentmodule:: amaranth.vendor 5 | 6 | The :class:`GowinPlatform` class provides a base platform to support Gowin toolchains. 7 | 8 | The Apicula and Gowin toolchains are supported. 9 | 10 | .. autoclass:: GowinPlatform 11 | -------------------------------------------------------------------------------- /docs/platform/lattice.rst: -------------------------------------------------------------------------------- 1 | Lattice 2 | ####### 3 | 4 | .. currentmodule:: amaranth.vendor 5 | 6 | The :class:`LatticePlatform` class provides a base platform to support Lattice toolchains (not including iCE40 devices, which are supported by :class:`SiliconBluePlatform`). Currently supported devices include ECP5, MachXO2, MachXO3L, and Nexus. 7 | 8 | The Trellis and Diamond toolchains are supported. 9 | 10 | .. autoclass:: LatticePlatform 11 | -------------------------------------------------------------------------------- /docs/platform/quicklogic.rst: -------------------------------------------------------------------------------- 1 | Quicklogic 2 | ########## 3 | 4 | .. currentmodule:: amaranth.vendor 5 | 6 | The :class:`QuicklogicPlatform` class provides a base platform to support Quicklogic toolchains. 7 | 8 | The Symbiflow toolchain is supported. 9 | 10 | .. autoclass:: QuicklogicPlatform 11 | -------------------------------------------------------------------------------- /docs/platform/siliconblue.rst: -------------------------------------------------------------------------------- 1 | SiliconBlue 2 | ########### 3 | 4 | .. currentmodule:: amaranth.vendor 5 | 6 | The :class:`SiliconBluePlatform` class provides a base platform to support Lattice (earlier SiliconBlue) iCE40 devices. 7 | 8 | The IceStorm and iCECube2 toolchains are supported. 9 | 10 | .. autoclass:: SiliconBluePlatform 11 | -------------------------------------------------------------------------------- /docs/platform/xilinx.rst: -------------------------------------------------------------------------------- 1 | Xilinx 2 | ###### 3 | 4 | .. currentmodule:: amaranth.vendor 5 | 6 | The :class:`XilinxPlatform` class provides a base platform to support Xilinx toolchains. 7 | 8 | The ISE, Vivado, and Symbiflow toolchains are supported. 9 | 10 | .. autoclass:: XilinxPlatform 11 | -------------------------------------------------------------------------------- /docs/reference.rst: -------------------------------------------------------------------------------- 1 | Language reference 2 | ################## 3 | 4 | .. py:module:: amaranth.hdl 5 | 6 | .. warning:: 7 | 8 | This reference is a work in progress and is seriously incomplete! 9 | 10 | While the wording below states that anything not described in this document isn't covered by the backwards compatibility guarantee, this should be ignored until the document is complete and this warning is removed. 11 | 12 | This reference describes the Python classes that underlie the Amaranth language's syntax. It assumes familiarity with the :doc:`language guide `. 13 | 14 | 15 | .. _lang-stability: 16 | 17 | Backwards compatibility 18 | ======================= 19 | 20 | As part of the Amaranth backwards compatibility guarantee, any behaviors described in this document will not change from a version to another without at least one version including a warning about the impending change. Any nontrivial change to these behaviors must also go through the public review as a part of the `Amaranth Request for Comments process `_. 21 | 22 | Conversely, any behavior not documented here is subject to change at any time with or without notice, and any names under the :mod:`amaranth.hdl` module that are not explicitly included in this document, even if they do not begin with an underscore, are internal to the implementation of the language. 23 | 24 | 25 | .. _lang-importing: 26 | 27 | Importing syntax 28 | ================ 29 | 30 | There are two ways to import the Amaranth syntax into a Python file: by importing the :ref:`prelude ` or by importing individual names from the :mod:`amaranth.hdl` module. Since the prelude is kept small and rarely extended to avoid breaking downstream code that uses a glob import, there are some names that are only exported from the :mod:`amaranth.hdl` module. The following three snippets are equivalent: 31 | 32 | .. testcode:: 33 | 34 | from amaranth import * 35 | 36 | m = Module() 37 | 38 | .. testcode:: 39 | 40 | import amaranth as am 41 | 42 | m = am.Module() 43 | 44 | .. testcode:: 45 | 46 | from amaranth.hdl import Module 47 | 48 | m = Module() 49 | 50 | The prelude exports exactly the following names: 51 | 52 | .. must be kept in sync with amaranth/__init__.py! 53 | 54 | * :class:`Shape` 55 | * :func:`unsigned` 56 | * :func:`signed` 57 | * :class:`Value` 58 | * :class:`Const` 59 | * :func:`C` 60 | * :func:`Mux` 61 | * :func:`Cat` 62 | * :class:`Array` 63 | * :class:`Signal` 64 | * :class:`ClockSignal` 65 | * :class:`ResetSignal` 66 | * :class:`Format` 67 | * :class:`Print` 68 | * :func:`Assert` 69 | * :class:`Module` 70 | * :class:`ClockDomain` 71 | * :class:`Elaboratable` 72 | * :class:`Fragment` 73 | * :class:`Instance` 74 | * :class:`Memory` 75 | * :class:`DomainRenamer` 76 | * :class:`ResetInserter` 77 | * :class:`EnableInserter` 78 | 79 | 80 | .. _lang-srcloc: 81 | 82 | Source locations 83 | ================ 84 | 85 | Many functions and methods in Amaranth take the :py:`src_loc_at=0` keyword argument. These language constructs may inspect the call stack to determine the file and line of its call site, which will be used to annotate generated code when a netlist is generated or to improve diagnostic messages. 86 | 87 | Some call sites are not relevant for an Amaranth designer; e.g. when an Amaranth language construct is called from a user-defined utility function, the source location of the call site within this utility function is usually not interesting to the designer. In these cases, one or more levels of function calls can be removed from consideration using the :py:`src_loc_at` argument as follows (using :meth:`Shape.cast` to demonstrate the concept): 88 | 89 | .. testcode:: 90 | 91 | def my_shape_cast(obj, *, src_loc_at=0): 92 | ... # additionally process `obj`... 93 | return Shape.cast(obj, src_loc_at=1 + src_loc_at) 94 | 95 | The number :py:`1` corresponds to the number of call stack frames that should be skipped. 96 | 97 | 98 | Shapes 99 | ====== 100 | 101 | See also the introduction to :ref:`shapes ` and :ref:`casting from shape-like objects ` in the language guide. 102 | 103 | .. autoclass:: Shape 104 | .. autofunction:: unsigned 105 | .. autofunction:: signed 106 | .. autoclass:: ShapeCastable() 107 | .. autoclass:: ShapeLike() 108 | 109 | 110 | Values 111 | ====== 112 | 113 | See also the introduction to :ref:`values ` and :ref:`casting from value-like objects ` in the language guide. 114 | 115 | .. autoclass:: Value 116 | :special-members: __bool__, __pos__, __neg__, __add__, __radd__, __sub__, __rsub__, __mul__, __rmul__, __mod__, __rmod__, __floordiv__, __rfloordiv__, __eq__, __ne__, __lt__, __le__, __gt__, __ge__, __abs__, __invert__, __and__, __rand__, __or__, __ror__, __xor__, __rxor__, __lshift__, __rlshift__, __rshift__, __rrshift__, __len__, __getitem__, __contains__, __hash__ 117 | .. autoclass:: ValueCastable() 118 | .. autoclass:: ValueLike() 119 | -------------------------------------------------------------------------------- /docs/start.rst: -------------------------------------------------------------------------------- 1 | Getting started 2 | ############### 3 | 4 | This section demonstrates the basic Amaranth workflow to provide a cursory overview of the language and the toolchain. See the :doc:`tutorial ` for a step-by-step introduction to the language, and the :doc:`language guide ` for a detailed explanation of every language construct. 5 | 6 | .. TODO: add link to build system doc 7 | .. TODO: add link to more complex examples? 8 | 9 | 10 | A counter 11 | ========= 12 | 13 | As a first example, consider a counter with a fixed limit, enable, and overflow. The code for this example is shown below. :download:`Download <_code/up_counter.py>` and run it: 14 | 15 | .. code-block:: shell 16 | 17 | $ python3 up_counter.py 18 | 19 | 20 | Implementing a counter 21 | ---------------------- 22 | 23 | A 16-bit up counter with enable input, overflow output, and a limit fixed at design time can be implemented in Amaranth as follows: 24 | 25 | .. literalinclude:: _code/up_counter.py 26 | :linenos: 27 | :lineno-match: 28 | :end-before: # --- TEST --- 29 | 30 | The reusable building block of Amaranth designs is a ``Component``: a Python class declares its interface (``en`` and ``ovf``, in this case) and implements the ``elaborate`` method that defines its behavior. 31 | 32 | .. TODO: link to Elaboratable reference 33 | 34 | Most ``elaborate`` implementations use a ``Module`` helper to describe combinational (``m.d.comb``) and synchronous (``m.d.sync``) logic controlled with conditional syntax (``m.If``, ``m.Elif``, ``m.Else``) similar to Python's. They can also instantiate vendor-defined black boxes or modules written in other HDLs. 35 | 36 | .. TODO: link to DSL reference 37 | 38 | 39 | Testing a counter 40 | ----------------- 41 | 42 | To verify its functionality, the counter can be simulated for a small amount of time, with a test bench driving it and checking a few simple conditions: 43 | 44 | .. literalinclude:: _code/up_counter.py 45 | :linenos: 46 | :lineno-match: 47 | :start-after: # --- TEST --- 48 | :end-before: # --- CONVERT --- 49 | 50 | The testbench is implemented as a Python :py:`async` function that is simulated concurrently with the counter itself. The testbench can inspect the simulated signals using :py:`ctx.get(sig)`, update them using :py:`ctx.set(sig, val)`, and advance the simulation by one clock cycle with :py:`await ctx.tick()`. See the :doc:`simulator documentation ` for details. 51 | 52 | When run, the testbench finishes successfully, since all of the assertions hold, and produces a VCD file with waveforms recorded for every :class:`Signal` as well as the clock of the ``sync`` domain: 53 | 54 | .. wavedrom:: start/up_counter 55 | 56 | { 57 | "signal": [ 58 | {"name": "clk", "wave": "p.........."}, 59 | {"name": "count", "wave": "===========", "data": ["17", "18", "19", "20", "21", "22", "23", "24", "25", "0", "1"]}, 60 | {"name": "en", "wave": "1.........."}, 61 | {"name": "ovf", "wave": "0.......10."}, 62 | ], 63 | "head": { 64 | "tock": 48 65 | } 66 | } 67 | 68 | 69 | Converting a counter 70 | -------------------- 71 | 72 | Although some Amaranth workflows do not include Verilog at all, it is still the de facto standard for HDL interoperability. Any Amaranth design can be converted to synthesizable Verilog using the corresponding backend: 73 | 74 | .. literalinclude:: _code/up_counter.py 75 | :linenos: 76 | :lineno-match: 77 | :start-after: # --- CONVERT --- 78 | 79 | The signals that will be connected to the ports of the top-level Verilog module should be specified explicitly. The rising edge clock and synchronous reset signals of the ``sync`` domain are added automatically; if necessary, the control signals can be configured explicitly. The result is the following Verilog code (lightly edited for clarity): 80 | 81 | .. TODO: link to clock domain section of language reference 82 | 83 | .. literalinclude:: _code/up_counter.v 84 | :language: verilog 85 | :linenos: 86 | 87 | To aid debugging, the generated Verilog code has the same general structure as the Amaranth source code (although more verbose), and contains extensive source location information. 88 | 89 | .. note:: 90 | 91 | Unfortunately, at the moment none of the supported toolchains will use the source location information in diagnostic messages. 92 | 93 | 94 | A blinking LED 95 | ============== 96 | 97 | Although Amaranth works well as a standalone HDL, it also includes a build system that integrates with FPGA toolchains, and many board definition files for common developer boards that include pinouts and programming adapter invocations. The following code will blink a LED with a frequency of 1 Hz on any board that has a LED and an oscillator: 98 | 99 | .. literalinclude:: _code/led_blinker.py 100 | :linenos: 101 | :lineno-match: 102 | :end-before: # --- BUILD --- 103 | 104 | The ``LEDBlinker`` module will use the first LED available on the board, and derive the clock divisor from the oscillator frequency specified in the clock constraint. It can be used, for example, with the `Lattice iCEStick evaluation board `_, one of the many boards already supported by Amaranth: 105 | 106 | .. TODO: link to list of supported boards 107 | 108 | .. todo:: 109 | 110 | Link to the installation instructions for the FOSS iCE40 toolchain, probably as a part of board documentation. 111 | 112 | .. literalinclude:: _code/led_blinker.py 113 | :linenos: 114 | :lineno-match: 115 | :start-after: # --- BUILD --- 116 | 117 | With only a single line of code, the design is synthesized, placed, routed, and programmed to the on-board Flash memory. Although not all applications will use the Amaranth build system, the designs that choose it can benefit from the "turnkey" built-in workflows; if necessary, the built-in workflows can be customized to include user-specified options, commands, and files. 118 | 119 | .. TODO: link to build system reference 120 | 121 | .. note:: 122 | 123 | The ability to check with minimal effort whether the entire toolchain functions correctly is so important that it is built into every board definition file. To use it with the iCEStick board, run: 124 | 125 | .. code-block:: shell 126 | 127 | $ python3 -m amaranth_boards.icestick 128 | 129 | This command will build and program a test bitstream similar to the example above. 130 | -------------------------------------------------------------------------------- /docs/stdlib.rst: -------------------------------------------------------------------------------- 1 | Standard library 2 | ################ 3 | 4 | The :mod:`amaranth.lib` module, also known as the standard library, provides modules that falls into one of the three categories: 5 | 6 | 1. Modules that will used by essentially all idiomatic Amaranth code, or which are necessary for interoperability. This includes :mod:`amaranth.lib.enum` (enumerations), :mod:`amaranth.lib.data` (data structures), :mod:`amaranth.lib.wiring` (interfaces and components), :mod:`amaranth.lib.meta` (interface metadata), and :mod:`amaranth.lib.stream` (data streams). 7 | 2. Modules that abstract common functionality whose implementation differs between hardware platforms. This includes :mod:`amaranth.lib.memory` and :mod:`amaranth.lib.cdc`. 8 | 3. Modules that have essentially one correct implementation and are of broad utility in digital designs. This includes :mod:`amaranth.lib.fifo`, and :mod:`amaranth.lib.crc`. 9 | 10 | As part of the Amaranth backwards compatibility guarantee, any behaviors described in these documents will not change from a version to another without at least one version including a warning about the impending change. Any nontrivial change to these behaviors must also go through the public review as a part of the `Amaranth Request for Comments process `_. 11 | 12 | The Amaranth standard library is separate from the Amaranth language: everything provided in it could have been implemented in a third-party library. 13 | 14 | .. toctree:: 15 | :maxdepth: 2 16 | 17 | stdlib/enum 18 | stdlib/data 19 | stdlib/wiring 20 | stdlib/meta 21 | stdlib/stream 22 | stdlib/memory 23 | stdlib/io 24 | stdlib/cdc 25 | stdlib/fifo 26 | stdlib/crc 27 | -------------------------------------------------------------------------------- /docs/stdlib/_images/stream_pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amaranth-lang/amaranth/916791022cb9cf96a552756d0e00efe2a4b16aba/docs/stdlib/_images/stream_pipeline.png -------------------------------------------------------------------------------- /docs/stdlib/cdc.rst: -------------------------------------------------------------------------------- 1 | Clock domain crossing 2 | ##################### 3 | 4 | .. py:module:: amaranth.lib.cdc 5 | 6 | The :mod:`amaranth.lib.cdc` module provides building blocks for transferring data between clock domains. 7 | 8 | 9 | .. autoclass:: FFSynchronizer() 10 | .. autoclass:: AsyncFFSynchronizer() 11 | .. autoclass:: ResetSynchronizer() 12 | .. autoclass:: PulseSynchronizer() 13 | 14 | -------------------------------------------------------------------------------- /docs/stdlib/crc.rst: -------------------------------------------------------------------------------- 1 | Cyclic redundancy checks 2 | ######################## 3 | 4 | .. py:module:: amaranth.lib.crc 5 | 6 | The :mod:`amaranth.lib.crc` module provides facilities for computing cyclic redundancy checks (CRCs) 7 | in software and in hardware. 8 | 9 | 10 | Introduction 11 | ============ 12 | 13 | The essentials of a CRC computation are specified with an :class:`Algorithm` object, which defines 14 | CRC width, polynomial, initial value, input/output reflection, and output XOR. Many commonly used 15 | CRC algorithms are available in the :py:mod:`~amaranth.lib.crc.catalog` module, while most other 16 | CRC designs can be accommodated by manually constructing an :class:`Algorithm`. 17 | 18 | An :class:`Algorithm` is specialized for a particular data width to obtain :class:`Parameters`, 19 | which fully define a CRC computation. :meth:`Parameters.compute` computes a CRC in software, while 20 | :meth:`Parameters.create` creates a :class:`Processor` that computes a CRC in hardware. 21 | 22 | 23 | Examples 24 | ======== 25 | 26 | .. testsetup:: 27 | 28 | from amaranth import * 29 | 30 | m = Module() 31 | 32 | .. testcode:: 33 | 34 | from amaranth.lib.crc import Algorithm 35 | from amaranth.lib.crc.catalog import CRC16_CCITT, CRC16_USB 36 | 37 | 38 | # Compute a CRC in hardware using the predefined CRC16-CCITT algorithm and a data word 39 | # width of 8 bits (in other words, computing it over bytes). 40 | m.submodules.crc16_ccitt = crc16_ccitt = CRC16_CCITT().create() 41 | 42 | # Compute a CRC in hardware using the predefined CRC16-USB algorithm and a data word 43 | # width of 32 bits. 44 | m.submodules.crc16_usb = crc16_usb = CRC16_USB(32).create() 45 | 46 | # Compute a CRC in software using a custom CRC algorithm and explicitly specified data word 47 | # width. 48 | algo = Algorithm(crc_width=16, polynomial=0x1021, initial_crc=0xffff, 49 | reflect_input=False, reflect_output=False, xor_output=0x0000) 50 | assert algo(data_width=8).compute(b"123456789") == 0x29b1 51 | 52 | 53 | Algorithms and parameters 54 | ========================= 55 | 56 | .. autoclass:: Algorithm 57 | :special-members: __call__ 58 | 59 | .. autoclass:: Parameters 60 | 61 | 62 | CRC computation 63 | =============== 64 | 65 | .. autoclass:: Processor() 66 | 67 | 68 | Predefined algorithms 69 | ===================== 70 | 71 | The following predefined CRC algorithms are available: 72 | 73 | .. toctree:: 74 | 75 | crc/catalog 76 | -------------------------------------------------------------------------------- /docs/stdlib/crc/catalog.rst: -------------------------------------------------------------------------------- 1 | Algorithm catalog 2 | ################# 3 | 4 | .. automodule:: amaranth.lib.crc.catalog 5 | -------------------------------------------------------------------------------- /docs/stdlib/data.rst: -------------------------------------------------------------------------------- 1 | Data structures 2 | ############### 3 | 4 | .. py:module:: amaranth.lib.data 5 | 6 | The :mod:`amaranth.lib.data` module provides a way to describe the bitwise layout of values and a proxy class for accessing fields of values using the attribute access and indexing syntax. 7 | 8 | 9 | Introduction 10 | ============ 11 | 12 | 13 | Overview 14 | ++++++++ 15 | 16 | This module provides four related facilities: 17 | 18 | 1. Low-level bitwise layout description via :class:`Field` and :class:`Layout`. These classes are rarely used directly, but are the foundation on which all other functionality is built. They are also useful for introspection. 19 | 2. High-level bitwise layout description via :class:`StructLayout`, :class:`UnionLayout`, :class:`ArrayLayout`, and :class:`FlexibleLayout`. These classes are the ones most often used directly, in particular :class:`StructLayout` and :class:`ArrayLayout`. 20 | 3. Data views via :class:`View` or its user-defined subclasses. This class is used to apply a layout description to a plain :class:`Value`, enabling structured access to its bits. 21 | 4. Data classes :class:`Struct` and :class:`Union`. These classes are data views with a layout that is defined using Python :term:`variable annotations ` (also known as type annotations). 22 | 23 | To use this module, add the following imports to the beginning of the file: 24 | 25 | .. testcode:: 26 | 27 | from amaranth.lib import data 28 | 29 | 30 | Motivation 31 | ++++++++++ 32 | 33 | The fundamental Amaranth type is a :class:`Value`: a sequence of bits that can also be used as a number. Manipulating values directly is sufficient for simple applications, but in more complex ones, values are often more than just a sequence of bits; they have well-defined internal structure. 34 | 35 | .. testsetup:: 36 | 37 | from amaranth import * 38 | m = Module() 39 | 40 | For example, consider a module that processes pixels, converting them from RGB to grayscale. The color pixel format is RGB565: 41 | 42 | .. wavedrom:: data/rgb565_layout 43 | 44 | { 45 | "reg": [ 46 | {"name": ".red", "bits": 5, "type": 2}, 47 | {"name": ".green", "bits": 6, "type": 3}, 48 | {"name": ".blue", "bits": 5, "type": 4} 49 | ], 50 | "config": { 51 | "lanes": 1, 52 | "compact": true, 53 | "vflip": true, 54 | "hspace": 650 55 | } 56 | } 57 | 58 | This module could be implemented (using a fast but *very* approximate method) as follows: 59 | 60 | .. testcode:: 61 | 62 | i_color = Signal(16) 63 | o_gray = Signal(8) 64 | 65 | m.d.comb += o_gray.eq((i_color[0:5] + i_color[5:11] + i_color[11:16]) << 1) 66 | 67 | While this implementation works, it is repetitive, error-prone, hard to read, and laborous to change; all because the color components are referenced using bit offsets. To improve it, the structure can be described with a :class:`Layout` so that the components can be referenced by name: 68 | 69 | .. testcode:: 70 | 71 | from amaranth.lib import data, enum 72 | 73 | rgb565_layout = data.StructLayout({ 74 | "red": 5, 75 | "green": 6, 76 | "blue": 5 77 | }) 78 | 79 | i_color = Signal(rgb565_layout) 80 | o_gray = Signal(8) 81 | 82 | m.d.comb += o_gray.eq((i_color.red + i_color.green + i_color.blue) << 1) 83 | 84 | The :class:`View` is :ref:`value-like ` and can be used anywhere a plain value can be used. For example, it can be assigned to in the usual way: 85 | 86 | .. testcode:: 87 | 88 | m.d.comb += i_color.eq(0) # everything is black 89 | 90 | 91 | Composing layouts 92 | +++++++++++++++++ 93 | 94 | Layouts are composable: a :class:`Layout` is a :ref:`shape ` and can be used as a part of another layout. In this case, an attribute access through a view returns a view as well. 95 | 96 | For example, consider a module that processes RGB pixels in groups of up to four at a time, provided by another module, and accumulates their average intensity: 97 | 98 | .. testcode:: 99 | 100 | input_layout = data.StructLayout({ 101 | "pixels": data.ArrayLayout(rgb565_layout, 4), 102 | "valid": 4 103 | }) 104 | 105 | i_stream = Signal(input_layout) 106 | r_accum = Signal(32) 107 | 108 | m.d.sync += r_accum.eq( 109 | r_accum + sum((i_stream.pixels[n].red + 110 | i_stream.pixels[n].green + 111 | i_stream.pixels[n].blue) 112 | * i_stream.valid[n] 113 | for n in range(len(i_stream.valid)))) 114 | 115 | Note how the width of :py:`i_stream` is never defined explicitly; it is instead inferred from the shapes of its fields. 116 | 117 | In the previous section, the precise bitwise layout was important, since RGB565 is an interchange format. In this section however the exact bit positions do not matter, since the layout is only used internally to communicate between two modules in the same design. It is sufficient that both of them use the same layout. 118 | 119 | 120 | Defining layouts 121 | ++++++++++++++++ 122 | 123 | Data layouts can be defined in a few different ways depending on the use case. 124 | 125 | In case the data format is defined using a family of layouts instead of a single specific one, a function can be used: 126 | 127 | .. testcode:: 128 | 129 | def rgb_layout(r_bits, g_bits, b_bits): 130 | return data.StructLayout({ 131 | "red": unsigned(r_bits), 132 | "green": unsigned(g_bits), 133 | "blue": unsigned(b_bits) 134 | }) 135 | 136 | rgb565_layout = rgb_layout(5, 6, 5) 137 | rgb24_layout = rgb_layout(8, 8, 8) 138 | 139 | In case the data has related operations or transformations, :class:`View` can be subclassed to define methods implementing them: 140 | 141 | .. testcode:: 142 | 143 | class RGBLayout(data.StructLayout): 144 | def __init__(self, r_bits, g_bits, b_bits): 145 | super().__init__({ 146 | "red": unsigned(r_bits), 147 | "green": unsigned(g_bits), 148 | "blue": unsigned(b_bits) 149 | }) 150 | 151 | def __call__(self, value): 152 | return RGBView(self, value) 153 | 154 | class RGBView(data.View): 155 | def brightness(self): 156 | return (self.red + self.green + self.blue)[-8:] 157 | 158 | Here, an instance of the :py:`RGBLayout` class itself is :ref:`shape-like ` and can be used anywhere a shape is accepted. When a :class:`Signal` is constructed with this layout, the returned value is wrapped in an :py:`RGBView`: 159 | 160 | .. doctest:: 161 | 162 | >>> pixel = Signal(RGBLayout(5, 6, 5)) 163 | >>> len(pixel.as_value()) 164 | 16 165 | >>> pixel.red 166 | (slice (sig pixel) 0:5) 167 | 168 | In case the data format is static, :class:`Struct` (or :class:`Union`) can be subclassed instead of :class:`View`, to reduce the amount of boilerplate needed: 169 | 170 | .. testcode:: 171 | 172 | class IEEE754Single(data.Struct): 173 | fraction: 23 174 | exponent: 8 = 0x7f 175 | sign: 1 176 | 177 | def is_subnormal(self): 178 | return self.exponent == 0 179 | 180 | 181 | Discriminated unions 182 | ++++++++++++++++++++ 183 | 184 | This module provides a :class:`UnionLayout`, which is rarely needed by itself, but is very useful in combination with a *discriminant*: a enumeration indicating which field of the union contains valid data. 185 | 186 | For example, consider a module that can direct another module to perform one of a few operations, each of which requires its own parameters. The two modules could communicate through a channel with a layout like this: 187 | 188 | .. testcode:: 189 | 190 | class Command(data.Struct): 191 | class Kind(enum.Enum): 192 | SET_ADDR = 0 193 | SEND_DATA = 1 194 | 195 | valid : 1 196 | kind : Kind 197 | params : data.UnionLayout({ 198 | "set_addr": data.StructLayout({ 199 | "addr": unsigned(32) 200 | }), 201 | "send_data": data.StructLayout({ 202 | "byte": unsigned(8) 203 | }) 204 | }) 205 | 206 | Here, the shape of the :py:`Command` is inferred, being large enough to accommodate the biggest of all defined parameter structures, and it is not necessary to manage it manually. 207 | 208 | One module could submit a command with: 209 | 210 | .. testcode:: 211 | 212 | cmd = Signal(Command) 213 | 214 | m.d.comb += [ 215 | cmd.valid.eq(1), 216 | cmd.kind.eq(Command.Kind.SET_ADDR), 217 | cmd.params.set_addr.addr.eq(0x00001234) 218 | ] 219 | 220 | The other would react to commands as follows: 221 | 222 | .. testcode:: 223 | 224 | addr = Signal(32) 225 | 226 | with m.If(cmd.valid): 227 | with m.Switch(cmd.kind): 228 | with m.Case(Command.Kind.SET_ADDR): 229 | m.d.sync += addr.eq(cmd.params.set_addr.addr) 230 | with m.Case(Command.Kind.SEND_DATA): 231 | ... 232 | 233 | 234 | Modeling structured data 235 | ======================== 236 | 237 | .. autoclass:: Field 238 | .. autoclass:: Layout() 239 | 240 | 241 | Common data layouts 242 | =================== 243 | 244 | .. autoclass:: StructLayout 245 | .. autoclass:: UnionLayout 246 | .. autoclass:: ArrayLayout 247 | .. autoclass:: FlexibleLayout 248 | 249 | 250 | Data views 251 | ========== 252 | 253 | .. autoclass:: View 254 | .. autoclass:: Const 255 | 256 | 257 | Data classes 258 | ============ 259 | 260 | .. autoclass:: Struct 261 | .. autoclass:: Union 262 | -------------------------------------------------------------------------------- /docs/stdlib/enum.rst: -------------------------------------------------------------------------------- 1 | Enumerations 2 | ############ 3 | 4 | .. py:module:: amaranth.lib.enum 5 | 6 | The :mod:`amaranth.lib.enum` module is a drop-in replacement for the standard :mod:`enum` module that provides extended :class:`Enum`, :class:`IntEnum`, :class:`Flag`, and :class:`IntFlag` classes with the ability to specify a shape explicitly. 7 | 8 | A shape can be specified for an enumeration with the ``shape=`` keyword argument: 9 | 10 | .. testsetup:: 11 | 12 | from amaranth import * 13 | 14 | .. testcode:: 15 | 16 | from amaranth.lib import enum 17 | 18 | class Funct(enum.Enum, shape=4): 19 | ADD = 0 20 | SUB = 1 21 | MUL = 2 22 | 23 | .. doctest:: 24 | 25 | >>> Shape.cast(Funct) 26 | unsigned(4) 27 | >>> Value.cast(Funct.ADD) 28 | (const 4'd0) 29 | 30 | Any :ref:`constant-castable ` expression can be used as the value of a member: 31 | 32 | .. testcode:: 33 | 34 | class Op(enum.Enum, shape=1): 35 | REG = 0 36 | IMM = 1 37 | 38 | class Instr(enum.Enum, shape=5): 39 | ADD = Cat(Funct.ADD, Op.REG) 40 | ADDI = Cat(Funct.ADD, Op.IMM) 41 | SUB = Cat(Funct.SUB, Op.REG) 42 | SUBI = Cat(Funct.SUB, Op.IMM) 43 | ... 44 | 45 | .. doctest:: 46 | 47 | >>> Instr.SUBI 48 | 49 | 50 | The ``shape=`` argument is optional. If not specified, classes from this module behave exactly the same as classes from the standard :mod:`enum` module, and likewise, this module re-exports everything exported by the standard :mod:`enum` module. 51 | 52 | .. testcode:: 53 | 54 | import amaranth.lib.enum 55 | 56 | class NormalEnum(amaranth.lib.enum.Enum): 57 | SPAM = 0 58 | HAM = 1 59 | 60 | In this way, this module is a drop-in replacement for the standard :mod:`enum` module, and in an Amaranth project, all ``import enum`` statements may be replaced with ``from amaranth.lib import enum``. 61 | 62 | Signals with :class:`Enum` or :class:`Flag` based shape are automatically wrapped in the :class:`EnumView` or :class:`FlagView` value-like wrappers, which ensure type safety. Any :ref:`value-like ` can also be explicitly wrapped in a view class by casting it to the enum type: 63 | 64 | .. doctest:: 65 | 66 | >>> a = Signal(Funct) 67 | >>> b = Signal(Op) 68 | >>> type(a) 69 | 70 | >>> a == b 71 | Traceback (most recent call last): 72 | File "", line 1, in 73 | TypeError: an EnumView can only be compared to value or other EnumView of the same enum type 74 | >>> c = Signal(4) 75 | >>> type(Funct(c)) 76 | 77 | 78 | Like the standard Python :class:`enum.IntEnum` and :class:`enum.IntFlag` classes, the Amaranth :class:`IntEnum` and :class:`IntFlag` classes are loosely typed and will not be subject to wrapping in view classes: 79 | 80 | .. testcode:: 81 | 82 | class TransparentEnum(enum.IntEnum, shape=unsigned(4)): 83 | FOO = 0 84 | BAR = 1 85 | 86 | .. doctest:: 87 | 88 | >>> a = Signal(TransparentEnum) 89 | >>> type(a) is Signal 90 | True 91 | 92 | It is also possible to define a custom view class for a given enum: 93 | 94 | .. testcode:: 95 | 96 | class InstrView(enum.EnumView): 97 | def has_immediate(self): 98 | return (self == Instr.ADDI) | (self == Instr.SUBI) 99 | 100 | class Instr(enum.Enum, shape=5, view_class=InstrView): 101 | ADD = Cat(Funct.ADD, Op.REG) 102 | ADDI = Cat(Funct.ADD, Op.IMM) 103 | SUB = Cat(Funct.SUB, Op.REG) 104 | SUBI = Cat(Funct.SUB, Op.IMM) 105 | 106 | .. doctest:: 107 | 108 | >>> a = Signal(Instr) 109 | >>> type(a) 110 | 111 | >>> a.has_immediate() 112 | (| (== (sig a) (const 5'd16)) (== (sig a) (const 5'd17))) 113 | 114 | Metaclass 115 | ========= 116 | 117 | .. autoclass:: EnumType() 118 | 119 | 120 | Base classes 121 | ============ 122 | 123 | .. autoclass:: Enum() 124 | .. autoclass:: IntEnum() 125 | .. autoclass:: Flag() 126 | .. autoclass:: IntFlag() 127 | 128 | View classes 129 | ============ 130 | 131 | .. autoclass:: EnumView() 132 | .. autoclass:: FlagView() -------------------------------------------------------------------------------- /docs/stdlib/fifo.rst: -------------------------------------------------------------------------------- 1 | First-in first-out queues 2 | ######################### 3 | 4 | .. py:module:: amaranth.lib.fifo 5 | 6 | The ``amaranth.lib.fifo`` module provides building blocks for first-in, first-out queues. 7 | 8 | 9 | .. autoclass:: FIFOInterface 10 | 11 | .. note:: 12 | 13 | The :class:`FIFOInterface` class can be used directly to substitute a FIFO in tests, or inherited from in a custom FIFO implementation. 14 | 15 | .. autoclass:: SyncFIFO(*, width, depth) 16 | .. autoclass:: SyncFIFOBuffered(*, width, depth) 17 | .. autoclass:: AsyncFIFO(*, width, depth, r_domain="read", w_domain="write", exact_depth=False) 18 | .. autoclass:: AsyncFIFOBuffered(*, width, depth, r_domain="read", w_domain="write", exact_depth=False) 19 | -------------------------------------------------------------------------------- /docs/tutorial.rst: -------------------------------------------------------------------------------- 1 | Tutorial 2 | ======== 3 | 4 | .. todo:: 5 | 6 | The official tutorial is still being written. Until it's ready, consider following one of the tutorials written by the Amaranth community: 7 | 8 | * `Learning FPGA Design with nMigen `_ by Vivonomicon; 9 | * `"I want to learn nMigen" `_ by kbob; 10 | * `A tutorial for using Amaranth HDL `_ by Robert Baruch. 11 | * `Graded exercises for Amaranth HDL `_ by Robert Baruch. 12 | * `My journey with the Amaranth HDL `_ by David Sporn, focussed on setting up the workstation, using formal verification and setting up continuous integration. 13 | -------------------------------------------------------------------------------- /examples/basic/alu.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.lib import wiring 3 | from amaranth.lib.wiring import In, Out 4 | from amaranth.cli import main 5 | 6 | class ALU(wiring.Component): 7 | def __init__(self, width): 8 | super().__init__({ 9 | "sel": In(2), 10 | "a": In(width), 11 | "b": In(width), 12 | "o": Out(width), 13 | "co": Out(1), 14 | }) 15 | 16 | def elaborate(self, platform): 17 | m = Module() 18 | with m.If(self.sel == 0b00): 19 | m.d.comb += self.o.eq(self.a | self.b) 20 | with m.Elif(self.sel == 0b01): 21 | m.d.comb += self.o.eq(self.a & self.b) 22 | with m.Elif(self.sel == 0b10): 23 | m.d.comb += self.o.eq(self.a ^ self.b) 24 | with m.Else(): 25 | m.d.comb += Cat(self.o, self.co).eq(self.a - self.b) 26 | return m 27 | 28 | 29 | if __name__ == "__main__": 30 | alu = ALU(width=16) 31 | main(alu) 32 | -------------------------------------------------------------------------------- /examples/basic/alu_hier.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.lib import wiring 3 | from amaranth.lib.wiring import In, Out 4 | from amaranth.cli import main 5 | 6 | 7 | class Adder(wiring.Component): 8 | def __init__(self, width): 9 | super().__init__({ 10 | "a": In(width), 11 | "b": In(width), 12 | "o": Out(width), 13 | }) 14 | 15 | def elaborate(self, platform): 16 | m = Module() 17 | m.d.comb += self.o.eq(self.a + self.b) 18 | return m 19 | 20 | 21 | class Subtractor(wiring.Component): 22 | def __init__(self, width): 23 | super().__init__({ 24 | "a": In(width), 25 | "b": In(width), 26 | "o": Out(width), 27 | }) 28 | 29 | def elaborate(self, platform): 30 | m = Module() 31 | m.d.comb += self.o.eq(self.a - self.b) 32 | return m 33 | 34 | 35 | class ALU(wiring.Component): 36 | def __init__(self, width): 37 | super().__init__({ 38 | "op": In(1), 39 | "a": In(width), 40 | "b": In(width), 41 | "o": Out(width), 42 | }) 43 | self.add = Adder(width) 44 | self.sub = Subtractor(width) 45 | 46 | def elaborate(self, platform): 47 | m = Module() 48 | m.submodules.add = self.add 49 | m.submodules.sub = self.sub 50 | m.d.comb += [ 51 | self.add.a.eq(self.a), 52 | self.sub.a.eq(self.a), 53 | self.add.b.eq(self.b), 54 | self.sub.b.eq(self.b), 55 | ] 56 | with m.If(self.op): 57 | m.d.comb += self.o.eq(self.sub.o) 58 | with m.Else(): 59 | m.d.comb += self.o.eq(self.add.o) 60 | return m 61 | 62 | 63 | if __name__ == "__main__": 64 | alu = ALU(width=16) 65 | main(alu) 66 | -------------------------------------------------------------------------------- /examples/basic/arst.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.lib import wiring 3 | from amaranth.lib.wiring import In, Out 4 | from amaranth.cli import main 5 | 6 | 7 | class ClockDivisor(wiring.Component): 8 | o: Out(1) 9 | 10 | def __init__(self, factor): 11 | super().__init__() 12 | self.v = Signal(factor) 13 | 14 | def elaborate(self, platform): 15 | m = Module() 16 | m.d.sync += self.v.eq(self.v + 1) 17 | m.d.comb += self.o.eq(self.v[-1]) 18 | return m 19 | 20 | 21 | if __name__ == "__main__": 22 | m = Module() 23 | m.domains.sync = sync = ClockDomain("sync", async_reset=True) 24 | m.submodules.ctr = ctr = ClockDivisor(factor=16) 25 | main(m, ports=[ctr.o, sync.clk]) 26 | -------------------------------------------------------------------------------- /examples/basic/cdc.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.lib.cdc import FFSynchronizer 3 | from amaranth.cli import main 4 | 5 | 6 | i, o = Signal(name="i"), Signal(name="o") 7 | m = Module() 8 | m.submodules += FFSynchronizer(i, o) 9 | 10 | if __name__ == "__main__": 11 | main(m, ports=[i, o]) 12 | -------------------------------------------------------------------------------- /examples/basic/ctr.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.lib import wiring 3 | from amaranth.lib.wiring import In, Out 4 | from amaranth.cli import main 5 | 6 | 7 | class Counter(wiring.Component): 8 | o: Out(1) 9 | 10 | def __init__(self, width): 11 | super().__init__() 12 | self.v = Signal(width, init=2**width-1) 13 | 14 | def elaborate(self, platform): 15 | m = Module() 16 | m.d.sync += self.v.eq(self.v + 1) 17 | m.d.comb += self.o.eq(self.v[-1]) 18 | return m 19 | 20 | 21 | ctr = Counter(width=16) 22 | if __name__ == "__main__": 23 | main(ctr) 24 | -------------------------------------------------------------------------------- /examples/basic/ctr_en.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.lib import wiring 3 | from amaranth.lib.wiring import In, Out 4 | from amaranth.sim import * 5 | from amaranth.back import verilog 6 | 7 | 8 | class Counter(wiring.Component): 9 | o: Out(1) 10 | en: In(1) 11 | 12 | def __init__(self, width): 13 | super().__init__() 14 | self.v = Signal(width, init=2**width-1) 15 | 16 | def elaborate(self, platform): 17 | m = Module() 18 | m.d.sync += self.v.eq(self.v + 1) 19 | m.d.comb += self.o.eq(self.v[-1]) 20 | return EnableInserter(self.en)(m) 21 | 22 | 23 | ctr = Counter(width=16) 24 | 25 | print(verilog.convert(ctr)) 26 | 27 | sim = Simulator(ctr) 28 | sim.add_clock(Period(MHz=1)) 29 | async def testbench_ce(ctx): 30 | await ctx.tick().repeat(3) 31 | ctx.set(ctr.en, 1) 32 | await ctx.tick().repeat(3) 33 | ctx.set(ctr.en, 0) 34 | await ctx.tick().repeat(3) 35 | ctx.set(ctr.en,1) 36 | sim.add_testbench(testbench_ce) 37 | with sim.write_vcd("ctrl.vcd", "ctrl.gtkw", traces=[ctr.en, ctr.v, ctr.o]): 38 | sim.run_until(Period(us=100)) 39 | -------------------------------------------------------------------------------- /examples/basic/fsm.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.lib import wiring 3 | from amaranth.lib.wiring import In, Out 4 | from amaranth.cli import main 5 | 6 | 7 | class UARTReceiver(wiring.Component): 8 | i: In(1) 9 | data: Out(8) 10 | rdy: Out(1) 11 | ack: In(1) 12 | err: Out(1) 13 | 14 | def __init__(self, divisor): 15 | super().__init__() 16 | self.divisor = divisor 17 | 18 | def elaborate(self, platform): 19 | m = Module() 20 | 21 | ctr = Signal(range(self.divisor)) 22 | stb = Signal() 23 | with m.If(ctr == 0): 24 | m.d.sync += ctr.eq(self.divisor - 1) 25 | m.d.comb += stb.eq(1) 26 | with m.Else(): 27 | m.d.sync += ctr.eq(ctr - 1) 28 | 29 | bit = Signal(3) 30 | with m.FSM() as fsm: 31 | with m.State("START"): 32 | with m.If(~self.i): 33 | m.next = "DATA" 34 | m.d.sync += [ 35 | ctr.eq(self.divisor // 2), 36 | bit.eq(7), 37 | ] 38 | with m.State("DATA"): 39 | with m.If(stb): 40 | m.d.sync += [ 41 | bit.eq(bit - 1), 42 | self.data.eq(Cat(self.i, self.data)) 43 | ] 44 | with m.If(bit == 0): 45 | m.next = "STOP" 46 | with m.State("STOP"): 47 | with m.If(stb): 48 | with m.If(self.i): 49 | m.next = "DONE" 50 | with m.Else(): 51 | m.next = "ERROR" 52 | 53 | with m.State("DONE"): 54 | m.d.comb += self.rdy.eq(1) 55 | with m.If(self.ack): 56 | m.next = "START" 57 | 58 | m.d.comb += self.err.eq(fsm.ongoing("ERROR")) 59 | with m.State("ERROR"): 60 | pass 61 | 62 | return m 63 | 64 | 65 | if __name__ == "__main__": 66 | rx = UARTReceiver(20) 67 | main(rx) 68 | -------------------------------------------------------------------------------- /examples/basic/inst.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.lib import wiring 3 | from amaranth.lib.wiring import In, Out 4 | from amaranth.cli import main 5 | 6 | 7 | class System(wiring.Component): 8 | adr: In(16) 9 | dat_r: In(8) 10 | dat_w: Out(8) 11 | we: In(1) 12 | 13 | def elaborate(self, platform): 14 | m = Module() 15 | m.submodules.cpu = Instance("CPU", 16 | p_RESET_ADDR=0xfff0, 17 | i_d_adr =self.adr, 18 | i_d_dat_r=self.dat_r, 19 | o_d_dat_w=self.dat_w, 20 | i_d_we =self.we, 21 | ) 22 | return m 23 | 24 | 25 | if __name__ == "__main__": 26 | sys = System() 27 | main(sys) 28 | -------------------------------------------------------------------------------- /examples/basic/mem.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.lib import wiring 3 | from amaranth.lib.wiring import In, Out 4 | from amaranth.lib.memory import Memory 5 | from amaranth.cli import main 6 | 7 | 8 | class RegisterFile(wiring.Component): 9 | adr: In(4) 10 | dat_r: Out(8) 11 | dat_w: In(8) 12 | we: In(1) 13 | 14 | def __init__(self): 15 | super().__init__() 16 | self.mem = Memory(shape=8, depth=16, init=[0xaa, 0x55]) 17 | 18 | def elaborate(self, platform): 19 | m = Module() 20 | m.submodules.mem = self.mem 21 | rdport = self.mem.read_port() 22 | wrport = self.mem.write_port() 23 | m.d.comb += [ 24 | rdport.addr.eq(self.adr), 25 | self.dat_r.eq(rdport.data), 26 | wrport.addr.eq(self.adr), 27 | wrport.data.eq(self.dat_w), 28 | wrport.en.eq(self.we), 29 | ] 30 | return m 31 | 32 | 33 | if __name__ == "__main__": 34 | rf = RegisterFile() 35 | main(rf) 36 | -------------------------------------------------------------------------------- /examples/basic/pmux.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.lib import wiring 3 | from amaranth.lib.wiring import In, Out 4 | from amaranth.cli import main 5 | 6 | 7 | class ParMux(wiring.Component): 8 | def __init__(self, width): 9 | super().__init__({ 10 | "s": In(3), 11 | "a": In(width), 12 | "b": In(width), 13 | "c": In(width), 14 | "o": Out(width), 15 | }) 16 | 17 | def elaborate(self, platform): 18 | m = Module() 19 | with m.Switch(self.s): 20 | with m.Case("--1"): 21 | m.d.comb += self.o.eq(self.a) 22 | with m.Case("-1-"): 23 | m.d.comb += self.o.eq(self.b) 24 | with m.Case("1--"): 25 | m.d.comb += self.o.eq(self.c) 26 | with m.Default(): 27 | m.d.comb += self.o.eq(0) 28 | return m 29 | 30 | 31 | if __name__ == "__main__": 32 | pmux = ParMux(width=16) 33 | main(pmux) 34 | -------------------------------------------------------------------------------- /examples/basic/por.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.cli import main 3 | 4 | 5 | m = Module() 6 | cd_por = ClockDomain(reset_less=True) 7 | cd_sync = ClockDomain() 8 | m.domains += cd_por, cd_sync 9 | 10 | delay = Signal(range(256), init=255) 11 | with m.If(delay != 0): 12 | m.d.por += delay.eq(delay - 1) 13 | m.d.comb += [ 14 | ClockSignal().eq(cd_por.clk), 15 | ResetSignal().eq(delay != 0), 16 | ] 17 | 18 | if __name__ == "__main__": 19 | main(m, ports=[cd_por.clk]) 20 | -------------------------------------------------------------------------------- /examples/basic/uart.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.lib import wiring 3 | from amaranth.lib.wiring import In, Out 4 | 5 | 6 | class UART(wiring.Component): 7 | """ 8 | Parameters 9 | ---------- 10 | divisor : int 11 | Set to ``round(clk-rate / baud-rate)``. 12 | E.g. ``12e6 / 115200`` = ``104``. 13 | """ 14 | def __init__(self, divisor, data_bits=8): 15 | assert divisor >= 4 16 | 17 | self.data_bits = data_bits 18 | self.divisor = divisor 19 | 20 | super().__init__({ 21 | "tx_o": Out(1), 22 | "rx_i": In(1), 23 | 24 | "tx_data": In(data_bits), 25 | "tx_rdy": In(1), 26 | "tx_ack": Out(1), 27 | 28 | "rx_data": Out(data_bits), 29 | "rx_err": Out(1), 30 | "rx_ovf": Out(1), 31 | "rx_rdy": Out(1), 32 | "rx_ack": In(1), 33 | }) 34 | 35 | def elaborate(self, platform): 36 | m = Module() 37 | 38 | tx_phase = Signal(range(self.divisor)) 39 | tx_shreg = Signal(1 + self.data_bits + 1, init=-1) 40 | tx_count = Signal(range(len(tx_shreg) + 1)) 41 | 42 | m.d.comb += self.tx_o.eq(tx_shreg[0]) 43 | with m.If(tx_count == 0): 44 | m.d.comb += self.tx_ack.eq(1) 45 | with m.If(self.tx_rdy): 46 | m.d.sync += [ 47 | tx_shreg.eq(Cat(C(0, 1), self.tx_data, C(1, 1))), 48 | tx_count.eq(len(tx_shreg)), 49 | tx_phase.eq(self.divisor - 1), 50 | ] 51 | with m.Else(): 52 | with m.If(tx_phase != 0): 53 | m.d.sync += tx_phase.eq(tx_phase - 1) 54 | with m.Else(): 55 | m.d.sync += [ 56 | tx_shreg.eq(Cat(tx_shreg[1:], C(1, 1))), 57 | tx_count.eq(tx_count - 1), 58 | tx_phase.eq(self.divisor - 1), 59 | ] 60 | 61 | rx_phase = Signal(range(self.divisor)) 62 | rx_shreg = Signal(1 + self.data_bits + 1, init=-1) 63 | rx_count = Signal(range(len(rx_shreg) + 1)) 64 | 65 | m.d.comb += self.rx_data.eq(rx_shreg[1:-1]) 66 | with m.If(rx_count == 0): 67 | m.d.comb += self.rx_err.eq(~(~rx_shreg[0] & rx_shreg[-1])) 68 | with m.If(~self.rx_i): 69 | with m.If(self.rx_ack | ~self.rx_rdy): 70 | m.d.sync += [ 71 | self.rx_rdy.eq(0), 72 | self.rx_ovf.eq(0), 73 | rx_count.eq(len(rx_shreg)), 74 | rx_phase.eq(self.divisor // 2), 75 | ] 76 | with m.Else(): 77 | m.d.sync += self.rx_ovf.eq(1) 78 | with m.If(self.rx_ack): 79 | m.d.sync += self.rx_rdy.eq(0) 80 | with m.Else(): 81 | with m.If(rx_phase != 0): 82 | m.d.sync += rx_phase.eq(rx_phase - 1) 83 | with m.Else(): 84 | m.d.sync += [ 85 | rx_shreg.eq(Cat(rx_shreg[1:], self.rx_i)), 86 | rx_count.eq(rx_count - 1), 87 | rx_phase.eq(self.divisor - 1), 88 | ] 89 | with m.If(rx_count == 1): 90 | m.d.sync += self.rx_rdy.eq(1) 91 | 92 | return m 93 | 94 | 95 | if __name__ == "__main__": 96 | uart = UART(divisor=5) 97 | 98 | import argparse 99 | 100 | parser = argparse.ArgumentParser() 101 | p_action = parser.add_subparsers(dest="action") 102 | p_action.add_parser("simulate") 103 | p_action.add_parser("generate") 104 | 105 | args = parser.parse_args() 106 | if args.action == "simulate": 107 | from amaranth.sim import Simulator, Passive, Period 108 | 109 | sim = Simulator(uart) 110 | sim.add_clock(Period(MHz=1)) 111 | 112 | async def testbench_loopback(ctx): 113 | async for val in ctx.changed(uart.tx_o): 114 | ctx.set(uart.rx_i, val) 115 | 116 | sim.add_testbench(testbench_loopback, background=True) 117 | 118 | async def testbench_transmit(ctx): 119 | assert ctx.get(uart.tx_ack) 120 | assert not ctx.get(uart.rx_rdy) 121 | 122 | ctx.set(uart.tx_data, 0x5A) 123 | ctx.set(uart.tx_rdy, 1) 124 | await ctx.tick() 125 | ctx.set(uart.tx_rdy, 0) 126 | await ctx.tick() 127 | assert not ctx.get(uart.tx_ack) 128 | 129 | await ctx.tick().repeat(uart.divisor * 12) 130 | 131 | assert ctx.get(uart.tx_ack) 132 | assert ctx.get(uart.rx_rdy) 133 | assert not ctx.get(uart.rx_err) 134 | assert ctx.get(uart.rx_data) == 0x5A 135 | 136 | ctx.set(uart.rx_ack, 1) 137 | await ctx.tick() 138 | ctx.set(uart.rx_ack, 0) 139 | await ctx.tick() 140 | assert not ctx.get(uart.rx_rdy) 141 | 142 | sim.add_testbench(testbench_transmit) 143 | 144 | with sim.write_vcd("uart.vcd", "uart.gtkw"): 145 | sim.run() 146 | 147 | if args.action == "generate": 148 | from amaranth.back import verilog 149 | 150 | print(verilog.convert(uart)) 151 | -------------------------------------------------------------------------------- /examples/board/01_blinky.py: -------------------------------------------------------------------------------- 1 | # If the design does not create a "sync" clock domain, it is created by the Amaranth build system 2 | # using the platform default clock (and default reset, if any). 3 | 4 | from amaranth import * 5 | from amaranth_boards.ice40_hx1k_blink_evn import ICE40HX1KBlinkEVNPlatform 6 | 7 | 8 | class Blinky(Elaboratable): 9 | def elaborate(self, platform): 10 | led = platform.request("led", 0) 11 | timer = Signal(20) 12 | 13 | m = Module() 14 | m.d.sync += timer.eq(timer + 1) 15 | m.d.comb += led.o.eq(timer[-1]) 16 | return m 17 | 18 | 19 | if __name__ == "__main__": 20 | platform = ICE40HX1KBlinkEVNPlatform() 21 | platform.build(Blinky(), do_program=True) 22 | -------------------------------------------------------------------------------- /examples/board/02_domain.py: -------------------------------------------------------------------------------- 1 | # If more control over clocking and resets is required, a "sync" clock domain could be created 2 | # explicitly, which overrides the default behavior. Any other clock domains could also be 3 | # independently created in addition to the main "sync" domain. 4 | 5 | from amaranth import * 6 | from amaranth_boards.ice40_hx1k_blink_evn import ICE40HX1KBlinkEVNPlatform 7 | 8 | 9 | class BlinkyWithDomain(Elaboratable): 10 | def elaborate(self, platform): 11 | clk3p3 = platform.request("clk3p3") 12 | led = platform.request("led", 0) 13 | timer = Signal(20) 14 | 15 | m = Module() 16 | m.domains.sync = ClockDomain() 17 | m.d.comb += ClockSignal().eq(clk3p3.i) 18 | m.d.sync += timer.eq(timer + 1) 19 | m.d.comb += led.o.eq(timer[-1]) 20 | return m 21 | 22 | 23 | if __name__ == "__main__": 24 | platform = ICE40HX1KBlinkEVNPlatform() 25 | platform.build(BlinkyWithDomain(), do_program=True) 26 | -------------------------------------------------------------------------------- /pdm_build.py: -------------------------------------------------------------------------------- 1 | import os 2 | from datetime import datetime 3 | from pdm.backend.hooks.version import SCMVersion 4 | from pdm.backend._vendor.packaging.version import Version 5 | 6 | 7 | def format_version(version: SCMVersion) -> str: 8 | major, minor, patch = (int(n) for n in str(version.version).split(".")[:3]) 9 | dirty = f"+{datetime.utcnow():%Y%m%d.%H%M%S}" if version.dirty else "" 10 | if version.distance is None: 11 | return f"{major}.{minor}.{patch}{dirty}" 12 | else: 13 | return f"{major}.{minor}.{patch}.dev{version.distance}{dirty}" 14 | 15 | 16 | def pdm_build_initialize(context): 17 | version = Version(context.config.metadata["version"]) 18 | 19 | # This is done in a PDM build hook without specifying `dynamic = [..., "version"]` to put all 20 | # of the static metadata into pyproject.toml. Tools other than PDM will not execute this script 21 | # and will use the generic version of the documentation URL (which redirects to /latest). 22 | if version.is_prerelease: 23 | url_version = "latest" 24 | else: 25 | url_version = f"v{version}" 26 | context.config.metadata["urls"]["Documentation"] += url_version 27 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # Project metadata 2 | 3 | [tool.pdm.version] 4 | source = "scm" 5 | version_format = "pdm_build:format_version" 6 | 7 | [project] 8 | dynamic = ["version"] 9 | 10 | name = "amaranth" 11 | description = "Amaranth hardware definition language" 12 | readme = "README.md" 13 | authors = [{name = "Amaranth HDL contributors"}] 14 | license = { text = "BSD-2-clause" } 15 | 16 | requires-python = "~=3.9" 17 | dependencies = [ 18 | "jschon~=0.11.1", # for amaranth.lib.meta 19 | "pyvcd>=0.2.2,<0.5", # for amaranth.sim.pysim 20 | "Jinja2~=3.0", # for amaranth.build 21 | ] 22 | 23 | [project.optional-dependencies] 24 | # This version requirement needs to be synchronized with: 25 | # - pyproject.toml: tool.pdm.dev-dependencies.test 26 | # - amaranth/back/verilog.py: _convert_rtlil_text 27 | # - docs/install.rst: yosys-version 28 | builtin-yosys = ["amaranth-yosys>=0.40"] 29 | remote-build = ["paramiko~=2.7"] 30 | 31 | [project.scripts] 32 | amaranth-rpc = "amaranth.rpc:main" 33 | 34 | [project.entry-points."amaranth.lib.meta"] 35 | "0.5/component.json" = "amaranth.lib.wiring:ComponentMetadata" 36 | 37 | [project.urls] 38 | "Homepage" = "https://amaranth-lang.org/" 39 | "Documentation" = "https://amaranth-lang.org/docs/amaranth/" # modified in pdm_build.py 40 | "Source Code" = "https://github.com/amaranth-lang/amaranth" 41 | "Bug Tracker" = "https://github.com/amaranth-lang/amaranth/issues" 42 | 43 | # Build system configuration 44 | 45 | [build-system] 46 | requires = ["pdm-backend~=2.3.0"] 47 | build-backend = "pdm.backend" 48 | 49 | [tool.pdm.build] 50 | # If amaranth 0.3 is checked out with git (e.g. as a part of a persistent editable install or 51 | # a git worktree cached by tools like poetry), it can have an empty `nmigen` directory left over, 52 | # which causes a hard error because setuptools cannot determine the top-level package. 53 | # Add a workaround to improve experience for people upgrading from old checkouts. 54 | includes = ["amaranth/"] 55 | 56 | source-includes = [ 57 | ".gitignore", 58 | ".coveragerc", 59 | ".env.toolchain", 60 | "CONTRIBUTING.txt", 61 | ] 62 | 63 | # Development workflow configuration 64 | 65 | [tool.pdm.dev-dependencies] 66 | # This version requirement needs to be synchronized with the one in pyproject.toml above! 67 | test = [ 68 | "yowasp-yosys>=0.40", 69 | "coverage", 70 | ] 71 | docs = [ 72 | "sphinx~=7.1", 73 | "sphinxcontrib-platformpicker~=1.4", 74 | "sphinxcontrib-yowasp-wavedrom==1.8", # exact version to avoid changes in rendering 75 | "sphinx-rtd-theme~=2.0", 76 | "sphinx-autobuild", 77 | ] 78 | examples = [ 79 | "amaranth-boards @ git+https://github.com/amaranth-lang/amaranth-boards.git" 80 | ] 81 | 82 | [tool.pdm.scripts] 83 | _.env_file = ".env.toolchain" 84 | 85 | test.composite = ["test-code", "test-docs", "coverage-xml"] 86 | test-code.env = {PYTHONWARNINGS = "error"} 87 | test-code.cmd = "python -m coverage run -m unittest discover -t . -s tests -v" 88 | test-docs.cmd = "sphinx-build -b doctest docs/ docs/_build" 89 | 90 | document.cmd = "sphinx-build docs/ docs/_build/ -W --keep-going" 91 | document-live.cmd = "sphinx-autobuild docs/ docs/_build/ --watch amaranth" 92 | document-linkcheck.cmd = "sphinx-build docs/ docs/_linkcheck/ -b linkcheck" 93 | 94 | coverage-text.cmd = "python -m coverage report" 95 | coverage-html.cmd = "python -m coverage html" 96 | coverage-xml.cmd = "python -m coverage xml" 97 | 98 | extract-schemas.call = "amaranth.lib.meta:_extract_schemas('amaranth', base_uri='https://amaranth-lang.org/schema/amaranth')" 99 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amaranth-lang/amaranth/916791022cb9cf96a552756d0e00efe2a4b16aba/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_build_plat.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.build.plat import * 3 | 4 | from .utils import * 5 | 6 | 7 | class MockPlatform(Platform): 8 | resources = [] 9 | connectors = [] 10 | 11 | required_tools = [] 12 | 13 | def toolchain_prepare(self, fragment, name, **kwargs): 14 | raise NotImplementedError 15 | 16 | 17 | class PlatformTestCase(FHDLTestCase): 18 | def setUp(self): 19 | self.platform = MockPlatform() 20 | 21 | def test_add_file_str(self): 22 | self.platform.add_file("x.txt", "foo") 23 | self.assertEqual(self.platform.extra_files["x.txt"], "foo") 24 | 25 | def test_add_file_bytes(self): 26 | self.platform.add_file("x.txt", b"foo") 27 | self.assertEqual(self.platform.extra_files["x.txt"], b"foo") 28 | 29 | def test_add_file_exact_duplicate(self): 30 | self.platform.add_file("x.txt", b"foo") 31 | self.platform.add_file("x.txt", b"foo") 32 | 33 | def test_add_file_io(self): 34 | with open(__file__) as f: 35 | self.platform.add_file("x.txt", f) 36 | with open(__file__) as f: 37 | self.assertEqual(self.platform.extra_files["x.txt"], f.read()) 38 | 39 | def test_add_file_wrong_filename(self): 40 | with self.assertRaisesRegex(TypeError, 41 | r"^File name must be a string, not 1$"): 42 | self.platform.add_file(1, "") 43 | 44 | def test_add_file_wrong_contents(self): 45 | with self.assertRaisesRegex(TypeError, 46 | r"^File contents must be str, bytes, or a file-like object, not 1$"): 47 | self.platform.add_file("foo", 1) 48 | 49 | def test_add_file_wrong_duplicate(self): 50 | self.platform.add_file("foo", "") 51 | with self.assertRaisesRegex(ValueError, 52 | r"^File 'foo' already exists$"): 53 | self.platform.add_file("foo", "bar") 54 | 55 | def test_iter_files(self): 56 | self.platform.add_file("foo.v", "") 57 | self.platform.add_file("bar.v", "") 58 | self.platform.add_file("baz.vhd", "") 59 | self.assertEqual(list(self.platform.iter_files(".v")), 60 | ["foo.v", "bar.v"]) 61 | self.assertEqual(list(self.platform.iter_files(".vhd")), 62 | ["baz.vhd"]) 63 | self.assertEqual(list(self.platform.iter_files(".v", ".vhd")), 64 | ["foo.v", "bar.v", "baz.vhd"]) 65 | -------------------------------------------------------------------------------- /tests/test_examples.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import subprocess 3 | from pathlib import Path 4 | 5 | from .utils import * 6 | 7 | 8 | def example_test(name): 9 | path = (Path(__file__).parent / ".." / "examples" / name).resolve() 10 | def test_function(self): 11 | subprocess.check_call([sys.executable, str(path), "generate", "-t", "v"], 12 | stdout=subprocess.DEVNULL) 13 | return test_function 14 | 15 | 16 | class ExamplesTestCase(FHDLTestCase): 17 | test_alu = example_test("basic/alu.py") 18 | test_alu_hier = example_test("basic/alu_hier.py") 19 | test_arst = example_test("basic/arst.py") 20 | test_cdc = example_test("basic/cdc.py") 21 | test_ctr = example_test("basic/ctr.py") 22 | test_ctr_en = example_test("basic/ctr_en.py") 23 | test_fsm = example_test("basic/fsm.py") 24 | test_inst = example_test("basic/inst.py") 25 | test_mem = example_test("basic/mem.py") 26 | test_pmux = example_test("basic/pmux.py") 27 | test_por = example_test("basic/por.py") 28 | 29 | def test_uart(self): 30 | path = (Path(__file__).parent / ".." / "examples" / "basic" / "uart.py").resolve() 31 | subprocess.check_call([sys.executable, str(path), "generate"], 32 | stdout=subprocess.DEVNULL) 33 | -------------------------------------------------------------------------------- /tests/test_hdl_cd.py: -------------------------------------------------------------------------------- 1 | from amaranth.hdl._cd import * 2 | 3 | from .utils import * 4 | 5 | 6 | class ClockDomainTestCase(FHDLTestCase): 7 | def test_name(self): 8 | sync = ClockDomain() 9 | self.assertEqual(sync.name, "sync") 10 | self.assertEqual(sync.clk.name, "clk") 11 | self.assertEqual(sync.rst.name, "rst") 12 | pix = ClockDomain() 13 | self.assertEqual(pix.name, "pix") 14 | self.assertEqual(pix.clk.name, "pix_clk") 15 | self.assertEqual(pix.rst.name, "pix_rst") 16 | cd_pix = ClockDomain() 17 | self.assertEqual(cd_pix.name, "pix") 18 | dom = [ClockDomain("foo")][0] 19 | self.assertEqual(dom.name, "foo") 20 | with self.assertRaisesRegex(ValueError, 21 | r"^Clock domain name must be specified explicitly$"): 22 | ClockDomain() 23 | 24 | def test_edge(self): 25 | sync = ClockDomain() 26 | self.assertEqual(sync.clk_edge, "pos") 27 | sync = ClockDomain(clk_edge="pos") 28 | self.assertEqual(sync.clk_edge, "pos") 29 | sync = ClockDomain(clk_edge="neg") 30 | self.assertEqual(sync.clk_edge, "neg") 31 | 32 | def test_edge_wrong(self): 33 | with self.assertRaisesRegex(ValueError, 34 | r"^Domain clock edge must be one of 'pos' or 'neg', not 'xxx'$"): 35 | ClockDomain("sync", clk_edge="xxx") 36 | 37 | def test_with_reset(self): 38 | pix = ClockDomain() 39 | self.assertIsNotNone(pix.clk) 40 | self.assertIsNotNone(pix.rst) 41 | self.assertFalse(pix.async_reset) 42 | 43 | def test_without_reset(self): 44 | pix = ClockDomain(reset_less=True) 45 | self.assertIsNotNone(pix.clk) 46 | self.assertIsNone(pix.rst) 47 | self.assertFalse(pix.async_reset) 48 | 49 | def test_async_reset(self): 50 | pix = ClockDomain(async_reset=True) 51 | self.assertIsNotNone(pix.clk) 52 | self.assertIsNotNone(pix.rst) 53 | self.assertTrue(pix.async_reset) 54 | 55 | def test_rename(self): 56 | sync = ClockDomain() 57 | self.assertEqual(sync.name, "sync") 58 | self.assertEqual(sync.clk.name, "clk") 59 | self.assertEqual(sync.rst.name, "rst") 60 | sync.rename("pix") 61 | self.assertEqual(sync.name, "pix") 62 | self.assertEqual(sync.clk.name, "pix_clk") 63 | self.assertEqual(sync.rst.name, "pix_rst") 64 | 65 | def test_rename_reset_less(self): 66 | sync = ClockDomain(reset_less=True) 67 | self.assertEqual(sync.name, "sync") 68 | self.assertEqual(sync.clk.name, "clk") 69 | sync.rename("pix") 70 | self.assertEqual(sync.name, "pix") 71 | self.assertEqual(sync.clk.name, "pix_clk") 72 | 73 | def test_wrong_name_comb(self): 74 | with self.assertRaisesRegex(ValueError, 75 | r"^Domain 'comb' may not be clocked$"): 76 | comb = ClockDomain() 77 | 78 | def test_local(self): 79 | with self.assertWarnsRegex(DeprecationWarning, 80 | r"^`local` is deprecated and has no effect$"): 81 | sync = ClockDomain(local=False) 82 | with self.assertWarnsRegex(DeprecationWarning, 83 | r"^`local` is deprecated and has no effect$"): 84 | self.assertTrue(sync.local) -------------------------------------------------------------------------------- /tests/test_hdl_mem.py: -------------------------------------------------------------------------------- 1 | # amaranth: UnusedElaboratable=no 2 | 3 | from amaranth.hdl import * 4 | from amaranth.hdl._mem import * 5 | 6 | from .utils import * 7 | 8 | 9 | class MemoryDataTestCase(FHDLTestCase): 10 | def test_repr(self): 11 | data = MemoryData(shape=8, depth=4, init=[]) 12 | self.assertRepr(data, "(memory-data data)") 13 | 14 | def test_row(self): 15 | data = MemoryData(shape=8, depth=4, init=[]) 16 | self.assertRepr(data[2], "(memory-row (memory-data data) 2)") 17 | 18 | def test_row_wrong(self): 19 | data = MemoryData(shape=8, depth=4, init=[]) 20 | with self.assertRaisesRegex(IndexError, 21 | r"^Index 4 is out of bounds \(memory has 4 rows\)$"): 22 | data[4] 23 | 24 | def test_row_elab(self): 25 | data = MemoryData(shape=8, depth=4, init=[]) 26 | m = Module() 27 | a = Signal(8) 28 | with self.assertRaisesRegex(ValueError, 29 | r"^Value \(memory-row \(memory-data data\) 0\) can only be used in simulator processes$"): 30 | m.d.comb += a.eq(data[0]) 31 | with self.assertRaisesRegex(ValueError, 32 | r"^Value \(memory-row \(memory-data data\) 0\) can only be used in simulator processes$"): 33 | m.d.comb += data[0].eq(1) 34 | 35 | 36 | class InitTestCase(FHDLTestCase): 37 | def test_ones(self): 38 | init = MemoryData.Init([-1, 12], shape=8, depth=2) 39 | self.assertEqual(list(init), [0xff, 12]) 40 | init = MemoryData.Init([-1, -12], shape=signed(8), depth=2) 41 | self.assertEqual(list(init), [-1, -12]) 42 | 43 | def test_trunc(self): 44 | with self.assertWarnsRegex(SyntaxWarning, 45 | r"^Initial value -2 is signed, but the memory shape is unsigned\(8\)$"): 46 | init = MemoryData.Init([-2, 12], shape=8, depth=2) 47 | self.assertEqual(list(init), [0xfe, 12]) 48 | with self.assertWarnsRegex(SyntaxWarning, 49 | r"^Initial value 258 will be truncated to the memory shape unsigned\(8\)$"): 50 | init = MemoryData.Init([258, 129], shape=8, depth=2) 51 | self.assertEqual(list(init), [2, 129]) 52 | with self.assertWarnsRegex(SyntaxWarning, 53 | r"^Initial value 128 will be truncated to the memory shape signed\(8\)$"): 54 | init = MemoryData.Init([128], shape=signed(8), depth=1) 55 | self.assertEqual(list(init), [-128]) 56 | with self.assertWarnsRegex(SyntaxWarning, 57 | r"^Initial value -129 will be truncated to the memory shape signed\(8\)$"): 58 | init = MemoryData.Init([-129], shape=signed(8), depth=1) 59 | self.assertEqual(list(init), [127]) 60 | -------------------------------------------------------------------------------- /tests/test_hdl_time.py: -------------------------------------------------------------------------------- 1 | from amaranth.hdl._time import * 2 | 3 | from .utils import * 4 | 5 | 6 | class PeriodTestCase(FHDLTestCase): 7 | def test_constructor(self): 8 | self.assertEqual(Period().femtoseconds, 0) 9 | 10 | self.assertEqual(Period(fs=5).femtoseconds, 5) 11 | self.assertEqual(Period(ps=5).femtoseconds, 5_000) 12 | self.assertEqual(Period(ns=5).femtoseconds, 5_000_000) 13 | self.assertEqual(Period(us=5).femtoseconds, 5_000_000_000) 14 | self.assertEqual(Period(ms=5).femtoseconds, 5_000_000_000_000) 15 | self.assertEqual(Period( s=5).femtoseconds, 5_000_000_000_000_000) 16 | 17 | self.assertEqual(Period(GHz=5).femtoseconds, 200_000) 18 | self.assertEqual(Period(MHz=5).femtoseconds, 200_000_000) 19 | self.assertEqual(Period(kHz=5).femtoseconds, 200_000_000_000) 20 | self.assertEqual(Period( Hz=5).femtoseconds, 200_000_000_000_000) 21 | 22 | def test_constructor_exceptions(self): 23 | with self.assertRaisesRegex(TypeError, 24 | r"^Period accepts at most one argument$"): 25 | Period(s=5, ms = 3) 26 | 27 | with self.assertRaisesRegex(TypeError, 28 | r"^foo is not a valid unit$"): 29 | Period(foo=5) 30 | 31 | with self.assertRaisesRegex(TypeError, 32 | r"^s value must be a real number$"): 33 | Period(s="five") 34 | 35 | with self.assertRaisesRegex(ZeroDivisionError, 36 | r"^Frequency can't be zero$"): 37 | Period(Hz=0) 38 | 39 | with self.assertRaisesRegex(ValueError, 40 | r"^Frequency can't be negative$"): 41 | Period(Hz=-1) 42 | 43 | def test_accessors(self): 44 | self.assertEqual(Period(s=5).seconds, 5.0) 45 | self.assertEqual(Period(s=5).milliseconds, 5_000.0) 46 | self.assertEqual(Period(s=5).microseconds, 5_000_000.0) 47 | self.assertEqual(Period(s=5).nanoseconds, 5_000_000_000.0) 48 | self.assertEqual(Period(s=5).picoseconds, 5_000_000_000_000.0) 49 | self.assertEqual(Period(s=5).femtoseconds, 5_000_000_000_000_000) 50 | 51 | self.assertEqual(Period(GHz=5).gigahertz, 5.0) 52 | self.assertEqual(Period(GHz=5).megahertz, 5_000.0) 53 | self.assertEqual(Period(GHz=5).kilohertz, 5_000_000.0) 54 | self.assertEqual(Period(GHz=5).hertz, 5_000_000_000.0) 55 | 56 | def test_accessor_exceptions(self): 57 | with self.assertRaisesRegex(ZeroDivisionError, 58 | r"^Can't calculate the frequency of a zero period$"): 59 | Period(s=0).hertz 60 | 61 | with self.assertRaisesRegex(ValueError, 62 | r"^Can't calculate the frequency of a negative period$"): 63 | Period(s=-1).hertz 64 | 65 | def test_operators(self): 66 | for a, b in [(3, 5), (3, 3), (5, 3)]: 67 | self.assertEqual(Period(s=a) < Period(s=b), a < b) 68 | self.assertEqual(Period(s=a) <= Period(s=b), a <= b) 69 | self.assertEqual(Period(s=a) == Period(s=b), a == b) 70 | self.assertEqual(Period(s=a) != Period(s=b), a != b) 71 | self.assertEqual(Period(s=a) > Period(s=b), a > b) 72 | self.assertEqual(Period(s=a) >= Period(s=b), a >= b) 73 | 74 | self.assertEqual(hash(Period(fs=5)), hash(5)) 75 | 76 | self.assertFalse(Period()) 77 | self.assertTrue(Period(s=5)) 78 | 79 | self.assertEqual(-Period(s=5), Period(s=-5)) 80 | self.assertEqual(+Period(s=5), Period(s=5)) 81 | self.assertEqual(abs(Period(s=-5)), Period(s=5)) 82 | 83 | self.assertEqual(Period(s=3) + Period(ms=5), Period(ms=3005)) 84 | self.assertEqual(Period(s=3) - Period(ms=5), Period(ms=2995)) 85 | self.assertEqual(Period(s=3) * 5, Period(s=15)) 86 | self.assertEqual(3 * Period(s=5), Period(s=15)) 87 | self.assertEqual(Period(s=15) / 3, Period(s=5)) 88 | self.assertEqual(Period(s=15) / Period(s=3), 5.0) 89 | self.assertEqual(Period(s=8) // Period(s=3), 2) 90 | self.assertEqual(Period(s=8) % Period(s=3), Period(s=2)) 91 | 92 | def test_invalid_operands(self): 93 | with self.assertRaises(TypeError): 94 | Period(s=5) > 3 95 | with self.assertRaises(TypeError): 96 | Period(s=5) >= 3 97 | with self.assertRaises(TypeError): 98 | Period(s=5) < 3 99 | with self.assertRaises(TypeError): 100 | Period(s=5) <= 3 101 | self.assertFalse(Period(s=5) == 3) 102 | self.assertTrue(Period(s=5) != 3) 103 | 104 | with self.assertRaises(TypeError): 105 | Period(s=5) + 3 106 | with self.assertRaises(TypeError): 107 | Period(s=5) - 3 108 | with self.assertRaises(TypeError): 109 | Period(s=5) * Period(s=3) 110 | with self.assertRaises(TypeError): 111 | Period(s=5) / "three" 112 | with self.assertRaises(TypeError): 113 | Period(s=5) // 3 114 | with self.assertRaises(TypeError): 115 | Period(s=5) % 3 116 | 117 | def test_str(self): 118 | self.assertEqual(str(Period( s=5)), "5s") 119 | self.assertEqual(str(Period(ms=5)), "5ms") 120 | self.assertEqual(str(Period(us=5)), "5us") 121 | self.assertEqual(str(Period(ns=5)), "5ns") 122 | self.assertEqual(str(Period(ps=5)), "5ps") 123 | self.assertEqual(str(Period(fs=5)), "5fs") 124 | 125 | def test_repr(self): 126 | self.assertRepr(Period( s=5), "Period(s=5)") 127 | self.assertRepr(Period(ms=5), "Period(ms=5)") 128 | 129 | def test_format(self): 130 | with self.assertRaisesRegex(ValueError, 131 | r"^Invalid format specifier 'foo' for object of type 'Period'"): 132 | f"{Period(s=5):foo}" 133 | 134 | self.assertEqual(f"{Period(ms=1234):}", "1.234s") 135 | self.assertEqual(f"{Period(ms=1234):ms}", "1234ms") 136 | self.assertEqual(f"{Period(ms=1234):.1}", "1.2s") 137 | self.assertEqual(f"{Period(ms=1234): }", "1.234 s") 138 | self.assertEqual(f"{Period(ms=1234):10}", " 1.234s") 139 | 140 | self.assertEqual(f"{Period(MHz=1250):.0Hz}", "1250000000Hz") 141 | self.assertEqual(f"{Period(MHz=1250):.0kHz}", "1250000kHz") 142 | self.assertEqual(f"{Period(MHz=1250):.0MHz}", "1250MHz") 143 | self.assertEqual(f"{Period(MHz=1250):.0GHz}", "1GHz") 144 | -------------------------------------------------------------------------------- /tests/test_lib_cdc.py: -------------------------------------------------------------------------------- 1 | # amaranth: UnusedElaboratable=no 2 | 3 | from amaranth.hdl import * 4 | from amaranth.sim import * 5 | from amaranth.lib.cdc import * 6 | 7 | from .utils import * 8 | 9 | 10 | class FFSynchronizerTestCase(FHDLTestCase): 11 | def test_stages_wrong(self): 12 | with self.assertRaisesRegex(TypeError, 13 | r"^Synchronization stage count must be a positive integer, not 0$"): 14 | FFSynchronizer(Signal(), Signal(), stages=0) 15 | with self.assertRaisesRegex(ValueError, 16 | r"^Synchronization stage count may not safely be less than 2$"): 17 | FFSynchronizer(Signal(), Signal(), stages=1) 18 | 19 | def test_basic(self): 20 | i = Signal() 21 | o = Signal() 22 | frag = FFSynchronizer(i, o) 23 | 24 | sim = Simulator(frag) 25 | sim.add_clock(Period(MHz=1)) 26 | async def testbench(ctx): 27 | self.assertEqual(ctx.get(o), 0) 28 | ctx.set(i, 1) 29 | await ctx.tick() 30 | self.assertEqual(ctx.get(o), 0) 31 | await ctx.tick() 32 | self.assertEqual(ctx.get(o), 1) 33 | sim.add_testbench(testbench) 34 | sim.run() 35 | 36 | def test_init_value(self): 37 | i = Signal(init=1) 38 | o = Signal() 39 | frag = FFSynchronizer(i, o, init=1) 40 | 41 | sim = Simulator(frag) 42 | sim.add_clock(Period(MHz=1)) 43 | async def testbench(ctx): 44 | self.assertEqual(ctx.get(o), 1) 45 | ctx.set(i, 0) 46 | await ctx.tick() 47 | self.assertEqual(ctx.get(o), 1) 48 | await ctx.tick() 49 | self.assertEqual(ctx.get(o), 0) 50 | sim.add_testbench(testbench) 51 | sim.run() 52 | 53 | def test_reset_value(self): 54 | i = Signal(init=1) 55 | o = Signal() 56 | with self.assertWarnsRegex(DeprecationWarning, 57 | r"^`reset=` is deprecated, use `init=` instead$"): 58 | frag = FFSynchronizer(i, o, reset=1) 59 | 60 | sim = Simulator(frag) 61 | sim.add_clock(Period(MHz=1)) 62 | async def testbench(ctx): 63 | self.assertEqual(ctx.get(o), 1) 64 | ctx.set(i, 0) 65 | await ctx.tick() 66 | self.assertEqual(ctx.get(o), 1) 67 | await ctx.tick() 68 | self.assertEqual(ctx.get(o), 0) 69 | sim.add_testbench(testbench) 70 | sim.run() 71 | 72 | def test_reset_wrong(self): 73 | i = Signal(init=1) 74 | o = Signal() 75 | with self.assertRaisesRegex(ValueError, 76 | r"^Cannot specify both `reset` and `init`$"): 77 | FFSynchronizer(i, o, reset=1, init=1) 78 | 79 | 80 | class AsyncFFSynchronizerTestCase(FHDLTestCase): 81 | def test_stages_wrong(self): 82 | with self.assertRaisesRegex(TypeError, 83 | r"^Synchronization stage count must be a positive integer, not 0$"): 84 | ResetSynchronizer(Signal(), stages=0) 85 | with self.assertRaisesRegex(ValueError, 86 | r"^Synchronization stage count may not safely be less than 2$"): 87 | ResetSynchronizer(Signal(), stages=1) 88 | 89 | def test_edge_wrong(self): 90 | with self.assertRaisesRegex(ValueError, 91 | r"^AsyncFFSynchronizer async edge must be one of 'pos' or 'neg', not 'xxx'$"): 92 | AsyncFFSynchronizer(Signal(), Signal(), o_domain="sync", async_edge="xxx") 93 | 94 | def test_width_wrong(self): 95 | with self.assertRaisesRegex(ValueError, 96 | r"^AsyncFFSynchronizer input width must be 1, not 2$"): 97 | AsyncFFSynchronizer(Signal(2), Signal(), o_domain="sync") 98 | with self.assertRaisesRegex(ValueError, 99 | r"^AsyncFFSynchronizer output width must be 1, not 2$"): 100 | AsyncFFSynchronizer(Signal(), Signal(2), o_domain="sync") 101 | 102 | def test_pos_edge(self): 103 | i = Signal() 104 | o = Signal() 105 | m = Module() 106 | m.domains += ClockDomain("sync") 107 | m.submodules += AsyncFFSynchronizer(i, o) 108 | 109 | sim = Simulator(m) 110 | sim.add_clock(Period(MHz=1)) 111 | async def testbench(ctx): 112 | # initial reset 113 | self.assertEqual(ctx.get(i), 0) 114 | self.assertEqual(ctx.get(o), 1) 115 | await ctx.tick() 116 | self.assertEqual(ctx.get(o), 1) 117 | await ctx.tick() 118 | self.assertEqual(ctx.get(o), 0) 119 | await ctx.tick() 120 | self.assertEqual(ctx.get(o), 0) 121 | await ctx.tick() 122 | 123 | ctx.set(i, 1) 124 | self.assertEqual(ctx.get(o), 1) 125 | await ctx.tick() 126 | self.assertEqual(ctx.get(o), 1) 127 | ctx.set(i, 0) 128 | await ctx.tick() 129 | self.assertEqual(ctx.get(o), 1) 130 | await ctx.tick() 131 | self.assertEqual(ctx.get(o), 0) 132 | await ctx.tick() 133 | self.assertEqual(ctx.get(o), 0) 134 | await ctx.tick() 135 | sim.add_testbench(testbench) 136 | with sim.write_vcd("test.vcd"): 137 | sim.run() 138 | 139 | def test_neg_edge(self): 140 | i = Signal(init=1) 141 | o = Signal() 142 | m = Module() 143 | m.domains += ClockDomain("sync") 144 | m.submodules += AsyncFFSynchronizer(i, o, async_edge="neg") 145 | 146 | sim = Simulator(m) 147 | sim.add_clock(Period(MHz=1)) 148 | async def testbench(ctx): 149 | # initial reset 150 | self.assertEqual(ctx.get(i), 1) 151 | self.assertEqual(ctx.get(o), 1) 152 | await ctx.tick() 153 | self.assertEqual(ctx.get(o), 1) 154 | await ctx.tick() 155 | self.assertEqual(ctx.get(o), 0) 156 | await ctx.tick() 157 | self.assertEqual(ctx.get(o), 0) 158 | await ctx.tick() 159 | 160 | ctx.set(i, 0) 161 | self.assertEqual(ctx.get(o), 1) 162 | await ctx.tick() 163 | self.assertEqual(ctx.get(o), 1) 164 | ctx.set(i, 1) 165 | await ctx.tick() 166 | self.assertEqual(ctx.get(o), 1) 167 | await ctx.tick() 168 | self.assertEqual(ctx.get(o), 0) 169 | await ctx.tick() 170 | self.assertEqual(ctx.get(o), 0) 171 | await ctx.tick() 172 | sim.add_testbench(testbench) 173 | with sim.write_vcd("test.vcd"): 174 | sim.run() 175 | 176 | 177 | class ResetSynchronizerTestCase(FHDLTestCase): 178 | def test_stages_wrong(self): 179 | with self.assertRaisesRegex(TypeError, 180 | r"^Synchronization stage count must be a positive integer, not 0$"): 181 | ResetSynchronizer(Signal(), stages=0) 182 | with self.assertRaisesRegex(ValueError, 183 | r"^Synchronization stage count may not safely be less than 2$"): 184 | ResetSynchronizer(Signal(), stages=1) 185 | 186 | def test_basic(self): 187 | arst = Signal() 188 | m = Module() 189 | m.domains += ClockDomain("sync") 190 | m.submodules += ResetSynchronizer(arst) 191 | s = Signal(init=1) 192 | m.d.sync += s.eq(0) 193 | 194 | sim = Simulator(m) 195 | sim.add_clock(Period(MHz=1)) 196 | async def testbench(ctx): 197 | # initial reset 198 | self.assertEqual(ctx.get(s), 1) 199 | await ctx.tick() 200 | self.assertEqual(ctx.get(s), 1) 201 | await ctx.tick() 202 | self.assertEqual(ctx.get(s), 1) 203 | await ctx.tick() 204 | self.assertEqual(ctx.get(s), 0) 205 | await ctx.tick() 206 | 207 | ctx.set(arst, 1) 208 | self.assertEqual(ctx.get(s), 0) 209 | await ctx.tick() 210 | self.assertEqual(ctx.get(s), 1) 211 | ctx.set(arst, 0) 212 | await ctx.tick() 213 | self.assertEqual(ctx.get(s), 1) 214 | await ctx.tick() 215 | self.assertEqual(ctx.get(s), 1) 216 | await ctx.tick() 217 | self.assertEqual(ctx.get(s), 0) 218 | await ctx.tick() 219 | sim.add_testbench(testbench) 220 | with sim.write_vcd("test.vcd"): 221 | sim.run() 222 | 223 | 224 | # TODO: test with distinct clocks 225 | class PulseSynchronizerTestCase(FHDLTestCase): 226 | def test_stages_wrong(self): 227 | with self.assertRaisesRegex(TypeError, 228 | r"^Synchronization stage count must be a positive integer, not 0$"): 229 | PulseSynchronizer("w", "r", stages=0) 230 | with self.assertRaisesRegex(ValueError, 231 | r"^Synchronization stage count may not safely be less than 2$"): 232 | PulseSynchronizer("w", "r", stages=1) 233 | 234 | def test_smoke(self): 235 | m = Module() 236 | m.domains += ClockDomain("sync") 237 | ps = m.submodules.dut = PulseSynchronizer("sync", "sync") 238 | 239 | sim = Simulator(m) 240 | sim.add_clock(Period(MHz=1)) 241 | async def testbench(ctx): 242 | ctx.set(ps.i, 0) 243 | # TODO: think about reset 244 | for n in range(5): 245 | await ctx.tick() 246 | # Make sure no pulses are generated in quiescent state 247 | for n in range(3): 248 | await ctx.tick() 249 | self.assertEqual(ctx.get(ps.o), 0) 250 | # Check conservation of pulses 251 | accum = 0 252 | for n in range(10): 253 | ctx.set(ps.i, 1 if n < 4 else 0) 254 | await ctx.tick() 255 | accum += ctx.get(ps.o) 256 | self.assertEqual(accum, 4) 257 | sim.add_testbench(testbench) 258 | sim.run() 259 | -------------------------------------------------------------------------------- /tests/test_lib_meta.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from amaranth import * 4 | from amaranth.lib.meta import * 5 | 6 | 7 | class AnnotationTestCase(unittest.TestCase): 8 | def test_init_subclass(self): 9 | class MyAnnotation(Annotation): 10 | schema = { 11 | "$schema": "https://json-schema.org/draft/2020-12/schema", 12 | "$id": "https://example.com/schema/test/0.1/my-annotation.json", 13 | "type": "string", 14 | } 15 | 16 | @property 17 | def origin(self): 18 | return "foo" 19 | 20 | @property 21 | def as_json(self): 22 | return "foo" 23 | 24 | self.assertRegex(repr(MyAnnotation()), r"<.+\.MyAnnotation for 'foo'>") 25 | 26 | def test_init_subclass_wrong_schema(self): 27 | with self.assertRaisesRegex(TypeError, r"Annotation schema must be a dict, not 'foo'"): 28 | class MyAnnotation(Annotation): 29 | schema = "foo" 30 | 31 | def test_init_subclass_schema_missing_id(self): 32 | with self.assertRaisesRegex(InvalidSchema, r"'\$id' keyword is missing from Annotation schema: {}"): 33 | class MyAnnotation(Annotation): 34 | schema = {} 35 | 36 | def test_init_subclass_schema_missing_schema(self): 37 | with self.assertRaises(InvalidSchema): 38 | class MyAnnotation(Annotation): 39 | schema = { 40 | "$id": "https://example.com/schema/test/0.1/my-annotation.json", 41 | } 42 | 43 | def test_init_subclass_schema_error(self): 44 | with self.assertRaises(InvalidSchema): 45 | class MyAnnotation(Annotation): 46 | schema = { 47 | "$schema": "https://json-schema.org/draft/2020-12/schema", 48 | "$id": "https://example.com/schema/test/0.1/my-annotation.json", 49 | "type": "foo", 50 | } 51 | 52 | def test_validate(self): 53 | class MyAnnotation(Annotation): 54 | schema = { 55 | "$schema": "https://json-schema.org/draft/2020-12/schema", 56 | "$id": "https://example.com/schema/test/0.1/my-annotation.json", 57 | "type": "object", 58 | "properties": { 59 | "foo": { 60 | "enum": [ "bar" ], 61 | }, 62 | }, 63 | "additionalProperties": False, 64 | "required": [ 65 | "foo", 66 | ], 67 | } 68 | MyAnnotation.validate({"foo": "bar"}) 69 | 70 | def test_validate_error(self): 71 | class MyAnnotation(Annotation): 72 | schema = { 73 | "$schema": "https://json-schema.org/draft/2020-12/schema", 74 | "$id": "https://example.com/schema/test/0.1/my-annotation.json", 75 | "type": "object", 76 | "properties": { 77 | "foo": { 78 | "enum": [ "bar" ], 79 | }, 80 | }, 81 | "additionalProperties": False, 82 | "required": [ 83 | "foo", 84 | ], 85 | } 86 | with self.assertRaises(InvalidAnnotation): 87 | MyAnnotation.validate({"foo": "baz"}) 88 | -------------------------------------------------------------------------------- /tests/test_lib_stream.py: -------------------------------------------------------------------------------- 1 | from amaranth.hdl import * 2 | from amaranth.lib import stream, wiring, fifo 3 | from amaranth.lib.wiring import In, Out 4 | 5 | from .utils import * 6 | 7 | 8 | class StreamTestCase(FHDLTestCase): 9 | def test_nav_nar(self): 10 | sig = stream.Signature(2) 11 | self.assertRepr(sig, f"stream.Signature(2)") 12 | self.assertEqual(sig.always_valid, False) 13 | self.assertEqual(sig.always_ready, False) 14 | self.assertEqual(sig.members, wiring.SignatureMembers({ 15 | "payload": Out(2), 16 | "valid": Out(1), 17 | "ready": In(1) 18 | })) 19 | intf = sig.create() 20 | self.assertRepr(intf, 21 | f"stream.Interface(payload=(sig intf__payload), valid=(sig intf__valid), " 22 | f"ready=(sig intf__ready))") 23 | self.assertIs(intf.signature, sig) 24 | self.assertIsInstance(intf.payload, Signal) 25 | self.assertIs(intf.p, intf.payload) 26 | self.assertIsInstance(intf.valid, Signal) 27 | self.assertIsInstance(intf.ready, Signal) 28 | 29 | def test_av_nar(self): 30 | sig = stream.Signature(2, always_valid=True) 31 | self.assertRepr(sig, f"stream.Signature(2, always_valid=True)") 32 | self.assertEqual(sig.always_valid, True) 33 | self.assertEqual(sig.always_ready, False) 34 | self.assertEqual(sig.members, wiring.SignatureMembers({ 35 | "payload": Out(2), 36 | "valid": Out(1), 37 | "ready": In(1) 38 | })) 39 | intf = sig.create() 40 | self.assertRepr(intf, 41 | f"stream.Interface(payload=(sig intf__payload), valid=(const 1'd1), " 42 | f"ready=(sig intf__ready))") 43 | self.assertIs(intf.signature, sig) 44 | self.assertIsInstance(intf.payload, Signal) 45 | self.assertIs(intf.p, intf.payload) 46 | self.assertIsInstance(intf.valid, Const) 47 | self.assertEqual(intf.valid.value, 1) 48 | self.assertIsInstance(intf.ready, Signal) 49 | 50 | def test_nav_ar(self): 51 | sig = stream.Signature(2, always_ready=True) 52 | self.assertRepr(sig, f"stream.Signature(2, always_ready=True)") 53 | self.assertEqual(sig.always_valid, False) 54 | self.assertEqual(sig.always_ready, True) 55 | self.assertEqual(sig.members, wiring.SignatureMembers({ 56 | "payload": Out(2), 57 | "valid": Out(1), 58 | "ready": In(1) 59 | })) 60 | intf = sig.create() 61 | self.assertRepr(intf, 62 | f"stream.Interface(payload=(sig intf__payload), valid=(sig intf__valid), " 63 | f"ready=(const 1'd1))") 64 | self.assertIs(intf.signature, sig) 65 | self.assertIsInstance(intf.payload, Signal) 66 | self.assertIs(intf.p, intf.payload) 67 | self.assertIsInstance(intf.valid, Signal) 68 | self.assertIsInstance(intf.ready, Const) 69 | self.assertEqual(intf.ready.value, 1) 70 | 71 | def test_av_ar(self): 72 | sig = stream.Signature(2, always_valid=True, always_ready=True) 73 | self.assertRepr(sig, f"stream.Signature(2, always_valid=True, always_ready=True)") 74 | self.assertEqual(sig.always_valid, True) 75 | self.assertEqual(sig.always_ready, True) 76 | self.assertEqual(sig.members, wiring.SignatureMembers({ 77 | "payload": Out(2), 78 | "valid": Out(1), 79 | "ready": In(1) 80 | })) 81 | intf = sig.create() 82 | self.assertRepr(intf, 83 | f"stream.Interface(payload=(sig intf__payload), valid=(const 1'd1), " 84 | f"ready=(const 1'd1))") 85 | self.assertIs(intf.signature, sig) 86 | self.assertIsInstance(intf.payload, Signal) 87 | self.assertIs(intf.p, intf.payload) 88 | self.assertIsInstance(intf.valid, Const) 89 | self.assertEqual(intf.valid.value, 1) 90 | self.assertIsInstance(intf.ready, Const) 91 | self.assertEqual(intf.ready.value, 1) 92 | 93 | def test_eq(self): 94 | sig_nav_nar = stream.Signature(2) 95 | sig_av_nar = stream.Signature(2, always_valid=True) 96 | sig_nav_ar = stream.Signature(2, always_ready=True) 97 | sig_av_ar = stream.Signature(2, always_valid=True, always_ready=True) 98 | sig_av_ar2 = stream.Signature(3, always_valid=True, always_ready=True) 99 | self.assertNotEqual(sig_nav_nar, None) 100 | self.assertEqual(sig_nav_nar, sig_nav_nar) 101 | self.assertEqual(sig_av_nar, sig_av_nar) 102 | self.assertEqual(sig_nav_ar, sig_nav_ar) 103 | self.assertEqual(sig_av_ar, sig_av_ar) 104 | self.assertEqual(sig_av_ar2, sig_av_ar2) 105 | self.assertNotEqual(sig_nav_nar, sig_av_nar) 106 | self.assertNotEqual(sig_av_nar, sig_nav_ar) 107 | self.assertNotEqual(sig_nav_ar, sig_av_ar) 108 | self.assertNotEqual(sig_av_ar, sig_nav_nar) 109 | self.assertNotEqual(sig_av_ar, sig_av_ar2) 110 | 111 | def test_payload_init(self): 112 | sig = stream.Signature(2, payload_init=0b10) 113 | intf = sig.create() 114 | self.assertEqual(intf.payload.init, 0b10) 115 | 116 | def test_interface_create_bad(self): 117 | with self.assertRaisesRegex(TypeError, 118 | r"^Signature of stream\.Interface must be a stream\.Signature, not " 119 | r"Signature\(\{\}\)$"): 120 | stream.Interface(wiring.Signature({})) 121 | 122 | 123 | class FIFOStreamCompatTestCase(FHDLTestCase): 124 | def test_r_stream(self): 125 | queue = fifo.SyncFIFOBuffered(width=4, depth=16) 126 | r = queue.r_stream 127 | self.assertFalse(r.signature.always_valid) 128 | self.assertFalse(r.signature.always_ready) 129 | self.assertIs(r.payload, queue.r_data) 130 | self.assertIs(r.valid, queue.r_rdy) 131 | self.assertIs(r.ready, queue.r_en) 132 | 133 | def test_w_stream(self): 134 | queue = fifo.SyncFIFOBuffered(width=4, depth=16) 135 | w = queue.w_stream 136 | self.assertFalse(w.signature.always_valid) 137 | self.assertFalse(w.signature.always_ready) 138 | self.assertIs(w.payload, queue.w_data) 139 | self.assertIs(w.valid, queue.w_en) 140 | self.assertIs(w.ready, queue.w_rdy) 141 | -------------------------------------------------------------------------------- /tests/test_tracer.py: -------------------------------------------------------------------------------- 1 | from amaranth.hdl._ast import * 2 | from amaranth.hdl import _ast 3 | from types import SimpleNamespace 4 | 5 | from .utils import * 6 | 7 | class TracerTestCase(FHDLTestCase): 8 | def test_fast(self): 9 | s1 = Signal() 10 | self.assertEqual(s1.name, "s1") 11 | s2 = Signal() 12 | self.assertEqual(s2.name, "s2") 13 | 14 | def test_call_variants(self): 15 | args = [] 16 | kwargs = {} 17 | s1 = Signal() 18 | self.assertEqual(s1.name, "s1") 19 | s2 = Signal(init=0) 20 | self.assertEqual(s2.name, "s2") 21 | s3 = Signal(*args, **kwargs) 22 | self.assertEqual(s3.name, "s3") 23 | s4 = _ast.Signal() 24 | self.assertEqual(s4.name, "s4") 25 | s5 = _ast.Signal(init=0) 26 | self.assertEqual(s5.name, "s5") 27 | s6 = _ast.Signal(*args, **kwargs) 28 | self.assertEqual(s6.name, "s6") 29 | 30 | def test_name(self): 31 | class Dummy: 32 | s1 = Signal() 33 | self.assertEqual(s1.name, "s1") 34 | s2 = Signal() 35 | self.assertEqual(s2.name, "s2") 36 | 37 | def test_attr(self): 38 | ns = SimpleNamespace() 39 | ns.s1 = Signal() 40 | self.assertEqual(ns.s1.name, "s1") 41 | ns.s2 = Signal() 42 | self.assertEqual(ns.s2.name, "s2") 43 | 44 | def test_index(self): 45 | l = [None] 46 | l[0] = Signal() 47 | self.assertEqual(l[0].name, "$signal") 48 | 49 | def test_deref_cell(self): 50 | s1 = Signal() 51 | self.assertEqual(s1.name, "s1") 52 | s2 = Signal() 53 | self.assertEqual(s2.name, "s2") 54 | 55 | def dummy(): 56 | return s1, s2 57 | 58 | def test_deref_free(self): 59 | def inner(): 60 | nonlocal s3, s4 61 | s3 = Signal() 62 | s4 = Signal() 63 | return s1, s2 64 | 65 | s1 = Signal() 66 | s2 = Signal() 67 | s3 = None 68 | s4 = None 69 | inner() 70 | self.assertEqual(s1.name, "s1") 71 | self.assertEqual(s2.name, "s2") 72 | self.assertEqual(s3.name, "s3") 73 | self.assertEqual(s4.name, "s4") 74 | 75 | def test_long(self): 76 | test = "" 77 | for i in range(100000): 78 | test += f"dummy{i} = None\n" 79 | test += "s1 = Signal()\n" 80 | test += "s2 = Signal()\n" 81 | ns = {"Signal": Signal} 82 | exec(test, ns) 83 | self.assertEqual(ns["s1"].name, "s1") 84 | self.assertEqual(ns["s2"].name, "s2") 85 | 86 | def test_deref_fast(self): 87 | def inner(s2): 88 | s1 = Signal() 89 | s2 = Signal() 90 | self.assertEqual(s1.name, "s1") 91 | self.assertEqual(s2.name, "s2") 92 | 93 | def dummy(): 94 | return s1, s2 95 | 96 | inner(None) 97 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from amaranth.utils import * 4 | from amaranth._utils import _ignore_deprecated 5 | 6 | 7 | class Log2TestCase(unittest.TestCase): 8 | def test_ceil_log2(self): 9 | self.assertEqual(ceil_log2(0), 0) 10 | self.assertEqual(ceil_log2(1), 0) 11 | self.assertEqual(ceil_log2(2), 1) 12 | self.assertEqual(ceil_log2(3), 2) 13 | self.assertEqual(ceil_log2(4), 2) 14 | self.assertEqual(ceil_log2(5), 3) 15 | self.assertEqual(ceil_log2(8), 3) 16 | self.assertEqual(ceil_log2(9), 4) 17 | with self.assertRaises(TypeError): 18 | ceil_log2(1.5) 19 | with self.assertRaisesRegex(ValueError, r"^-1 is negative$"): 20 | ceil_log2(-1) 21 | 22 | def test_exact_log2(self): 23 | self.assertEqual(exact_log2(1), 0) 24 | self.assertEqual(exact_log2(2), 1) 25 | self.assertEqual(exact_log2(4), 2) 26 | self.assertEqual(exact_log2(8), 3) 27 | for val in [-1, 0, 3, 5, 6, 7, 9]: 28 | with self.assertRaisesRegex(ValueError, (f"^{val} is not a power of 2$")): 29 | exact_log2(val) 30 | with self.assertRaises(TypeError): 31 | exact_log2(1.5) 32 | 33 | def test_bits_for(self): 34 | self.assertEqual(bits_for(-4), 3) 35 | self.assertEqual(bits_for(-3), 3) 36 | self.assertEqual(bits_for(-2), 2) 37 | self.assertEqual(bits_for(-1), 1) 38 | self.assertEqual(bits_for(0), 1) 39 | self.assertEqual(bits_for(1), 1) 40 | self.assertEqual(bits_for(2), 2) 41 | self.assertEqual(bits_for(3), 2) 42 | self.assertEqual(bits_for(4), 3) 43 | self.assertEqual(bits_for(5), 3) 44 | self.assertEqual(bits_for(-4, True), 3) 45 | self.assertEqual(bits_for(-3, True), 3) 46 | self.assertEqual(bits_for(-2, True), 2) 47 | self.assertEqual(bits_for(-1, True), 1) 48 | self.assertEqual(bits_for(0, True), 1) 49 | self.assertEqual(bits_for(1, True), 2) 50 | self.assertEqual(bits_for(2, True), 3) 51 | self.assertEqual(bits_for(3, True), 3) 52 | self.assertEqual(bits_for(4, True), 4) 53 | self.assertEqual(bits_for(5, True), 4) 54 | with self.assertRaises(TypeError): 55 | bits_for(1.5) 56 | -------------------------------------------------------------------------------- /tests/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import platform 3 | import re 4 | import shutil 5 | import subprocess 6 | import sys 7 | import textwrap 8 | import traceback 9 | import unittest 10 | 11 | from amaranth.hdl._ast import * 12 | from amaranth.hdl._ir import * 13 | from amaranth.back import rtlil 14 | from amaranth._toolchain import require_tool 15 | 16 | 17 | __all__ = ["FHDLTestCase"] 18 | 19 | 20 | class FHDLTestCase(unittest.TestCase): 21 | maxDiff = None 22 | 23 | def assertRepr(self, obj, repr_str): 24 | if isinstance(obj, list): 25 | obj = Statement.cast(obj) 26 | def squish_repr(repr_str): 27 | repr_str = re.sub(r"\s+", " ", repr_str) 28 | repr_str = re.sub(r"\( (?=\()", "(", repr_str) 29 | repr_str = re.sub(r"\) (?=\))", ")", repr_str) 30 | return repr_str.strip() 31 | def format_repr(input_repr, *, indent=" "): 32 | output_repr = [] 33 | prefix = "\n" 34 | name = None 35 | index = 0 36 | stack = [] 37 | current = "" 38 | for char in input_repr: 39 | if char == "(": 40 | stack.append((prefix, name, index)) 41 | name, index = None, 0 42 | output_repr.append(char) 43 | if len(stack) == 1: 44 | prefix += indent 45 | output_repr.append(prefix) 46 | elif char == ")": 47 | indented = (len(stack) == 1 or name in ("module", "top")) 48 | prefix, name, index = stack.pop() 49 | if indented: 50 | output_repr.append(prefix) 51 | output_repr.append(char) 52 | elif char == " ": 53 | if name is None: 54 | name = current 55 | if name in ("module", "top"): 56 | prefix += indent 57 | else: 58 | index += 1 59 | current = "" 60 | if len(stack) == 1 or name == "module" and index >= 3 or name == "top": 61 | output_repr.append(prefix) 62 | else: 63 | output_repr.append(char) 64 | elif name is None: 65 | current += char 66 | output_repr.append(char) 67 | else: 68 | output_repr.append(char) 69 | return "".join(output_repr) 70 | # print("\n" + format_repr(squish_repr(repr(obj)))) 71 | self.assertEqual(format_repr(squish_repr(repr(obj))), format_repr(squish_repr(repr_str))) 72 | 73 | def assertFormal(self, spec, ports=None, mode="bmc", depth=1): 74 | if sys.version_info >= (3, 11) and platform.python_implementation() == 'PyPy': 75 | self.skipTest("sby is broken with pypy-3.11 without https://github.com/YosysHQ/sby/pull/323") 76 | 77 | stack = traceback.extract_stack() 78 | for frame in reversed(stack): 79 | if os.path.dirname(__file__) not in frame.filename: 80 | break 81 | caller = frame 82 | 83 | spec_root, _ = os.path.splitext(caller.filename) 84 | spec_dir = os.path.dirname(spec_root) 85 | spec_name = "{}_{}".format( 86 | os.path.basename(spec_root).replace("test_", "spec_"), 87 | caller.name.replace("test_", "") 88 | ) 89 | 90 | # The sby -f switch seems not fully functional when sby is reading from stdin. 91 | if os.path.exists(os.path.join(spec_dir, spec_name)): 92 | shutil.rmtree(os.path.join(spec_dir, spec_name)) 93 | 94 | if mode == "hybrid": 95 | # A mix of BMC and k-induction, as per personal communication with Claire Wolf. 96 | script = "setattr -unset init w:* a:amaranth.sample_reg %d" 97 | mode = "bmc" 98 | else: 99 | script = "" 100 | 101 | config = textwrap.dedent("""\ 102 | [options] 103 | mode {mode} 104 | depth {depth} 105 | wait on 106 | multiclock on 107 | 108 | [engines] 109 | smtbmc 110 | 111 | [script] 112 | read_rtlil top.il 113 | prep 114 | {script} 115 | 116 | [file top.il] 117 | {rtlil} 118 | """).format( 119 | mode=mode, 120 | depth=depth, 121 | script=script, 122 | rtlil=rtlil.convert(spec, ports=ports, platform="formal"), 123 | ) 124 | with subprocess.Popen( 125 | [require_tool("sby"), "-f", "-d", spec_name], 126 | cwd=spec_dir, env={**os.environ, "PYTHONWARNINGS":"ignore"}, 127 | universal_newlines=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE) as proc: 128 | stdout, stderr = proc.communicate(config) 129 | if proc.returncode != 0: 130 | self.fail("Formal verification failed:\n" + stdout) 131 | --------------------------------------------------------------------------------