docs/lang: document instances.

This commit is contained in:
Catherine 2024-01-22 22:24:02 +00:00
parent a5dd63246c
commit 53f7b628b3

View file

@ -1385,7 +1385,7 @@ Elaboration
Amaranth designs are built from a hierarchy of smaller subdivisions, which are called *elaboratables*. The process of creating a data structure representing the behavior of a complete design by composing such subdivisions together is called *elaboration*.
An elaboratable is any Python object that inherits from the :class:`Elaboratable` base class and implements the ``elaborate`` method:
An elaboratable is any Python object that inherits from the :class:`Elaboratable` base class and implements the :meth:`~Elaboratable.elaborate` method:
.. testcode::
@ -1397,19 +1397,19 @@ An elaboratable is any Python object that inherits from the :class:`Elaboratable
return m
The ``elaborate`` method must either return an instance of :class:`Module` to describe the behavior of the elaboratable, or delegate it by returning another elaboratable object.
The :meth:`~Elaboratable.elaborate` method must either return an instance of :class:`Module` or :class:`Instance` to describe the behavior of the elaboratable, or delegate it by returning another elaboratable object.
.. note::
Instances of :class:`Module` also implement the ``elaborate`` method, which returns a special object that represents a fragment of a netlist. Such an object cannot be constructed without using :class:`Module`.
Instances of :class:`Module` also implement the :meth:`~Elaboratable.elaborate` method, which returns a special object that represents a fragment of a netlist. Such an object cannot be constructed without using :class:`Module`.
The :pc:`platform` argument received by the ``elaborate`` method can be :pc:`None`, an instance of :ref:`a built-in platform <platform>`, or a custom object. It is used for `dependency injection <https://en.wikipedia.org/wiki/Dependency_injection>`_ and to contain the state of a design while it is being elaborated.
The :pc:`platform` argument received by the :meth:`~Elaboratable.elaborate` method can be :pc:`None`, an instance of :ref:`a built-in platform <platform>`, or a custom object. It is used for `dependency injection <https://en.wikipedia.org/wiki/Dependency_injection>`_ and to contain the state of a design while it is being elaborated.
.. important::
The ``elaborate`` method should not modify the ``self`` object it receives other than for debugging and experimentation. Elaborating the same design twice with two identical platform objects should produce two identical netlists. If the design needs to be modified after construction, this should happen before elaboration.
The :meth:`~Elaboratable.elaborate` method should not modify the ``self`` object it receives other than for debugging and experimentation. Elaborating the same design twice with two identical platform objects should produce two identical netlists. If the design needs to be modified after construction, this should happen before elaboration.
It is not possible to ensure that a design which modifies itself during elaboration is correctly converted to a netlist because the relative order in which the ``elaborate`` methods are called within a single design is not guaranteed.
It is not possible to ensure that a design which modifies itself during elaboration is correctly converted to a netlist because the relative order in which the :meth:`~Elaboratable.elaborate` methods are called within a single design is not guaranteed.
The Amaranth standard library provides *components*: elaboratable objects that also include a description of their interface. Unless otherwise necessary, an elaboratable should inherit from :class:`amaranth.lib.wiring.Component` rather than plain :class:`Elaboratable`. See the :ref:`introduction to interfaces and components <wiring-introduction>` for details.
@ -1443,6 +1443,8 @@ A submodule can also be added without specifying a name:
If a name is not explicitly specified for a submodule, one will be generated and assigned automatically. Designs with many autogenerated names can be difficult to debug, so a name should usually be supplied.
A non-Amaranth design unit can be added as a submodule using an :ref:`instance <lang-instance>`.
.. _lang-controlinserter:
@ -1582,7 +1584,97 @@ Memories
.. todo:: Write this section.
.. _lang-instance:
Instances
=========
.. todo:: Write this section.
.. attributes are not documented because they can be easily used to break soundness and we don't document them for signals either; they are rarely necessary for interoperability
A submodule written in a non-Amaranth language is called an *instance*. An instance can be written in any language supported by the synthesis toolchain; usually, that is (System)Verilog, VHDL, or a language that is translated to one of those two. Adding an instance as a submodule corresponds to "module instantiation" in (System)Verilog and "component instantiation" in VHDL, and is done by specifying the following:
* The *type* of an instance is the name of a (System)Verilog module, VHDL entity or component, or another HDL design unit that is being instantiated.
* 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.
An instance can be added as a submodule using the :pc:`m.submodules.name = Instance("type", ...)` syntax, where :pc:`"type"` is the type of the instance as a string (which is passed to the synthesis toolchain uninterpreted), and :pc:`...` 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 :pc:`a_ANAME=attr` or :pc:`("a", "ANAME", attr)` syntaxes. The :pc:`attr` must be an :class:`int`, a :class:`str`, or a :class:`Const`.
* A parameter is specified using the :pc:`p_PNAME=param` or :pc:`("p", "PNAME", param)` syntaxes. The :pc:`param` must be an :class:`int`, a :class:`str`, or a :class:`Const`.
* An input is specified using the :pc:`i_INAME=in_val` or :pc:`("i", "INAME", in_val)` syntaxes. The :pc:`in_val` must be a :ref:`value-like <lang-valuelike>` object.
* An output is specified using the :pc:`o_ONAME=out_val` or :pc:`("o", "ONAME", out_val)` syntaxes. The :pc:`out_val` must be a :ref:`value-like <lang-valuelike>` object that casts to a :class:`Signal`.
The two following examples use both syntaxes to add the same instance of type ``external`` as a submodule named ``processor``:
.. testcode::
:hide:
i_data = Signal(8)
o_data = Signal(8)
m = Module()
.. testcode::
m.submodules.processor = Instance("external",
p_width=8,
i_clk=ClockSignal(),
i_rst=ResetSignal(),
i_en=1,
i_mode=Const(3, unsigned(4)),
i_data_in=i_data,
o_data_out=o_data,
)
.. testcode::
:hide:
m = Module()
.. testcode::
m.submodules.processor = Instance("external",
("p", "width", 8),
("i", "clk", ClockSignal()),
("i", "rst", ResetSignal()),
("i", "en", 1),
("i", "mode", Const(3, unsigned(4))),
("i", "data_in", i_data),
("o", "data_out", o_data),
)
Like a regular submodule, an instance can also be added without specifying a name:
.. testcode::
m.submodules += Instance("external",
# ...
)
.. tip::
If a name is not explicitly specified for a submodule, one will be generated and assigned automatically. Designs with many autogenerated names can be difficult to debug, so a name should usually be supplied.
Although an :class:`Instance` is not an elaboratable, as a special case, it can be returned from the :pc:`elaborate()` method. This is conveinent for implementing an elaboratable that adorns an instance with an Amaranth interface:
.. testcode::
from amaranth import vendor
class FlipFlop(Elaboratable):
def __init__(self):
self.d = Signal()
self.q = Signal()
def elaborate(self, platform):
# Decide on the instance to use based on the platform we are elaborating for.
if isinstance(platform, vendor.LatticeICE40Platform):
return Instance("SB_DFF",
i_C=ClockSignal(),
i_D=self.d,
o_Q=self.q
)
else:
raise NotImplementedError