Skip to content

Implement RFC 61: Minimal streams #1266

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions amaranth/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,16 +76,6 @@ def decorator_like(*args, **kwargs):
return decorator_like


def extend(cls):
def decorator(f):
if isinstance(f, property):
name = f.fget.__name__
else:
name = f.__name__
setattr(cls, name, f)
return decorator


def get_linter_options(filename):
first_line = linecache.getline(filename, 1)
if first_line:
Expand Down
17 changes: 17 additions & 0 deletions amaranth/lib/fifo.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from ..utils import ceil_log2
from .cdc import FFSynchronizer, AsyncFFSynchronizer
from .memory import Memory
from . import stream


__all__ = ["FIFOInterface", "SyncFIFO", "SyncFIFOBuffered", "AsyncFIFO", "AsyncFIFOBuffered"]
Expand Down Expand Up @@ -93,6 +94,22 @@ def __init__(self, *, width, depth):
self.r_en = Signal()
self.r_level = Signal(range(depth + 1))

@property
def w_stream(self):
w_stream = stream.Signature(self.width).flip().create()
w_stream.payload = self.w_data
w_stream.valid = self.w_en
w_stream.ready = self.w_rdy
return w_stream

@property
def r_stream(self):
r_stream = stream.Signature(self.width).create()
r_stream.payload = self.r_data
r_stream.valid = self.r_rdy
r_stream.ready = self.r_en
return r_stream


def _incr(signal, modulo):
if modulo == 2 ** len(signal):
Expand Down
122 changes: 122 additions & 0 deletions amaranth/lib/stream.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
from ..hdl import *
from .._utils import final
from . import wiring
from .wiring import In, Out


@final
class Signature(wiring.Signature):
"""Signature of a unidirectional data stream.

.. note::

"Minimal streams" as defined in `RFC 61`_ lack support for complex payloads, such as
multiple lanes or packetization, as well as introspection of the payload. This limitation
will be lifted in a later release.

.. _RFC 61: https://amaranth-lang.org/rfcs/0061-minimal-streams.html

Parameters
----------
payload_shape : :class:`~.hdl.ShapeLike`
Shape of the payload.
always_valid : :class:`bool`
Whether the stream has a payload available each cycle.
always_ready : :class:`bool`
Whether the stream has its payload accepted whenever it is available (i.e. whether it lacks
support for backpressure).

Members
-------
payload : :py:`Out(payload_shape)`
Payload.
valid : :py:`Out(1)`
Whether a payload is available. If the stream is :py:`always_valid`, :py:`Const(1)`.
ready : :py:`In(1)`
Whether a payload is accepted. If the stream is :py:`always_ready`, :py:`Const(1)`.
"""
def __init__(self, payload_shape: ShapeLike, *, always_valid=False, always_ready=False):
Shape.cast(payload_shape)
self._payload_shape = payload_shape
self._always_valid = bool(always_valid)
self._always_ready = bool(always_ready)

super().__init__({
"payload": Out(payload_shape),
"valid": Out(1),
"ready": In(1)
})

# payload_shape intentionally not introspectable (for now)

@property
def always_valid(self):
return self._always_valid

@property
def always_ready(self):
return self._always_ready

def __eq__(self, other):
return (type(other) is type(self) and
other._payload_shape == self._payload_shape and
other.always_valid == self.always_valid and
other.always_ready == self.always_ready)

def create(self, *, path=None, src_loc_at=0):
return Interface(self, path=path, src_loc_at=1 + src_loc_at)

def __repr__(self):
always_valid_repr = "" if not self._always_valid else ", always_valid=True"
always_ready_repr = "" if not self._always_ready else ", always_ready=True"
return f"stream.Signature({self._payload_shape!r}{always_valid_repr}{always_ready_repr})"


@final
class Interface:
"""A unidirectional data stream.

Attributes
----------
signature : :class:`Signature`
Signature of this data stream.
"""

payload: Signal
valid: 'Signal | Const'
ready: 'Signal | Const'

def __init__(self, signature: Signature, *, path=None, src_loc_at=0):
if not isinstance(signature, Signature):
raise TypeError(f"Signature of stream.Interface must be a stream.Signature, not "
f"{signature!r}")
self._signature = signature
self.__dict__.update(signature.members.create(path=path, src_loc_at=1 + src_loc_at))
if signature.always_valid:
self.valid = Const(1)
if signature.always_ready:
self.ready = Const(1)

@property
def signature(self):
return self._signature

@property
def p(self):
"""Shortcut for :py:`self.payload`.

This shortcut reduces repetition when manipulating the payload, for example:

.. code::

m.d.comb += [
self.o_stream.p.result.eq(self.i_stream.p.first + self.i_stream.p.second),
self.o_stream.valid.eq(self.i_stream.valid),
self.i_stream.ready.eq(self.o_stream.ready),
]
"""
return self.payload

def __repr__(self):
return (f"stream.Interface(payload={self.payload!r}, valid={self.valid!r}, "
f"ready={self.ready!r})")
26 changes: 16 additions & 10 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,23 @@ Migrating from version 0.4

Apply the following changes to code written against Amaranth 0.4 to migrate it to version 0.5:

* Update uses of :py:`reset=` keyword argument to :py:`init=`.
* Ensure all elaboratables are subclasses of :class:`Elaboratable`.
* Replace uses of :py:`m.Case()` with no patterns with :py:`m.Default()`.
* Replace uses of :py:`Value.matches()` with no patterns with :py:`Const(1)`.
* Update uses of :py:`amaranth.utils.log2_int(need_pow2=False)` to :func:`amaranth.utils.ceil_log2`.
* Update uses of :py:`amaranth.utils.log2_int(need_pow2=True)` to :func:`amaranth.utils.exact_log2`.
* Update uses of :py:`reset=` keyword argument to :py:`init=`.
* Convert uses of :py:`Simulator.add_sync_process` used as testbenches to :meth:`Simulator.add_testbench <amaranth.sim.Simulator.add_testbench>`.
* Convert other uses of :py:`Simulator.add_sync_process` to :meth:`Simulator.add_process <amaranth.sim.Simulator.add_process>`.
* Convert simulator processes and testbenches to use the new async API.
* Replace uses of :py:`amaranth.hdl.Memory` with :class:`amaranth.lib.memory.Memory`.
* Ensure clock domains aren't used outside the module that defines them, or its submodules; move clock domain definitions upwards in the hierarchy as necessary
* Replace imports of :py:`amaranth.asserts.Assert`, :py:`Assume`, and :py:`Cover` with imports from :py:`amaranth.hdl`.
* Remove uses of :py:`name=` keyword argument of :py:`Assert`, :py:`Assume`, and :py:`Cover`; a message can be used instead.
* Ensure all elaboratables are subclasses of :class:`Elaboratable`.
* Ensure clock domains aren't used outside the module that defines them, or its submodules; move clock domain definitions upwards in the hierarchy as necessary
* Remove uses of :py:`amaranth.lib.coding.*` by inlining or copying the implementation of the modules.
* Replace uses of :py:`amaranth.hdl.Memory` with :class:`amaranth.lib.memory.Memory`.
* Update uses of :py:`platform.request` to pass :py:`dir="-"` and use :mod:`amaranth.lib.io` buffers.
* Remove uses of :py:`amaranth.lib.coding.*` by inlining or copying the implementation of the modules.
* Convert uses of :py:`Simulator.add_sync_process` used as testbenches to :meth:`Simulator.add_testbench <amaranth.sim.Simulator.add_testbench>`.
* Convert other uses of :py:`Simulator.add_sync_process` to :meth:`Simulator.add_process <amaranth.sim.Simulator.add_process>`.
* Convert simulator processes and testbenches to use the new async API.
* Update uses of :meth:`Simulator.add_clock <amaranth.sim.Simulator.add_clock>` with explicit :py:`phase` to take into account simulator no longer adding implicit :py:`period / 2`. (Previously, :meth:`Simulator.add_clock <amaranth.sim.Simulator.add_clock>` was documented to first toggle the clock at the time :py:`phase`, but actually first toggled the clock at :py:`period / 2 + phase`.)
* Update uses of :meth:`Simulator.run_until <amaranth.sim.Simulator.run_until>` to remove the :py:`run_passive=True` argument. If the code uses :py:`run_passive=False`, ensure it still works with the new behavior.
* Update uses of :py:`amaranth.utils.log2_int(need_pow2=False)` to :func:`amaranth.utils.ceil_log2`.
* Update uses of :py:`amaranth.utils.log2_int(need_pow2=True)` to :func:`amaranth.utils.exact_log2`.


Implemented RFCs
Expand All @@ -55,6 +55,7 @@ Implemented RFCs
.. _RFC 27: https://amaranth-lang.org/rfcs/0027-simulator-testbenches.html
.. _RFC 30: https://amaranth-lang.org/rfcs/0030-component-metadata.html
.. _RFC 36: https://amaranth-lang.org/rfcs/0036-async-testbench-functions.html
.. _RFC 42: https://amaranth-lang.org/rfcs/0042-const-from-shape-castable.html
.. _RFC 39: https://amaranth-lang.org/rfcs/0039-empty-case.html
.. _RFC 43: https://amaranth-lang.org/rfcs/0043-rename-reset-to-init.html
.. _RFC 45: https://amaranth-lang.org/rfcs/0045-lib-memory.html
Expand All @@ -65,6 +66,7 @@ Implemented RFCs
.. _RFC 55: https://amaranth-lang.org/rfcs/0055-lib-io.html
.. _RFC 58: https://amaranth-lang.org/rfcs/0058-valuecastable-format.html
.. _RFC 59: https://amaranth-lang.org/rfcs/0059-no-domain-upwards-propagation.html
.. _RFC 61: https://amaranth-lang.org/rfcs/0061-minimal-streams.html
.. _RFC 62: https://amaranth-lang.org/rfcs/0062-memory-data.html
.. _RFC 63: https://amaranth-lang.org/rfcs/0063-remove-lib-coding.html
.. _RFC 65: https://amaranth-lang.org/rfcs/0065-format-struct-enum.html
Expand All @@ -74,6 +76,7 @@ Implemented RFCs
* `RFC 30`_: Component metadata
* `RFC 36`_: Async testbench functions
* `RFC 39`_: Change semantics of no-argument ``m.Case()``
* `RFC 42`_: ``Const`` from shape-castable
* `RFC 43`_: Rename ``reset=`` to ``init=``
* `RFC 45`_: Move ``hdl.Memory`` to ``lib.Memory``
* `RFC 46`_: Change ``Shape.cast(range(1))`` to ``unsigned(0)``
Expand All @@ -83,6 +86,7 @@ Implemented RFCs
* `RFC 55`_: New ``lib.io`` components
* `RFC 58`_: Core support for ``ValueCastable`` formatting
* `RFC 59`_: Get rid of upwards propagation of clock domains
* `RFC 61`_: Minimal streams
* `RFC 62`_: The ``MemoryData`` class
* `RFC 63`_: Remove ``amaranth.lib.coding``
* `RFC 65`_: Special formatting for structures and enums
Expand All @@ -103,6 +107,7 @@ Language changes
* Changed: :py:`Value.matches()` with no patterns is :py:`Const(0)` instead of :py:`Const(1)`. (`RFC 39`_)
* Changed: :py:`Signal(range(stop), init=stop)` warning has been changed into a hard error and made to trigger on any out-of range value.
* Changed: :py:`Signal(range(0))` is now valid without a warning.
* Changed: :py:`Const(value, shape)` now accepts shape-castable objects as :py:`shape`. (`RFC 42`_)
* Changed: :py:`Shape.cast(range(1))` is now :py:`unsigned(0)`. (`RFC 46`_)
* Changed: the :py:`reset=` argument of :class:`Signal`, :meth:`Signal.like`, :class:`amaranth.lib.wiring.Member`, :class:`amaranth.lib.cdc.FFSynchronizer`, and :py:`m.FSM()` has been renamed to :py:`init=`. (`RFC 43`_)
* Changed: :class:`Shape` has been made immutable and hashable.
Expand Down Expand Up @@ -130,6 +135,7 @@ Standard library changes
* Added: :class:`amaranth.lib.io.SingleEndedPort`, :class:`amaranth.lib.io.DifferentialPort`. (`RFC 55`_)
* Added: :class:`amaranth.lib.io.Buffer`, :class:`amaranth.lib.io.FFBuffer`, :class:`amaranth.lib.io.DDRBuffer`. (`RFC 55`_)
* Added: :mod:`amaranth.lib.meta`, :class:`amaranth.lib.wiring.ComponentMetadata`. (`RFC 30`_)
* Added: :mod:`amaranth.lib.stream`. (`RFC 61`_)
* Deprecated: :mod:`amaranth.lib.coding`. (`RFC 63`_)
* Removed: (deprecated in 0.4) :mod:`amaranth.lib.scheduler`. (`RFC 19`_)
* Removed: (deprecated in 0.4) :class:`amaranth.lib.fifo.FIFOInterface` with :py:`fwft=False`. (`RFC 20`_)
Expand Down
5 changes: 3 additions & 2 deletions docs/stdlib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ Standard library

The :mod:`amaranth.lib` module, also known as the standard library, provides modules that falls into one of the three categories:

1. Modules that will used by essentially all idiomatic Amaranth code, or which are necessary for interoperability. This includes :mod:`amaranth.lib.enum` (enumerations), :mod:`amaranth.lib.data` (data structures), :mod:`amaranth.lib.wiring` (interfaces and components), and :mod:`amaranth.lib.meta` (interface metadata).
2. Modules that abstract common functionality whose implementation differs between hardware platforms. This includes :mod:`amaranth.lib.cdc`, :mod:`amaranth.lib.memory`.
1. Modules that will used by essentially all idiomatic Amaranth code, or which are necessary for interoperability. This includes :mod:`amaranth.lib.enum` (enumerations), :mod:`amaranth.lib.data` (data structures), :mod:`amaranth.lib.wiring` (interfaces and components), :mod:`amaranth.lib.meta` (interface metadata), and :mod:`amaranth.lib.stream` (data streams).
2. Modules that abstract common functionality whose implementation differs between hardware platforms. This includes :mod:`amaranth.lib.memory` and :mod:`amaranth.lib.cdc`.
3. Modules that have essentially one correct implementation and are of broad utility in digital designs. This includes :mod:`amaranth.lib.coding`, :mod:`amaranth.lib.fifo`, and :mod:`amaranth.lib.crc`.

As part of the Amaranth backwards compatibility guarantee, any behaviors described in these documents will not change from a version to another without at least one version including a warning about the impending change. Any nontrivial change to these behaviors must also go through the public review as a part of the `Amaranth Request for Comments process <https://amaranth-lang.org/rfcs/>`_.
Expand All @@ -18,6 +18,7 @@ The Amaranth standard library is separate from the Amaranth language: everything
stdlib/data
stdlib/wiring
stdlib/meta
stdlib/stream
stdlib/memory
stdlib/io
stdlib/cdc
Expand Down
Binary file added docs/stdlib/_images/stream_pipeline.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading