├── 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 |
--------------------------------------------------------------------------------