Skip to content

Commit

Permalink
Deploying to main from @ amaranth-lang/amaranth@324c37f 🚀
Browse files Browse the repository at this point in the history
  • Loading branch information
github-merge-queue[bot] committed May 21, 2024
1 parent facc373 commit 0fdf254
Show file tree
Hide file tree
Showing 52 changed files with 1,331 additions and 296 deletions.
2 changes: 1 addition & 1 deletion docs/amaranth/latest/.buildinfo
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Sphinx build info version 1
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
config: 9aa2837be947243943edbd876a51ac86
config: c636b60df3401a133e587ed2203b6bbe
tags: 645f666f9bcd5a90fca523b33c5a78b7
Binary file modified docs/amaranth/latest/.doctrees/environment.pickle
Binary file not shown.
Binary file modified docs/amaranth/latest/.doctrees/guide.doctree
Binary file not shown.
Binary file modified docs/amaranth/latest/.doctrees/stdlib.doctree
Binary file not shown.
Binary file modified docs/amaranth/latest/.doctrees/stdlib/data.doctree
Binary file not shown.
Binary file modified docs/amaranth/latest/.doctrees/stdlib/enum.doctree
Binary file not shown.
Binary file added docs/amaranth/latest/.doctrees/stdlib/io.doctree
Binary file not shown.
32 changes: 18 additions & 14 deletions docs/amaranth/latest/_sources/guide.rst.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1673,17 +1673,17 @@ Amaranth provides support for memories in the standard library module :mod:`amar
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.
To interoperate with external circuitry, Amaranth provides *core 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, core 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>`.
Core 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.
A *core I/O port* is a core 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::

Expand All @@ -1695,23 +1695,23 @@ An *I/O port* is an I/O value representing a connection to a port of the topmost
>>> port.width
4

I/O ports can be named in the same way as :ref:`signals <lang-signalname>`:
Core 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.
If two core 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 a core 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:
Core I/O values support only a limited set of :ref:`sequence <python:typesseq>` operators, all of which return another core I/O value. The following table lists the operators provided by Amaranth for core I/O values:

=============== ============================== ===================
Operation Description Notes
Expand Down Expand Up @@ -1747,9 +1747,9 @@ An instance can be added as a submodule using the :py:`m.submodules.name = Insta

* 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 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>`.
* 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:`core 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 a :ref:`core 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 a :ref:`core 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``:

Expand Down Expand Up @@ -1833,31 +1833,35 @@ Although an :class:`Instance` is not an elaboratable, as a special case, it can
I/O buffer instances
====================

An *I/O buffer instance* is a submodule that allows connecting :ref:`I/O values <lang-iovalues>` and 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).
.. note::

I/O buffer instances are a low-level primitive which is documented to ensure that the standard library does not rely on private interfaces in the core language. Most designers should use the :mod:`amaranth.lib.io` module instead.

An *I/O buffer instance* is a submodule that allows connecting :ref:`core I/O values <lang-iovalues>` and 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 combinationally drives a signal :py:`i` by the port:
In the input configuration, the buffer instance combinationally drives a signal :py:`i` by the port:

.. testcode::

port = IOPort(4)
port_i = Signal(4)
m.submodules += IOBufferInstance(port, i=port_i)

In the output configuration, the buffer combinationally drives the port by a value :py:`o`:
In the output configuration, the buffer instance combinationally drives the port by a value :py:`o`:

.. testcode::

port = IOPort(4)
port_o = Signal(4)
m.submodules += IOBufferInstance(port, o=port_o)

In the tristatable output configuration, the buffer combinationally 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:
In the tristatable output configuration, the buffer instance combinationally 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::

Expand All @@ -1866,7 +1870,7 @@ In the tristatable output configuration, the buffer combinationally drives the p
port_oe = Signal()
m.submodules += IOBufferInstance(port, o=port_o, oe=port_oe)

In the bidirectional (input/output) configuration, the buffer combinationally drives a signal :py:`i` by the port, combinationally 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:
In the bidirectional (input/output) configuration, the buffer instance combinationally drives a signal :py:`i` by the port, combinationally 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::

Expand Down
1 change: 1 addition & 0 deletions docs/amaranth/latest/_sources/stdlib.rst.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ The Amaranth standard library is separate from the Amaranth language: everything
stdlib/wiring
stdlib/meta
stdlib/memory
stdlib/io
stdlib/cdc
stdlib/coding
stdlib/fifo
Expand Down
209 changes: 209 additions & 0 deletions docs/amaranth/latest/_sources/stdlib/io.rst.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
Input/output buffers
====================

.. py:module:: amaranth.lib.io
The :mod:`amaranth.lib.io` module provides a platform-independent way to instantiate platform-specific input/output buffers: combinational, synchronous, and double data rate (DDR).


Introduction
------------

The Amaranth language provides :ref:`core I/O values <lang-iovalues>` that designate connections to external devices, and :ref:`I/O buffer instances <lang-iobufferinstance>` that implement platform-independent combinational I/O buffers. This low-level mechanism is foundational to all I/O in Amaranth and must be used whenever a device-specific platform is unavailable, but is limited in its capabilities. The :mod:`amaranth.lib.io` module builds on top of it to provide *library I/O ports* that specialize and annotate I/O values, and *buffer components* that connect ports to logic.

.. note::

Unfortunately, the terminology related to I/O has several ambiguities:

* A "port" could refer to an *interface port* (:class:`.Signal` objects created by the :mod:`amaranth.lib.wiring` module), a *core I/O port* (:class:`amaranth.hdl.IOPort` object), or a *library I/O port* (:class:`amaranth.lib.io.PortLike` object).
* A "I/O buffer" could refer to an *I/O buffer instance* (:class:`amaranth.hdl.IOBufferInstance`) or a *I/O buffer component* (:class:`amaranth.lib.io.Buffer`, :class:`.FFBuffer`, or :class:`.DDRBuffer` objects).

Amaranth documentation always uses the least ambiguous form of these terms.


Examples
--------

.. testsetup::

from amaranth import *

class MockPlatform:
def request(self, name, *, dir):
from amaranth.hdl import IOPort
from amaranth.lib import io
if name == "led":
return io.SingleEndedPort(IOPort(1, name=name), direction="o")
if name == "clk24":
return io.SingleEndedPort(IOPort(1, name=name), direction="i")
if name == "d":
return io.SingleEndedPort(IOPort(8, name=name), direction="io")
if name == "re":
return io.SingleEndedPort(IOPort(1, name=name), direction="i")
if name == "we":
return io.SingleEndedPort(IOPort(1, name=name), direction="i")
if name == "dclk":
return io.SingleEndedPort(IOPort(1, name=name), direction="o")
if name == "dout":
return io.SingleEndedPort(IOPort(8, name=name), direction="o")
raise NameError
def get_io_buffer(self, buffer):
return Fragment()

def build(self, top):
from amaranth.back import rtlil
return rtlil.convert(Fragment.get(top, self), ports=[])


All of the following examples assume that one of the built-in FPGA platforms is used.

.. testcode::

from amaranth.lib import io, wiring
from amaranth.lib.wiring import In, Out


LED output
++++++++++

In this example, a library I/O port for a LED is requested from the platform and driven to blink the LED:

.. testcode::

class Toplevel(Elaboratable):
def elaborate(self, platform):
m = Module()

delay = Signal(24)
state = Signal()
with m.If(delay == 0):
m.d.sync += delay.eq(~0)
m.d.sync += state.eq(~state)
with m.Else():
m.d.sync += delay.eq(delay - 1)

m.submodules.led = led = io.Buffer("o", platform.request("led", dir="-"))
m.d.comb += led.o.eq(state)

return m

.. testcode::
:hide:

MockPlatform().build(Toplevel())


Clock input
+++++++++++

In this example, a clock domain is created and driven from an external clock source:

.. testcode::

class Toplevel(Elaboratable):
def elaborate(self, platform):
m = Module()

m.domains.sync = cd_sync = ClockDomain(local=True)

m.submodules.clk24 = clk24 = io.Buffer("i", platform.request("clk24", dir="-"))
m.d.comb += cd_sync.clk.eq(clk24.i)

...

return m

.. testcode::
:hide:

MockPlatform().build(Toplevel())


Bidirectional bus
+++++++++++++++++

This example implements a peripheral for a clocked parallel bus. This peripheral can store and recall one byte of data. The data is stored with a write enable pulse, and recalled with a read enable pulse:

.. testcode::

class Toplevel(Elaboratable):
def elaborate(self, platform):
m = Module()

m.submodules.bus_d = bus_d = io.FFBuffer("io", platform.request("d", dir="-"))
m.submodules.bus_re = bus_re = io.Buffer("i", platform.request("re", dir="-"))
m.submodules.bus_we = bus_we = io.Buffer("i", platform.request("we", dir="-"))

data = Signal.like(bus_d.i)
with m.If(bus_re.i):
m.d.comb += bus_d.oe.eq(1)
m.d.comb += bus_d.o.eq(data)
with m.Elif(bus_we.i):
m.d.sync += data.eq(bus_d.i)

return m

.. testcode::
:hide:

MockPlatform().build(Toplevel())

This bus requires a turn-around time of at least 1 cycle to avoid electrical contention.

Note that data appears on the bus one cycle after the read enable input is asserted, and that the write enable input stores the data present on the bus in the *previous* cycle. This is called *pipelining* and is typical for clocked buses; see :class:`.FFBuffer` for a waveform diagram. Although it increases the maximum clock frequency at which the bus can run, it also makes the bus signaling more complicated.


Clock forwarding
++++++++++++++++

In this example of a `source-synchronous interface <https://en.wikipedia.org/wiki/Source-synchronous>`__, a clock signal is generated with the same phase as the DDR data signals associated with it:

.. testcode::

class SourceSynchronousOutput(wiring.Component):
dout: In(16)

def elaborate(self, platform):
m = Module()

m.submodules.bus_dclk = bus_dclk = \
io.DDRBuffer("o", platform.request("dclk", dir="-"))
m.d.comb += [
bus_dclk.o[0].eq(1),
bus_dclk.o[1].eq(0),
]

m.submodules.bus_dout = bus_dout = \
io.DDRBuffer("o", platform.request("dout", dir="-"))
m.d.comb += [
bus_dout.o[0].eq(self.dout[:8]),
bus_dout.o[1].eq(self.dout[8:]),
]

return m

.. testcode::
:hide:

MockPlatform().build(SourceSynchronousOutput())

This component transmits :py:`dout` on each cycle as two halves: the low 8 bits on the rising edge of the data clock, and the high 8 bits on the falling edge of the data clock. The transmission is *edge-aligned*, meaning that the data edges exactly coincide with the clock edges.


Ports
-----

.. autoclass:: Direction()

.. autoclass:: PortLike
.. autoclass:: SingleEndedPort
.. autoclass:: DifferentialPort


Buffers
-------

.. autoclass:: Buffer(direction, port)
.. autoclass:: FFBuffer(direction, port, *, i_domain=None, o_domain=None)
.. autoclass:: DDRBuffer(direction, port, *, i_domain=None, o_domain=None)
2 changes: 1 addition & 1 deletion docs/amaranth/latest/_static/documentation_options.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
var DOCUMENTATION_OPTIONS = {
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
VERSION: '0.5.0.dev298',
VERSION: '0.5.0.dev299',
LANGUAGE: 'en',
COLLAPSE_INDEX: false,
BUILDER: 'html',
Expand Down
Loading

0 comments on commit 0fdf254

Please sign in to comment.