Implement RFC 53: Low-level I/O primitives.
Co-authored-by: Catherine <whitequark@whitequark.org> Co-authored-by: mcclure <mcclure@users.noreply.github.com>
This commit is contained in:
parent
18b54ded0a
commit
744576011f
16 changed files with 1364 additions and 436 deletions
|
|
@ -49,6 +49,7 @@ Implemented RFCs
|
|||
.. _RFC 45: https://amaranth-lang.org/rfcs/0045-lib-memory.html
|
||||
.. _RFC 46: https://amaranth-lang.org/rfcs/0046-shape-range-1.html
|
||||
.. _RFC 50: https://amaranth-lang.org/rfcs/0050-print.html
|
||||
.. _RFC 53: https://amaranth-lang.org/rfcs/0053-ioport.html
|
||||
|
||||
* `RFC 17`_: Remove ``log2_int``
|
||||
* `RFC 27`_: Testbench processes for the simulator
|
||||
|
|
@ -57,6 +58,7 @@ Implemented RFCs
|
|||
* `RFC 45`_: Move ``hdl.Memory`` to ``lib.Memory``
|
||||
* `RFC 46`_: Change ``Shape.cast(range(1))`` to ``unsigned(0)``
|
||||
* `RFC 50`_: ``Print`` statement and string formatting
|
||||
* `RFC 53`_: Low-level I/O primitives
|
||||
|
||||
|
||||
Language changes
|
||||
|
|
@ -64,9 +66,10 @@ Language changes
|
|||
|
||||
.. currentmodule:: amaranth.hdl
|
||||
|
||||
* Added: :class:`ast.Slice` objects have been made const-castable.
|
||||
* Added: :class:`Slice` objects have been made const-castable.
|
||||
* Added: :func:`amaranth.utils.ceil_log2`, :func:`amaranth.utils.exact_log2`. (`RFC 17`_)
|
||||
* Added: :class:`Format` objects, :class:`Print` statements, messages in :class:`Assert`, :class:`Assume` and :class:`Cover`. (`RFC 50`_)
|
||||
* Added: IO values, :class:`IOPort` objects, :class:`IOBufferInstance` objects. (`RFC 53`_)
|
||||
* Changed: ``m.Case()`` with no patterns is never active instead of always active. (`RFC 39`_)
|
||||
* Changed: ``Value.matches()`` with no patterns is ``Const(0)`` instead of ``Const(1)``. (`RFC 39`_)
|
||||
* Changed: ``Signal(range(stop), init=stop)`` warning has been changed into a hard error and made to trigger on any out-of range value.
|
||||
|
|
@ -75,6 +78,7 @@ Language changes
|
|||
* Changed: the ``reset=`` argument of :class:`Signal`, :meth:`Signal.like`, :class:`amaranth.lib.wiring.Member`, :class:`amaranth.lib.cdc.FFSynchronizer`, and ``m.FSM()`` has been renamed to ``init=``. (`RFC 43`_)
|
||||
* Changed: :class:`Shape` has been made immutable and hashable.
|
||||
* Changed: :class:`Assert`, :class:`Assume`, :class:`Cover` have been moved to :mod:`amaranth.hdl` from :mod:`amaranth.asserts`. (`RFC 50`_)
|
||||
* Changed: :class:`Instance` IO ports now accept only IO values, not plain values. (`RFC 53`_)
|
||||
* Deprecated: :func:`amaranth.utils.log2_int`. (`RFC 17`_)
|
||||
* Deprecated: :class:`amaranth.hdl.Memory`. (`RFC 45`_)
|
||||
* Removed: (deprecated in 0.4) :meth:`Const.normalize`. (`RFC 5`_)
|
||||
|
|
@ -218,7 +222,7 @@ Language changes
|
|||
* Changed: :meth:`Value.cast` casts :class:`ValueCastable` objects recursively.
|
||||
* Changed: :meth:`Value.cast` treats instances of classes derived from both :class:`enum.Enum` and :class:`int` (including :class:`enum.IntEnum`) as enumerations rather than integers.
|
||||
* Changed: :meth:`Value.matches` with an empty list of patterns returns ``Const(1)`` rather than ``Const(0)``, to match the behavior of ``with m.Case():``.
|
||||
* Changed: :class:`Cat` warns if an enumeration without an explicitly specified shape is used. (`RFC 3`_)
|
||||
* Changed: :func:`Cat` warns if an enumeration without an explicitly specified shape is used. (`RFC 3`_)
|
||||
* Changed: ``signed(0)`` is no longer constructible. (The semantics of this shape were never defined.)
|
||||
* Changed: :meth:`Value.__abs__` returns an unsigned value.
|
||||
* Deprecated: :class:`ast.Sample`, :class:`ast.Past`, :class:`ast.Stable`, :class:`ast.Rose`, :class:`ast.Fell`. (Predating the RFC process.)
|
||||
|
|
|
|||
151
docs/guide.rst
151
docs/guide.rst
|
|
@ -328,7 +328,7 @@ They may also be provided as a pattern to the :ref:`match operator <lang-matchop
|
|||
At the moment, only the following expressions are constant-castable:
|
||||
|
||||
* :class:`Const`
|
||||
* :class:`Cat`
|
||||
* :func:`Cat`
|
||||
* :class:`Slice`
|
||||
|
||||
This list will be expanded in the future.
|
||||
|
|
@ -707,21 +707,21 @@ The result of any bit sequence operation is an unsigned value.
|
|||
|
||||
The following table lists the bit sequence operations provided by Amaranth:
|
||||
|
||||
======================= ================================================ ======
|
||||
Operation Description Notes
|
||||
======================= ================================================ ======
|
||||
``len(a)`` bit length; value width [#opS1]_
|
||||
``a[i:j:k]`` bit slicing by constant subscripts [#opS2]_
|
||||
``iter(a)`` bit iteration
|
||||
``a.bit_select(b, w)`` overlapping part select with variable offset
|
||||
``a.word_select(b, w)`` non-overlapping part select with variable offset
|
||||
``Cat(a, b)`` concatenation [#opS3]_
|
||||
``a.replicate(n)`` replication
|
||||
======================= ================================================ ======
|
||||
========================= ================================================ ========
|
||||
Operation Description Notes
|
||||
========================= ================================================ ========
|
||||
:py:`len(a)` bit length; value width [#opS1]_
|
||||
:py:`a[i:j:k]` bit slicing by constant subscripts [#opS2]_
|
||||
:py:`iter(a)` bit iteration
|
||||
:py:`a.bit_select(b, w)` overlapping part select with variable offset
|
||||
:py:`a.word_select(b, w)` non-overlapping part select with variable offset
|
||||
:py:`Cat(a, b)` concatenation [#opS3]_
|
||||
:py:`a.replicate(n)` replication
|
||||
========================= ================================================ ========
|
||||
|
||||
.. [#opS1] Words "length" and "width" have the same meaning when talking about Amaranth values. Conventionally, "width" is used.
|
||||
.. [#opS2] All variations of the Python slice notation are supported, including "extended slicing". E.g. all of ``a[0]``, ``a[1:9]``, ``a[2:]``, ``a[:-2]``, ``a[::-1]``, ``a[0:8:2]`` select bits in the same way as other Python sequence types select their elements.
|
||||
.. [#opS3] In the concatenated value, ``a`` occupies the least significant bits, and ``b`` the most significant bits. Any number of arguments (zero, one, two, or more) are supported.
|
||||
.. [#opS2] All variations of the Python slice notation are supported, including "extended slicing". E.g. all of :py:`a[0]`, :py:`a[1:9]`, :py:`a[2:]`, :py:`a[:-2]`, :py:`a[::-1]`, :py:`a[0:8:2]` select bits in the same way as other Python sequence types select their elements.
|
||||
.. [#opS3] In the concatenated value, :py:`a` occupies the least significant bits, and :py:`b` the most significant bits. Any number of arguments (zero, one, two, or more) are supported.
|
||||
|
||||
For the operators introduced by Amaranth, the following table explains them in terms of Python code operating on tuples of bits rather than Amaranth values:
|
||||
|
||||
|
|
@ -1677,6 +1677,66 @@ Memories
|
|||
Amaranth provides support for memories in the standard library module :mod:`amaranth.lib.memory`.
|
||||
|
||||
|
||||
.. _lang-iovalues:
|
||||
|
||||
I/O values
|
||||
==========
|
||||
|
||||
To interoperate with external circuitry, Amaranth provides *I/O values*, which represent bundles of wires carrying uninterpreted signals. Unlike regular :ref:`values <lang-values>`, which represent binary numbers and can be :ref:`assigned <lang-assigns>` to create a unidirectional connection or used in computations, I/O values represent electrical signals that may be digital or analog and have no :ref:`shape <lang-shapes>`, cannot be assigned, used in computations, or simulated.
|
||||
|
||||
I/O values are only used to define connections between non-Amaranth building blocks that traverse an Amaranth design, including :ref:`instances <lang-instance>` and :ref:`I/O buffer instances <lang-iobufferinstance>`.
|
||||
|
||||
|
||||
.. _lang-ioports:
|
||||
|
||||
I/O ports
|
||||
---------
|
||||
|
||||
An *I/O port* is an I/O value representing a connection to a port of the topmost module in the :ref:`design hierarchy <lang-submodules>`. It can be created with an explicitly specified width.
|
||||
|
||||
.. testcode::
|
||||
|
||||
from amaranth.hdl import IOPort
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> port = IOPort(4)
|
||||
>>> port.width
|
||||
4
|
||||
|
||||
I/O ports can be named in the same way as :ref:`signals <lang-signalname>`:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> clk_port = IOPort(1, name="clk")
|
||||
>>> clk_port.name
|
||||
'clk'
|
||||
|
||||
If two I/O ports with the same name exist in a design, one of them will be renamed to remove the ambiguity. Because the name of an I/O port is significant, they should be named unambiguously.
|
||||
|
||||
|
||||
.. _lang-ioops:
|
||||
|
||||
I/O operators
|
||||
-------------
|
||||
|
||||
I/O values support only a limited set of :ref:`sequence <python:typesseq>` operators, all of which return another I/O value. The following table lists the I/O operators provided by Amaranth:
|
||||
|
||||
=============== ============================== ===================
|
||||
Operation Description Notes
|
||||
=============== ============================== ===================
|
||||
:py:`len(a)` length; width [#iopS1]_
|
||||
:py:`a[i:j:k]` slicing by constant subscripts [#iopS2]_
|
||||
:py:`iter(a)` iteration
|
||||
:py:`Cat(a, b)` concatenation [#iopS3]_ [#iopS4]_
|
||||
=============== ============================== ===================
|
||||
|
||||
.. [#iopS1] Words "length" and "width" have the same meaning when talking about Amaranth I/O values. Conventionally, "width" is used.
|
||||
.. [#iopS2] All variations of the Python slice notation are supported, including "extended slicing". E.g. all of :py:`a[0]`, :py:`a[1:9]`, :py:`a[2:]`, :py:`a[:-2]`, :py:`a[::-1]`, :py:`a[0:8:2]` select wires in the same way as other Python sequence types select their elements.
|
||||
.. [#iopS3] In the concatenated value, :py:`a` occupies the lower indices and :py:`b` the higher indices. Any number of arguments (zero, one, two, or more) are supported.
|
||||
.. [#iopS4] Concatenation of zero arguments, :py:`Cat()`, returns a 0-bit regular value, however any such value is accepted (and ignored) anywhere an I/O value is expected.
|
||||
|
||||
|
||||
.. _lang-instance:
|
||||
|
||||
Instances
|
||||
|
|
@ -1690,14 +1750,15 @@ A submodule written in a non-Amaranth language is called an *instance*. An insta
|
|||
* The *name* of an instance is the name of the submodule within the containing elaboratable.
|
||||
* The *attributes* of an instance correspond to attributes of a (System)Verilog module instance, or a custom attribute of a VHDL entity or component instance. Attributes applied to instances are interpreted by the synthesis toolchain rather than the HDL.
|
||||
* The *parameters* of an instance correspond to parameters of a (System)Verilog module instance, or a generic constant of a VHDL entity or component instance. Not all HDLs allow their design units to be parameterized during instantiation.
|
||||
* The *inputs* and *outputs* of an instance correspond to inputs and outputs of the external design unit.
|
||||
* The *inputs*, *outputs*, and *inouts* of an instance correspond to input ports, output ports, and bidirectional ports of the external design unit.
|
||||
|
||||
An instance can be added as a submodule using the :py:`m.submodules.name = Instance("type", ...)` syntax, where :py:`"type"` is the type of the instance as a string (which is passed to the synthesis toolchain uninterpreted), and :py:`...` is a list of parameters, inputs, and outputs. Depending on whether the name of an attribute, parameter, input, or output can be written as a part of a Python identifier or not, one of two possible syntaxes is used to specify them:
|
||||
|
||||
* An attribute is specified using the :py:`a_ANAME=attr` or :py:`("a", "ANAME", attr)` syntaxes. The :py:`attr` must be an :class:`int`, a :class:`str`, or a :class:`Const`.
|
||||
* A parameter is specified using the :py:`p_PNAME=param` or :py:`("p", "PNAME", param)` syntaxes. The :py:`param` must be an :class:`int`, a :class:`str`, or a :class:`Const`.
|
||||
* An input is specified using the :py:`i_INAME=in_val` or :py:`("i", "INAME", in_val)` syntaxes. The :py:`in_val` must be a :ref:`value-like <lang-valuelike>` object.
|
||||
* An output is specified using the :py:`o_ONAME=out_val` or :py:`("o", "ONAME", out_val)` syntaxes. The :py:`out_val` must be a :ref:`value-like <lang-valuelike>` object that casts to a :class:`Signal`.
|
||||
* An input is specified using the :py:`i_INAME=in_val` or :py:`("i", "INAME", in_val)` syntaxes. The :py:`in_val` must be an :ref:`I/O value <lang-iovalues>` or a :ref:`value-like <lang-valuelike>` object.
|
||||
* An output is specified using the :py:`o_ONAME=out_val` or :py:`("o", "ONAME", out_val)` syntaxes. The :py:`out_val` must be an :ref:`I/O value <lang-iovalues>` or a :ref:`value-like <lang-valuelike>` object that casts to a :ref:`signal <lang-signals>`, a concatenation of signals, or a slice of a signal.
|
||||
* An inout is specified using the :py:`io_IONAME=inout_val` or :py:`("io", "IONAME", inout_val)` syntaxes. The :py:`inout_val` must be an :ref:`I/O value <lang-iovalues>`.
|
||||
|
||||
The two following examples use both syntaxes to add the same instance of type ``external`` as a submodule named ``processor``:
|
||||
|
||||
|
|
@ -1706,6 +1767,7 @@ The two following examples use both syntaxes to add the same instance of type ``
|
|||
|
||||
i_data = Signal(8)
|
||||
o_data = Signal(8)
|
||||
io_pin = IOPort(1)
|
||||
m = Module()
|
||||
|
||||
.. testcode::
|
||||
|
|
@ -1718,6 +1780,7 @@ The two following examples use both syntaxes to add the same instance of type ``
|
|||
i_mode=Const(3, unsigned(4)),
|
||||
i_data_in=i_data,
|
||||
o_data_out=o_data,
|
||||
io_pin=io_pin,
|
||||
)
|
||||
|
||||
.. testcode::
|
||||
|
|
@ -1735,6 +1798,7 @@ The two following examples use both syntaxes to add the same instance of type ``
|
|||
("i", "mode", Const(3, unsigned(4))),
|
||||
("i", "data_in", i_data),
|
||||
("o", "data_out", o_data),
|
||||
("io", "pin", io_pin),
|
||||
)
|
||||
|
||||
Like a regular submodule, an instance can also be added without specifying a name:
|
||||
|
|
@ -1770,4 +1834,55 @@ Although an :class:`Instance` is not an elaboratable, as a special case, it can
|
|||
o_Q=self.q
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
.. _lang-iobufferinstance:
|
||||
|
||||
I/O buffer instances
|
||||
====================
|
||||
|
||||
An *I/O buffer instance* is a submodule that allows assigning :ref:`I/O values <lang-iovalues>` to or from regular :ref:`values <lang-values>` without the use of an external, toolchain- and technology-dependent :ref:`instance <lang-instance>`. It can be created in four configurations: input, output, tristatable output, and bidirectional (input/output).
|
||||
|
||||
.. testcode::
|
||||
|
||||
from amaranth.hdl import IOBufferInstance
|
||||
|
||||
m = Module()
|
||||
|
||||
In the input configuration, the buffer combinatorially drives a signal :py:`i` by the port:
|
||||
|
||||
.. testcode::
|
||||
|
||||
port = IOPort(4)
|
||||
port_i = Signal(4)
|
||||
m.submodules.ibuf = IOBufferInstance(port, i=port_i)
|
||||
|
||||
In the output configuration, the buffer combinatorially drives the port by a value :py:`o`:
|
||||
|
||||
.. testcode::
|
||||
|
||||
port = IOPort(4)
|
||||
port_o = Signal(4)
|
||||
m.submodules.obuf = IOBufferInstance(port, o=port_o)
|
||||
|
||||
In the tristatable output configuration, the buffer combinatorially drives the port by a value :py:`o` if :py:`oe` is asserted, and does not drive (leaves in a high-impedance state, or tristates) the port otherwise:
|
||||
|
||||
.. testcode::
|
||||
|
||||
port = IOPort(4)
|
||||
port_o = Signal(4)
|
||||
port_oe = Signal()
|
||||
m.submodules.obuft = IOBufferInstance(port, o=port_o, oe=port_oe)
|
||||
|
||||
In the bidirectional (input/output) configuration, the buffer combiatorially drives a signal :py:`i` by the port, combinatorially drives the port by a value :py:`o` if :py:`oe` is asserted, and does not drive (leaves in a high-impedance state, or tristates) the port otherwise:
|
||||
|
||||
.. testcode::
|
||||
|
||||
port = IOPort(4)
|
||||
port_i = Signal(4)
|
||||
port_o = Signal(4)
|
||||
port_oe = Signal()
|
||||
m.submodules.iobuf = IOBufferInstance(port, i=port_i, o=port_o, oe=port_oe)
|
||||
|
||||
The width of the :py:`i` and :py:`o` values (when present) must be the same as the width of the port, and the width of the :py:`oe` value must be 1.
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ The prelude exports exactly the following names:
|
|||
* :class:`Const`
|
||||
* :func:`C`
|
||||
* :func:`Mux`
|
||||
* :class:`Cat`
|
||||
* :func:`Cat`
|
||||
* :class:`Array`
|
||||
* :class:`Signal`
|
||||
* :class:`ClockSignal`
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue