├── tests ├── __init__.py ├── test_csr_event.py ├── test_periph.py ├── test_wishbone_sram.py ├── test_csr_action.py ├── test_csr_wishbone.py ├── test_event.py ├── test_gpio.py └── test_csr_bus.py ├── docs ├── .gitignore ├── _static │ ├── logo.png │ └── custom.css ├── index.rst ├── cover.rst └── conf.py ├── .github ├── CODEOWNERS └── workflows │ └── main.yaml ├── amaranth_soc ├── wishbone │ ├── __init__.py │ ├── sram.py │ └── bus.py ├── csr │ ├── __init__.py │ ├── event.py │ ├── wishbone.py │ ├── action.py │ └── reg.py ├── __init__.py ├── periph.py ├── event.py └── gpio.py ├── .editorconfig ├── .coveragerc ├── .gitignore ├── README.md ├── pdm_build.py ├── LICENSE.txt └── pyproject.toml /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _build/ 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @jfng 2 | -------------------------------------------------------------------------------- /amaranth_soc/wishbone/__init__.py: -------------------------------------------------------------------------------- 1 | from .bus import * 2 | -------------------------------------------------------------------------------- /docs/_static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amaranth-lang/amaranth-soc/HEAD/docs/_static/logo.png -------------------------------------------------------------------------------- /amaranth_soc/csr/__init__.py: -------------------------------------------------------------------------------- 1 | from .bus import * 2 | from .event import * 3 | from .reg import * 4 | from . import action 5 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | System on Chip toolkit 2 | ###################### 3 | 4 | .. warning:: 5 | 6 | This manual is a work in progress and is seriously incomplete! 7 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | include = 4 | amaranth_soc/* 5 | omit = 6 | amaranth_soc/test/* 7 | */__init__.py 8 | 9 | [report] 10 | exclude_lines = 11 | :nocov: 12 | partial_branches = 13 | :nobr: 14 | -------------------------------------------------------------------------------- /.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 | # coverage 13 | /.coverage 14 | /htmlcov 15 | 16 | # tests 17 | *.vcd 18 | *.gtkw 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # System on Chip toolkit for Amaranth HDL 2 | 3 | TODO 4 | 5 | ## License 6 | 7 | Amaranth is released under the very permissive two-clause BSD license. Under the terms of this license, you are authorized to use Amaranth for closed-source proprietary designs. 8 | 9 | See [LICENSE.txt](LICENSE.txt) file for full copyright and license info. 10 | -------------------------------------------------------------------------------- /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 | Language & toolchain 10 | Standard I/O components 11 | index 12 | -------------------------------------------------------------------------------- /amaranth_soc/__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 -------------------------------------------------------------------------------- /pdm_build.py: -------------------------------------------------------------------------------- 1 | from pdm.backend._vendor.packaging.version import Version 2 | 3 | 4 | # This is done in a PDM build hook without specifying `dynamic = [..., "version"]` to put all 5 | # of the static metadata into pyproject.toml. Tools other than PDM will not execute this script 6 | # and will use the generic version of the documentation URL (which redirects to /latest). 7 | def pdm_build_initialize(context): 8 | version = Version(context.config.metadata["version"]) 9 | if version.is_prerelease: 10 | url_version = "latest" 11 | else: 12 | url_version = f"v{version}" 13 | context.config.metadata["urls"]["Documentation"] += url_version 14 | -------------------------------------------------------------------------------- /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: 325px; } 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 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2019-2021 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 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | import os, sys 2 | sys.path.insert(0, os.path.abspath(".")) 3 | 4 | import time 5 | import amaranth_soc 6 | 7 | project = "System on Chip toolkit for Amaranth HDL" 8 | version = amaranth_soc.__version__.replace(".editable", "") 9 | release = version.split("+")[0] 10 | copyright = time.strftime("2020—%Y, Amaranth project contributors") 11 | 12 | extensions = [ 13 | "sphinx.ext.intersphinx", 14 | "sphinx.ext.doctest", 15 | "sphinx.ext.todo", 16 | "sphinx.ext.autodoc", 17 | "sphinx.ext.napoleon", 18 | "sphinx_rtd_theme", 19 | ] 20 | 21 | with open(".gitignore") as f: 22 | exclude_patterns = [line.strip() for line in f.readlines()] 23 | 24 | root_doc = "cover" 25 | 26 | intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} 27 | 28 | todo_include_todos = True 29 | 30 | autodoc_member_order = "bysource" 31 | autodoc_default_options = { 32 | "members": True 33 | } 34 | autodoc_preserve_defaults = True 35 | 36 | napoleon_google_docstring = False 37 | napoleon_numpy_docstring = True 38 | napoleon_use_ivar = True 39 | napoleon_include_init_with_doc = True 40 | napoleon_include_special_with_doc = True 41 | 42 | html_theme = "sphinx_rtd_theme" 43 | html_static_path = ["_static"] 44 | html_css_files = ["custom.css"] 45 | html_logo = "_static/logo.png" 46 | 47 | rst_prolog = """ 48 | .. role:: pc(code) 49 | :language: python 50 | """ 51 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # Project metadata 2 | 3 | [tool.pdm.version] 4 | source = "scm" 5 | 6 | [project] 7 | dynamic = ["version"] 8 | 9 | name = "amaranth-soc" 10 | description = "System on Chip toolkit for Amaranth HDL" 11 | authors = [{name = "Amaranth HDL contributors"}] 12 | license = {file = "LICENSE.txt"} 13 | 14 | requires-python = "~=3.9" 15 | dependencies = [ 16 | # this version requirement needs to be synchronized with the one in .github/workflows/main.yml 17 | "amaranth>=0.5,<0.6", 18 | ] 19 | 20 | [project.urls] 21 | "Homepage" = "https://amaranth-lang.org/" 22 | "Documentation" = "https://amaranth-lang.org/docs/amaranth-soc/" # modified in pdm_build.py 23 | "Source Code" = "https://github.com/amaranth-lang/amaranth-soc" 24 | "Bug Tracker" = "https://github.com/amaranth-lang/amaranth-soc/issues" 25 | 26 | # Build system configuration 27 | 28 | [build-system] 29 | requires = ["pdm-backend"] 30 | build-backend = "pdm.backend" 31 | 32 | # Development workflow configuration 33 | 34 | [tool.pdm.dev-dependencies] 35 | test = [ 36 | "coverage", 37 | ] 38 | docs = [ 39 | "sphinx~=7.1", 40 | "sphinx-rtd-theme~=1.2", 41 | "sphinx-autobuild", 42 | ] 43 | 44 | [tool.pdm.scripts] 45 | test.composite = ["test-code"] 46 | # TODO: Once the amaranth requirement is bumped to 0.6, remove the RFC 66 warning filter and fix the deprecations. 47 | test-code.env = {PYTHONWARNINGS = "error,ignore:Per RFC 66,:DeprecationWarning"} 48 | test-code.cmd = "python -m coverage run -m unittest discover -t . -s tests -v" 49 | test-docs.cmd = "sphinx-build -b doctest docs docs/_build" 50 | 51 | document.cmd = "sphinx-build docs docs/_build" 52 | document-live.cmd = "sphinx-autobuild docs docs/_build --watch amaranth_soc" 53 | 54 | coverage-text.cmd = "python -m coverage report" 55 | coverage-html.cmd = "python -m coverage html" 56 | -------------------------------------------------------------------------------- /amaranth_soc/csr/event.py: -------------------------------------------------------------------------------- 1 | # amaranth: UnusedElaboratable=no 2 | from math import ceil 3 | 4 | from amaranth import * 5 | from amaranth.lib import wiring 6 | from amaranth.lib.wiring import In, Out, flipped, connect 7 | from amaranth.utils import ceil_log2 8 | 9 | from . import Multiplexer 10 | from .reg import Register, Field, FieldAction 11 | from .. import event 12 | from ..memory import MemoryMap 13 | 14 | 15 | __all__ = ["EventMonitor"] 16 | 17 | 18 | class _EventMaskRegister(Register, access="rw"): 19 | def __init__(self, width): 20 | super().__init__({"mask": Field(FieldAction, width, access="rw")}) 21 | 22 | 23 | class EventMonitor(wiring.Component): 24 | """Event monitor. 25 | 26 | A monitor for subordinate event sources, with a CSR bus interface. 27 | 28 | CSR registers 29 | ------------- 30 | enable : ``event_map.size``, read/write 31 | Enabled events. See :meth:`..event.EventMap.sources` for layout. 32 | pending : ``event_map.size``, read/clear 33 | Pending events. See :meth:`..event.EventMap.sources` for layout. 34 | 35 | Parameters 36 | ---------- 37 | event_map : :class:`..event.EventMap` 38 | A collection of event sources. 39 | trigger : :class:`..event.Source.Trigger` 40 | Trigger mode. See :class:`..event.Source`. 41 | data_width : int 42 | CSR bus data width. See :class:`..csr.Interface`. 43 | alignment : int, power-of-2 exponent 44 | CSR address alignment. See :class:`..memory.MemoryMap`. 45 | 46 | Attributes 47 | ---------- 48 | src : :class:`..event.Source` 49 | Event source. Its input line is asserted by the monitor when a subordinate event is enabled 50 | and pending. 51 | bus : :class:`..csr.Interface` 52 | CSR bus interface. 53 | """ 54 | def __init__(self, event_map, *, trigger="level", data_width, alignment=0, name=None): 55 | if not isinstance(data_width, int) or data_width <= 0: 56 | raise ValueError(f"Data width must be a positive integer, not {data_width!r}") 57 | if not isinstance(alignment, int) or alignment < 0: 58 | raise ValueError(f"Alignment must be a non-negative integer, not {alignment!r}") 59 | 60 | self._monitor = event.Monitor(event_map, trigger=trigger) 61 | self._enable = _EventMaskRegister(event_map.size) 62 | self._pending = _EventMaskRegister(event_map.size) 63 | 64 | reg_size = (event_map.size + data_width - 1) // data_width 65 | addr_width = 1 + max(ceil_log2(reg_size), alignment) 66 | memory_map = MemoryMap(addr_width=addr_width, data_width=data_width, alignment=alignment) 67 | memory_map.add_resource(self._enable, size=reg_size, name=("enable",)) 68 | memory_map.add_resource(self._pending, size=reg_size, name=("pending",)) 69 | 70 | self._mux = Multiplexer(memory_map) 71 | 72 | super().__init__({ 73 | "src": Out(self._monitor.src.signature), 74 | "bus": In(self._mux.bus.signature), 75 | }) 76 | self.bus.memory_map = self._mux.bus.memory_map 77 | 78 | def elaborate(self, platform): 79 | m = Module() 80 | m.submodules.monitor = self._monitor 81 | m.submodules.mux = self._mux 82 | 83 | connect(m, flipped(self.src), self._monitor.src) 84 | connect(m, self.bus, self._mux.bus) 85 | 86 | with m.If(self._enable.element.w_stb): 87 | m.d.sync += self._monitor.enable.eq(self._enable.element.w_data) 88 | m.d.comb += self._enable.element.r_data.eq(self._monitor.enable) 89 | 90 | with m.If(self._pending.element.w_stb): 91 | m.d.comb += self._monitor.clear.eq(self._pending.element.w_data) 92 | m.d.comb += self._pending.element.r_data.eq(self._monitor.pending) 93 | 94 | return m 95 | -------------------------------------------------------------------------------- /amaranth_soc/wishbone/sram.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.lib import wiring 3 | from amaranth.lib.wiring import In 4 | from amaranth.lib.memory import MemoryData, Memory 5 | from amaranth.utils import exact_log2 6 | 7 | from ..memory import MemoryMap 8 | from .bus import Signature 9 | 10 | 11 | __all__ = ["WishboneSRAM"] 12 | 13 | 14 | class WishboneSRAM(wiring.Component): 15 | """Wishbone-attached SRAM. 16 | 17 | Wishbone bus accesses have a latency of one clock cycle. 18 | 19 | Arguments 20 | --------- 21 | size : :class:`int`, power of two 22 | SRAM size, in units of ``granularity`` bits. 23 | data_width : ``8``, ``16``, ``32`` or ``64`` 24 | Wishbone bus data width. 25 | granularity : ``8``, ``16``, ``32`` or ``64``, optional 26 | Wishbone bus granularity. If unspecified, it defaults to ``data_width``. 27 | writable : bool 28 | Write capability. If disabled, writes are ignored. Enabled by default. 29 | init : iterable of initial values, optional 30 | Initial values for memory rows. There are ``(size * granularity) // data_width`` rows, 31 | and each row has a shape of ``unsigned(data_width)``. 32 | 33 | Members 34 | ------- 35 | wb_bus : ``In(wishbone.Signature(...))`` 36 | Wishbone bus interface. 37 | 38 | Raises 39 | ------ 40 | :exc:`ValueError` 41 | If ``size * granularity`` is lesser than ``data_width``. 42 | """ 43 | def __init__(self, *, size, data_width, granularity=None, writable=True, init=()): 44 | if granularity is None: 45 | granularity = data_width 46 | 47 | if not isinstance(size, int) or size <= 0 or size & size-1: 48 | raise TypeError(f"Size must be an integer power of two, not {size!r}") 49 | if data_width not in (8, 16, 32, 64): 50 | raise TypeError(f"Data width must be 8, 16, 32 or 64, not {data_width!r}") 51 | if granularity not in (8, 16, 32, 64): 52 | raise TypeError(f"Granularity must be 8, 16, 32 or 64, not {granularity!r}") 53 | if size * granularity < data_width: 54 | raise ValueError(f"The product of size {size} and granularity {granularity} must be " 55 | f"greater than or equal to data width {data_width}, not " 56 | f"{size * granularity}") 57 | 58 | self._size = size 59 | self._writable = bool(writable) 60 | self._mem_data = MemoryData(depth=(size * granularity) // data_width, 61 | shape=unsigned(data_width), init=init) 62 | self._mem = Memory(self._mem_data) 63 | 64 | super().__init__({"wb_bus": In(Signature(addr_width=exact_log2(self._mem.depth), 65 | data_width=data_width, granularity=granularity))}) 66 | 67 | self.wb_bus.memory_map = MemoryMap(addr_width=exact_log2(size), data_width=granularity) 68 | self.wb_bus.memory_map.add_resource(self._mem, name=("mem",), size=size) 69 | self.wb_bus.memory_map.freeze() 70 | 71 | @property 72 | def size(self): 73 | return self._size 74 | 75 | @property 76 | def writable(self): 77 | return self._writable 78 | 79 | @property 80 | def init(self): 81 | return self._mem_data.init 82 | 83 | @init.setter 84 | def init(self, init): 85 | self._mem_data.init = init 86 | 87 | def elaborate(self, platform): 88 | m = Module() 89 | m.submodules.mem = self._mem 90 | 91 | read_port = self._mem.read_port() 92 | m.d.comb += [ 93 | read_port.addr.eq(self.wb_bus.adr), 94 | self.wb_bus.dat_r.eq(read_port.data), 95 | ] 96 | 97 | if self.writable: 98 | write_port = self._mem.write_port(granularity=self.wb_bus.granularity) 99 | m.d.comb += [ 100 | write_port.addr.eq(self.wb_bus.adr), 101 | write_port.data.eq(self.wb_bus.dat_w), 102 | ] 103 | 104 | with m.If(self.wb_bus.ack): 105 | m.d.sync += self.wb_bus.ack.eq(0) 106 | with m.Elif(self.wb_bus.cyc & self.wb_bus.stb): 107 | if self.writable: 108 | m.d.comb += write_port.en.eq(Mux(self.wb_bus.we, self.wb_bus.sel, 0)) 109 | m.d.comb += read_port.en.eq(~self.wb_bus.we) 110 | m.d.sync += self.wb_bus.ack.eq(1) 111 | 112 | return m 113 | -------------------------------------------------------------------------------- /amaranth_soc/csr/wishbone.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.lib import wiring 3 | from amaranth.lib.wiring import In, flipped 4 | from amaranth.utils import exact_log2 5 | 6 | from . import Interface 7 | from .. import wishbone 8 | from ..memory import MemoryMap 9 | 10 | 11 | __all__ = ["WishboneCSRBridge"] 12 | 13 | 14 | class WishboneCSRBridge(wiring.Component): 15 | """Wishbone to CSR bridge. 16 | 17 | A bus bridge for accessing CSR registers from Wishbone. This bridge supports any Wishbone 18 | data width greater or equal to CSR data width and performs appropriate address translation. 19 | 20 | Latency 21 | ------- 22 | 23 | Reads and writes always take ``self.data_width // csr_bus.data_width + 1`` cycles to complete, 24 | regardless of the select inputs. Write side effects occur simultaneously with acknowledgement. 25 | 26 | Parameters 27 | ---------- 28 | csr_bus : :class:`..csr.Interface` 29 | CSR bus driven by the bridge. 30 | data_width : int 31 | Wishbone bus data width. Optional. If ``None``, defaults to ``csr_bus.data_width``. 32 | name : :class:`..memory.MemoryMap.Name` 33 | Window name. Optional. 34 | 35 | Attributes 36 | ---------- 37 | wb_bus : :class:`..wishbone.Interface` 38 | Wishbone bus provided by the bridge. 39 | """ 40 | def __init__(self, csr_bus, *, data_width=None, name=None): 41 | if isinstance(csr_bus, wiring.FlippedInterface): 42 | csr_bus_unflipped = flipped(csr_bus) 43 | else: 44 | csr_bus_unflipped = csr_bus 45 | if not isinstance(csr_bus_unflipped, Interface): 46 | raise TypeError(f"CSR bus must be an instance of csr.Interface, not " 47 | f"{csr_bus_unflipped!r}") 48 | if csr_bus.data_width not in (8, 16, 32, 64): 49 | raise ValueError(f"CSR bus data width must be one of 8, 16, 32, 64, not " 50 | f"{csr_bus.data_width!r}") 51 | if data_width is None: 52 | data_width = csr_bus.data_width 53 | 54 | ratio = data_width // csr_bus.data_width 55 | wb_sig = wishbone.Signature(addr_width=max(0, csr_bus.addr_width - exact_log2(ratio)), 56 | data_width=data_width, 57 | granularity=csr_bus.data_width) 58 | 59 | super().__init__({"wb_bus": In(wb_sig)}) 60 | 61 | self.wb_bus.memory_map = MemoryMap(addr_width=csr_bus.addr_width, 62 | data_width=csr_bus.data_width) 63 | # Since granularity of the Wishbone interface matches the data width of the CSR bus, 64 | # no width conversion is performed, even if the Wishbone data width is greater. 65 | self.wb_bus.memory_map.add_window(csr_bus.memory_map, name=name) 66 | 67 | self._csr_bus = csr_bus 68 | 69 | @property 70 | def csr_bus(self): 71 | return self._csr_bus 72 | 73 | def elaborate(self, platform): 74 | csr_bus = self.csr_bus 75 | wb_bus = self.wb_bus 76 | 77 | m = Module() 78 | 79 | cycle = Signal(range(len(wb_bus.sel) + 1)) 80 | m.d.comb += csr_bus.addr.eq(Cat(cycle[:exact_log2(len(wb_bus.sel))], wb_bus.adr)) 81 | 82 | with m.If(wb_bus.cyc & wb_bus.stb): 83 | with m.Switch(cycle): 84 | def segment(index): 85 | return slice(index * wb_bus.granularity, (index + 1) * wb_bus.granularity) 86 | 87 | for index, sel_index in enumerate(wb_bus.sel): 88 | with m.Case(index): 89 | if index > 0: 90 | # CSR reads are registered, and we need to re-register them. 91 | m.d.sync += wb_bus.dat_r[segment(index - 1)].eq(csr_bus.r_data) 92 | m.d.comb += csr_bus.r_stb.eq(sel_index & ~wb_bus.we) 93 | m.d.comb += csr_bus.w_data.eq(wb_bus.dat_w[segment(index)]) 94 | m.d.comb += csr_bus.w_stb.eq(sel_index & wb_bus.we) 95 | m.d.sync += cycle.eq(index + 1) 96 | 97 | with m.Default(): 98 | m.d.sync += wb_bus.dat_r[segment(index)].eq(csr_bus.r_data) 99 | m.d.sync += wb_bus.ack.eq(1) 100 | 101 | with m.If(wb_bus.ack): 102 | m.d.sync += cycle.eq(0) 103 | m.d.sync += wb_bus.ack.eq(0) 104 | 105 | return m 106 | -------------------------------------------------------------------------------- /tests/test_csr_event.py: -------------------------------------------------------------------------------- 1 | # amaranth: UnusedElaboratable=no 2 | 3 | import unittest 4 | from amaranth import * 5 | from amaranth.sim import * 6 | 7 | from amaranth_soc.csr import * 8 | from amaranth_soc import event 9 | 10 | 11 | class EventMonitorTestCase(unittest.TestCase): 12 | def test_params(self): 13 | event_map = event.EventMap() 14 | monitor = EventMonitor(event_map, trigger="rise", data_width=16, alignment=4) 15 | self.assertEqual(monitor.src.trigger, event.Source.Trigger.RISE) 16 | self.assertEqual(monitor.bus.data_width, 16) 17 | self.assertEqual(monitor.bus.memory_map.alignment, 4) 18 | 19 | def test_wrong_data_width(self): 20 | with self.assertRaisesRegex(ValueError, 21 | r"Data width must be a positive integer, not 'foo'"): 22 | EventMonitor(event.EventMap(), data_width='foo') 23 | with self.assertRaisesRegex(ValueError, 24 | r"Data width must be a positive integer, not 0"): 25 | EventMonitor(event.EventMap(), data_width=0) 26 | 27 | def test_wrong_alignment(self): 28 | with self.assertRaisesRegex(ValueError, 29 | r"Alignment must be a non-negative integer, not 'foo'"): 30 | EventMonitor(event.EventMap(), data_width=8, alignment="foo") 31 | with self.assertRaisesRegex(ValueError, 32 | r"Alignment must be a non-negative integer, not -1"): 33 | EventMonitor(event.EventMap(), data_width=8, alignment=-1) 34 | 35 | def test_wrong_trigger(self): 36 | with self.assertRaisesRegex(ValueError, 37 | r"'foo' is not a valid Source.Trigger"): 38 | EventMonitor(event.EventMap(), data_width=8, trigger="foo") 39 | 40 | def test_csr_regs(self): 41 | sub_0 = event.Source(path=("sub_0",)) 42 | sub_1 = event.Source(path=("sub_1",)) 43 | event_map = event.EventMap() 44 | event_map.add(sub_0) 45 | event_map.add(sub_1) 46 | monitor = EventMonitor(event_map, data_width=8) 47 | resources = list(monitor.bus.memory_map.all_resources()) 48 | self.assertEqual(len(resources), 2) 49 | enable, enable_range = resources[0].resource, (resources[0].start, resources[0].end) 50 | pending, pending_range = resources[1].resource, (resources[1].start, resources[1].end) 51 | self.assertEqual( 52 | (enable.element.width, enable.element.access, enable_range), 53 | (2, Element.Access.RW, (0, 1)) 54 | ) 55 | self.assertEqual( 56 | (pending.element.width, pending.element.access, pending_range), 57 | (2, Element.Access.RW, (1, 2)) 58 | ) 59 | 60 | 61 | class EventMonitorSimulationTestCase(unittest.TestCase): 62 | def test_simple(self): 63 | sub = event.Source() 64 | event_map = event.EventMap() 65 | event_map.add(sub) 66 | dut = EventMonitor(event_map, data_width=8) 67 | 68 | addr_enable = 0x0 69 | addr_pending = 0x1 70 | 71 | async def testbench(ctx): 72 | ctx.set(sub.i, 1) 73 | self.assertEqual(ctx.get(sub.trg), 1) 74 | self.assertEqual(ctx.get(dut.src.i), 0) 75 | 76 | ctx.set(dut.bus.addr, addr_enable) 77 | ctx.set(dut.bus.r_stb, 1) 78 | await ctx.tick() 79 | self.assertEqual(ctx.get(dut.bus.r_data), 0b0) 80 | ctx.set(dut.bus.r_stb, 0) 81 | 82 | ctx.set(dut.bus.addr, addr_enable) 83 | ctx.set(dut.bus.w_stb, 1) 84 | ctx.set(dut.bus.w_data, 0b1) 85 | await ctx.tick() 86 | ctx.set(dut.bus.w_stb, 0) 87 | await ctx.tick() 88 | self.assertEqual(ctx.get(dut.src.i), 1) 89 | 90 | ctx.set(sub.i, 0) 91 | self.assertEqual(ctx.get(sub.trg), 0) 92 | 93 | ctx.set(dut.bus.addr, addr_pending) 94 | ctx.set(dut.bus.r_stb, 1) 95 | await ctx.tick() 96 | self.assertEqual(ctx.get(dut.bus.r_data), 0b1) 97 | ctx.set(dut.bus.r_stb, 0) 98 | 99 | ctx.set(dut.bus.addr, addr_pending) 100 | ctx.set(dut.bus.w_stb, 1) 101 | ctx.set(dut.bus.w_data, 0b1) 102 | await ctx.tick() 103 | ctx.set(dut.bus.w_stb, 0) 104 | await ctx.tick() 105 | 106 | ctx.set(dut.bus.addr, addr_pending) 107 | ctx.set(dut.bus.r_stb, 1) 108 | await ctx.tick() 109 | self.assertEqual(ctx.get(dut.bus.r_data), 0b0) 110 | ctx.set(dut.bus.r_stb, 0) 111 | 112 | sim = Simulator(dut) 113 | sim.add_clock(1e-6) 114 | sim.add_testbench(testbench) 115 | with sim.write_vcd(vcd_file="test.vcd"): 116 | sim.run() 117 | 118 | 119 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | pull_request: 4 | schedule: 5 | - cron: '0 0 * * *' # test daily against git HEAD of dependencies 6 | 7 | name: CI 8 | jobs: 9 | 10 | test: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | python-version: 15 | - '3.9' 16 | - '3.10' 17 | - '3.11' 18 | - '3.12' 19 | - '3.13' 20 | - 'pypy-3.9' 21 | - 'pypy-3.10' 22 | # this version range needs to be synchronized with the one in pyproject.toml 23 | amaranth-version: 24 | - '0.5' 25 | - 'git' 26 | allow-failure: 27 | - false 28 | continue-on-error: '${{ matrix.allow-failure }}' 29 | name: 'test (${{ matrix.python-version }}, HDL ${{ matrix.amaranth-version }})' 30 | steps: 31 | - name: Check out source code 32 | uses: actions/checkout@v4 33 | with: 34 | fetch-depth: 0 35 | - name: Set up PDM 36 | uses: pdm-project/setup-pdm@v4 37 | with: 38 | python-version: ${{ matrix.python-version }} 39 | - name: Install dependencies 40 | run: | 41 | pip install codecov 42 | pdm install --dev 43 | - name: Install Amaranth release 44 | if: ${{ matrix.amaranth-version != 'git' }} 45 | run: | 46 | pip install 'amaranth==${{ matrix.amaranth-version }}' 47 | - name: Install Amaranth from git 48 | if: ${{ matrix.amaranth-version == 'git' }} 49 | run: | 50 | pip install git+https://github.com/amaranth-lang/amaranth.git 51 | - name: Run tests 52 | run: | 53 | pdm run test 54 | - name: Submit code coverage 55 | run: 56 | codecov 57 | 58 | document: 59 | runs-on: ubuntu-latest 60 | steps: 61 | - name: Check out source code 62 | uses: actions/checkout@v4 63 | with: 64 | fetch-depth: 0 65 | - name: Fetch tags from upstream repository 66 | run: | 67 | git fetch --tags https://github.com/amaranth-lang/amaranth-soc.git 68 | - name: Set up PDM 69 | uses: pdm-project/setup-pdm@v4 70 | with: 71 | python-version: '3.12' 72 | - name: Install dependencies 73 | run: | 74 | pdm install --dev 75 | - name: Build documentation 76 | run: | 77 | pdm run document 78 | - name: Upload documentation archive 79 | uses: actions/upload-artifact@v4 80 | with: 81 | name: docs 82 | path: docs/_build 83 | 84 | required: # group all required workflows into one to avoid reconfiguring this in Actions settings 85 | needs: 86 | - test 87 | - document 88 | if: always() && !contains(needs.*.result, 'cancelled') 89 | runs-on: ubuntu-latest 90 | steps: 91 | - run: ${{ contains(needs.*.result, 'failure') && 'false' || 'true' }} 92 | 93 | publish-docs: 94 | needs: document 95 | if: github.repository == 'amaranth-lang/amaranth-soc' 96 | runs-on: ubuntu-latest 97 | steps: 98 | - name: Check out source code 99 | uses: actions/checkout@v4 100 | with: 101 | fetch-depth: 0 102 | - name: Download documentation archive 103 | uses: actions/download-artifact@v4 104 | with: 105 | name: docs 106 | path: docs/ 107 | - name: Publish development documentation 108 | if: github.event_name == 'push' && github.event.ref == 'refs/heads/main' 109 | uses: JamesIves/github-pages-deploy-action@releases/v4 110 | with: 111 | repository-name: amaranth-lang/amaranth-lang.github.io 112 | ssh-key: ${{ secrets.PAGES_DEPLOY_KEY }} 113 | branch: main 114 | folder: docs/ 115 | target-folder: docs/amaranth-soc/latest/ 116 | - name: Publish release documentation 117 | if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v') 118 | uses: JamesIves/github-pages-deploy-action@releases/v4 119 | with: 120 | repository-name: amaranth-lang/amaranth-lang.github.io 121 | ssh-key: ${{ secrets.PAGES_DEPLOY_KEY }} 122 | branch: main 123 | folder: docs/ 124 | target-folder: docs/amaranth-soc/${{ github.ref_name }}/ 125 | 126 | publish-docs-dev: 127 | needs: document 128 | if: github.repository != 'amaranth-lang/amaranth-soc' 129 | runs-on: ubuntu-latest 130 | steps: 131 | - name: Check out source code 132 | uses: actions/checkout@v4 133 | with: 134 | fetch-depth: 0 135 | - name: Download documentation archive 136 | uses: actions/download-artifact@v4 137 | with: 138 | name: docs 139 | path: pages/docs/${{ github.ref_name }}/ 140 | - name: Disable Jekyll 141 | run: | 142 | touch pages/.nojekyll 143 | - name: Publish documentation for a branch 144 | uses: JamesIves/github-pages-deploy-action@releases/v4 145 | with: 146 | folder: pages/ 147 | clean: false 148 | -------------------------------------------------------------------------------- /amaranth_soc/periph.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | from collections.abc import Mapping 3 | 4 | from amaranth.utils import bits_for 5 | 6 | from .memory import MemoryMap 7 | from . import event 8 | 9 | 10 | __all__ = ["ConstantValue", "ConstantBool", "ConstantInt", "ConstantMap", "PeripheralInfo"] 11 | 12 | 13 | class ConstantValue: 14 | pass 15 | 16 | 17 | class ConstantBool(ConstantValue): 18 | """Boolean constant. 19 | 20 | Parameters 21 | ---------- 22 | value : bool 23 | Constant value. 24 | """ 25 | def __init__(self, value): 26 | if not isinstance(value, bool): 27 | raise TypeError(f"Value must be a bool, not {value!r}") 28 | self._value = value 29 | 30 | @property 31 | def value(self): 32 | return self._value 33 | 34 | def __repr__(self): 35 | return f"ConstantBool({self.value})" 36 | 37 | 38 | class ConstantInt(ConstantValue): 39 | """Integer constant. 40 | 41 | Parameters 42 | ---------- 43 | value : int 44 | Constant value. 45 | width : int 46 | Width in bits. Optional. ``bits_for(value)`` by default. 47 | signed : bool 48 | Signedness. Optional. ``value < 0`` by default. 49 | """ 50 | def __init__(self, value, *, width=None, signed=None): 51 | if not isinstance(value, int): 52 | raise TypeError(f"Value must be an integer, not {value!r}") 53 | self._value = value 54 | 55 | if width is None: 56 | width = bits_for(value) 57 | if not isinstance(width, int): 58 | raise TypeError(f"Width must be an integer, not {width!r}") 59 | if width < bits_for(value): 60 | raise ValueError(f"Width must be greater than or equal to the number of bits needed " 61 | f"to represent {value}") 62 | self._width = width 63 | 64 | if signed is None: 65 | signed = value < 0 66 | if not isinstance(signed, bool): 67 | raise TypeError(f"Signedness must be a bool, not {signed!r}") 68 | self._signed = signed 69 | 70 | @property 71 | def value(self): 72 | return self._value 73 | 74 | @property 75 | def width(self): 76 | return self._width 77 | 78 | @property 79 | def signed(self): 80 | return self._signed 81 | 82 | def __repr__(self): 83 | return f"ConstantInt({self.value}, width={self.width}, signed={self.signed})" 84 | 85 | 86 | class ConstantMap(Mapping): 87 | """Named constant map. 88 | 89 | A read-only container for named constants. Keys are iterated in insertion order. 90 | 91 | Parameters 92 | ---------- 93 | **constants : dict(str : :class:`ConstantValue`) 94 | Named constants. 95 | 96 | Examples 97 | -------- 98 | >>> ConstantMap(RX_FIFO_DEPTH=16) 99 | ConstantMap([('RX_FIFO_DEPTH', ConstantInt(16, width=5, signed=False))]) 100 | """ 101 | def __init__(self, **constants): 102 | self._storage = OrderedDict() 103 | for key, value in constants.items(): 104 | if isinstance(value, bool): 105 | value = ConstantBool(value) 106 | if isinstance(value, int): 107 | value = ConstantInt(value) 108 | if not isinstance(value, ConstantValue): 109 | raise TypeError(f"Constant value must be an instance of ConstantValue, not " 110 | f"{value!r}") 111 | self._storage[key] = value 112 | 113 | def __getitem__(self, key): 114 | return self._storage[key] 115 | 116 | def __iter__(self): 117 | yield from self._storage 118 | 119 | def __len__(self): 120 | return len(self._storage) 121 | 122 | def __repr__(self): 123 | return f"ConstantMap({list(self._storage.items())})" 124 | 125 | 126 | class PeripheralInfo: 127 | """Peripheral metadata. 128 | 129 | A unified description of the local resources of a peripheral. It may be queried in order to 130 | recover its memory windows, CSR registers, event sources and configuration constants. 131 | 132 | Parameters 133 | ---------- 134 | memory_map : :class:`MemoryMap` 135 | Memory map of the peripheral. 136 | irq : :class:`event.Source` 137 | IRQ line of the peripheral. Optional. 138 | constant_map : :class:`ConstantMap` 139 | Constant map of the peripheral. Optional. 140 | """ 141 | def __init__(self, *, memory_map, irq=None, constant_map=None): 142 | if not isinstance(memory_map, MemoryMap): 143 | raise TypeError(f"Memory map must be an instance of MemoryMap, not {memory_map!r}") 144 | memory_map.freeze() 145 | self._memory_map = memory_map 146 | 147 | if irq is not None and not isinstance(irq, event.Source): 148 | raise TypeError(f"IRQ line must be an instance of event.Source, not {irq!r}") 149 | self._irq = irq 150 | 151 | if constant_map is None: 152 | constant_map = ConstantMap() 153 | if not isinstance(constant_map, ConstantMap): 154 | raise TypeError(f"Constant map must be an instance of ConstantMap, not " 155 | f"{constant_map!r}") 156 | self._constant_map = constant_map 157 | 158 | @property 159 | def memory_map(self): 160 | """Memory map. 161 | 162 | Return value 163 | ------------ 164 | A :class:`MemoryMap` describing the local address space of the peripheral. 165 | """ 166 | return self._memory_map 167 | 168 | @property 169 | def irq(self): 170 | """IRQ line. 171 | 172 | Return value 173 | ------------ 174 | An :class:`event.Source` used by the peripheral to request interrupts. If provided, its 175 | event map describes local events. 176 | 177 | Exceptions 178 | ---------- 179 | Raises :exn:`NotImplementedError` if the peripheral info does not have an IRQ line. 180 | """ 181 | if self._irq is None: 182 | raise NotImplementedError("Peripheral info does not have an IRQ line") 183 | return self._irq 184 | 185 | @property 186 | def constant_map(self): 187 | """Constant map. 188 | 189 | Return value 190 | ------------ 191 | A :class:`ConstantMap` containing configuration constants of the peripheral. 192 | """ 193 | return self._constant_map 194 | -------------------------------------------------------------------------------- /tests/test_periph.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from amaranth_soc.periph import * 4 | from amaranth_soc.memory import MemoryMap 5 | from amaranth_soc import event 6 | 7 | 8 | class ConstantBoolTestCase(unittest.TestCase): 9 | def test_init(self): 10 | a = ConstantBool(True) 11 | b = ConstantBool(False) 12 | self.assertTrue(a.value) 13 | self.assertFalse(b.value) 14 | 15 | def test_value_wrong(self): 16 | with self.assertRaisesRegex(TypeError, r"Value must be a bool, not 'foo'"): 17 | ConstantBool("foo") 18 | 19 | def test_repr(self): 20 | self.assertEqual(repr(ConstantBool(True)), "ConstantBool(True)") 21 | 22 | 23 | class ConstantIntTestCase(unittest.TestCase): 24 | def test_init(self): 25 | c = ConstantInt(5, width=8, signed=True) 26 | self.assertEqual(c.value, 5) 27 | self.assertEqual(c.width, 8) 28 | self.assertEqual(c.signed, True) 29 | 30 | def test_init_default(self): 31 | c = ConstantInt(5) 32 | self.assertEqual(c.value, 5) 33 | self.assertEqual(c.width, 3) 34 | self.assertEqual(c.signed, False) 35 | 36 | def test_value_wrong(self): 37 | with self.assertRaisesRegex(TypeError, r"Value must be an integer, not 'foo'"): 38 | ConstantInt("foo") 39 | 40 | def test_width_wrong(self): 41 | with self.assertRaisesRegex(TypeError, r"Width must be an integer, not 'foo'"): 42 | ConstantInt(5, width="foo") 43 | 44 | def test_width_overflow(self): 45 | with self.assertRaisesRegex(ValueError, 46 | r"Width must be greater than or equal to the number of bits needed to represent 5"): 47 | ConstantInt(5, width=1) 48 | 49 | def test_signed_wrong(self): 50 | with self.assertRaisesRegex(TypeError, r"Signedness must be a bool, not 'foo'"): 51 | ConstantInt(5, signed="foo") 52 | 53 | def test_repr(self): 54 | self.assertEqual( 55 | repr(ConstantInt(-5, width=8, signed=True)), 56 | "ConstantInt(-5, width=8, signed=True)" 57 | ) 58 | 59 | 60 | class ConstantMapTestCase(unittest.TestCase): 61 | def test_init(self): 62 | constant_map = ConstantMap(A=5, B=True, C=ConstantBool(False)) 63 | self.assertEqual( 64 | repr(constant_map), "ConstantMap([" 65 | "('A', ConstantInt(5, width=3, signed=False)), " 66 | "('B', ConstantBool(True)), " 67 | "('C', ConstantBool(False))])", 68 | ) 69 | 70 | def test_init_wrong_value(self): 71 | with self.assertRaisesRegex(TypeError, 72 | r"Constant value must be an instance of ConstantValue, not \('foo', 'bar'\)"): 73 | ConstantMap(A=("foo", "bar")) 74 | 75 | def test_getitem(self): 76 | a = ConstantInt(1) 77 | b = ConstantBool(False) 78 | constant_map = ConstantMap(A=a, B=b) 79 | self.assertIs(constant_map["A"], a) 80 | self.assertIs(constant_map["B"], b) 81 | 82 | def test_iter(self): 83 | a = ConstantInt(1) 84 | b = ConstantBool(False) 85 | constant_map = ConstantMap(B=b, A=a) 86 | self.assertEqual(list(constant_map.items()), [ 87 | ("B", b), 88 | ("A", a), 89 | ]) 90 | 91 | def test_len(self): 92 | a = ConstantInt(1) 93 | b = ConstantBool(False) 94 | constant_map = ConstantMap(B=b, A=a) 95 | self.assertEqual(len(constant_map), 2) 96 | 97 | 98 | class PeripheralInfoTestCase(unittest.TestCase): 99 | def test_memory_map(self): 100 | memory_map = MemoryMap(addr_width=1, data_width=8) 101 | info = PeripheralInfo(memory_map=memory_map) 102 | self.assertIs(info.memory_map, memory_map) 103 | 104 | def test_memory_map_frozen(self): 105 | memory_map = MemoryMap(addr_width=1, data_width=8) 106 | info = PeripheralInfo(memory_map=memory_map) 107 | with self.assertRaisesRegex(ValueError, 108 | r"Memory map has been frozen. Cannot add resource 'a'"): 109 | memory_map.add_resource("a", name="foo", size=3) 110 | 111 | def test_memory_map_wrong(self): 112 | with self.assertRaisesRegex(TypeError, 113 | r"Memory map must be an instance of MemoryMap, not 'foo'"): 114 | info = PeripheralInfo(memory_map="foo") 115 | 116 | def test_irq(self): 117 | memory_map = MemoryMap(addr_width=1, data_width=8) 118 | irq = event.Source.Signature().create(path=("irq",)) 119 | info = PeripheralInfo(memory_map=memory_map, irq=irq) 120 | self.assertIs(info.irq, irq) 121 | 122 | def test_irq_none(self): 123 | memory_map = MemoryMap(addr_width=1, data_width=8) 124 | info = PeripheralInfo(memory_map=memory_map, irq=None) 125 | with self.assertRaisesRegex(NotImplementedError, 126 | r"Peripheral info does not have an IRQ line"): 127 | info.irq 128 | 129 | def test_irq_default(self): 130 | memory_map = MemoryMap(addr_width=1, data_width=8) 131 | info = PeripheralInfo(memory_map=memory_map) 132 | with self.assertRaisesRegex(NotImplementedError, 133 | r"Peripheral info does not have an IRQ line"): 134 | info.irq 135 | 136 | def test_irq_wrong(self): 137 | memory_map = MemoryMap(addr_width=1, data_width=8) 138 | with self.assertRaisesRegex(TypeError, 139 | r"IRQ line must be an instance of event.Source, not 'foo'"): 140 | info = PeripheralInfo(memory_map=memory_map, irq="foo") 141 | 142 | def test_constant_map(self): 143 | constant_map = ConstantMap() 144 | memory_map = MemoryMap(addr_width=1, data_width=8) 145 | info = PeripheralInfo(memory_map=memory_map, constant_map=constant_map) 146 | self.assertIs(info.constant_map, constant_map) 147 | 148 | def test_constant_map_none(self): 149 | memory_map = MemoryMap(addr_width=1, data_width=8) 150 | info = PeripheralInfo(memory_map=memory_map, constant_map=None) 151 | self.assertIsInstance(info.constant_map, ConstantMap) 152 | self.assertEqual(info.constant_map, {}) 153 | 154 | def test_constant_map_default(self): 155 | memory_map = MemoryMap(addr_width=1, data_width=8) 156 | info = PeripheralInfo(memory_map=memory_map) 157 | self.assertIsInstance(info.constant_map, ConstantMap) 158 | self.assertEqual(info.constant_map, {}) 159 | 160 | def test_constant_map_wrong(self): 161 | memory_map = MemoryMap(addr_width=1, data_width=8) 162 | with self.assertRaisesRegex(TypeError, 163 | r"Constant map must be an instance of ConstantMap, not 'foo'"): 164 | info = PeripheralInfo(memory_map=memory_map, constant_map="foo") 165 | -------------------------------------------------------------------------------- /tests/test_wishbone_sram.py: -------------------------------------------------------------------------------- 1 | # amaranth: UnusedElaboratable=no 2 | 3 | import unittest 4 | from amaranth import * 5 | from amaranth.sim import * 6 | 7 | from amaranth_soc import wishbone 8 | from amaranth_soc.wishbone.sram import WishboneSRAM 9 | 10 | 11 | class WishboneSRAMTestCase(unittest.TestCase): 12 | def test_init(self): 13 | # default granularity, writable, no initial values 14 | dut_1 = WishboneSRAM(size=1024, data_width=32) 15 | self.assertEqual(dut_1.size, 1024) 16 | self.assertEqual(dut_1.writable, True) 17 | self.assertEqual(list(dut_1.init), [0 for _ in range(1024)]) 18 | self.assertEqual(dut_1.wb_bus.addr_width, 10) 19 | self.assertEqual(dut_1.wb_bus.data_width, 32) 20 | self.assertEqual(dut_1.wb_bus.granularity, 32) 21 | self.assertEqual(dut_1.wb_bus.features, frozenset()) 22 | self.assertEqual(dut_1.wb_bus.memory_map.addr_width, 10) 23 | self.assertEqual(dut_1.wb_bus.memory_map.data_width, 32) 24 | self.assertEqual(dut_1.wb_bus.memory_map.alignment, 0) 25 | self.assertEqual(list(dut_1.wb_bus.memory_map.resources()), 26 | [(dut_1._mem, ("mem",), (0, 1024))]) 27 | # custom granularity, read-only, with initial values 28 | dut_2 = WishboneSRAM(size=4, data_width=16, granularity=8, writable=False, 29 | init=(0xbbaa, 0xddcc)) 30 | self.assertEqual(dut_2.size, 4) 31 | self.assertEqual(dut_2.writable, False) 32 | self.assertEqual(list(dut_2.init), [0xbbaa, 0xddcc]) 33 | self.assertEqual(dut_2.wb_bus.addr_width, 1) 34 | self.assertEqual(dut_2.wb_bus.data_width, 16) 35 | self.assertEqual(dut_2.wb_bus.granularity, 8) 36 | self.assertEqual(dut_2.wb_bus.features, frozenset()) 37 | self.assertEqual(dut_2.wb_bus.memory_map.addr_width, 2) 38 | self.assertEqual(dut_2.wb_bus.memory_map.data_width, 8) 39 | self.assertEqual(dut_2.wb_bus.memory_map.alignment, 0) 40 | self.assertEqual(list(dut_2.wb_bus.memory_map.resources()), 41 | [(dut_2._mem, ("mem",), (0, 4))]) 42 | 43 | def test_memory_data_init_set(self): 44 | dut = WishboneSRAM(size=4, data_width=16, granularity=8) 45 | self.assertEqual(list(dut.init), [0x0000, 0x0000]) 46 | dut.init = [0xbbaa, 0xddcc] 47 | self.assertEqual(list(dut._mem_data.init), [0xbbaa, 0xddcc]) 48 | 49 | def test_init_wrong_size(self): 50 | with self.assertRaisesRegex(TypeError, r"Size must be an integer power of two, not 1.0"): 51 | WishboneSRAM(size=1.0, data_width=32) 52 | with self.assertRaisesRegex(TypeError, r"Size must be an integer power of two, not 3"): 53 | WishboneSRAM(size=3, data_width=32) 54 | 55 | def test_init_wrong_data_width(self): 56 | with self.assertRaisesRegex(TypeError, r"Data width must be 8, 16, 32 or 64, not 'foo'"): 57 | WishboneSRAM(size=1024, data_width="foo") 58 | with self.assertRaisesRegex(TypeError, r"Data width must be 8, 16, 32 or 64, not 128"): 59 | WishboneSRAM(size=1024, data_width=128) 60 | 61 | def test_init_wrong_granularity(self): 62 | with self.assertRaisesRegex(TypeError, r"Granularity must be 8, 16, 32 or 64, not 'foo'"): 63 | WishboneSRAM(size=1024, data_width=32, granularity="foo") 64 | with self.assertRaisesRegex(TypeError, r"Granularity must be 8, 16, 32 or 64, not 128"): 65 | WishboneSRAM(size=1024, data_width=32, granularity=128) 66 | 67 | def test_init_size_smaller_than_data_width(self): 68 | with self.assertRaisesRegex(ValueError, 69 | r"The product of size 2 and granularity 8 must be greater than or equal to data " 70 | r"width 32, not 16"): 71 | WishboneSRAM(size=2, data_width=32, granularity=8) 72 | 73 | def test_sim_writable(self): 74 | dut = WishboneSRAM(size=128, data_width=32, granularity=8, writable=True, init=range(32)) 75 | 76 | async def testbench(ctx): 77 | self.assertEqual(ctx.get(dut.wb_bus.ack), 0) 78 | for i in range(32): 79 | self.assertEqual(ctx.get(dut._mem_data[i]), i) 80 | 81 | await ctx.tick() 82 | 83 | # - left shift all values by 24 bits: 84 | 85 | for i in range(32): 86 | ctx.set(dut.wb_bus.cyc, 1) 87 | ctx.set(dut.wb_bus.stb, 1) 88 | ctx.set(dut.wb_bus.adr, i) 89 | ctx.set(dut.wb_bus.sel, 0b1001) 90 | ctx.set(dut.wb_bus.we, 1) 91 | ctx.set(dut.wb_bus.dat_w, (i << 24) | 0x00ffff00) 92 | await ctx.tick() 93 | self.assertEqual(ctx.get(dut.wb_bus.ack), 1) 94 | await ctx.tick() 95 | self.assertEqual(ctx.get(dut.wb_bus.ack), 0) 96 | ctx.set(dut.wb_bus.cyc, 0) 97 | ctx.set(dut.wb_bus.stb, 0) 98 | await ctx.tick() 99 | 100 | for i in range(32): 101 | self.assertEqual(ctx.get(dut._mem_data[i]), i << 24) 102 | 103 | await ctx.tick() 104 | 105 | # - right shift all values by 24 bits: 106 | 107 | ctx.set(dut.wb_bus.cyc, 1) 108 | ctx.set(dut.wb_bus.stb, 1) 109 | 110 | for i in range(32): 111 | ctx.set(dut.wb_bus.adr, i) 112 | ctx.set(dut.wb_bus.sel, 0b1001) 113 | ctx.set(dut.wb_bus.we, 1) 114 | ctx.set(dut.wb_bus.dat_w, i | 0x00ffff00) 115 | await ctx.tick() 116 | self.assertEqual(ctx.get(dut.wb_bus.ack), 1) 117 | await ctx.tick() 118 | self.assertEqual(ctx.get(dut.wb_bus.ack), 0) 119 | 120 | ctx.set(dut.wb_bus.cyc, 0) 121 | ctx.set(dut.wb_bus.stb, 0) 122 | 123 | for i in range(32): 124 | self.assertEqual(ctx.get(dut._mem_data[i]), i) 125 | 126 | sim = Simulator(dut) 127 | sim.add_clock(1e-6) 128 | sim.add_testbench(testbench) 129 | with sim.write_vcd(vcd_file="test.vcd"): 130 | sim.run() 131 | 132 | def test_sim_readonly(self): 133 | dut = WishboneSRAM(size=128, data_width=32, granularity=8, writable=False, init=range(32)) 134 | 135 | async def testbench(ctx): 136 | for i in range(32): 137 | self.assertEqual(ctx.get(dut._mem_data[i]), i) 138 | 139 | for i in range(32): 140 | ctx.set(dut.wb_bus.cyc, 1) 141 | ctx.set(dut.wb_bus.stb, 1) 142 | ctx.set(dut.wb_bus.adr, i) 143 | ctx.set(dut.wb_bus.sel, 0xf) 144 | ctx.set(dut.wb_bus.we, 1) 145 | ctx.set(dut.wb_bus.dat_w, 0xffffffff) 146 | await ctx.tick().until(dut.wb_bus.ack) 147 | ctx.set(dut.wb_bus.cyc, 0) 148 | ctx.set(dut.wb_bus.stb, 0) 149 | await ctx.tick() 150 | 151 | for i in range(32): 152 | self.assertEqual(ctx.get(dut._mem_data[i]), i) 153 | 154 | sim = Simulator(dut) 155 | sim.add_clock(1e-6) 156 | sim.add_testbench(testbench) 157 | with sim.write_vcd(vcd_file="test.vcd"): 158 | sim.run() 159 | -------------------------------------------------------------------------------- /tests/test_csr_action.py: -------------------------------------------------------------------------------- 1 | # amaranth: UnusedElaboratable=no 2 | 3 | import unittest 4 | from amaranth import * 5 | from amaranth.sim import * 6 | 7 | from amaranth_soc.csr import action 8 | 9 | 10 | class RTestCase(unittest.TestCase): 11 | def test_simple(self): 12 | f = action.R(unsigned(4)) 13 | self.assertEqual(f.r_data.shape(), unsigned(4)) 14 | self.assertTrue(f.port.access.readable()) 15 | self.assertFalse(f.port.access.writable()) 16 | 17 | def test_sim(self): 18 | dut = action.R(unsigned(4)) 19 | 20 | async def testbench(ctx): 21 | ctx.set(dut.r_data, 0xa) 22 | ctx.set(dut.port.r_stb, 1) 23 | self.assertEqual(ctx.get(dut.port.r_data), 0xa) 24 | self.assertEqual(ctx.get(dut.r_stb), 1) 25 | 26 | sim = Simulator(dut) 27 | sim.add_testbench(testbench) 28 | with sim.write_vcd(vcd_file="test.vcd"): 29 | sim.run() 30 | 31 | 32 | class WTestCase(unittest.TestCase): 33 | def test_simple(self): 34 | f = action.W(unsigned(4)) 35 | self.assertEqual(f.w_data.shape(), unsigned(4)) 36 | self.assertFalse(f.port.access.readable()) 37 | self.assertTrue(f.port.access.writable()) 38 | 39 | def test_sim(self): 40 | dut = action.W(unsigned(4)) 41 | 42 | async def testbench(ctx): 43 | ctx.set(dut.port.w_data, 0xa) 44 | ctx.set(dut.port.w_stb, 1) 45 | self.assertEqual(ctx.get(dut.w_data), 0xa) 46 | self.assertEqual(ctx.get(dut.w_stb), 1) 47 | 48 | sim = Simulator(dut) 49 | sim.add_testbench(testbench) 50 | with sim.write_vcd(vcd_file="test.vcd"): 51 | sim.run() 52 | 53 | 54 | class RWTestCase(unittest.TestCase): 55 | def test_simple(self): 56 | f4 = action.RW(unsigned(4), init=0x5) 57 | self.assertEqual(f4.data.shape(), unsigned(4)) 58 | self.assertEqual(f4.init, 0x5) 59 | self.assertTrue(f4.port.access.readable()) 60 | self.assertTrue(f4.port.access.writable()) 61 | 62 | f8 = action.RW(signed(8)) 63 | self.assertEqual(f8.data.shape(), signed(8)) 64 | self.assertEqual(f8.init, 0) 65 | self.assertTrue(f8.port.access.readable()) 66 | self.assertTrue(f8.port.access.writable()) 67 | 68 | def test_sim(self): 69 | dut = action.RW(unsigned(4), init=0x5) 70 | 71 | async def testbench(ctx): 72 | self.assertEqual(ctx.get(dut.port.r_data), 0x5) 73 | self.assertEqual(ctx.get(dut.data), 0x5) 74 | ctx.set(dut.port.w_stb, 1) 75 | ctx.set(dut.port.w_data, 0xa) 76 | await ctx.tick() 77 | self.assertEqual(ctx.get(dut.port.r_data), 0xa) 78 | self.assertEqual(ctx.get(dut.data), 0xa) 79 | 80 | sim = Simulator(dut) 81 | sim.add_clock(1e-6) 82 | sim.add_testbench(testbench) 83 | with sim.write_vcd(vcd_file="test.vcd"): 84 | sim.run() 85 | 86 | 87 | class RW1CTestCase(unittest.TestCase): 88 | def test_simple(self): 89 | f4 = action.RW1C(unsigned(4), init=0x5) 90 | self.assertEqual(f4.data.shape(), unsigned(4)) 91 | self.assertEqual(f4.set .shape(), unsigned(4)) 92 | self.assertEqual(f4.init, 0x5) 93 | self.assertTrue(f4.port.access.readable()) 94 | self.assertTrue(f4.port.access.writable()) 95 | 96 | f8 = action.RW1C(signed(8)) 97 | self.assertEqual(f8.data.shape(), signed(8)) 98 | self.assertEqual(f8.set .shape(), signed(8)) 99 | self.assertEqual(f8.init, 0) 100 | self.assertTrue(f8.port.access.readable()) 101 | self.assertTrue(f8.port.access.writable()) 102 | 103 | def test_sim(self): 104 | dut = action.RW1C(unsigned(4), init=0xf) 105 | 106 | async def testbench(ctx): 107 | self.assertEqual(ctx.get(dut.port.r_data), 0xf) 108 | self.assertEqual(ctx.get(dut.data), 0xf) 109 | ctx.set(dut.port.w_stb, 1) 110 | ctx.set(dut.port.w_data, 0x5) 111 | await ctx.tick() 112 | self.assertEqual(ctx.get(dut.port.r_data), 0xa) 113 | self.assertEqual(ctx.get(dut.data), 0xa) 114 | 115 | ctx.set(dut.port.w_data, 0x3) 116 | ctx.set(dut.set, 0x4) 117 | await ctx.tick() 118 | self.assertEqual(ctx.get(dut.port.r_data), 0xc) 119 | self.assertEqual(ctx.get(dut.data), 0xc) 120 | 121 | sim = Simulator(dut) 122 | sim.add_clock(1e-6) 123 | sim.add_testbench(testbench) 124 | with sim.write_vcd(vcd_file="test.vcd"): 125 | sim.run() 126 | 127 | 128 | class RW1STestCase(unittest.TestCase): 129 | def test_simple(self): 130 | f4 = action.RW1S(unsigned(4), init=0x5) 131 | self.assertEqual(f4.data .shape(), unsigned(4)) 132 | self.assertEqual(f4.clear.shape(), unsigned(4)) 133 | self.assertEqual(f4.init, 0x5) 134 | self.assertTrue(f4.port.access.readable()) 135 | self.assertTrue(f4.port.access.writable()) 136 | 137 | f8 = action.RW1S(signed(8)) 138 | self.assertEqual(f8.data .shape(), signed(8)) 139 | self.assertEqual(f8.clear.shape(), signed(8)) 140 | self.assertEqual(f8.init, 0) 141 | self.assertTrue(f8.port.access.readable()) 142 | self.assertTrue(f8.port.access.writable()) 143 | 144 | def test_sim(self): 145 | dut = action.RW1S(unsigned(4), init=0x5) 146 | 147 | async def testbench(ctx): 148 | self.assertEqual(ctx.get(dut.port.r_data), 0x5) 149 | self.assertEqual(ctx.get(dut.data), 0x5) 150 | ctx.set(dut.port.w_stb, 1) 151 | ctx.set(dut.port.w_data, 0xa) 152 | await ctx.tick() 153 | self.assertEqual(ctx.get(dut.port.r_data), 0xf) 154 | self.assertEqual(ctx.get(dut.data), 0xf) 155 | 156 | ctx.set(dut.port.w_data, 0x3) 157 | ctx.set(dut.clear, 0x7) 158 | await ctx.tick() 159 | self.assertEqual(ctx.get(dut.port.r_data), 0xb) 160 | self.assertEqual(ctx.get(dut.data), 0xb) 161 | 162 | sim = Simulator(dut) 163 | sim.add_clock(1e-6) 164 | sim.add_testbench(testbench) 165 | with sim.write_vcd(vcd_file="test.vcd"): 166 | sim.run() 167 | 168 | 169 | class ResRAW0TestCase(unittest.TestCase): 170 | def test_simple(self): 171 | f = action.ResRAW0(unsigned(4)) 172 | self.assertEqual(f.port.shape, unsigned(4)) 173 | self.assertFalse(f.port.access.readable()) 174 | self.assertFalse(f.port.access.writable()) 175 | self.assertIsInstance(f.elaborate(platform=None), Module) 176 | 177 | class ResRAWLTestCase(unittest.TestCase): 178 | def test_simple(self): 179 | f = action.ResRAWL(unsigned(4)) 180 | self.assertEqual(f.port.shape, unsigned(4)) 181 | self.assertFalse(f.port.access.readable()) 182 | self.assertFalse(f.port.access.writable()) 183 | self.assertIsInstance(f.elaborate(platform=None), Module) 184 | 185 | class ResR0WATestCase(unittest.TestCase): 186 | def test_simple(self): 187 | f = action.ResR0WA(unsigned(4)) 188 | self.assertEqual(f.port.shape, unsigned(4)) 189 | self.assertFalse(f.port.access.readable()) 190 | self.assertFalse(f.port.access.writable()) 191 | self.assertIsInstance(f.elaborate(platform=None), Module) 192 | 193 | class ResR0W0TestCase(unittest.TestCase): 194 | def test_simple(self): 195 | f = action.ResR0W0(unsigned(4)) 196 | self.assertEqual(f.port.shape, unsigned(4)) 197 | self.assertFalse(f.port.access.readable()) 198 | self.assertFalse(f.port.access.writable()) 199 | self.assertIsInstance(f.elaborate(platform=None), Module) 200 | -------------------------------------------------------------------------------- /amaranth_soc/csr/action.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.lib.wiring import In, Out 3 | 4 | from .reg import FieldAction 5 | 6 | 7 | __all__ = ["R", "W", "RW", "RW1C", "RW1S", "ResRAW0", "ResRAWL", "ResR0WA", "ResR0W0"] 8 | 9 | 10 | class R(FieldAction): 11 | """A read-only field action. 12 | 13 | Parameters 14 | ---------- 15 | shape : :ref:`shape-like object ` 16 | Shape of the field. 17 | 18 | Interface attributes 19 | -------------------- 20 | port : :class:`FieldPort` 21 | Field port. 22 | r_data : Signal(shape) 23 | Read data. Drives ``port.r_data``. See :class:`FieldPort`. 24 | r_stb : Signal() 25 | Read strobe. Driven by ``port.r_stb``. See :class:`FieldPort`. 26 | """ 27 | def __init__(self, shape): 28 | super().__init__(shape, access="r", members={ 29 | "r_data": In(shape), 30 | "r_stb": Out(1) 31 | }) 32 | 33 | def elaborate(self, platform): 34 | m = Module() 35 | m.d.comb += [ 36 | self.port.r_data.eq(self.r_data), 37 | self.r_stb.eq(self.port.r_stb), 38 | ] 39 | return m 40 | 41 | 42 | class W(FieldAction): 43 | """A write-only field action. 44 | 45 | Parameters 46 | ---------- 47 | shape : :ref:`shape-like object ` 48 | Shape of the field. 49 | 50 | Interface attributes 51 | -------------------- 52 | port : :class:`FieldPort` 53 | Field port. 54 | w_data : Signal(shape) 55 | Write data. Driven by ``port.w_data``. See :class:`FieldPort`. 56 | w_stb : Signal() 57 | Write strobe. Driven by ``port.w_stb``. See :class:`FieldPort`. 58 | """ 59 | def __init__(self, shape): 60 | super().__init__(shape, access="w", members={ 61 | "w_data": Out(shape), 62 | "w_stb": Out(1), 63 | }) 64 | 65 | def elaborate(self, platform): 66 | m = Module() 67 | m.d.comb += [ 68 | self.w_data.eq(self.port.w_data), 69 | self.w_stb.eq(self.port.w_stb), 70 | ] 71 | return m 72 | 73 | 74 | class RW(FieldAction): 75 | """A read/write field action, with built-in storage. 76 | 77 | Storage is updated with the value of ``port.w_data`` one clock cycle after ``port.w_stb`` is 78 | asserted. 79 | 80 | Parameters 81 | ---------- 82 | shape : :ref:`shape-like object ` 83 | Shape of the field. 84 | init : :class:`int` 85 | Storage initial value. 86 | 87 | Interface attributes 88 | -------------------- 89 | port : :class:`FieldPort` 90 | Field port. 91 | data : Signal(shape) 92 | Storage output. 93 | """ 94 | def __init__(self, shape, *, init=0): 95 | super().__init__(shape, access="rw", members={ 96 | "data": Out(shape), 97 | }) 98 | self._storage = Signal(shape, init=init) 99 | self._init = init 100 | 101 | @property 102 | def init(self): 103 | return self._init 104 | 105 | def elaborate(self, platform): 106 | m = Module() 107 | 108 | with m.If(self.port.w_stb): 109 | m.d.sync += self._storage.eq(self.port.w_data) 110 | 111 | m.d.comb += [ 112 | self.port.r_data.eq(self._storage), 113 | self.data.eq(self._storage), 114 | ] 115 | 116 | return m 117 | 118 | 119 | class RW1C(FieldAction): 120 | """A read/write-one-to-clear field action, with built-in storage. 121 | 122 | Storage bits are: 123 | * cleared by high bits in ``port.w_data``, one clock cycle after ``port.w_stb`` is asserted; 124 | * set by high bits in ``set``, one clock cycle after they are asserted. 125 | 126 | If a storage bit is set and cleared on the same clock cycle, setting it has precedence. 127 | 128 | Parameters 129 | ---------- 130 | shape : :ref:`shape-like object ` 131 | Shape of the field. 132 | init : :class:`int` 133 | Storage initial value. 134 | 135 | Interface attributes 136 | -------------------- 137 | port : :class:`FieldPort` 138 | Field port. 139 | data : Signal(shape) 140 | Storage output. 141 | set : Signal(shape) 142 | Mask to set storage bits. 143 | """ 144 | def __init__(self, shape, *, init=0): 145 | super().__init__(shape, access="rw", members={ 146 | "data": Out(shape), 147 | "set": In(shape), 148 | }) 149 | self._storage = Signal(shape, init=init) 150 | self._init = init 151 | 152 | @property 153 | def init(self): 154 | return self._init 155 | 156 | def elaborate(self, platform): 157 | m = Module() 158 | 159 | for i, storage_bit in enumerate(self._storage): 160 | with m.If(self.port.w_stb & self.port.w_data[i]): 161 | m.d.sync += storage_bit.eq(0) 162 | with m.If(self.set[i]): 163 | m.d.sync += storage_bit.eq(1) 164 | 165 | m.d.comb += [ 166 | self.port.r_data.eq(self._storage), 167 | self.data.eq(self._storage), 168 | ] 169 | 170 | return m 171 | 172 | 173 | class RW1S(FieldAction): 174 | """A read/write-one-to-set field action, with built-in storage. 175 | 176 | Storage bits are: 177 | * set by high bits in ``port.w_data``, one clock cycle after ``port.w_stb`` is asserted; 178 | * cleared by high bits in ``clear``, one clock cycle after they are asserted. 179 | 180 | If a storage bit is set and cleared on the same clock cycle, setting it has precedence. 181 | 182 | Parameters 183 | ---------- 184 | shape : :ref:`shape-like object ` 185 | Shape of the field. 186 | init : :class:`int` 187 | Storage initial value. 188 | 189 | Interface attributes 190 | -------------------- 191 | port : :class:`FieldPort` 192 | Field port. 193 | data : Signal(shape) 194 | Storage output. 195 | clear : Signal(shape) 196 | Mask to clear storage bits. 197 | """ 198 | def __init__(self, shape, *, init=0): 199 | super().__init__(shape, access="rw", members={ 200 | "clear": In(shape), 201 | "data": Out(shape), 202 | }) 203 | self._storage = Signal(shape, init=init) 204 | self._init = init 205 | 206 | @property 207 | def init(self): 208 | return self._init 209 | 210 | def elaborate(self, platform): 211 | m = Module() 212 | 213 | for i, storage_bit in enumerate(self._storage): 214 | with m.If(self.clear[i]): 215 | m.d.sync += storage_bit.eq(0) 216 | with m.If(self.port.w_stb & self.port.w_data[i]): 217 | m.d.sync += storage_bit.eq(1) 218 | 219 | m.d.comb += [ 220 | self.port.r_data.eq(self._storage), 221 | self.data.eq(self._storage), 222 | ] 223 | 224 | return m 225 | 226 | 227 | class _Reserved(FieldAction): 228 | _doc_template = """ 229 | {description} 230 | 231 | Parameters 232 | ---------- 233 | shape : :ref:`shape-like object ` 234 | Shape of the field. 235 | 236 | Interface attributes 237 | -------------------- 238 | port : :class:`FieldPort` 239 | Field port. 240 | """ 241 | def __init__(self, shape): 242 | super().__init__(shape, access="nc") 243 | 244 | def elaborate(self, platform): 245 | return Module() 246 | 247 | 248 | class ResRAW0(_Reserved): 249 | __doc__ = _Reserved._doc_template.format(description=""" 250 | A reserved read-any/write-zero field action. 251 | """) 252 | 253 | 254 | class ResRAWL(_Reserved): 255 | __doc__ = _Reserved._doc_template.format(description=""" 256 | A reserved read-any/write-last field action. 257 | """) 258 | 259 | 260 | class ResR0WA(_Reserved): 261 | __doc__ = _Reserved._doc_template.format(description=""" 262 | A reserved read-zero/write-any field action. 263 | """) 264 | 265 | 266 | class ResR0W0(_Reserved): 267 | __doc__ = _Reserved._doc_template.format(description=""" 268 | A reserved read-zero/write-zero field action. 269 | """) 270 | -------------------------------------------------------------------------------- /amaranth_soc/event.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.lib import enum, wiring 3 | from amaranth.lib.wiring import In, Out 4 | 5 | 6 | __all__ = ["Source", "EventMap", "Monitor"] 7 | 8 | 9 | class Source(wiring.PureInterface): 10 | class Trigger(enum.Enum): 11 | """Event trigger mode.""" 12 | LEVEL = "level" 13 | RISE = "rise" 14 | FALL = "fall" 15 | 16 | class Signature(wiring.Signature): 17 | """Event source signature. 18 | 19 | Parameters 20 | ---------- 21 | trigger : :class:`Source.Trigger` 22 | Trigger mode. An event can be edge- or level-triggered by the input line. 23 | 24 | Interface attributes 25 | -------------------- 26 | i : Signal() 27 | Input line. Sampled in order to detect an event. 28 | trg : Signal() 29 | Event trigger. Asserted when an event occurs, according to the trigger mode. 30 | """ 31 | def __init__(self, *, trigger="level"): 32 | super().__init__({ 33 | "i": Out(1), 34 | "trg": In(1), 35 | }) 36 | self._trigger = Source.Trigger(trigger) 37 | 38 | @property 39 | def trigger(self): 40 | return self._trigger 41 | 42 | def create(self, *, path=None, src_loc_at=0): 43 | """Create a compatible interface. 44 | 45 | See :meth:`wiring.Signature.create` for details. 46 | 47 | Returns 48 | ------- 49 | A :class:`Source` object using this signature. 50 | """ 51 | return Source(trigger=self.trigger, path=path, src_loc_at=1 + src_loc_at) 52 | 53 | def __eq__(self, other): 54 | """Compare signatures. 55 | 56 | Two signatures are equal if they have the same trigger mode. 57 | """ 58 | return isinstance(other, Source.Signature) and self.trigger == other.trigger 59 | 60 | def __repr__(self): 61 | return f"event.Source.Signature({self.members!r})" 62 | 63 | """Event source interface. 64 | 65 | Parameters 66 | ---------- 67 | trigger : :class:`Source.Trigger` 68 | Trigger mode. An event can be edge- or level-triggered by the input line. 69 | path : iter(:class:`str`) 70 | Path to this event source interface. Optional. See :class:`wiring.PureInterface`. 71 | 72 | Attributes 73 | ---------- 74 | event_map : :class:`EventMap` 75 | A collection of event sources. 76 | """ 77 | def __init__(self, *, trigger="level", path=None, src_loc_at=0): 78 | super().__init__(Source.Signature(trigger=trigger), path=path, src_loc_at=1 + src_loc_at) 79 | self._event_map = None 80 | 81 | @property 82 | def trigger(self): 83 | return self.signature.trigger 84 | 85 | @property 86 | def event_map(self): 87 | if self._event_map is None: 88 | raise AttributeError(f"{self!r} does not have an event map") 89 | return self._event_map 90 | 91 | @event_map.setter 92 | def event_map(self, event_map): 93 | if not isinstance(event_map, EventMap): 94 | raise TypeError(f"Event map must be an instance of EventMap, not {event_map!r}") 95 | event_map.freeze() 96 | self._event_map = event_map 97 | 98 | def __repr__(self): 99 | return f"event.Source({self.signature!r})" 100 | 101 | 102 | class EventMap: 103 | """Event map. 104 | 105 | An event map is a description of a set of events. It is built by adding event sources 106 | and can be queried later to determine their index. Event indexing is done implicitly by 107 | increment, starting at 0. 108 | """ 109 | def __init__(self): 110 | self._sources = dict() 111 | self._frozen = False 112 | 113 | @property 114 | def size(self): 115 | """Size of the event map. 116 | 117 | Return value 118 | ------------ 119 | The number of event sources in the map. 120 | """ 121 | return len(self._sources) 122 | 123 | def freeze(self): 124 | """Freeze the event map. 125 | 126 | Once the event map is frozen, sources cannot be added anymore. 127 | """ 128 | self._frozen = True 129 | 130 | def add(self, src): 131 | """Add an event source. 132 | 133 | Arguments 134 | --------- 135 | src : :class:`Source` 136 | Event source. 137 | 138 | Exceptions 139 | ---------- 140 | Raises :exn:`ValueError` if the event map is frozen. 141 | """ 142 | if self._frozen: 143 | raise ValueError("Event map has been frozen. Cannot add source") 144 | if not isinstance(src, Source): 145 | raise TypeError(f"Event source must be an instance of event.Source, not {src!r}") 146 | if id(src) not in self._sources: 147 | self._sources[id(src)] = src, self.size 148 | 149 | def index(self, src): 150 | """Get the index corresponding to an event source. 151 | 152 | Arguments 153 | --------- 154 | src : :class:`Source` 155 | Event source. 156 | 157 | Return value 158 | ------------ 159 | The index of the source. 160 | 161 | Exceptions 162 | ---------- 163 | Raises :exn:`KeyError` if the source is not found. 164 | """ 165 | if not isinstance(src, Source): 166 | raise TypeError(f"Event source must be an instance of event.Source, not {src!r}") 167 | _, index = self._sources[id(src)] 168 | return index 169 | 170 | def sources(self): 171 | """Iterate event sources. 172 | 173 | Yield values 174 | ------------ 175 | A tuple ``src, index`` corresponding to an event source and its index. 176 | """ 177 | yield from self._sources.values() 178 | 179 | 180 | class Monitor(wiring.Component): 181 | """Event monitor. 182 | 183 | A monitor for subordinate event sources. 184 | 185 | Parameters 186 | ---------- 187 | event_map : :class:`EventMap` 188 | A collection of event sources. 189 | trigger : :class:`Source.Trigger` 190 | Trigger mode. See :class:`Source`. 191 | 192 | Attributes 193 | ---------- 194 | src : :class:`Source` 195 | Event source. Its input is asserted when a subordinate event is enabled and pending. 196 | enable : Signal(event_map.size), bit mask, in 197 | Enabled events. 198 | pending : Signal(event_map.size), bit mask, out 199 | Pending events. 200 | clear : Signal(event_map.size), bit mask, in 201 | Clear selected pending events. 202 | """ 203 | def __init__(self, event_map, *, trigger="level"): 204 | if not isinstance(event_map, EventMap): 205 | raise TypeError(f"Event map must be an instance of EventMap, not {event_map!r}") 206 | super().__init__({ 207 | "src": Out(Source.Signature(trigger=trigger)), 208 | "enable": In(event_map.size), 209 | "pending": In(event_map.size), 210 | "clear": In(event_map.size), 211 | }) 212 | self.src.event_map = event_map 213 | 214 | def elaborate(self, platform): 215 | m = Module() 216 | 217 | for sub, index in self.src.event_map.sources(): 218 | if sub.trigger != Source.Trigger.LEVEL: 219 | sub_i_r = Signal.like(sub.i, name_suffix="_r") 220 | m.d.sync += sub_i_r.eq(sub.i) 221 | 222 | if sub.trigger == Source.Trigger.LEVEL: 223 | m.d.comb += sub.trg.eq(sub.i) 224 | elif sub.trigger == Source.Trigger.RISE: 225 | m.d.comb += sub.trg.eq(~sub_i_r & sub.i) 226 | elif sub.trigger == Source.Trigger.FALL: 227 | m.d.comb += sub.trg.eq( sub_i_r & ~sub.i) 228 | else: 229 | assert False # :nocov: 230 | 231 | with m.If(sub.trg): 232 | m.d.sync += self.pending[index].eq(1) 233 | with m.Elif(self.clear[index]): 234 | m.d.sync += self.pending[index].eq(0) 235 | 236 | m.d.comb += self.src.i.eq((self.enable & self.pending).any()) 237 | 238 | return m 239 | -------------------------------------------------------------------------------- /tests/test_csr_wishbone.py: -------------------------------------------------------------------------------- 1 | # amaranth: UnusedElaboratable=no 2 | 3 | import unittest 4 | from amaranth import * 5 | from amaranth.lib import wiring 6 | from amaranth.lib.wiring import In, Out 7 | from amaranth.sim import * 8 | 9 | from amaranth_soc import csr 10 | from amaranth_soc.csr.wishbone import * 11 | from amaranth_soc.memory import MemoryMap 12 | 13 | 14 | class _MockRegister(wiring.Component): 15 | def __init__(self, width, name): 16 | super().__init__({ 17 | "element": Out(csr.Element.Signature(width, "rw")), 18 | "r_count": Out(unsigned(8)), 19 | "w_count": Out(unsigned(8)), 20 | "data": Out(width) 21 | }) 22 | self._name = name 23 | 24 | def elaborate(self, platform): 25 | m = Module() 26 | 27 | with m.If(self.element.r_stb): 28 | m.d.sync += self.r_count.eq(self.r_count + 1) 29 | m.d.comb += self.element.r_data.eq(self.data) 30 | 31 | with m.If(self.element.w_stb): 32 | m.d.sync += self.w_count.eq(self.w_count + 1) 33 | m.d.sync += self.data.eq(self.element.w_data) 34 | 35 | return m 36 | 37 | def __repr__(self): 38 | return f"_MockRegister('{self._name}')" 39 | 40 | 41 | class WishboneCSRBridgeTestCase(unittest.TestCase): 42 | def test_wrong_csr_bus(self): 43 | with self.assertRaisesRegex(TypeError, 44 | r"CSR bus must be an instance of csr\.Interface, not 'foo'"): 45 | WishboneCSRBridge("foo") 46 | 47 | def test_wrong_csr_bus_data_width(self): 48 | csr_bus = csr.Signature(addr_width=10, data_width=7).create() 49 | with self.assertRaisesRegex(ValueError, 50 | r"CSR bus data width must be one of 8, 16, 32, 64, not 7"): 51 | WishboneCSRBridge(csr_bus) 52 | 53 | def test_narrow(self): 54 | reg_1 = _MockRegister( 8, name="reg_1") 55 | reg_2 = _MockRegister(16, name="reg_2") 56 | 57 | memory_map = MemoryMap(addr_width=10, data_width=8) 58 | memory_map.add_resource(reg_1, name=("reg_1",), size=1) 59 | memory_map.add_resource(reg_2, name=("reg_2",), size=2) 60 | 61 | mux = csr.Multiplexer(memory_map) 62 | dut = WishboneCSRBridge(mux.bus) 63 | 64 | async def testbench(ctx): 65 | ctx.set(dut.wb_bus.cyc, 1) 66 | ctx.set(dut.wb_bus.sel, 0b1) 67 | ctx.set(dut.wb_bus.we, 1) 68 | 69 | ctx.set(dut.wb_bus.adr, 0) 70 | ctx.set(dut.wb_bus.stb, 1) 71 | ctx.set(dut.wb_bus.dat_w, 0x55) 72 | await ctx.tick().repeat(2) 73 | self.assertEqual(ctx.get(dut.wb_bus.ack), 1) 74 | ctx.set(dut.wb_bus.stb, 0) 75 | await ctx.tick() 76 | self.assertEqual(ctx.get(dut.wb_bus.ack), 0) 77 | self.assertEqual(ctx.get(reg_1.r_count), 0) 78 | self.assertEqual(ctx.get(reg_1.w_count), 1) 79 | self.assertEqual(ctx.get(reg_1.data), 0x55) 80 | 81 | ctx.set(dut.wb_bus.adr, 1) 82 | ctx.set(dut.wb_bus.stb, 1) 83 | ctx.set(dut.wb_bus.dat_w, 0xaa) 84 | await ctx.tick().repeat(2) 85 | ctx.set(dut.wb_bus.stb, 0) 86 | self.assertEqual(ctx.get(dut.wb_bus.ack), 1) 87 | await ctx.tick() 88 | self.assertEqual(ctx.get(dut.wb_bus.ack), 0) 89 | self.assertEqual(ctx.get(reg_2.r_count), 0) 90 | self.assertEqual(ctx.get(reg_2.w_count), 0) 91 | self.assertEqual(ctx.get(reg_2.data), 0) 92 | 93 | ctx.set(dut.wb_bus.adr, 2) 94 | ctx.set(dut.wb_bus.stb, 1) 95 | ctx.set(dut.wb_bus.dat_w, 0xbb) 96 | await ctx.tick().repeat(2) 97 | self.assertEqual(ctx.get(dut.wb_bus.ack), 1) 98 | ctx.set(dut.wb_bus.stb, 0) 99 | await ctx.tick() 100 | self.assertEqual(ctx.get(dut.wb_bus.ack), 0) 101 | self.assertEqual(ctx.get(reg_2.r_count), 0) 102 | self.assertEqual(ctx.get(reg_2.w_count), 1) 103 | self.assertEqual(ctx.get(reg_2.data), 0xbbaa) 104 | 105 | ctx.set(dut.wb_bus.we, 0) 106 | 107 | ctx.set(dut.wb_bus.adr, 0) 108 | ctx.set(dut.wb_bus.stb, 1) 109 | await ctx.tick().repeat(2) 110 | self.assertEqual(ctx.get(dut.wb_bus.ack), 1) 111 | self.assertEqual(ctx.get(dut.wb_bus.dat_r), 0x55) 112 | ctx.set(dut.wb_bus.stb, 0) 113 | await ctx.tick() 114 | self.assertEqual(ctx.get(dut.wb_bus.ack), 0) 115 | self.assertEqual(ctx.get(reg_1.r_count), 1) 116 | self.assertEqual(ctx.get(reg_1.w_count), 1) 117 | 118 | ctx.set(dut.wb_bus.adr, 1) 119 | ctx.set(dut.wb_bus.stb, 1) 120 | await ctx.tick().repeat(2) 121 | self.assertEqual(ctx.get(dut.wb_bus.ack), 1) 122 | self.assertEqual(ctx.get(dut.wb_bus.dat_r), 0xaa) 123 | ctx.set(dut.wb_bus.stb, 0) 124 | await ctx.tick() 125 | self.assertEqual(ctx.get(dut.wb_bus.ack), 0) 126 | self.assertEqual(ctx.get(reg_2.r_count), 1) 127 | self.assertEqual(ctx.get(reg_2.w_count), 1) 128 | 129 | ctx.set(reg_2.data, 0x33333) 130 | 131 | ctx.set(dut.wb_bus.adr, 2) 132 | ctx.set(dut.wb_bus.stb, 1) 133 | await ctx.tick().repeat(2) 134 | self.assertEqual(ctx.get(dut.wb_bus.ack), 1) 135 | self.assertEqual(ctx.get(dut.wb_bus.dat_r), 0xbb) 136 | ctx.set(dut.wb_bus.stb, 0) 137 | await ctx.tick() 138 | self.assertEqual(ctx.get(dut.wb_bus.ack), 0) 139 | self.assertEqual(ctx.get(reg_2.r_count), 1) 140 | self.assertEqual(ctx.get(reg_2.w_count), 1) 141 | 142 | m = Module() 143 | m.submodules.reg_1 = reg_1 144 | m.submodules.reg_2 = reg_2 145 | m.submodules.mux = mux 146 | m.submodules.dut = dut 147 | 148 | sim = Simulator(m) 149 | sim.add_clock(1e-6) 150 | sim.add_testbench(testbench) 151 | with sim.write_vcd(vcd_file="test.vcd"): 152 | sim.run() 153 | 154 | def test_wide(self): 155 | reg = _MockRegister(32, name="reg") 156 | 157 | memory_map = MemoryMap(addr_width=10, data_width=8) 158 | memory_map.add_resource(reg, name=("reg",), size=4) 159 | 160 | mux = csr.Multiplexer(memory_map) 161 | dut = WishboneCSRBridge(mux.bus, data_width=32) 162 | 163 | async def testbench(ctx): 164 | ctx.set(dut.wb_bus.cyc, 1) 165 | ctx.set(dut.wb_bus.adr, 0) 166 | ctx.set(dut.wb_bus.we, 1) 167 | 168 | ctx.set(dut.wb_bus.dat_w, 0x44332211) 169 | ctx.set(dut.wb_bus.sel, 0b1111) 170 | ctx.set(dut.wb_bus.stb, 1) 171 | await ctx.tick().repeat(5) 172 | self.assertEqual(ctx.get(dut.wb_bus.ack), 1) 173 | ctx.set(dut.wb_bus.stb, 0) 174 | await ctx.tick() 175 | self.assertEqual(ctx.get(dut.wb_bus.ack), 0) 176 | self.assertEqual(ctx.get(reg.r_count), 0) 177 | self.assertEqual(ctx.get(reg.w_count), 1) 178 | self.assertEqual(ctx.get(reg.data), 0x44332211) 179 | 180 | # partial write 181 | ctx.set(dut.wb_bus.dat_w, 0xaabbccdd) 182 | ctx.set(dut.wb_bus.sel, 0b0110) 183 | ctx.set(dut.wb_bus.stb, 1) 184 | await ctx.tick().repeat(5) 185 | self.assertEqual(ctx.get(dut.wb_bus.ack), 1) 186 | ctx.set(dut.wb_bus.stb, 0) 187 | await ctx.tick() 188 | self.assertEqual(ctx.get(dut.wb_bus.ack), 0) 189 | self.assertEqual(ctx.get(reg.r_count), 0) 190 | self.assertEqual(ctx.get(reg.w_count), 1) 191 | self.assertEqual(ctx.get(reg.data), 0x44332211) 192 | 193 | ctx.set(dut.wb_bus.we, 0) 194 | 195 | ctx.set(dut.wb_bus.sel, 0b1111) 196 | ctx.set(dut.wb_bus.stb, 1) 197 | await ctx.tick().repeat(5) 198 | self.assertEqual(ctx.get(dut.wb_bus.ack), 1) 199 | self.assertEqual(ctx.get(dut.wb_bus.dat_r), 0x44332211) 200 | ctx.set(dut.wb_bus.stb, 0) 201 | await ctx.tick() 202 | self.assertEqual(ctx.get(dut.wb_bus.ack), 0) 203 | self.assertEqual(ctx.get(reg.r_count), 1) 204 | self.assertEqual(ctx.get(reg.w_count), 1) 205 | 206 | ctx.set(reg.data, 0xaaaaaaaa) 207 | 208 | # partial read 209 | ctx.set(dut.wb_bus.sel, 0b0110) 210 | ctx.set(dut.wb_bus.stb, 1) 211 | await ctx.tick().repeat(5) 212 | self.assertEqual(ctx.get(dut.wb_bus.ack), 1) 213 | self.assertEqual(ctx.get(dut.wb_bus.dat_r), 0x00332200) 214 | ctx.set(dut.wb_bus.stb, 0) 215 | await ctx.tick() 216 | self.assertEqual(ctx.get(dut.wb_bus.ack), 0) 217 | self.assertEqual(ctx.get(reg.r_count), 1) 218 | self.assertEqual(ctx.get(reg.w_count), 1) 219 | 220 | m = Module() 221 | m.submodules.reg = reg 222 | m.submodules.mux = mux 223 | m.submodules.dut = dut 224 | 225 | sim = Simulator(m) 226 | sim.add_clock(1e-6) 227 | sim.add_testbench(testbench) 228 | with sim.write_vcd(vcd_file="test.vcd"): 229 | sim.run() 230 | -------------------------------------------------------------------------------- /tests/test_event.py: -------------------------------------------------------------------------------- 1 | # amaranth: UnusedElaboratable=no 2 | 3 | import unittest 4 | from amaranth import * 5 | from amaranth.lib.wiring import * 6 | from amaranth.sim import * 7 | 8 | from amaranth_soc import event 9 | 10 | 11 | class SourceSignatureTestCase(unittest.TestCase): 12 | def test_level(self): 13 | sig = event.Source.Signature(trigger="level") 14 | self.assertEqual(sig.trigger, event.Source.Trigger.LEVEL) 15 | 16 | def test_rise(self): 17 | sig = event.Source.Signature(trigger="rise") 18 | self.assertEqual(sig.trigger, event.Source.Trigger.RISE) 19 | 20 | def test_fall(self): 21 | sig = event.Source.Signature(trigger="fall") 22 | self.assertEqual(sig.trigger, event.Source.Trigger.FALL) 23 | 24 | def test_create(self): 25 | sig = event.Source.Signature(trigger="level") 26 | src = sig.create(path=("foo", "bar")) 27 | self.assertIsInstance(src, event.Source) 28 | self.assertEqual(src.trigger, event.Source.Trigger.LEVEL) 29 | self.assertEqual(src.trg.name, "foo__bar__trg") 30 | self.assertEqual(src.signature, sig) 31 | 32 | def test_eq(self): 33 | self.assertEqual(event.Source.Signature(trigger="level"), event.Source.Signature()) 34 | self.assertEqual(event.Source.Signature(trigger="level"), 35 | event.Source.Signature(trigger=event.Source.Trigger.LEVEL)) 36 | # different trigger mode 37 | self.assertNotEqual(event.Source.Signature(trigger="level"), 38 | event.Source.Signature(trigger="rise")) 39 | self.assertNotEqual(event.Source.Signature(trigger="level"), 40 | event.Source.Signature(trigger="fall")) 41 | self.assertNotEqual(event.Source.Signature(trigger="rise"), 42 | event.Source.Signature(trigger="fall")) 43 | 44 | def test_trigger_wrong(self): 45 | with self.assertRaisesRegex(ValueError, r"'foo' is not a valid Source.Trigger"): 46 | src = event.Source.Signature(trigger="foo") 47 | 48 | 49 | class SourceTestCase(unittest.TestCase): 50 | def test_simple(self): 51 | src = event.Source(trigger="level", path=("foo", "bar")) 52 | self.assertIsInstance(src, event.Source) 53 | self.assertEqual(src.trigger, event.Source.Trigger.LEVEL) 54 | self.assertEqual(src.trg.name, "foo__bar__trg") 55 | 56 | def test_set_map(self): 57 | src = event.Source() 58 | event_map = event.EventMap() 59 | src.event_map = event_map 60 | self.assertIs(src.event_map, event_map) 61 | 62 | def test_get_map_none(self): 63 | src = event.Source() 64 | with self.assertRaisesRegex(AttributeError, 65 | r"event\.Source\(.*\) does not have an event map"): 66 | src.event_map 67 | 68 | def test_get_map_frozen(self): 69 | src = event.Source() 70 | src.event_map = event.EventMap() 71 | with self.assertRaisesRegex(ValueError, 72 | r"Event map has been frozen\. Cannot add source"): 73 | src.event_map.add(event.Source.Signature().create()) 74 | 75 | def test_set_wrong_map(self): 76 | src = event.Source() 77 | with self.assertRaisesRegex(TypeError, 78 | r"Event map must be an instance of EventMap, not 'foo'"): 79 | src.event_map = "foo" 80 | 81 | 82 | class EventMapTestCase(unittest.TestCase): 83 | def test_add(self): 84 | src_0 = event.Source(path=("src_0",)) 85 | src_1 = event.Source(path=("src_1",)) 86 | event_map = event.EventMap() 87 | event_map.add(src_0) 88 | event_map.add(src=src_1) 89 | self.assertTrue(id(src_0) in event_map._sources) 90 | self.assertTrue(id(src_1) in event_map._sources) 91 | 92 | def test_add_wrong(self): 93 | event_map = event.EventMap() 94 | with self.assertRaisesRegex(TypeError, 95 | r"Event source must be an instance of event.Source, not 'foo'"): 96 | event_map.add("foo") 97 | 98 | def test_add_wrong_frozen(self): 99 | event_map = event.EventMap() 100 | event_map.freeze() 101 | with self.assertRaisesRegex(ValueError, 102 | r"Event map has been frozen. Cannot add source"): 103 | event_map.add(event.Source.Signature().create()) 104 | 105 | def test_size(self): 106 | event_map = event.EventMap() 107 | event_map.add(event.Source()) 108 | event_map.add(event.Source()) 109 | self.assertEqual(event_map.size, 2) 110 | 111 | def test_index(self): 112 | src_0 = event.Source() 113 | src_1 = event.Source() 114 | event_map = event.EventMap() 115 | event_map.add(src_0) 116 | event_map.add(src_1) 117 | self.assertEqual(event_map.index(src_0), 0) 118 | self.assertEqual(event_map.index(src=src_1), 1) 119 | 120 | def test_index_add_twice(self): 121 | src = event.Source() 122 | event_map = event.EventMap() 123 | event_map.add(src) 124 | event_map.add(src) 125 | self.assertEqual(event_map.index(src), 0) 126 | self.assertEqual(event_map.size, 1) 127 | 128 | def test_index_wrong(self): 129 | event_map = event.EventMap() 130 | with self.assertRaisesRegex(TypeError, 131 | r"Event source must be an instance of event.Source, not 'foo'"): 132 | event_map.index("foo") 133 | 134 | def test_index_not_found(self): 135 | src = event.Source() 136 | event_map = event.EventMap() 137 | with self.assertRaises(KeyError): 138 | event_map.index(src) 139 | 140 | def test_iter_sources(self): 141 | src_0 = event.Source() 142 | src_1 = event.Source() 143 | event_map = event.EventMap() 144 | event_map.add(src_0) 145 | event_map.add(src_1) 146 | self.assertEqual(list(event_map.sources()), [ 147 | (src_0, 0), 148 | (src_1, 1), 149 | ]) 150 | 151 | 152 | class MonitorTestCase(unittest.TestCase): 153 | def test_simple(self): 154 | sub_0 = event.Source(path=("sub_0",)) 155 | sub_1 = event.Source(path=("sub_1",)) 156 | event_map = event.EventMap() 157 | event_map.add(sub_0) 158 | event_map.add(sub_1) 159 | dut = event.Monitor(event_map, trigger="rise") 160 | self.assertIs(dut.src.event_map, event_map) 161 | self.assertEqual(dut.src.trigger, event.Source.Trigger.RISE) 162 | self.assertEqual(len(dut.enable), 2) 163 | self.assertEqual(len(dut.pending), 2) 164 | self.assertEqual(len(dut.clear), 2) 165 | 166 | def test_event_map_wrong(self): 167 | with self.assertRaisesRegex(TypeError, 168 | r"Event map must be an instance of EventMap, not 'foo'"): 169 | dut = event.Monitor(event_map="foo") 170 | 171 | def test_events(self): 172 | sub_0 = event.Source(trigger="level", path=("sub_0",)) 173 | sub_1 = event.Source(trigger="rise", path=("sub_1",)) 174 | sub_2 = event.Source(trigger="fall", path=("sub_2",)) 175 | event_map = event.EventMap() 176 | event_map.add(sub_0) 177 | event_map.add(sub_1) 178 | event_map.add(sub_2) 179 | dut = event.Monitor(event_map) 180 | 181 | async def testbench(ctx): 182 | ctx.set(sub_0.i, 1) 183 | ctx.set(sub_1.i, 0) 184 | ctx.set(sub_2.i, 1) 185 | await ctx.tick() 186 | self.assertEqual(ctx.get(sub_0.trg), 1) 187 | self.assertEqual(ctx.get(sub_1.trg), 0) 188 | self.assertEqual(ctx.get(sub_2.trg), 0) 189 | self.assertEqual(ctx.get(dut.pending), 0b001) 190 | self.assertEqual(ctx.get(dut.src.i), 0) 191 | 192 | ctx.set(dut.enable, 0b111) 193 | await ctx.tick() 194 | self.assertEqual(ctx.get(dut.src.i), 1) 195 | 196 | ctx.set(dut.clear, 0b001) 197 | await ctx.tick() 198 | self.assertEqual(ctx.get(dut.pending), 0b001) 199 | self.assertEqual(ctx.get(dut.src.i), 1) 200 | 201 | ctx.set(sub_0.i, 0) 202 | self.assertEqual(ctx.get(sub_0.trg), 0) 203 | self.assertEqual(ctx.get(sub_1.trg), 0) 204 | self.assertEqual(ctx.get(sub_2.trg), 0) 205 | await ctx.tick() 206 | self.assertEqual(ctx.get(dut.pending), 0b000) 207 | self.assertEqual(ctx.get(dut.src.i), 0) 208 | 209 | ctx.set(sub_1.i, 1) 210 | self.assertEqual(ctx.get(sub_0.trg), 0) 211 | self.assertEqual(ctx.get(sub_1.trg), 1) 212 | self.assertEqual(ctx.get(sub_2.trg), 0) 213 | await ctx.tick() 214 | self.assertEqual(ctx.get(dut.pending), 0b010) 215 | self.assertEqual(ctx.get(dut.src.i), 1) 216 | 217 | ctx.set(sub_2.i, 0) 218 | self.assertEqual(ctx.get(sub_0.trg), 0) 219 | self.assertEqual(ctx.get(sub_1.trg), 0) 220 | self.assertEqual(ctx.get(sub_2.trg), 1) 221 | await ctx.tick() 222 | self.assertEqual(ctx.get(dut.pending), 0b110) 223 | self.assertEqual(ctx.get(dut.src.i), 1) 224 | 225 | ctx.set(dut.clear, 0b110) 226 | self.assertEqual(ctx.get(sub_0.trg), 0) 227 | self.assertEqual(ctx.get(sub_1.trg), 0) 228 | self.assertEqual(ctx.get(sub_2.trg), 0) 229 | await ctx.tick() 230 | self.assertEqual(ctx.get(dut.pending), 0b000) 231 | self.assertEqual(ctx.get(dut.src.i), 0) 232 | 233 | sim = Simulator(dut) 234 | sim.add_clock(1e-6) 235 | sim.add_testbench(testbench) 236 | with sim.write_vcd(vcd_file="test.vcd"): 237 | sim.run() 238 | -------------------------------------------------------------------------------- /amaranth_soc/gpio.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.lib import enum, wiring 3 | from amaranth.lib.wiring import In, Out, flipped, connect 4 | 5 | from . import csr 6 | 7 | 8 | __all__ = ["PinMode", "PinSignature", "Peripheral"] 9 | 10 | 11 | class PinMode(enum.Enum, shape=unsigned(2)): 12 | """GPIO pin mode. 13 | 14 | The 2-bit values of this enumeration can be written to a :class:`Peripheral.Mode` field to 15 | configure the pins of a :class:`Peripheral`. 16 | """ 17 | 18 | #: `Input-only` mode. 19 | #: 20 | #: The pin output is disabled but remains connected to its :class:`Peripheral.Output` field. 21 | #: Its :attr:`Peripheral.alt_mode` bit is wired to 0. 22 | INPUT_ONLY = 0b00 23 | 24 | #: `Push-pull` mode. 25 | #: 26 | #: The pin output is enabled and connected to its :class:`Peripheral.Output` field. Its 27 | #: :attr:`Peripheral.alt_mode` bit is wired to 0. 28 | PUSH_PULL = 0b01 29 | 30 | #: `Open-drain` mode. 31 | #: 32 | #: The pin output is enabled when the value of its :class:`Peripheral.Output` field is 0, and 33 | #: is itself wired to 0. Its :attr:`Peripheral.alt_mode` bit is wired to 0. 34 | OPEN_DRAIN = 0b10 35 | 36 | #: `Alternate` mode. 37 | #: 38 | #: The pin output is disabled but remains connected to its :class:`Peripheral.Output` field. 39 | #: Its :attr:`Peripheral.alt_mode` bit is wired to 1. 40 | ALTERNATE = 0b11 41 | 42 | 43 | class PinSignature(wiring.Signature): 44 | """GPIO pin signature. 45 | 46 | Interface attributes 47 | -------------------- 48 | i : :class:`Signal` 49 | Input. 50 | o : :class:`Signal` 51 | Output. 52 | oe : :class:`Signal` 53 | Output enable. 54 | """ 55 | def __init__(self): 56 | super().__init__({ 57 | "i": In(unsigned(1)), 58 | "o": Out(unsigned(1)), 59 | "oe": Out(unsigned(1)), 60 | }) 61 | 62 | 63 | class Peripheral(wiring.Component): 64 | class Mode(csr.Register, access="rw"): 65 | """Mode register. 66 | 67 | This :class:`csr.Register` contains an array of ``pin_count`` read/write fields. Each field 68 | is 2-bit wide and its possible values are defined by the :class:`PinMode` enumeration. 69 | 70 | If ``pin_count`` is 8, then the register has the following fields: 71 | 72 | .. bitfield:: 73 | :bits: 16 74 | 75 | [ 76 | { "name": "pin[0]", "bits": 2, "attr": "RW" }, 77 | { "name": "pin[1]", "bits": 2, "attr": "RW" }, 78 | { "name": "pin[2]", "bits": 2, "attr": "RW" }, 79 | { "name": "pin[3]", "bits": 2, "attr": "RW" }, 80 | { "name": "pin[4]", "bits": 2, "attr": "RW" }, 81 | { "name": "pin[5]", "bits": 2, "attr": "RW" }, 82 | { "name": "pin[6]", "bits": 2, "attr": "RW" }, 83 | { "name": "pin[7]", "bits": 2, "attr": "RW" }, 84 | ] 85 | 86 | Parameters 87 | ---------- 88 | pin_count : :class:`int` 89 | Number of GPIO pins. 90 | """ 91 | def __init__(self, pin_count): 92 | super().__init__({ 93 | "pin": [csr.Field(csr.action.RW, PinMode) for _ in range(pin_count)], 94 | }) 95 | 96 | class Input(csr.Register, access="r"): 97 | """Input register. 98 | 99 | This :class:`csr.Register` contains an array of ``pin_count`` read-only fields. Each field 100 | is 1-bit wide and driven by the input of its associated pin in the :attr:`Peripheral.pins` 101 | array. 102 | 103 | Values sampled from pin inputs go through :attr:`Peripheral.input_stages` synchronization 104 | stages (on a rising edge of ``ClockSignal("sync")``) before reaching the register. 105 | 106 | If ``pin_count`` is 8, then the register has the following fields: 107 | 108 | .. bitfield:: 109 | :bits: 8 110 | 111 | [ 112 | { "name": "pin[0]", "bits": 1, "attr": "R" }, 113 | { "name": "pin[1]", "bits": 1, "attr": "R" }, 114 | { "name": "pin[2]", "bits": 1, "attr": "R" }, 115 | { "name": "pin[3]", "bits": 1, "attr": "R" }, 116 | { "name": "pin[4]", "bits": 1, "attr": "R" }, 117 | { "name": "pin[5]", "bits": 1, "attr": "R" }, 118 | { "name": "pin[6]", "bits": 1, "attr": "R" }, 119 | { "name": "pin[7]", "bits": 1, "attr": "R" }, 120 | ] 121 | 122 | Parameters 123 | ---------- 124 | pin_count : :class:`int` 125 | Number of GPIO pins. 126 | """ 127 | def __init__(self, pin_count): 128 | super().__init__({ 129 | "pin": [csr.Field(csr.action.R, unsigned(1)) for _ in range(pin_count)], 130 | }) 131 | 132 | class Output(csr.Register, access="rw"): 133 | """Output register. 134 | 135 | This :class:`csr.Register` contains an array of ``pin_count`` read/write fields. Each field 136 | is 1-bit wide and drives the output of its associated pin in the :attr:`Peripheral.pins` 137 | array, depending on its associated :class:`~Peripheral.Mode` field. 138 | 139 | If ``pin_count`` is 8, then the register has the following fields: 140 | 141 | .. bitfield:: 142 | :bits: 8 143 | 144 | [ 145 | { "name": "pin[0]", "bits": 1, "attr": "RW" }, 146 | { "name": "pin[1]", "bits": 1, "attr": "RW" }, 147 | { "name": "pin[2]", "bits": 1, "attr": "RW" }, 148 | { "name": "pin[3]", "bits": 1, "attr": "RW" }, 149 | { "name": "pin[4]", "bits": 1, "attr": "RW" }, 150 | { "name": "pin[5]", "bits": 1, "attr": "RW" }, 151 | { "name": "pin[6]", "bits": 1, "attr": "RW" }, 152 | { "name": "pin[7]", "bits": 1, "attr": "RW" }, 153 | ] 154 | 155 | Parameters 156 | ---------- 157 | pin_count : :class:`int` 158 | Number of GPIO pins. 159 | """ 160 | class _FieldAction(csr.FieldAction): 161 | def __init__(self): 162 | super().__init__(shape=unsigned(1), access="rw", members=( 163 | ("data", Out(unsigned(1))), 164 | ("set", In(unsigned(1))), 165 | ("clr", In(unsigned(1))), 166 | )) 167 | self._storage = Signal(unsigned(1)) 168 | 169 | def elaborate(self, platform): 170 | m = Module() 171 | 172 | with m.If(self.set != self.clr): 173 | m.d.sync += self._storage.eq(self.set) 174 | with m.Elif(self.port.w_stb): 175 | m.d.sync += self._storage.eq(self.port.w_data) 176 | 177 | m.d.comb += [ 178 | self.port.r_data.eq(self._storage), 179 | self.data.eq(self._storage), 180 | ] 181 | 182 | return m 183 | 184 | def __init__(self, pin_count): 185 | super().__init__({ 186 | "pin": [csr.Field(self._FieldAction) for _ in range(pin_count)], 187 | }) 188 | 189 | class SetClr(csr.Register, access="w"): 190 | """Output set/clear register. 191 | 192 | This :class:`csr.Register` contains an array of ``pin_count`` write-only fields. Each field 193 | is 2-bit wide; writing it can modify its associated :class:`~Peripheral.Output` field as a 194 | side-effect. 195 | 196 | If ``pin_count`` is 8, then the register has the following fields: 197 | 198 | .. bitfield:: 199 | :bits: 16 200 | 201 | [ 202 | { "name": "pin[0]", "bits": 2, "attr": "W" }, 203 | { "name": "pin[1]", "bits": 2, "attr": "W" }, 204 | { "name": "pin[2]", "bits": 2, "attr": "W" }, 205 | { "name": "pin[3]", "bits": 2, "attr": "W" }, 206 | { "name": "pin[4]", "bits": 2, "attr": "W" }, 207 | { "name": "pin[5]", "bits": 2, "attr": "W" }, 208 | { "name": "pin[6]", "bits": 2, "attr": "W" }, 209 | { "name": "pin[7]", "bits": 2, "attr": "W" }, 210 | ] 211 | 212 | - Writing `0b01` to a field sets its associated :class:`~Peripheral.Output` field. 213 | - Writing `0b10` to a field clears its associated :class:`~Peripheral.Output` field. 214 | - Writing `0b00` or `0b11` to a field has no side-effect. 215 | 216 | Parameters 217 | ---------- 218 | pin_count : :class:`int` 219 | Number of GPIO pins. 220 | """ 221 | def __init__(self, pin_count): 222 | pin_fields = { 223 | "set": csr.Field(csr.action.W, unsigned(1)), 224 | "clr": csr.Field(csr.action.W, unsigned(1)), 225 | } 226 | super().__init__({ 227 | "pin": [pin_fields for _ in range(pin_count)], 228 | }) 229 | 230 | """GPIO peripheral. 231 | 232 | Parameters 233 | ---------- 234 | pin_count : :class:`int` 235 | Number of GPIO pins. 236 | addr_width : :class:`int` 237 | CSR bus address width. 238 | data_width : :class:`int` 239 | CSR bus data width. 240 | input_stages : :class:`int` 241 | Number of synchronization stages between pin inputs and the :class:`~Peripheral.Input` 242 | register. Optional. Defaults to ``2``. 243 | 244 | Attributes 245 | ---------- 246 | bus : :class:`csr.Interface` 247 | CSR bus interface providing access to registers. 248 | pins : :class:`list` of :class:`wiring.PureInterface` of :class:`PinSignature` 249 | GPIO pin interfaces. 250 | alt_mode : :class:`Signal` 251 | Indicates which members of the :attr:`Peripheral.pins` array are in alternate mode. 252 | 253 | Raises 254 | ------ 255 | :exc:`TypeError` 256 | If ``pin_count`` is not a positive integer. 257 | :exc:`TypeError` 258 | If ``input_stages`` is not a non-negative integer. 259 | """ 260 | def __init__(self, *, pin_count, addr_width, data_width, input_stages=2): 261 | if not isinstance(pin_count, int) or pin_count <= 0: 262 | raise TypeError(f"Pin count must be a positive integer, not {pin_count!r}") 263 | if not isinstance(input_stages, int) or input_stages < 0: 264 | raise TypeError(f"Input stages must be a non-negative integer, not {input_stages!r}") 265 | 266 | regs = csr.Builder(addr_width=addr_width, data_width=data_width) 267 | 268 | self._mode = regs.add("Mode", self.Mode(pin_count)) 269 | self._input = regs.add("Input", self.Input(pin_count)) 270 | self._output = regs.add("Output", self.Output(pin_count)) 271 | self._setclr = regs.add("SetClr", self.SetClr(pin_count)) 272 | 273 | self._bridge = csr.Bridge(regs.as_memory_map()) 274 | 275 | super().__init__({ 276 | "bus": In(csr.Signature(addr_width=addr_width, data_width=data_width)), 277 | "pins": Out(PinSignature()).array(pin_count), 278 | "alt_mode": Out(unsigned(pin_count)), 279 | }) 280 | self.bus.memory_map = self._bridge.bus.memory_map 281 | 282 | self._pin_count = pin_count 283 | self._input_stages = input_stages 284 | 285 | @property 286 | def pin_count(self): 287 | """Number of GPIO pins. 288 | 289 | Returns 290 | ------- 291 | :class:`int` 292 | """ 293 | return self._pin_count 294 | 295 | @property 296 | def input_stages(self): 297 | """Number of synchronization stages between pin inputs and the :class:`~Peripheral.Input` 298 | register. 299 | 300 | Returns 301 | ------- 302 | :class:`int` 303 | """ 304 | return self._input_stages 305 | 306 | def elaborate(self, platform): 307 | m = Module() 308 | m.submodules.bridge = self._bridge 309 | 310 | connect(m, flipped(self.bus), self._bridge.bus) 311 | 312 | for n, pin in enumerate(self.pins): 313 | pin_i_sync = pin.i 314 | for stage in range(self.input_stages): 315 | pin_i_sync_ff = Signal(reset_less=True, name=f"pin_{n}_i_sync_ff_{stage}") 316 | m.d.sync += pin_i_sync_ff.eq(pin_i_sync) 317 | pin_i_sync = pin_i_sync_ff 318 | del pin_i_sync_ff 319 | 320 | m.d.comb += self._input.f.pin[n].r_data.eq(pin_i_sync) 321 | 322 | with m.If(self._setclr.f.pin[n].set.w_stb & self._setclr.f.pin[n].set.w_data): 323 | m.d.comb += self._output.f.pin[n].set.eq(1) 324 | with m.If(self._setclr.f.pin[n].clr.w_stb & self._setclr.f.pin[n].clr.w_data): 325 | m.d.comb += self._output.f.pin[n].clr.eq(1) 326 | 327 | with m.Switch(self._mode.f.pin[n].data): 328 | with m.Case(PinMode.INPUT_ONLY): 329 | m.d.comb += [ 330 | pin.o .eq(self._output.f.pin[n].data), 331 | pin.oe.eq(0), 332 | ] 333 | with m.Case(PinMode.PUSH_PULL): 334 | m.d.comb += [ 335 | pin.o .eq(self._output.f.pin[n].data), 336 | pin.oe.eq(1), 337 | ] 338 | with m.Case(PinMode.OPEN_DRAIN): 339 | m.d.comb += [ 340 | pin.o .eq(0), 341 | pin.oe.eq(~self._output.f.pin[n].data), 342 | ] 343 | with m.Case(PinMode.ALTERNATE): 344 | m.d.comb += [ 345 | pin.o .eq(self._output.f.pin[n].data), 346 | pin.oe.eq(0), 347 | ] 348 | m.d.comb += self.alt_mode[n].eq(1) 349 | 350 | return m 351 | -------------------------------------------------------------------------------- /tests/test_gpio.py: -------------------------------------------------------------------------------- 1 | # amaranth: UnusedElaboratable=no 2 | 3 | import unittest 4 | from amaranth import * 5 | from amaranth.sim import * 6 | 7 | from amaranth_soc import gpio 8 | 9 | 10 | class PeripheralTestCase(unittest.TestCase): 11 | def test_init(self): 12 | dut_1 = gpio.Peripheral(pin_count=4, addr_width=2, data_width=8) 13 | self.assertEqual(dut_1.pin_count, 4) 14 | self.assertEqual(dut_1.input_stages, 2) 15 | self.assertEqual(dut_1.bus.addr_width, 2) 16 | self.assertEqual(dut_1.bus.data_width, 8) 17 | dut_2 = gpio.Peripheral(pin_count=1, addr_width=8, data_width=16, input_stages=3) 18 | self.assertEqual(dut_2.pin_count, 1) 19 | self.assertEqual(dut_2.input_stages, 3) 20 | self.assertEqual(dut_2.bus.addr_width, 8) 21 | self.assertEqual(dut_2.bus.data_width, 16) 22 | 23 | def test_init_wrong_pin_count(self): 24 | with self.assertRaisesRegex(TypeError, 25 | r"Pin count must be a positive integer, not 'foo'"): 26 | gpio.Peripheral(pin_count="foo", addr_width=2, data_width=8) 27 | with self.assertRaisesRegex(TypeError, 28 | r"Pin count must be a positive integer, not 0"): 29 | gpio.Peripheral(pin_count=0, addr_width=2, data_width=8) 30 | 31 | def test_init_wrong_input_stages(self): 32 | with self.assertRaisesRegex(TypeError, 33 | r"Input stages must be a non-negative integer, not 'foo'"): 34 | gpio.Peripheral(pin_count=1, addr_width=2, data_width=8, input_stages="foo") 35 | with self.assertRaisesRegex(TypeError, 36 | r"Input stages must be a non-negative integer, not -1"): 37 | gpio.Peripheral(pin_count=1, addr_width=2, data_width=8, input_stages=-1) 38 | 39 | async def _csr_access(self, ctx, dut, addr, r_stb=0, w_stb=0, w_data=0, r_data=0): 40 | ctx.set(dut.bus.addr, addr) 41 | ctx.set(dut.bus.r_stb, r_stb) 42 | ctx.set(dut.bus.w_stb, w_stb) 43 | ctx.set(dut.bus.w_data, w_data) 44 | await ctx.tick() 45 | if r_stb: 46 | self.assertEqual(ctx.get(dut.bus.r_data), r_data) 47 | ctx.set(dut.bus.r_stb, 0) 48 | ctx.set(dut.bus.w_stb, 0) 49 | 50 | def test_sim(self): 51 | dut = gpio.Peripheral(pin_count=4, addr_width=2, data_width=8) 52 | 53 | mode_addr = 0x0 54 | input_addr = 0x1 55 | output_addr = 0x2 56 | setclr_addr = 0x3 57 | 58 | async def testbench(ctx): 59 | # INPUT_ONLY mode ===================================================================== 60 | 61 | # - read Mode: 62 | await self._csr_access(ctx, dut, mode_addr, r_stb=1, r_data=0b00000000) 63 | for n in range(4): 64 | self.assertEqual(ctx.get(dut.alt_mode[n]), 0) 65 | self.assertEqual(ctx.get(dut.pins[n].oe), 0) 66 | self.assertEqual(ctx.get(dut.pins[n].o), 0) 67 | 68 | # - read Input: 69 | ctx.set(dut.pins[1].i, 1) 70 | ctx.set(dut.pins[3].i, 1) 71 | await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0x0) 72 | ctx.set(dut.pins[1].i, 0) 73 | ctx.set(dut.pins[3].i, 0) 74 | await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0x0) 75 | await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0xa) 76 | await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0x0) 77 | 78 | # - write 0xf to Output: 79 | await self._csr_access(ctx, dut, output_addr, r_stb=1, r_data=0x0, w_stb=1, w_data=0xf) 80 | await ctx.tick() 81 | for n in range(4): 82 | self.assertEqual(ctx.get(dut.pins[n].oe), 0) 83 | self.assertEqual(ctx.get(dut.pins[n].o), 1) 84 | 85 | # - write 0x22 to SetClr (clear pins[0] and pins[2]): 86 | await self._csr_access(ctx, dut, setclr_addr, w_stb=1, w_data=0x22) 87 | await ctx.tick() 88 | for n in range(4): 89 | self.assertEqual(ctx.get(dut.pins[n].oe), 0) 90 | self.assertEqual(ctx.get(dut.pins[0].o), 0) 91 | self.assertEqual(ctx.get(dut.pins[1].o), 1) 92 | self.assertEqual(ctx.get(dut.pins[2].o), 0) 93 | self.assertEqual(ctx.get(dut.pins[3].o), 1) 94 | 95 | # - write 0x0 to Output: 96 | await self._csr_access(ctx, dut, output_addr, r_stb=1, r_data=0xa, w_stb=1, w_data=0x0) 97 | await ctx.tick() 98 | for n in range(4): 99 | self.assertEqual(ctx.get(dut.pins[n].oe), 0) 100 | self.assertEqual(ctx.get(dut.pins[n].o), 0) 101 | 102 | # - write 0x44 to SetClr (set pins[1] and pins[3]): 103 | await self._csr_access(ctx, dut, setclr_addr, w_stb=1, w_data=0x44) 104 | await ctx.tick() 105 | for n in range(4): 106 | self.assertEqual(ctx.get(dut.pins[n].oe), 0) 107 | self.assertEqual(ctx.get(dut.pins[0].o), 0) 108 | self.assertEqual(ctx.get(dut.pins[1].o), 1) 109 | self.assertEqual(ctx.get(dut.pins[2].o), 0) 110 | self.assertEqual(ctx.get(dut.pins[3].o), 1) 111 | 112 | # - write 0x0 to Output: 113 | await self._csr_access(ctx, dut, output_addr, r_stb=1, r_data=0xa, w_stb=1, w_data=0x0) 114 | await ctx.tick() 115 | for n in range(4): 116 | self.assertEqual(ctx.get(dut.pins[n].oe), 0) 117 | self.assertEqual(ctx.get(dut.pins[n].o), 0) 118 | 119 | # - write 0xff to SetClr (no-op): 120 | await self._csr_access(ctx, dut, setclr_addr, w_stb=1, w_data=0xff) 121 | await ctx.tick() 122 | for n in range(4): 123 | self.assertEqual(ctx.get(dut.pins[n].oe), 0) 124 | self.assertEqual(ctx.get(dut.pins[n].o), 0) 125 | 126 | # PUSH_PULL mode ====================================================================== 127 | 128 | # - write Mode: 129 | await self._csr_access(ctx, dut, mode_addr, w_stb=1, w_data=0b01010101) 130 | await ctx.tick() 131 | for n in range(4): 132 | self.assertEqual(ctx.get(dut.alt_mode[n]), 0) 133 | self.assertEqual(ctx.get(dut.pins[n].oe), 1) 134 | self.assertEqual(ctx.get(dut.pins[n].o), 0) 135 | 136 | # - read Input: 137 | ctx.set(dut.pins[1].i, 1) 138 | ctx.set(dut.pins[3].i, 1) 139 | await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0x0) 140 | ctx.set(dut.pins[1].i, 0) 141 | ctx.set(dut.pins[3].i, 0) 142 | await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0x0) 143 | await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0xa) 144 | await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0x0) 145 | 146 | # - write 0xf to Output: 147 | await self._csr_access(ctx, dut, output_addr, r_stb=1, r_data=0x0, w_stb=1, w_data=0xf) 148 | await ctx.tick() 149 | for n in range(4): 150 | self.assertEqual(ctx.get(dut.pins[n].oe), 1) 151 | self.assertEqual(ctx.get(dut.pins[n].o), 1) 152 | 153 | # - write 0x22 to SetClr (clear pins[0] and pins[2]): 154 | await self._csr_access(ctx, dut, setclr_addr, w_stb=1, w_data=0x22) 155 | await ctx.tick() 156 | for n in range(4): 157 | self.assertEqual(ctx.get(dut.pins[n].oe), 1) 158 | self.assertEqual(ctx.get(dut.pins[0].o), 0) 159 | self.assertEqual(ctx.get(dut.pins[1].o), 1) 160 | self.assertEqual(ctx.get(dut.pins[2].o), 0) 161 | self.assertEqual(ctx.get(dut.pins[3].o), 1) 162 | 163 | # - write 0x0 to Output: 164 | await self._csr_access(ctx, dut, output_addr, r_stb=1, r_data=0xa, w_stb=1, w_data=0x0) 165 | await ctx.tick() 166 | for n in range(4): 167 | self.assertEqual(ctx.get(dut.pins[n].oe), 1) 168 | self.assertEqual(ctx.get(dut.pins[n].o), 0) 169 | 170 | # - write 0x44 to SetClr (set pins[1] and pins[3]): 171 | await self._csr_access(ctx, dut, setclr_addr, w_stb=1, w_data=0x44) 172 | await ctx.tick() 173 | for n in range(4): 174 | self.assertEqual(ctx.get(dut.pins[n].oe), 1) 175 | self.assertEqual(ctx.get(dut.pins[0].o), 0) 176 | self.assertEqual(ctx.get(dut.pins[1].o), 1) 177 | self.assertEqual(ctx.get(dut.pins[2].o), 0) 178 | self.assertEqual(ctx.get(dut.pins[3].o), 1) 179 | 180 | # - write 0x0 to Output: 181 | await self._csr_access(ctx, dut, output_addr, r_stb=1, r_data=0xa, w_stb=1, w_data=0x0) 182 | await ctx.tick() 183 | for n in range(4): 184 | self.assertEqual(ctx.get(dut.pins[n].oe), 1) 185 | self.assertEqual(ctx.get(dut.pins[n].o), 0) 186 | 187 | # - write 0xff to SetClr (no-op): 188 | await self._csr_access(ctx, dut, setclr_addr, w_stb=1, w_data=0xff) 189 | await ctx.tick() 190 | for n in range(4): 191 | self.assertEqual(ctx.get(dut.pins[n].oe), 1) 192 | self.assertEqual(ctx.get(dut.pins[n].o), 0) 193 | 194 | # OPEN_DRAIN mode ===================================================================== 195 | 196 | # - write Mode: 197 | await self._csr_access(ctx, dut, mode_addr, w_stb=1, w_data=0b10101010) 198 | await ctx.tick() 199 | for n in range(4): 200 | self.assertEqual(ctx.get(dut.alt_mode[n]), 0) 201 | self.assertEqual(ctx.get(dut.pins[n].oe), 1) 202 | self.assertEqual(ctx.get(dut.pins[n].o), 0) 203 | 204 | # - read Input: 205 | ctx.set(dut.pins[1].i, 1) 206 | ctx.set(dut.pins[3].i, 1) 207 | await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0x0) 208 | ctx.set(dut.pins[1].i, 0) 209 | ctx.set(dut.pins[3].i, 0) 210 | await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0x0) 211 | await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0xa) 212 | await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0x0) 213 | 214 | # - write 0xf to Output: 215 | await self._csr_access(ctx, dut, output_addr, r_stb=1, r_data=0x0, w_stb=1, w_data=0xf) 216 | await ctx.tick() 217 | for n in range(4): 218 | self.assertEqual(ctx.get(dut.pins[n].oe), 0) 219 | self.assertEqual(ctx.get(dut.pins[n].o), 0) 220 | 221 | # - write 0x22 to SetClr (clear pins[0] and pins[2]): 222 | await self._csr_access(ctx, dut, setclr_addr, w_stb=1, w_data=0x22) 223 | await ctx.tick() 224 | self.assertEqual(ctx.get(dut.pins[0].oe), 1) 225 | self.assertEqual(ctx.get(dut.pins[1].oe), 0) 226 | self.assertEqual(ctx.get(dut.pins[2].oe), 1) 227 | self.assertEqual(ctx.get(dut.pins[3].oe), 0) 228 | for n in range(4): 229 | self.assertEqual(ctx.get(dut.pins[n].o), 0) 230 | 231 | # - write 0x0 to Output: 232 | await self._csr_access(ctx, dut, output_addr, r_stb=1, r_data=0xa, w_stb=1, w_data=0x0) 233 | await ctx.tick() 234 | for n in range(4): 235 | self.assertEqual(ctx.get(dut.pins[n].oe), 1) 236 | self.assertEqual(ctx.get(dut.pins[n].o), 0) 237 | 238 | # - write 0x44 to SetClr (set pins[1] and pins[3]): 239 | await self._csr_access(ctx, dut, setclr_addr, w_stb=1, w_data=0x44) 240 | await ctx.tick() 241 | self.assertEqual(ctx.get(dut.pins[0].oe), 1) 242 | self.assertEqual(ctx.get(dut.pins[1].oe), 0) 243 | self.assertEqual(ctx.get(dut.pins[2].oe), 1) 244 | self.assertEqual(ctx.get(dut.pins[3].oe), 0) 245 | for n in range(4): 246 | self.assertEqual(ctx.get(dut.pins[n].o), 0) 247 | 248 | # - write 0x0 to Output: 249 | await self._csr_access(ctx, dut, output_addr, r_stb=1, r_data=0xa, w_stb=1, w_data=0x0) 250 | await ctx.tick() 251 | for n in range(4): 252 | self.assertEqual(ctx.get(dut.pins[n].oe), 1) 253 | self.assertEqual(ctx.get(dut.pins[n].o), 0) 254 | 255 | # - write 0xff to SetClr (no-op): 256 | await self._csr_access(ctx, dut, setclr_addr, w_stb=1, w_data=0xff) 257 | await ctx.tick() 258 | for n in range(4): 259 | self.assertEqual(ctx.get(dut.pins[n].oe), 1) 260 | self.assertEqual(ctx.get(dut.pins[n].o), 0) 261 | 262 | # ALTERNATE mode ====================================================================== 263 | 264 | # - write Mode: 265 | await self._csr_access(ctx, dut, mode_addr, w_stb=1, w_data=0b11111111) 266 | await ctx.tick() 267 | for n in range(4): 268 | self.assertEqual(ctx.get(dut.alt_mode[n]), 1) 269 | self.assertEqual(ctx.get(dut.pins[n].oe), 0) 270 | self.assertEqual(ctx.get(dut.pins[n].o), 0) 271 | 272 | # - read Input: 273 | ctx.set(dut.pins[1].i, 1) 274 | ctx.set(dut.pins[3].i, 1) 275 | await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0x0) 276 | ctx.set(dut.pins[1].i, 0) 277 | ctx.set(dut.pins[3].i, 0) 278 | await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0x0) 279 | await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0xa) 280 | await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0x0) 281 | 282 | # - write 0xf to Output: 283 | await self._csr_access(ctx, dut, output_addr, r_stb=1, r_data=0x0, w_stb=1, w_data=0xf) 284 | await ctx.tick() 285 | for n in range(4): 286 | self.assertEqual(ctx.get(dut.pins[n].oe), 0) 287 | self.assertEqual(ctx.get(dut.pins[n].o), 1) 288 | 289 | # - write 0x22 to SetClr (clear pins[0] and pins[2]): 290 | await self._csr_access(ctx, dut, setclr_addr, w_stb=1, w_data=0x22) 291 | await ctx.tick() 292 | for n in range(4): 293 | self.assertEqual(ctx.get(dut.pins[n].oe), 0) 294 | self.assertEqual(ctx.get(dut.pins[0].o), 0) 295 | self.assertEqual(ctx.get(dut.pins[1].o), 1) 296 | self.assertEqual(ctx.get(dut.pins[2].o), 0) 297 | self.assertEqual(ctx.get(dut.pins[3].o), 1) 298 | 299 | # - write 0x0 to Output: 300 | await self._csr_access(ctx, dut, output_addr, r_stb=1, r_data=0xa, w_stb=1, w_data=0x0) 301 | await ctx.tick() 302 | for n in range(4): 303 | self.assertEqual(ctx.get(dut.pins[n].oe), 0) 304 | self.assertEqual(ctx.get(dut.pins[n].o), 0) 305 | 306 | # - write 0x44 to SetClr (set pins[1] and pins[3]): 307 | await self._csr_access(ctx, dut, setclr_addr, w_stb=1, w_data=0x44) 308 | await ctx.tick() 309 | for n in range(4): 310 | self.assertEqual(ctx.get(dut.pins[n].oe), 0) 311 | self.assertEqual(ctx.get(dut.pins[0].o), 0) 312 | self.assertEqual(ctx.get(dut.pins[1].o), 1) 313 | self.assertEqual(ctx.get(dut.pins[2].o), 0) 314 | self.assertEqual(ctx.get(dut.pins[3].o), 1) 315 | 316 | # - write 0x0 to Output: 317 | await self._csr_access(ctx, dut, output_addr, r_stb=1, r_data=0xa, w_stb=1, w_data=0x0) 318 | await ctx.tick() 319 | for n in range(4): 320 | self.assertEqual(ctx.get(dut.pins[n].oe), 0) 321 | self.assertEqual(ctx.get(dut.pins[n].o), 0) 322 | 323 | # - write 0xff to SetClr (no-op): 324 | await self._csr_access(ctx, dut, setclr_addr, w_stb=1, w_data=0xff) 325 | await ctx.tick() 326 | for n in range(4): 327 | self.assertEqual(ctx.get(dut.pins[n].oe), 0) 328 | self.assertEqual(ctx.get(dut.pins[n].o), 0) 329 | 330 | sim = Simulator(dut) 331 | sim.add_clock(1e-6) 332 | sim.add_testbench(testbench) 333 | with sim.write_vcd(vcd_file="test.vcd"): 334 | sim.run() 335 | 336 | def test_sim_without_input_sync(self): 337 | dut = gpio.Peripheral(pin_count=4, addr_width=2, data_width=8, input_stages=0) 338 | input_addr = 0x1 339 | 340 | async def testbench(ctx): 341 | await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0x0) 342 | ctx.set(dut.pins[1].i, 1) 343 | ctx.set(dut.pins[3].i, 1) 344 | await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0xa) 345 | ctx.set(dut.pins[1].i, 0) 346 | ctx.set(dut.pins[3].i, 0) 347 | await self._csr_access(ctx, dut, input_addr, r_stb=1, r_data=0x0) 348 | 349 | sim = Simulator(dut) 350 | sim.add_clock(1e-6) 351 | sim.add_testbench(testbench) 352 | with sim.write_vcd(vcd_file="test.vcd"): 353 | sim.run() 354 | -------------------------------------------------------------------------------- /tests/test_csr_bus.py: -------------------------------------------------------------------------------- 1 | # amaranth: UnusedElaboratable=no 2 | 3 | import unittest 4 | from amaranth import * 5 | from amaranth.lib import wiring 6 | from amaranth.lib.wiring import In, Out 7 | from amaranth.sim import * 8 | 9 | from amaranth_soc import csr 10 | from amaranth_soc.memory import MemoryMap 11 | 12 | 13 | class _MockRegister(wiring.Component): 14 | def __init__(self, width, access): 15 | super().__init__({"element": Out(csr.Element.Signature(width, access))}) 16 | 17 | 18 | class ElementSignatureTestCase(unittest.TestCase): 19 | def test_members_1_ro(self): 20 | sig = csr.Element.Signature(1, "r") 21 | self.assertEqual(sig.width, 1) 22 | self.assertEqual(sig.access, csr.Element.Access.R) 23 | self.assertEqual(sig.members, wiring.Signature({ 24 | "r_data": In(1), 25 | "r_stb": Out(1), 26 | }).members) 27 | 28 | def test_members_8_rw(self): 29 | sig = csr.Element.Signature(8, access="rw") 30 | self.assertEqual(sig.width, 8) 31 | self.assertEqual(sig.access, csr.Element.Access.RW) 32 | self.assertEqual(sig.members, wiring.Signature({ 33 | "r_data": In(8), 34 | "r_stb": Out(1), 35 | "w_data": Out(8), 36 | "w_stb": Out(1), 37 | }).members) 38 | 39 | def test_members_10_wo(self): 40 | sig = csr.Element.Signature(10, "w") 41 | self.assertEqual(sig.width, 10) 42 | self.assertEqual(sig.access, csr.Element.Access.W) 43 | self.assertEqual(sig.members, wiring.Signature({ 44 | "w_data": Out(10), 45 | "w_stb": Out(1), 46 | }).members) 47 | 48 | def test_members_0_rw(self): # degenerate but legal case 49 | sig = csr.Element.Signature(0, access=csr.Element.Access.RW) 50 | self.assertEqual(sig.width, 0) 51 | self.assertEqual(sig.access, csr.Element.Access.RW) 52 | self.assertEqual(sig.members, wiring.Signature({ 53 | "r_data": In(0), 54 | "r_stb": Out(1), 55 | "w_data": Out(0), 56 | "w_stb": Out(1), 57 | }).members) 58 | 59 | def test_create(self): 60 | sig = csr.Element.Signature(8, "rw") 61 | elem = sig.create(path=("foo", "bar")) 62 | self.assertIsInstance(elem, csr.Element) 63 | self.assertEqual(elem.width, 8) 64 | self.assertEqual(elem.access, csr.Element.Access.RW) 65 | self.assertEqual(elem.r_stb.name, "foo__bar__r_stb") 66 | self.assertEqual(elem.signature, sig) 67 | 68 | def test_eq(self): 69 | self.assertEqual(csr.Element.Signature(8, "r"), csr.Element.Signature(8, "r")) 70 | self.assertEqual(csr.Element.Signature(8, "r"), 71 | csr.Element.Signature(8, csr.Element.Access.R)) 72 | # different width 73 | self.assertNotEqual(csr.Element.Signature(8, "r"), csr.Element.Signature(1, "r")) 74 | # different access mode 75 | self.assertNotEqual(csr.Element.Signature(8, "r"), csr.Element.Signature(8, "w")) 76 | self.assertNotEqual(csr.Element.Signature(8, "r"), csr.Element.Signature(8, "rw")) 77 | self.assertNotEqual(csr.Element.Signature(8, "w"), csr.Element.Signature(8, "rw")) 78 | 79 | def test_wrong_width(self): 80 | with self.assertRaisesRegex(TypeError, 81 | r"Width must be a non-negative integer, not -1"): 82 | csr.Element.Signature(-1, "rw") 83 | 84 | def test_wrong_access(self): 85 | with self.assertRaisesRegex(ValueError, 86 | r"'wo' is not a valid Element.Access"): 87 | csr.Element.Signature(width=1, access="wo") 88 | 89 | 90 | class ElementTestCase(unittest.TestCase): 91 | def test_simple(self): 92 | elem = csr.Element(8, "rw", path=("foo", "bar")) 93 | self.assertEqual(elem.width, 8) 94 | self.assertEqual(elem.access, csr.Element.Access.RW) 95 | self.assertEqual(elem.r_stb.name, "foo__bar__r_stb") 96 | 97 | 98 | class SignatureTestCase(unittest.TestCase): 99 | def test_simple(self): 100 | sig = csr.Signature(addr_width=16, data_width=8) 101 | self.assertEqual(sig.addr_width, 16) 102 | self.assertEqual(sig.data_width, 8) 103 | self.assertEqual(sig.members, wiring.Signature({ 104 | "addr": Out(16), 105 | "r_data": In(8), 106 | "r_stb": Out(1), 107 | "w_data": Out(8), 108 | "w_stb": Out(1) 109 | }).members) 110 | 111 | def test_create(self): 112 | sig = csr.Signature(addr_width=16, data_width=8) 113 | iface = sig.create(path=("foo", "bar")) 114 | self.assertIsInstance(iface, csr.Interface) 115 | self.assertEqual(iface.addr_width, 16) 116 | self.assertEqual(iface.data_width, 8) 117 | self.assertEqual(iface.r_stb.name, "foo__bar__r_stb") 118 | self.assertEqual(iface.signature, sig) 119 | 120 | def test_eq(self): 121 | self.assertEqual(csr.Signature(addr_width=32, data_width=8), 122 | csr.Signature(addr_width=32, data_width=8)) 123 | # different addr_width 124 | self.assertNotEqual(csr.Signature(addr_width=16, data_width=16), 125 | csr.Signature(addr_width=32, data_width=16)) 126 | # different data_width 127 | self.assertNotEqual(csr.Signature(addr_width=32, data_width=8), 128 | csr.Signature(addr_width=32, data_width=16)) 129 | 130 | def test_wrong_addr_width(self): 131 | with self.assertRaisesRegex(TypeError, 132 | r"Address width must be a positive integer, not -1"): 133 | csr.Signature(addr_width=-1, data_width=8) 134 | 135 | def test_wrong_data_width(self): 136 | with self.assertRaisesRegex(TypeError, 137 | r"Data width must be a positive integer, not -1"): 138 | csr.Signature(addr_width=16, data_width=-1) 139 | 140 | 141 | class InterfaceTestCase(unittest.TestCase): 142 | def test_simple(self): 143 | iface = csr.Interface(addr_width=12, data_width=8, path=("foo", "bar")) 144 | self.assertEqual(iface.addr_width, 12) 145 | self.assertEqual(iface.data_width, 8) 146 | self.assertEqual(iface.r_stb.name, "foo__bar__r_stb") 147 | 148 | def test_set_map(self): 149 | iface = csr.Interface(addr_width=12, data_width=8) 150 | memory_map = MemoryMap(addr_width=12, data_width=8) 151 | iface.memory_map = memory_map 152 | self.assertIs(iface.memory_map, memory_map) 153 | 154 | def test_get_map_none(self): 155 | iface = csr.Interface(addr_width=16, data_width=8) 156 | with self.assertRaisesRegex(AttributeError, 157 | r"csr.Interface\(.*\) does not have a memory map"): 158 | iface.memory_map 159 | 160 | def test_set_wrong_map(self): 161 | iface = csr.Interface(addr_width=16, data_width=8) 162 | with self.assertRaisesRegex(TypeError, 163 | r"Memory map must be an instance of MemoryMap, not 'foo'"): 164 | iface.memory_map = "foo" 165 | 166 | def test_set_wrong_map_addr_width(self): 167 | iface = csr.Interface(addr_width=8, data_width=8) 168 | with self.assertRaisesRegex(ValueError, 169 | r"Memory map has address width 7, which is not the same as bus interface address " 170 | r"width 8"): 171 | iface.memory_map = MemoryMap(addr_width=7, data_width=8) 172 | 173 | def test_set_wrong_map_data_width(self): 174 | iface = csr.Interface(addr_width=8, data_width=8) 175 | with self.assertRaisesRegex(ValueError, 176 | r"Memory map has data width 7, which is not the same as bus interface data width " 177 | r"8"): 178 | iface.memory_map = MemoryMap(addr_width=8, data_width=7) 179 | 180 | 181 | class MultiplexerTestCase(unittest.TestCase): 182 | def test_memory_map(self): 183 | reg_4_rw = _MockRegister(4, "rw") 184 | reg_8_rw = _MockRegister(8, "rw") 185 | 186 | memory_map = MemoryMap(addr_width=2, data_width=4) 187 | memory_map.add_resource(reg_4_rw, name=("reg_4_rw",), size=1) 188 | memory_map.add_resource(reg_8_rw, name=("reg_8_rw",), size=2) 189 | memory_map.freeze() 190 | 191 | dut = csr.Multiplexer(memory_map) 192 | 193 | self.assertIs(dut.bus.memory_map, memory_map) 194 | self.assertEqual(dut.bus.addr_width, 2) 195 | self.assertEqual(dut.bus.data_width, 4) 196 | 197 | def test_wrong_memory_map(self): 198 | with self.assertRaisesRegex(TypeError, 199 | r"CSR multiplexer memory map must be an instance of MemoryMap, not 'foo'"): 200 | csr.Multiplexer("foo") 201 | 202 | def test_wrong_memory_map_resource(self): 203 | class _Reg(wiring.Component): 204 | pass 205 | # wrong name 206 | map_0 = MemoryMap(addr_width=1, data_width=8) 207 | map_0.add_resource(_Reg({"foo": Out(csr.Element.Signature(8, "rw"))}), name=("a",), size=1) 208 | with self.assertRaisesRegex(AttributeError, 209 | r"Signature of CSR register Name\('a'\) must have a csr\.Element\.Signature " 210 | r"member named 'element' and oriented as wiring\.Out"): 211 | csr.Multiplexer(map_0) 212 | # wrong direction 213 | map_1 = MemoryMap(addr_width=1, data_width=8) 214 | map_1.add_resource(_Reg({"element": In(csr.Element.Signature(8, "rw"))}), name=("a",), 215 | size=1) 216 | with self.assertRaisesRegex(AttributeError, 217 | r"Signature of CSR register Name\('a'\) must have a csr\.Element\.Signature " 218 | r"member named 'element' and oriented as wiring\.Out"): 219 | csr.Multiplexer(map_1) 220 | # wrong member type 221 | map_2 = MemoryMap(addr_width=1, data_width=8) 222 | map_2.add_resource(_Reg({"element": Out(unsigned(8))}), name=("a",), size=1) 223 | with self.assertRaisesRegex(AttributeError, 224 | r"Signature of CSR register Name\('a'\) must have a csr\.Element\.Signature " 225 | r"member named 'element' and oriented as wiring\.Out"): 226 | csr.Multiplexer(map_2) 227 | # wrong member signature 228 | map_3 = MemoryMap(addr_width=1, data_width=8) 229 | map_3.add_resource(_Reg({"element": Out(wiring.Signature({}))}), name=("a",), size=1) 230 | with self.assertRaisesRegex(AttributeError, 231 | r"Signature of CSR register Name\('a'\) must have a csr\.Element\.Signature " 232 | r"member named 'element' and oriented as wiring\.Out"): 233 | csr.Multiplexer(map_3) 234 | 235 | def test_wrong_memory_map_windows(self): 236 | memory_map_0 = MemoryMap(addr_width=1, data_width=8) 237 | memory_map_1 = MemoryMap(addr_width=1, data_width=8) 238 | memory_map_0.add_window(memory_map_1) 239 | with self.assertRaisesRegex(ValueError, r"CSR multiplexer memory map cannot have windows"): 240 | csr.Multiplexer(memory_map_0) 241 | 242 | def test_sim(self): 243 | for shadow_overlaps in [None, 0, 1]: 244 | with self.subTest(shadow_overlaps=shadow_overlaps): 245 | reg_4_r = _MockRegister( 4, "r") 246 | reg_8_w = _MockRegister( 8, "w") 247 | reg_16_rw = _MockRegister(16, "rw") 248 | 249 | memory_map = MemoryMap(addr_width=16, data_width=8) 250 | memory_map.add_resource(reg_4_r, name=("reg_4_r",), size=1) 251 | memory_map.add_resource(reg_8_w, name=("reg_8_w",), size=1) 252 | memory_map.add_resource(reg_16_rw, name=("reg_16_rw",), size=2) 253 | 254 | dut = csr.Multiplexer(memory_map, shadow_overlaps=shadow_overlaps) 255 | 256 | async def testbench(ctx): 257 | ctx.set(reg_4_r.element.r_data, 0xa) 258 | ctx.set(reg_16_rw.element.r_data, 0x5aa5) 259 | 260 | ctx.set(dut.bus.addr, 0) 261 | ctx.set(dut.bus.r_stb, 1) 262 | await ctx.tick() 263 | self.assertEqual(ctx.get(reg_4_r.element.r_stb), 1) 264 | self.assertEqual(ctx.get(reg_16_rw.element.r_stb), 0) 265 | self.assertEqual(ctx.get(dut.bus.r_data), 0xa) 266 | 267 | ctx.set(dut.bus.addr, 2) 268 | await ctx.tick() 269 | self.assertEqual(ctx.get(reg_4_r.element.r_stb), 0) 270 | self.assertEqual(ctx.get(reg_16_rw.element.r_stb), 1) 271 | self.assertEqual(ctx.get(dut.bus.r_data), 0xa5) 272 | 273 | ctx.set(dut.bus.addr, 3) # pipeline a read 274 | await ctx.tick() 275 | self.assertEqual(ctx.get(reg_4_r.element.r_stb), 0) 276 | self.assertEqual(ctx.get(reg_16_rw.element.r_stb), 0) 277 | self.assertEqual(ctx.get(dut.bus.r_data), 0x5a) 278 | ctx.set(dut.bus.r_stb, 0) 279 | 280 | ctx.set(dut.bus.addr, 1) 281 | ctx.set(dut.bus.w_data, 0x3d) 282 | ctx.set(dut.bus.w_stb, 1) 283 | await ctx.tick() 284 | self.assertEqual(ctx.get(reg_8_w.element.w_stb), 1) 285 | self.assertEqual(ctx.get(reg_8_w.element.w_data), 0x3d) 286 | self.assertEqual(ctx.get(reg_16_rw.element.w_stb), 0) 287 | 288 | ctx.set(dut.bus.w_stb, 0) 289 | ctx.set(dut.bus.addr, 2) # change address 290 | await ctx.tick() 291 | self.assertEqual(ctx.get(reg_8_w.element.w_stb), 0) 292 | 293 | ctx.set(dut.bus.addr, 2) 294 | ctx.set(dut.bus.w_data, 0x55) 295 | ctx.set(dut.bus.w_stb, 1) 296 | await ctx.tick() 297 | self.assertEqual(ctx.get(reg_8_w.element.w_stb), 0) 298 | self.assertEqual(ctx.get(reg_16_rw.element.w_stb), 0) 299 | ctx.set(dut.bus.addr, 3) # pipeline a write 300 | ctx.set(dut.bus.w_data, 0xaa) 301 | await ctx.tick() 302 | self.assertEqual(ctx.get(reg_8_w.element.w_stb), 0) 303 | self.assertEqual(ctx.get(reg_16_rw.element.w_stb), 1) 304 | self.assertEqual(ctx.get(reg_16_rw.element.w_data), 0xaa55) 305 | 306 | ctx.set(dut.bus.addr, 2) 307 | ctx.set(dut.bus.r_stb, 1) 308 | ctx.set(dut.bus.w_data, 0x66) 309 | ctx.set(dut.bus.w_stb, 1) 310 | await ctx.tick() 311 | self.assertEqual(ctx.get(reg_16_rw.element.r_stb), 1) 312 | self.assertEqual(ctx.get(reg_16_rw.element.w_stb), 0) 313 | self.assertEqual(ctx.get(dut.bus.r_data), 0xa5) 314 | ctx.set(dut.bus.addr, 3) # pipeline a read and a write 315 | ctx.set(dut.bus.w_data, 0xbb) 316 | await ctx.tick() 317 | self.assertEqual(ctx.get(dut.bus.r_data), 0x5a) 318 | self.assertEqual(ctx.get(reg_16_rw.element.r_stb), 0) 319 | self.assertEqual(ctx.get(reg_16_rw.element.w_stb), 1) 320 | self.assertEqual(ctx.get(reg_16_rw.element.w_data), 0xbb66) 321 | 322 | sim = Simulator(dut) 323 | sim.add_clock(1e-6) 324 | sim.add_testbench(testbench) 325 | with sim.write_vcd(vcd_file="test.vcd"): 326 | sim.run() 327 | 328 | 329 | class MultiplexerAlignedTestCase(unittest.TestCase): 330 | def test_sim(self): 331 | for shadow_overlaps in [None, 0, 1]: 332 | with self.subTest(shadow_overlaps=shadow_overlaps): 333 | reg_20_rw = _MockRegister(20, "rw") 334 | memory_map = MemoryMap(addr_width=16, data_width=8, alignment=2) 335 | memory_map.add_resource(reg_20_rw, name=("reg_20_rw",), size=3) 336 | 337 | dut = csr.Multiplexer(memory_map, shadow_overlaps=shadow_overlaps) 338 | 339 | async def testbench(ctx): 340 | ctx.set(dut.bus.w_stb, 1) 341 | ctx.set(dut.bus.addr, 0) 342 | ctx.set(dut.bus.w_data, 0x55) 343 | await ctx.tick() 344 | self.assertEqual(ctx.get(reg_20_rw.element.w_stb), 0) 345 | ctx.set(dut.bus.addr, 1) 346 | ctx.set(dut.bus.w_data, 0xaa) 347 | await ctx.tick() 348 | self.assertEqual(ctx.get(reg_20_rw.element.w_stb), 0) 349 | ctx.set(dut.bus.addr, 2) 350 | ctx.set(dut.bus.w_data, 0x33) 351 | await ctx.tick() 352 | self.assertEqual(ctx.get(reg_20_rw.element.w_stb), 0) 353 | ctx.set(dut.bus.addr, 3) 354 | ctx.set(dut.bus.w_data, 0xdd) 355 | await ctx.tick() 356 | self.assertEqual(ctx.get(reg_20_rw.element.w_stb), 1) 357 | self.assertEqual(ctx.get(reg_20_rw.element.w_data), 0x3aa55) 358 | 359 | sim = Simulator(dut) 360 | sim.add_clock(1e-6) 361 | sim.add_testbench(testbench) 362 | with sim.write_vcd(vcd_file="test.vcd"): 363 | sim.run() 364 | 365 | 366 | class DecoderTestCase(unittest.TestCase): 367 | def setUp(self): 368 | self.dut = csr.Decoder(addr_width=16, data_width=8) 369 | 370 | def test_align_to(self): 371 | sub_1 = csr.Interface(addr_width=10, data_width=8) 372 | sub_1.memory_map = MemoryMap(addr_width=10, data_width=8) 373 | self.assertEqual(self.dut.add(sub_1), (0, 0x400, 1)) 374 | 375 | self.assertEqual(self.dut.align_to(12), 0x1000) 376 | self.assertEqual(self.dut.align_to(alignment=12), 0x1000) 377 | 378 | sub_2 = csr.Interface(addr_width=10, data_width=8) 379 | sub_2.memory_map = MemoryMap(addr_width=10, data_width=8) 380 | self.assertEqual(self.dut.add(sub_2), (0x1000, 0x1400, 1)) 381 | 382 | def test_add_wrong_sub_bus(self): 383 | with self.assertRaisesRegex(TypeError, 384 | r"Subordinate bus must be an instance of csr\.Interface, not 1"): 385 | self.dut.add(sub_bus=1) 386 | 387 | def test_add_wrong_data_width(self): 388 | mux = csr.Multiplexer(MemoryMap(addr_width=10, data_width=16)) 389 | Fragment.get(mux, platform=None) # silence UnusedElaboratable 390 | 391 | with self.assertRaisesRegex(ValueError, 392 | r"Subordinate bus has data width 16, which is not the same as " 393 | r"decoder data width 8"): 394 | self.dut.add(mux.bus) 395 | 396 | def test_add_wrong_out_of_bounds(self): 397 | iface = csr.Interface(addr_width=17, data_width=8) 398 | iface.memory_map = MemoryMap(addr_width=17, data_width=8) 399 | with self.assertRaisesRegex(ValueError, 400 | r"Address range 0x0\.\.0x20000 out of bounds for memory map spanning " 401 | r"range 0x0\.\.0x10000 \(16 address bits\)"): 402 | self.dut.add(iface) 403 | 404 | def test_sim(self): 405 | reg_1 = _MockRegister(8, "rw") 406 | reg_2 = _MockRegister(8, "rw") 407 | 408 | memory_map_1 = MemoryMap(addr_width=10, data_width=8) 409 | memory_map_1.add_resource(reg_1, name=("reg_1",), size=1) 410 | 411 | memory_map_2 = MemoryMap(addr_width=10, data_width=8) 412 | memory_map_2.add_resource(reg_2, name=("reg_2",), size=1, addr=2) 413 | 414 | mux_1 = csr.Multiplexer(memory_map_1) 415 | mux_2 = csr.Multiplexer(memory_map_2) 416 | 417 | self.dut.add(mux_1.bus) 418 | self.dut.add(mux_2.bus) 419 | 420 | reg_1_info = self.dut.bus.memory_map.find_resource(reg_1) 421 | reg_2_info = self.dut.bus.memory_map.find_resource(reg_2) 422 | reg_1_addr = reg_1_info.start 423 | reg_2_addr = reg_2_info.start 424 | self.assertEqual(reg_1_addr, 0x0000) 425 | self.assertEqual(reg_2_addr, 0x0402) 426 | 427 | async def testbench(ctx): 428 | ctx.set(self.dut.bus.addr, reg_1_addr) 429 | ctx.set(self.dut.bus.w_stb, 1) 430 | ctx.set(self.dut.bus.w_data, 0x55) 431 | await ctx.tick() 432 | ctx.set(self.dut.bus.w_stb, 0) 433 | await ctx.tick() 434 | self.assertEqual(ctx.get(reg_1.element.w_data), 0x55) 435 | 436 | ctx.set(self.dut.bus.addr, reg_2_addr) 437 | ctx.set(self.dut.bus.w_stb, 1) 438 | ctx.set(self.dut.bus.w_data, 0xaa) 439 | await ctx.tick() 440 | ctx.set(self.dut.bus.w_stb, 0) 441 | await ctx.tick() 442 | self.assertEqual(ctx.get(reg_2.element.w_data), 0xaa) 443 | 444 | ctx.set(reg_1.element.r_data, 0x55) 445 | ctx.set(reg_2.element.r_data, 0xaa) 446 | 447 | ctx.set(self.dut.bus.addr, reg_1_addr) 448 | ctx.set(self.dut.bus.r_stb, 1) 449 | await ctx.tick() 450 | ctx.set(self.dut.bus.addr, reg_2_addr) 451 | self.assertEqual(ctx.get(self.dut.bus.r_data), 0x55) 452 | await ctx.tick() 453 | self.assertEqual(ctx.get(self.dut.bus.r_data), 0xaa) 454 | 455 | m = Module() 456 | m.submodules.dut = self.dut 457 | m.submodules.mux_1 = mux_1 458 | m.submodules.mux_2 = mux_2 459 | 460 | sim = Simulator(m) 461 | sim.add_clock(1e-6) 462 | sim.add_testbench(testbench) 463 | with sim.write_vcd(vcd_file="test.vcd"): 464 | sim.run() 465 | -------------------------------------------------------------------------------- /amaranth_soc/wishbone/bus.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.lib import enum, wiring 3 | from amaranth.lib.wiring import In, Out, flipped 4 | from amaranth.utils import exact_log2 5 | 6 | from ..memory import MemoryMap 7 | 8 | 9 | __all__ = ["CycleType", "BurstTypeExt", "Feature", "Signature", "Interface", "Decoder", "Arbiter"] 10 | 11 | 12 | class CycleType(enum.Enum): 13 | """Wishbone Registered Feedback cycle type.""" 14 | CLASSIC = 0b000 15 | CONST_BURST = 0b001 16 | INCR_BURST = 0b010 17 | END_OF_BURST = 0b111 18 | 19 | 20 | class BurstTypeExt(enum.Enum): 21 | """Wishbone Registered Feedback burst type extension.""" 22 | LINEAR = 0b00 23 | WRAP_4 = 0b01 24 | WRAP_8 = 0b10 25 | WRAP_16 = 0b11 26 | 27 | 28 | class Feature(enum.Enum): 29 | """Optional Wishbone interface signals.""" 30 | ERR = "err" 31 | RTY = "rty" 32 | STALL = "stall" 33 | LOCK = "lock" 34 | CTI = "cti" 35 | BTE = "bte" 36 | 37 | 38 | class Signature(wiring.Signature): 39 | """Wishbone interface signature. 40 | 41 | See the `Wishbone specification `_ for description 42 | of the Wishbone signals. The ``RST_I`` and ``CLK_I`` signals are provided as a part of 43 | the clock domain that drives the interface. 44 | 45 | Parameters 46 | ---------- 47 | addr_width : int 48 | Width of the address signal. 49 | data_width : ``8``, ``16``, ``32`` or ``64`` 50 | Width of the data signals ("port size" in Wishbone terminology). 51 | granularity : ``8``, ``16``, ``32``, ``64`` or ``None`` 52 | Granularity of select signals ("port granularity" in Wishbone terminology). 53 | Optional. If ``None`` (by default), the granularity is equal to ``data_width``. 54 | features : iter(:class:`Feature`) 55 | Selects additional signals that will be a part of this interface. 56 | Optional. 57 | 58 | Interface attributes 59 | -------------------- 60 | The correspondence between the Amaranth-SoC signals and the Wishbone signals changes depending 61 | on whether the interface acts as an initiator or a target. 62 | 63 | adr : Signal(addr_width) 64 | Corresponds to Wishbone signal ``ADR_O`` (initiator) or ``ADR_I`` (target). 65 | dat_w : Signal(data_width) 66 | Corresponds to Wishbone signal ``DAT_O`` (initiator) or ``DAT_I`` (target). 67 | dat_r : Signal(data_width) 68 | Corresponds to Wishbone signal ``DAT_I`` (initiator) or ``DAT_O`` (target). 69 | sel : Signal(data_width // granularity) 70 | Corresponds to Wishbone signal ``SEL_O`` (initiator) or ``SEL_I`` (target). 71 | cyc : Signal() 72 | Corresponds to Wishbone signal ``CYC_O`` (initiator) or ``CYC_I`` (target). 73 | stb : Signal() 74 | Corresponds to Wishbone signal ``STB_O`` (initiator) or ``STB_I`` (target). 75 | we : Signal() 76 | Corresponds to Wishbone signal ``WE_O`` (initiator) or ``WE_I`` (target). 77 | ack : Signal() 78 | Corresponds to Wishbone signal ``ACK_I`` (initiator) or ``ACK_O`` (target). 79 | err : Signal() 80 | Optional. Corresponds to Wishbone signal ``ERR_I`` (initiator) or ``ERR_O`` (target). 81 | rty : Signal() 82 | Optional. Corresponds to Wishbone signal ``RTY_I`` (initiator) or ``RTY_O`` (target). 83 | stall : Signal() 84 | Optional. Corresponds to Wishbone signal ``STALL_I`` (initiator) or ``STALL_O`` (target). 85 | lock : Signal() 86 | Optional. Corresponds to Wishbone signal ``LOCK_O`` (initiator) or ``LOCK_I`` (target). 87 | Amaranth-SoC Wishbone support assumes that initiators that don't want bus arbitration to 88 | happen in between two transactions need to use ``lock`` feature to guarantee this. An 89 | initiator without the ``lock`` feature may be arbitrated in between two transactions even 90 | if ``cyc`` is kept high. 91 | cti : Signal() 92 | Optional. Corresponds to Wishbone signal ``CTI_O`` (initiator) or ``CTI_I`` (target). 93 | bte : Signal() 94 | Optional. Corresponds to Wishbone signal ``BTE_O`` (initiator) or ``BTE_I`` (target). 95 | """ 96 | def __init__(self, *, addr_width, data_width, granularity=None, features=frozenset()): 97 | if granularity is None: 98 | granularity = data_width 99 | 100 | if not isinstance(addr_width, int) or addr_width < 0: 101 | raise TypeError(f"Address width must be a non-negative integer, not {addr_width!r}") 102 | if data_width not in (8, 16, 32, 64): 103 | raise ValueError(f"Data width must be one of 8, 16, 32, 64, not {data_width!r}") 104 | if granularity not in (8, 16, 32, 64): 105 | raise ValueError(f"Granularity must be one of 8, 16, 32, 64, not {granularity!r}") 106 | if granularity > data_width: 107 | raise ValueError(f"Granularity {granularity} may not be greater than data width " 108 | f"{data_width}") 109 | for feature in features: 110 | Feature(feature) # raises ValueError if feature is invalid 111 | 112 | self._addr_width = addr_width 113 | self._data_width = data_width 114 | self._granularity = granularity 115 | self._features = frozenset(Feature(f) for f in features) 116 | 117 | members = { 118 | "adr": Out(self.addr_width), 119 | "dat_w": Out(self.data_width), 120 | "dat_r": In(self.data_width), 121 | "sel": Out(self.data_width // self.granularity), 122 | "cyc": Out(1), 123 | "stb": Out(1), 124 | "we": Out(1), 125 | "ack": In(1), 126 | } 127 | if Feature.ERR in self.features: 128 | members["err"] = In(1) 129 | if Feature.RTY in self.features: 130 | members["rty"] = In(1) 131 | if Feature.STALL in self.features: 132 | members["stall"] = In(1) 133 | if Feature.LOCK in self.features: 134 | members["lock"] = Out(1) 135 | if Feature.CTI in self.features: 136 | members["cti"] = Out(CycleType) 137 | if Feature.BTE in self.features: 138 | members["bte"] = Out(BurstTypeExt) 139 | super().__init__(members) 140 | 141 | @property 142 | def addr_width(self): 143 | return self._addr_width 144 | 145 | @property 146 | def data_width(self): 147 | return self._data_width 148 | 149 | @property 150 | def granularity(self): 151 | return self._granularity 152 | 153 | @property 154 | def features(self): 155 | return self._features 156 | 157 | def create(self, *, path=None, src_loc_at=0): 158 | """Create a compatible interface. 159 | 160 | See :meth:`wiring.Signature.create` for details. 161 | 162 | Returns 163 | ------- 164 | An :class:`Interface` object using this signature. 165 | """ 166 | return Interface(addr_width=self.addr_width, data_width=self.data_width, 167 | granularity=self.granularity, features=self.features, 168 | path=path, src_loc_at=1 + src_loc_at) 169 | 170 | def __eq__(self, other): 171 | """Compare signatures. 172 | 173 | Two signatures are equal if they have the same address width, data width, granularity and 174 | features. 175 | """ 176 | return (isinstance(other, Signature) and 177 | self.addr_width == other.addr_width and 178 | self.data_width == other.data_width and 179 | self.granularity == other.granularity and 180 | self.features == other.features) 181 | 182 | def __repr__(self): 183 | return f"wishbone.Signature({self.members!r})" 184 | 185 | 186 | class Interface(wiring.PureInterface): 187 | """Wishbone bus interface. 188 | 189 | Note that the data width of the underlying memory map of the interface is equal to port 190 | granularity, not port size. If port granularity is less than port size, then the address width 191 | of the underlying memory map is extended to reflect that. 192 | 193 | Parameters 194 | ---------- 195 | addr_width : :class:`int` 196 | Width of the address signal. See :class:`Signature`. 197 | data_width : :class:`int` 198 | Width of the data signals. See :class:`Signature`. 199 | granularity : :class:`int` 200 | Granularity of select signals. Optional. See :class:`Signature`. 201 | features : iter(:class:`Feature`) 202 | Describes additional signals of this interface. Optional. See :class:`Signature`. 203 | path : iter(:class:`str`) 204 | Path to this Wishbone interface. Optional. See :class:`wiring.PureInterface`. 205 | 206 | Attributes 207 | ---------- 208 | memory_map: :class:`MemoryMap` 209 | Memory map of the bus. Optional. 210 | """ 211 | def __init__(self, *, addr_width, data_width, granularity=None, features=frozenset(), 212 | path=None, src_loc_at=0): 213 | super().__init__(Signature(addr_width=addr_width, data_width=data_width, 214 | granularity=granularity, features=features), 215 | path=path, src_loc_at=1 + src_loc_at) 216 | self._memory_map = None 217 | 218 | @property 219 | def addr_width(self): 220 | return self.signature.addr_width 221 | 222 | @property 223 | def data_width(self): 224 | return self.signature.data_width 225 | 226 | @property 227 | def granularity(self): 228 | return self.signature.granularity 229 | 230 | @property 231 | def features(self): 232 | return self.signature.features 233 | 234 | @property 235 | def memory_map(self): 236 | if self._memory_map is None: 237 | raise AttributeError(f"{self!r} does not have a memory map") 238 | return self._memory_map 239 | 240 | @memory_map.setter 241 | def memory_map(self, memory_map): 242 | if not isinstance(memory_map, MemoryMap): 243 | raise TypeError(f"Memory map must be an instance of MemoryMap, not {memory_map!r}") 244 | if memory_map.data_width != self.granularity: 245 | raise ValueError(f"Memory map has data width {memory_map.data_width}, which is " 246 | f"not the same as bus interface granularity {self.granularity}") 247 | granularity_bits = exact_log2(self.data_width // self.granularity) 248 | effective_addr_width = self.addr_width + granularity_bits 249 | if memory_map.addr_width != max(1, effective_addr_width): 250 | raise ValueError(f"Memory map has address width {memory_map.addr_width}, which is " 251 | f"not the same as the bus interface effective address width " 252 | f"{effective_addr_width} (= {self.addr_width} address bits + " 253 | f"{granularity_bits} granularity bits)") 254 | self._memory_map = memory_map 255 | 256 | def __repr__(self): 257 | return f"wishbone.Interface({self.signature!r})" 258 | 259 | 260 | class Decoder(wiring.Component): 261 | """Wishbone bus decoder. 262 | 263 | An address decoder for subordinate Wishbone buses. 264 | 265 | Parameters 266 | ---------- 267 | addr_width : :class:`int` 268 | Address width. See :class:`Signature`. 269 | data_width : :class:`int` 270 | Data width. See :class:`Signature`. 271 | granularity : :class:`int` 272 | Granularity. See :class:`Signature` 273 | features : iter(:class:`Feature`) 274 | Optional signal set. See :class:`Signature`. 275 | alignment : int, power-of-2 exponent 276 | Window alignment. Optional. See :class:`..memory.MemoryMap`. 277 | 278 | Attributes 279 | ---------- 280 | bus : :class:`Interface` 281 | Wishbone bus providing access to subordinate buses. 282 | """ 283 | def __init__(self, *, addr_width, data_width, granularity=None, features=frozenset(), 284 | alignment=0, name=None): 285 | if granularity is None: 286 | granularity = data_width 287 | super().__init__({"bus": In(Signature(addr_width=addr_width, data_width=data_width, 288 | granularity=granularity, features=features))}) 289 | self.bus.memory_map = MemoryMap( 290 | addr_width=max(1, addr_width + exact_log2(data_width // granularity)), 291 | data_width=granularity, alignment=alignment) 292 | self._subs = dict() 293 | 294 | def align_to(self, alignment): 295 | """Align the implicit address of the next window. 296 | 297 | See :meth:`MemoryMap.align_to` for details. 298 | """ 299 | return self.bus.memory_map.align_to(alignment) 300 | 301 | def add(self, sub_bus, *, name=None, addr=None, sparse=False): 302 | """Add a window to a subordinate bus. 303 | 304 | The decoder can perform either sparse or dense address translation. If dense address 305 | translation is used (the default), the subordinate bus must have the same data width as 306 | the decoder; the window will be contiguous. If sparse address translation is used, 307 | the subordinate bus may have data width less than the data width of the decoder; 308 | the window may be discontiguous. In either case, the granularity of the subordinate bus 309 | must be equal to or less than the granularity of the decoder. 310 | 311 | See :meth:`MemoryMap.add_resource` for details. 312 | """ 313 | if isinstance(sub_bus, wiring.FlippedInterface): 314 | sub_bus_unflipped = flipped(sub_bus) 315 | else: 316 | sub_bus_unflipped = sub_bus 317 | if not isinstance(sub_bus_unflipped, Interface): 318 | raise TypeError(f"Subordinate bus must be an instance of wishbone.Interface, not " 319 | f"{sub_bus_unflipped!r}") 320 | if sub_bus.granularity > self.bus.granularity: 321 | raise ValueError(f"Subordinate bus has granularity {sub_bus.granularity}, which is " 322 | f"greater than the decoder granularity {self.bus.granularity}") 323 | if not sparse: 324 | if sub_bus.data_width != self.bus.data_width: 325 | raise ValueError(f"Subordinate bus has data width {sub_bus.data_width}, which is " 326 | f"not the same as decoder data width {self.bus.data_width} " 327 | f"(required for dense address translation)") 328 | else: 329 | if sub_bus.granularity != sub_bus.data_width: 330 | raise ValueError(f"Subordinate bus has data width {sub_bus.data_width}, which is " 331 | f"not the same as its granularity {sub_bus.granularity} " 332 | f"(required for sparse address translation)") 333 | for opt_output in {"err", "rty", "stall"}: 334 | if hasattr(sub_bus, opt_output) and Feature(opt_output) not in self.bus.features: 335 | raise ValueError(f"Subordinate bus has optional output {opt_output!r}, but the " 336 | f"decoder does not have a corresponding input") 337 | 338 | self._subs[sub_bus.memory_map] = sub_bus 339 | return self.bus.memory_map.add_window(sub_bus.memory_map, name=name, addr=addr, 340 | sparse=sparse) 341 | 342 | def elaborate(self, platform): 343 | m = Module() 344 | 345 | ack_fanin = 0 346 | err_fanin = 0 347 | rty_fanin = 0 348 | stall_fanin = 0 349 | 350 | with m.Switch(self.bus.adr): 351 | for sub_map, sub_name, (sub_pat, sub_ratio) in self.bus.memory_map.window_patterns(): 352 | sub_bus = self._subs[sub_map] 353 | 354 | m.d.comb += [ 355 | sub_bus.adr.eq(self.bus.adr << exact_log2(sub_ratio)), 356 | sub_bus.dat_w.eq(self.bus.dat_w), 357 | sub_bus.sel.eq(Cat(sel.replicate(sub_ratio) for sel in self.bus.sel)), 358 | sub_bus.we.eq(self.bus.we), 359 | sub_bus.stb.eq(self.bus.stb), 360 | ] 361 | if hasattr(sub_bus, "lock"): 362 | m.d.comb += sub_bus.lock.eq(getattr(self.bus, "lock", 0)) 363 | if hasattr(sub_bus, "cti"): 364 | m.d.comb += sub_bus.cti.eq(getattr(self.bus, "cti", CycleType.CLASSIC)) 365 | if hasattr(sub_bus, "bte"): 366 | m.d.comb += sub_bus.bte.eq(getattr(self.bus, "bte", BurstTypeExt.LINEAR)) 367 | 368 | granularity_bits = exact_log2(self.bus.data_width // self.bus.granularity) 369 | with m.Case(sub_pat[:-granularity_bits if granularity_bits > 0 else None]): 370 | m.d.comb += [ 371 | sub_bus.cyc.eq(self.bus.cyc), 372 | self.bus.dat_r.eq(sub_bus.dat_r), 373 | ] 374 | ack_fanin |= sub_bus.ack 375 | if hasattr(sub_bus, "err"): 376 | err_fanin |= sub_bus.err 377 | if hasattr(sub_bus, "rty"): 378 | rty_fanin |= sub_bus.rty 379 | if hasattr(sub_bus, "stall"): 380 | stall_fanin |= sub_bus.stall 381 | 382 | m.d.comb += self.bus.ack.eq(ack_fanin) 383 | if hasattr(self.bus, "err"): 384 | m.d.comb += self.bus.err.eq(err_fanin) 385 | if hasattr(self.bus, "rty"): 386 | m.d.comb += self.bus.rty.eq(rty_fanin) 387 | if hasattr(self.bus, "stall"): 388 | m.d.comb += self.bus.stall.eq(stall_fanin) 389 | 390 | return m 391 | 392 | 393 | class Arbiter(wiring.Component): 394 | """Wishbone bus arbiter. 395 | 396 | A round-robin arbiter for initiators accessing a shared Wishbone bus. 397 | 398 | Parameters 399 | ---------- 400 | addr_width : int 401 | Address width. See :class:`Signature`. 402 | data_width : int 403 | Data width. See :class:`Signature`. 404 | granularity : int 405 | Granularity. See :class:`Signature` 406 | features : iter(:class:`Feature`) 407 | Optional signal set. See :class:`Signature`. 408 | 409 | Attributes 410 | ---------- 411 | bus : :class:`Interface` 412 | Shared Wishbone bus. 413 | """ 414 | def __init__(self, *, addr_width, data_width, granularity=None, features=frozenset()): 415 | super().__init__({"bus": Out(Signature(addr_width=addr_width, data_width=data_width, 416 | granularity=granularity, features=features))}) 417 | self._intrs = [] 418 | 419 | def add(self, intr_bus): 420 | """Add an initiator bus to the arbiter. 421 | 422 | The initiator bus must have the same address width and data width as the arbiter. The 423 | granularity of the initiator bus must be greater than or equal to the granularity of 424 | the arbiter. 425 | """ 426 | if not isinstance(intr_bus, Interface): 427 | raise TypeError(f"Initiator bus must be an instance of wishbone.Interface, not " 428 | f"{intr_bus!r}") 429 | if intr_bus.addr_width != self.bus.addr_width: 430 | raise ValueError(f"Initiator bus has address width {intr_bus.addr_width}, which is " 431 | f"not the same as arbiter address width {self.bus.addr_width}") 432 | if intr_bus.granularity < self.bus.granularity: 433 | raise ValueError(f"Initiator bus has granularity {intr_bus.granularity}, which is " 434 | f"lesser than the arbiter granularity {self.bus.granularity}") 435 | if intr_bus.data_width != self.bus.data_width: 436 | raise ValueError(f"Initiator bus has data width {intr_bus.data_width}, which is not " 437 | f"the same as arbiter data width {self.bus.data_width}") 438 | for opt_output in {"err", "rty"}: 439 | if hasattr(self.bus, opt_output) and not hasattr(intr_bus, opt_output): 440 | raise ValueError(f"Arbiter has optional output {opt_output!r}, but the initiator " 441 | f"bus does not have a corresponding input") 442 | self._intrs.append(intr_bus) 443 | 444 | def elaborate(self, platform): 445 | m = Module() 446 | 447 | requests = Signal(len(self._intrs)) 448 | grant = Signal(range(len(self._intrs))) 449 | m.d.comb += requests.eq(Cat(intr_bus.cyc for intr_bus in self._intrs)) 450 | 451 | bus_busy = self.bus.cyc 452 | if hasattr(self.bus, "lock"): 453 | # If LOCK is not asserted, we also wait for STB to be deasserted before granting bus 454 | # ownership to the next initiator. If we didn't, the next bus owner could receive 455 | # an ACK (or ERR, RTY) from the previous transaction when targeting the same 456 | # peripheral. 457 | bus_busy &= self.bus.lock | self.bus.stb 458 | 459 | with m.If(~bus_busy): 460 | with m.Switch(grant): 461 | for i in range(len(requests)): 462 | with m.Case(i): 463 | for pred in reversed(range(i)): 464 | with m.If(requests[pred]): 465 | m.d.sync += grant.eq(pred) 466 | for succ in reversed(range(i + 1, len(requests))): 467 | with m.If(requests[succ]): 468 | m.d.sync += grant.eq(succ) 469 | 470 | with m.Switch(grant): 471 | for i, intr_bus in enumerate(self._intrs): 472 | m.d.comb += intr_bus.dat_r.eq(self.bus.dat_r) 473 | if hasattr(intr_bus, "stall"): 474 | intr_bus_stall = Signal(init=1) 475 | m.d.comb += intr_bus.stall.eq(intr_bus_stall) 476 | 477 | with m.Case(i): 478 | ratio = intr_bus.granularity // self.bus.granularity 479 | m.d.comb += [ 480 | self.bus.adr.eq(intr_bus.adr), 481 | self.bus.dat_w.eq(intr_bus.dat_w), 482 | self.bus.sel.eq(Cat(sel.replicate(ratio) for sel in intr_bus.sel)), 483 | self.bus.we.eq(intr_bus.we), 484 | self.bus.stb.eq(intr_bus.stb), 485 | ] 486 | m.d.comb += self.bus.cyc.eq(intr_bus.cyc) 487 | if hasattr(self.bus, "lock"): 488 | m.d.comb += self.bus.lock.eq(getattr(intr_bus, "lock", 0)) 489 | if hasattr(self.bus, "cti"): 490 | m.d.comb += self.bus.cti.eq(getattr(intr_bus, "cti", CycleType.CLASSIC)) 491 | if hasattr(self.bus, "bte"): 492 | m.d.comb += self.bus.bte.eq(getattr(intr_bus, "bte", BurstTypeExt.LINEAR)) 493 | 494 | m.d.comb += intr_bus.ack.eq(self.bus.ack) 495 | if hasattr(intr_bus, "err"): 496 | m.d.comb += intr_bus.err.eq(getattr(self.bus, "err", 0)) 497 | if hasattr(intr_bus, "rty"): 498 | m.d.comb += intr_bus.rty.eq(getattr(self.bus, "rty", 0)) 499 | if hasattr(intr_bus, "stall"): 500 | m.d.comb += intr_bus_stall.eq(getattr(self.bus, "stall", ~self.bus.ack)) 501 | 502 | return m 503 | -------------------------------------------------------------------------------- /amaranth_soc/csr/reg.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Mapping, Sequence 2 | from contextlib import contextmanager 3 | from amaranth import * 4 | from amaranth.hdl import ShapeLike 5 | from amaranth.lib import enum, wiring 6 | from amaranth.lib.wiring import In, Out, connect, flipped 7 | from amaranth.utils import ceil_log2 8 | 9 | from ..memory import MemoryMap 10 | from .bus import Element, Signature, Multiplexer 11 | 12 | 13 | __all__ = [ 14 | "FieldPort", "Field", "FieldAction", "FieldActionMap", "FieldActionArray", 15 | "Register", "Builder", "Bridge", 16 | ] 17 | 18 | 19 | class FieldPort(wiring.PureInterface): 20 | class Access(enum.Enum): 21 | """Field access mode.""" 22 | R = "r" 23 | W = "w" 24 | RW = "rw" 25 | NC = "nc" 26 | 27 | def readable(self): 28 | return self == self.R or self == self.RW 29 | 30 | def writable(self): 31 | return self == self.W or self == self.RW 32 | 33 | class Signature(wiring.Signature): 34 | """CSR register field port signature. 35 | 36 | Parameters 37 | ---------- 38 | shape : :ref:`shape-like object ` 39 | Shape of the field. 40 | access : :class:`FieldPort.Access` 41 | Field access mode. 42 | 43 | Interface attributes 44 | -------------------- 45 | r_data : Signal(shape) 46 | Read data. Must always be valid, and is sampled when ``r_stb`` is asserted. 47 | r_stb : Signal() 48 | Read strobe. Fields with read side effects should perform them when this strobe is 49 | asserted. 50 | w_data : Signal(shape) 51 | Write data. Valid only when ``w_stb`` is asserted. 52 | w_stb : Signal() 53 | Write strobe. Fields should update their value or perform the write side effect when 54 | this strobe is asserted. 55 | """ 56 | def __init__(self, shape, access): 57 | if not isinstance(shape, ShapeLike): 58 | raise TypeError(f"Field shape must be a shape-like object, not {shape!r}") 59 | 60 | self._shape = Shape.cast(shape) 61 | self._access = FieldPort.Access(access) 62 | 63 | super().__init__({ 64 | "r_data": In(self.shape), 65 | "r_stb": Out(1), 66 | "w_data": Out(self.shape), 67 | "w_stb": Out(1), 68 | }) 69 | 70 | @property 71 | def shape(self): 72 | return self._shape 73 | 74 | @property 75 | def access(self): 76 | return self._access 77 | 78 | def create(self, *, path=None, src_loc_at=0): 79 | """Create a compatible interface. 80 | 81 | See :meth:`wiring.Signature.create` for details. 82 | 83 | Returns 84 | ------- 85 | A :class:`FieldPort` object using this signature. 86 | """ 87 | return FieldPort(self, path=path, src_loc_at=1 + src_loc_at) 88 | 89 | def __eq__(self, other): 90 | """Compare signatures. 91 | 92 | Two signatures are equal if they have the same shape and field access mode. 93 | """ 94 | return (isinstance(other, FieldPort.Signature) and 95 | Shape.cast(self.shape) == Shape.cast(other.shape) and 96 | self.access == other.access) 97 | 98 | def __repr__(self): 99 | return f"csr.FieldPort.Signature({self.members!r})" 100 | 101 | """CSR register field port. 102 | 103 | An interface between a CSR register and one of its fields. 104 | 105 | Parameters 106 | ---------- 107 | signature : :class:`FieldPort.Signature` 108 | Field port signature. 109 | path : iter(:class:`str`) 110 | Path to the field port. Optional. See :class:`wiring.PureInterface`. 111 | 112 | Attributes 113 | ---------- 114 | shape : :ref:`shape-like object ` 115 | Shape of the field. See :class:`FieldPort.Signature`. 116 | access : :class:`FieldPort.Access` 117 | Field access mode. See :class:`FieldPort.Signature`. 118 | 119 | Raises 120 | ------ 121 | :exc:`TypeError` 122 | If ``signature`` is not a :class:`FieldPort.Signature`. 123 | """ 124 | def __init__(self, signature, *, path=None, src_loc_at=0): 125 | if not isinstance(signature, FieldPort.Signature): 126 | raise TypeError(f"This interface requires a csr.FieldPort.Signature, not " 127 | f"{signature!r}") 128 | super().__init__(signature, path=path, src_loc_at=1 + src_loc_at) 129 | 130 | @property 131 | def shape(self): 132 | return self.signature.shape 133 | 134 | @property 135 | def access(self): 136 | return self.signature.access 137 | 138 | def __repr__(self): 139 | return f"csr.FieldPort({self.signature!r})" 140 | 141 | 142 | class Field: 143 | """Description of a CSR register field. 144 | 145 | Parameters 146 | ---------- 147 | action_cls : :class:`FieldAction` subclass 148 | The type of field action to be instantiated by :meth:`Field.create`. 149 | *args : :class:`tuple` 150 | Positional arguments passed to ``action_cls.__init__``. 151 | **kwargs : :class:`dict` 152 | Keyword arguments passed to ``action_cls.__init__``. 153 | 154 | Raises 155 | ------ 156 | :exc:`TypeError` 157 | If ``action_cls`` is not a subclass of :class:`FieldAction`. 158 | """ 159 | def __init__(self, action_cls, *args, **kwargs): 160 | if not issubclass(action_cls, FieldAction): 161 | raise TypeError(f"{action_cls.__qualname__} must be a subclass of csr.FieldAction") 162 | self._action_cls = action_cls 163 | self._args = args 164 | self._kwargs = kwargs 165 | 166 | def create(self): 167 | """Instantiate a field action. 168 | 169 | Returns 170 | ------- 171 | :class:`FieldAction` 172 | The object returned by ``action_cls(*args, **kwargs)``. 173 | """ 174 | return self._action_cls(*self._args, **self._kwargs) 175 | 176 | 177 | class FieldAction(wiring.Component): 178 | """CSR register field action. 179 | 180 | A :class:`~wiring.Component` mediating access between a CSR bus and a range of bits within a 181 | :class:`Register`. 182 | 183 | Parameters 184 | ---------- 185 | shape : :ref:`shape-like object ` 186 | Shape of the field. See :class:`FieldPort.Signature`. 187 | access : :class:`FieldPort.Access` 188 | Field access mode. See :class:`FieldPort.Signature`. 189 | members : iterable of (:class:`str`, :class:`wiring.Member`) key/value pairs 190 | Signature members. Optional, defaults to ``()``. A :class:`FieldPort.Signature` member 191 | named 'port' and oriented as input is always present in addition to these members. 192 | 193 | Interface attributes 194 | -------------------- 195 | port : :class:`FieldPort` 196 | Field port. 197 | 198 | Raises 199 | ------ 200 | :exc:`ValueError` 201 | If the key 'port' is used in ``members``. 202 | """ 203 | def __init__(self, shape, access, members=()): 204 | members = dict(members) 205 | if "port" in members: 206 | raise ValueError(f"'port' is a reserved name, which must not be assigned to " 207 | f"member {members['port']!r}") 208 | super().__init__({ 209 | "port": In(FieldPort.Signature(shape, access)), 210 | **members, 211 | }) 212 | 213 | 214 | class FieldActionMap(Mapping): 215 | """A mapping of field actions. 216 | 217 | Parameters 218 | ---------- 219 | fields : :class:`dict` of :class:`str` to (:class:`Field` or :class:`dict` or :class:`list`) 220 | Register fields. Fields are instantiated according to their type: 221 | - a :class:`Field` is instantiated as a :class:`FieldAction` (see :meth:`Field.create`); 222 | - a :class:`dict` is instantiated as a :class:`FieldActionMap`; 223 | - a :class:`list` is instantiated as a :class:`FieldArrayMap`. 224 | 225 | Raises 226 | ------ 227 | :exc:`TypeError` 228 | If ``fields`` is not a dict, or is empty. 229 | :exc:`TypeError` 230 | If ``fields`` has a key that is not a string, or is empty. 231 | :exc:`TypeError` 232 | If ``fields`` has a value that is neither a :class:`Field` object, a dict or a list of 233 | :class:`Field` objects. 234 | """ 235 | def __init__(self, fields): 236 | self._fields = {} 237 | 238 | if not isinstance(fields, dict) or len(fields) == 0: 239 | raise TypeError(f"Fields must be provided as a non-empty dict, not {fields!r}") 240 | 241 | for key, value in fields.items(): 242 | if not isinstance(key, str) or not key: 243 | raise TypeError(f"Field name must be a non-empty string, not {key!r}") 244 | 245 | if isinstance(value, Field): 246 | field = value.create() 247 | elif isinstance(value, dict): 248 | field = FieldActionMap(value) 249 | elif isinstance(value, list): 250 | field = FieldActionArray(value) 251 | else: 252 | raise TypeError(f"{value!r} must either be a Field object, a dict or a list of " 253 | f"Field objects") 254 | 255 | self._fields[key] = field 256 | 257 | def __getitem__(self, key): 258 | """Access a field by name or index. 259 | 260 | Returns 261 | -------- 262 | :class:`FieldAction` or :class:`FieldActionMap` or :class:`FieldActionArray` 263 | The field instance associated with ``key``. 264 | 265 | Raises 266 | ------ 267 | :exc:`KeyError` 268 | If there is no field instance associated with ``key``. 269 | """ 270 | return self._fields[key] 271 | 272 | def __getattr__(self, name): 273 | """Access a field by name. 274 | 275 | Returns 276 | ------- 277 | :class:`FieldAction` or :class:`FieldActionMap` or :class:`FieldActionArray` 278 | The field instance associated with ``name``. 279 | 280 | Raises 281 | ------ 282 | :exc:`AttributeError` 283 | If the field map does not have a field instance associated with ``name``. 284 | :exc:`AttributeError` 285 | If ``name`` is reserved (i.e. starts with an underscore). 286 | """ 287 | try: 288 | item = self[name] 289 | except KeyError: 290 | raise AttributeError(f"Field map does not have a field {name!r}; did you mean one of: " 291 | f"{', '.join(f'{name!r}' for name in self.keys())}?") 292 | if name.startswith("_"): 293 | raise AttributeError(f"Field map field {name!r} has a reserved name and may only be " 294 | f"accessed by indexing") 295 | return item 296 | 297 | def __iter__(self): 298 | """Iterate over the field map. 299 | 300 | Yields 301 | ------ 302 | :class:`str` 303 | Key (name) for accessing the field. 304 | """ 305 | yield from self._fields 306 | 307 | def __len__(self): 308 | """Field map size. 309 | 310 | Returns 311 | ------- 312 | :class:`int` 313 | The number of items in the map. 314 | """ 315 | return len(self._fields) 316 | 317 | def flatten(self): 318 | """Recursively iterate over the field map. 319 | 320 | Yields 321 | ------ 322 | iter(:class:`str`) 323 | Path of the field. It is prefixed by the name of every nested :class:`FieldActionMap` 324 | or :class:`FieldActionArray`. 325 | :class:`FieldAction` 326 | Field instance. 327 | """ 328 | for key, field in self.items(): 329 | if isinstance(field, (FieldActionMap, FieldActionArray)): 330 | for sub_path, sub_field in field.flatten(): 331 | yield (key, *sub_path), sub_field 332 | else: 333 | yield (key,), field 334 | 335 | 336 | class FieldActionArray(Sequence): 337 | """An array of CSR register fields. 338 | 339 | Parameters 340 | ---------- 341 | fields : :class:`list` of (:class:`Field` or :class:`dict` or :class:`list`) 342 | Register fields. Fields are instantiated according to their type: 343 | - a :class:`Field` is instantiated as a :class:`FieldAction` (see :meth:`Field.create`); 344 | - a :class:`dict` is instantiated as a :class:`FieldActionMap`; 345 | - a :class:`list` is instantiated as a :class:`FieldArrayMap`. 346 | 347 | Raises 348 | ------ 349 | :exc:`TypeError` 350 | If ``fields`` is not a list, or is empty. 351 | :exc:`TypeError` 352 | If ``fields`` has an item that is neither a :class:`Field` object, a dict or a list of 353 | :class:`Field` objects. 354 | """ 355 | def __init__(self, fields): 356 | self._fields = [] 357 | 358 | if not isinstance(fields, list) or len(fields) == 0: 359 | raise TypeError(f"Fields must be provided as a non-empty list, not {fields!r}") 360 | 361 | for item in fields: 362 | if isinstance(item, Field): 363 | field = item.create() 364 | elif isinstance(item, dict): 365 | field = FieldActionMap(item) 366 | elif isinstance(item, list): 367 | field = FieldActionArray(item) 368 | else: 369 | raise TypeError(f"{item!r} must be a Field object or a collection of Field " 370 | f"objects") 371 | 372 | self._fields.append(field) 373 | 374 | def __getitem__(self, key): 375 | """Access a field by index. 376 | 377 | Returns 378 | ------- 379 | :class:`FieldAction` or :class:`FieldActionMap` or :class:`FieldActionArray` 380 | The field instance associated with ``key``. 381 | """ 382 | return self._fields[key] 383 | 384 | def __len__(self): 385 | """Field array size. 386 | 387 | Returns 388 | ------- 389 | :class:`int` 390 | The number of items in the array. 391 | """ 392 | return len(self._fields) 393 | 394 | def flatten(self): 395 | """Iterate recursively over the field array. 396 | 397 | Yields 398 | ------ 399 | iter(:class:`str`) 400 | Path of the field. It is prefixed by the name of every nested :class:`FieldActionMap` 401 | or :class:`FieldActionArray`. 402 | :class:`FieldAction` 403 | Field instance. 404 | """ 405 | for key, field in enumerate(self._fields): 406 | if isinstance(field, (FieldActionMap, FieldActionArray)): 407 | for sub_path, sub_field in field.flatten(): 408 | yield (key, *sub_path), sub_field 409 | else: 410 | yield (key,), field 411 | 412 | 413 | class Register(wiring.Component): 414 | _doc_template = """ 415 | A CSR register. 416 | 417 | Parameters 418 | ---------- 419 | fields : :class:`dict` or :class:`list` or :class:`Field` 420 | Collection of register fields. If ``None`` (default), a dict is populated from Python 421 | :term:`variable annotations `. ``fields`` is used to create 422 | a :class:`FieldActionMap`, :class:`FieldActionArray`, or :class:`FieldAction`, 423 | depending on its type (dict, list, or Field). 424 | {parameters} 425 | 426 | Interface attributes 427 | -------------------- 428 | element : :class:`Element` 429 | Interface between this register and a CSR bus primitive. 430 | 431 | Attributes 432 | ---------- 433 | field : :class:`FieldActionMap` or :class:`FieldActionArray` or :class:`FieldAction` 434 | Collection of field instances. 435 | f : :class:`FieldActionMap` or :class:`FieldActionArray` or :class:`FieldAction` 436 | Shorthand for :attr:`Register.field`. 437 | 438 | Raises 439 | ------ 440 | :exc:`TypeError` 441 | If ``fields`` is neither ``None``, a :class:`dict`, a :class:`list`, or a :class:`Field`. 442 | :exc:`ValueError` 443 | If ``fields`` is not ``None`` and at least one variable annotation is a :class:`Field`. 444 | :exc:`ValueError` 445 | If ``element.access`` is not readable and at least one field is readable. 446 | :exc:`ValueError` 447 | If ``element.access`` is not writable and at least one field is writable. 448 | """ 449 | 450 | __doc__ = _doc_template.format(parameters=""" 451 | access : :class:`Element.Access` 452 | Element access mode. 453 | """) 454 | 455 | def __init_subclass__(cls, *, access=None, **kwargs): 456 | if access is not None: 457 | cls._access = Element.Access(access) 458 | cls.__doc__ = cls._doc_template.format(parameters="") 459 | super().__init_subclass__(**kwargs) 460 | 461 | def __init__(self, fields=None, access=None): 462 | if hasattr(self, "__annotations__"): 463 | def filter_fields(src): 464 | if isinstance(src, Field): 465 | return src 466 | if isinstance(src, (dict, list)): 467 | items = enumerate(src) if isinstance(src, list) else src.items() 468 | dst = dict() 469 | for key, value in items: 470 | if new_value := filter_fields(value): 471 | dst[key] = new_value 472 | return list(dst.values()) if isinstance(src, list) else dst 473 | 474 | annot_fields = filter_fields(self.__annotations__) 475 | 476 | if fields is None: 477 | fields = annot_fields 478 | elif annot_fields: 479 | raise ValueError(f"Field collection {fields} cannot be provided in addition to " 480 | f"field annotations: {', '.join(annot_fields)}") 481 | 482 | if access is not None: 483 | access = Element.Access(access) 484 | if hasattr(self, "_access") and access != self._access: 485 | raise ValueError(f"Element access mode {access} conflicts with the value " 486 | f"provided during class creation: {self._access}") 487 | elif hasattr(self, "_access"): 488 | access = self._access 489 | else: 490 | raise ValueError("Element access mode must be provided during class creation or " 491 | "instantiation") 492 | 493 | if isinstance(fields, dict): 494 | self._field = FieldActionMap(fields) 495 | elif isinstance(fields, list): 496 | self._field = FieldActionArray(fields) 497 | elif isinstance(fields, Field): 498 | self._field = fields.create() 499 | else: 500 | raise TypeError(f"Field collection must be a dict, list, or Field, not {fields!r}") 501 | 502 | width = 0 503 | for field_path, field in self: 504 | width += Shape.cast(field.port.shape).width 505 | if field.port.access.readable() and not access.readable(): 506 | raise ValueError(f"Field {'__'.join(field_path)} is readable, but element access " 507 | f"mode is {access}") 508 | if field.port.access.writable() and not access.writable(): 509 | raise ValueError(f"Field {'__'.join(field_path)} is writable, but element access " 510 | f"mode is {access}") 511 | 512 | super().__init__({"element": Out(Element.Signature(width, access))}) 513 | 514 | @property 515 | def field(self): 516 | return self._field 517 | 518 | @property 519 | def f(self): 520 | return self._field 521 | 522 | def __iter__(self): 523 | """Recursively iterate over the field collection. 524 | 525 | Yields 526 | ------ 527 | iter(:class:`str`) 528 | Path of the field. It is prefixed by the name of every nested :class:`FieldActionMap` 529 | or :class:`FieldActionArray`. 530 | :class:`FieldAction` 531 | Field instance. 532 | """ 533 | if isinstance(self.field, FieldAction): 534 | yield (), self.field 535 | else: 536 | yield from self.field.flatten() 537 | 538 | def elaborate(self, platform): 539 | m = Module() 540 | 541 | field_start = 0 542 | 543 | for field_path, field in self: 544 | field_width = Shape.cast(field.port.shape).width 545 | field_slice = slice(field_start, field_start + field_width) 546 | 547 | if field_path: 548 | m.submodules["__".join(str(key) for key in field_path)] = field 549 | else: # avoid empty name for a single un-named field 550 | m.submodules += field 551 | 552 | if field.port.access.readable(): 553 | m.d.comb += [ 554 | self.element.r_data[field_slice].eq(field.port.r_data), 555 | field.port.r_stb.eq(self.element.r_stb), 556 | ] 557 | if field.port.access.writable(): 558 | m.d.comb += [ 559 | field.port.w_data.eq(self.element.w_data[field_slice]), 560 | field.port.w_stb .eq(self.element.w_stb), 561 | ] 562 | 563 | field_start = field_slice.stop 564 | 565 | return m 566 | 567 | 568 | class Builder: 569 | """CSR builder. 570 | 571 | A CSR builder can organize a group of registers within an address range and convert it to a 572 | :class:`MemoryMap` for consumption by other SoC primitives. 573 | 574 | Parameters 575 | ---------- 576 | addr_width : :class:`int` 577 | Address width. 578 | data_width : :class:`int` 579 | Data width. 580 | granularity : :class:`int` 581 | Granularity. Optional, defaults to 8 bits. 582 | 583 | Raises 584 | ------ 585 | :exc:`TypeError` 586 | If ``addr_width`` is not a positive integer. 587 | :exc:`TypeError` 588 | If ``data_width`` is not a positive integer. 589 | :exc:`TypeError` 590 | If ``granularity`` is not a positive integer. 591 | :exc:`ValueError` 592 | If ``granularity`` is not a divisor of ``data_width`` 593 | """ 594 | def __init__(self, *, addr_width, data_width, granularity=8): 595 | if not isinstance(addr_width, int) or addr_width <= 0: 596 | raise TypeError(f"Address width must be a positive integer, not {addr_width!r}") 597 | if not isinstance(data_width, int) or data_width <= 0: 598 | raise TypeError(f"Data width must be a positive integer, not {data_width!r}") 599 | if not isinstance(granularity, int) or granularity <= 0: 600 | raise TypeError(f"Granularity must be a positive integer, not {granularity!r}") 601 | 602 | if data_width != (data_width // granularity) * granularity: 603 | raise ValueError(f"Granularity {granularity} is not a divisor of data width " 604 | f"{data_width}") 605 | 606 | self._addr_width = addr_width 607 | self._data_width = data_width 608 | self._granularity = granularity 609 | 610 | self._registers = dict() 611 | self._scope_stack = [] 612 | self._frozen = False 613 | 614 | @property 615 | def addr_width(self): 616 | return self._addr_width 617 | 618 | @property 619 | def data_width(self): 620 | return self._data_width 621 | 622 | @property 623 | def granularity(self): 624 | return self._granularity 625 | 626 | def freeze(self): 627 | """Freeze the builder. 628 | 629 | Once the builder is frozen, CSR registers cannot be added anymore. 630 | """ 631 | self._frozen = True 632 | 633 | def add(self, name, reg, *, offset=None): 634 | """Add a CSR register. 635 | 636 | Arguments 637 | --------- 638 | name : :class:`str` 639 | Register name. 640 | reg : :class:`Register` 641 | Register. 642 | offset : :class:`int` 643 | Register offset. Optional. 644 | 645 | Returns 646 | ------- 647 | :class:`Register` 648 | ``reg``, which is added to the builder. Its name is ``name``, prefixed by the names and 649 | indices of any parent :meth:`~Builder.Cluster` and :meth:`~Builder.Index`. 650 | 651 | Raises 652 | ------ 653 | :exc:`ValueError` 654 | If the builder is frozen. 655 | :exc:`TypeError` 656 | If ``name`` is not a string, or is empty. 657 | :exc:`TypeError` 658 | If ``reg` is not an instance of :class:`Register`. 659 | :exc:`ValueError` 660 | If ``reg`` is already added to the builder. 661 | :exc:`TypeError` 662 | If ``offset`` is not an integer, or is negative. 663 | :exc:`ValueError` 664 | If ``offset`` is not a multiple of ``self.data_width // self.granularity``. 665 | """ 666 | if not isinstance(reg, Register): 667 | raise TypeError(f"Register must be an instance of csr.Register, not {reg!r}") 668 | if self._frozen: 669 | raise ValueError(f"Builder is frozen. Cannot add register {reg!r}") 670 | 671 | if name is None or not (isinstance(name, str) and name): 672 | raise TypeError(f"Register name must be a non-empty string, not {name!r}") 673 | 674 | if offset is not None: 675 | if not (isinstance(offset, int) and offset >= 0): 676 | raise TypeError(f"Offset must be a non-negative integer, not {offset!r}") 677 | ratio = self.data_width // self.granularity 678 | if offset % ratio != 0: 679 | raise ValueError(f"Offset {offset:#x} must be a multiple of {ratio:#x} bytes") 680 | 681 | if id(reg) in self._registers: 682 | _, other_name, other_offset = self._registers[id(reg)] 683 | error_msg = f"Register {reg!r} is already added with name {other_name}" 684 | if other_offset is None: 685 | error_msg += " at an implicit offset" 686 | else: 687 | error_msg += f" at an explicit offset {other_offset:#x}" 688 | raise ValueError(error_msg) 689 | 690 | self._registers[id(reg)] = reg, (*self._scope_stack, name), offset 691 | return reg 692 | 693 | @contextmanager 694 | def Cluster(self, name): 695 | """Define a cluster. 696 | 697 | Arguments 698 | --------- 699 | name : :class:`str` 700 | Cluster name. 701 | 702 | Raises 703 | ------ 704 | :exc:`TypeError` 705 | If ``name`` is not a string, or is empty. 706 | """ 707 | if not (isinstance(name, str) and name): 708 | raise TypeError(f"Cluster name must be a non-empty string, not {name!r}") 709 | self._scope_stack.append(name) 710 | try: 711 | yield 712 | finally: 713 | assert self._scope_stack.pop() == name 714 | 715 | @contextmanager 716 | def Index(self, index): 717 | """Define an array index. 718 | 719 | Arguments 720 | --------- 721 | index : :class:`int` 722 | Array index. 723 | 724 | Raises 725 | ------ 726 | :exc:`TypeError` 727 | If ``index`` is not an integer, or is negative. 728 | """ 729 | if not (isinstance(index, int) and index >= 0): 730 | raise TypeError(f"Array index must be a non-negative integer, not {index!r}") 731 | self._scope_stack.append(index) 732 | try: 733 | yield 734 | finally: 735 | assert self._scope_stack.pop() == index 736 | 737 | def as_memory_map(self): 738 | self.freeze() 739 | memory_map = MemoryMap(addr_width=self.addr_width, data_width=self.data_width) 740 | for reg, reg_name, reg_offset in self._registers.values(): 741 | if reg_offset is not None: 742 | reg_addr = (reg_offset * self.granularity) // self.data_width 743 | else: 744 | reg_addr = None 745 | reg_size = (reg.element.width + self.data_width - 1) // self.data_width 746 | memory_map.add_resource(reg, name=reg_name, addr=reg_addr, size=reg_size, 747 | alignment=ceil_log2(reg_size)) 748 | memory_map.freeze() 749 | return memory_map 750 | 751 | 752 | class Bridge(wiring.Component): 753 | """CSR bridge. 754 | 755 | Parameters 756 | ---------- 757 | memory_map : :class:`MemoryMap` 758 | Memory map of CSR registers. 759 | 760 | Interface attributes 761 | -------------------- 762 | bus : :class:`Interface` 763 | CSR bus providing access to the contents of ``memory_map``. 764 | 765 | Raises 766 | ------ 767 | :exc:`TypeError` 768 | If ``memory_map`` is not a :class:`MemoryMap` object. 769 | :exc:`ValueError` 770 | If ``memory_map`` has windows. 771 | :exc:`TypeError` 772 | If ``memory_map`` has resources that are not :class:`Register` objects. 773 | """ 774 | def __init__(self, memory_map): 775 | if not isinstance(memory_map, MemoryMap): 776 | raise TypeError(f"CSR bridge memory map must be an instance of MemoryMap, not {memory_map!r}") 777 | if list(memory_map.windows()): 778 | raise ValueError("CSR bridge memory map cannot have windows") 779 | for reg, reg_name, (reg_start, reg_end) in memory_map.resources(): 780 | if not isinstance(reg, Register): 781 | raise TypeError(f"CSR register must be an instance of csr.Register, not {reg!r}") 782 | 783 | memory_map.freeze() 784 | self._mux = Multiplexer(memory_map) 785 | super().__init__({ 786 | "bus": In(Signature(addr_width=memory_map.addr_width, 787 | data_width=memory_map.data_width)) 788 | }) 789 | self.bus.memory_map = memory_map 790 | 791 | def elaborate(self, platform): 792 | m = Module() 793 | 794 | m.submodules.mux = self._mux 795 | for reg, reg_name, _ in self.bus.memory_map.resources(): 796 | m.submodules["__".join(reg_name)] = reg 797 | 798 | connect(m, flipped(self.bus), self._mux.bus) 799 | 800 | return m 801 | --------------------------------------------------------------------------------