Implement RFC 30: Component metadata.
Co-authored-by: Catherine <whitequark@whitequark.org>
This commit is contained in:
parent
1d2b9c309e
commit
496432edaa
12 changed files with 1024 additions and 9 deletions
|
|
@ -49,6 +49,7 @@ Implemented RFCs
|
|||
|
||||
.. _RFC 17: https://amaranth-lang.org/rfcs/0017-remove-log2-int.html
|
||||
.. _RFC 27: https://amaranth-lang.org/rfcs/0027-simulator-testbenches.html
|
||||
.. _RFC 30: https://amaranth-lang.org/rfcs/0030-component-metadata.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
|
||||
|
|
@ -65,6 +66,7 @@ Implemented RFCs
|
|||
|
||||
* `RFC 17`_: Remove ``log2_int``
|
||||
* `RFC 27`_: Testbench processes for the simulator
|
||||
* `RFC 30`_: Component metadata
|
||||
* `RFC 39`_: Change semantics of no-argument ``m.Case()``
|
||||
* `RFC 43`_: Rename ``reset=`` to ``init=``
|
||||
* `RFC 45`_: Move ``hdl.Memory`` to ``lib.Memory``
|
||||
|
|
@ -120,6 +122,7 @@ Standard library changes
|
|||
* Changed: :meth:`amaranth.lib.wiring.Signature.is_compliant` no longer rejects reset-less signals.
|
||||
* 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`_)
|
||||
* 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 ``fwft=False``. (`RFC 20`_)
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ import os, sys
|
|||
sys.path.insert(0, os.path.abspath("."))
|
||||
|
||||
import time
|
||||
import amaranth
|
||||
from importlib.metadata import version as package_version
|
||||
|
||||
|
||||
project = "Amaranth language & toolchain"
|
||||
version = amaranth.__version__.replace(".editable", "")
|
||||
version = package_version('amaranth').replace(".editable", "")
|
||||
release = version.split("+")[0]
|
||||
copyright = time.strftime("2020—%Y, Amaranth project contributors")
|
||||
|
||||
|
|
@ -25,7 +26,9 @@ with open(".gitignore") as f:
|
|||
|
||||
root_doc = "cover"
|
||||
|
||||
intersphinx_mapping = {"python": ("https://docs.python.org/3", None)}
|
||||
intersphinx_mapping = {
|
||||
"python": ("https://docs.python.org/3", None),
|
||||
}
|
||||
|
||||
todo_include_todos = True
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ 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, and are necessary for interoperability. This includes :mod:`amaranth.lib.enum` (enumerations), :mod:`amaranth.lib.data` (data structures), and :mod:`amaranth.lib.wiring` (interfaces and components).
|
||||
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`.
|
||||
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`.
|
||||
|
||||
|
|
@ -17,6 +17,7 @@ The Amaranth standard library is separate from the Amaranth language: everything
|
|||
stdlib/enum
|
||||
stdlib/data
|
||||
stdlib/wiring
|
||||
stdlib/meta
|
||||
stdlib/memory
|
||||
stdlib/cdc
|
||||
stdlib/coding
|
||||
|
|
|
|||
285
docs/stdlib/meta.rst
Normal file
285
docs/stdlib/meta.rst
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
.. _meta:
|
||||
|
||||
Interface metadata
|
||||
##################
|
||||
|
||||
.. py:module:: amaranth.lib.meta
|
||||
|
||||
The :mod:`amaranth.lib.meta` module provides a way to annotate objects in an Amaranth design and exchange these annotations with external tools in a standardized format.
|
||||
|
||||
.. _JSON Schema: https://json-schema.org
|
||||
|
||||
.. _"$id" keyword: https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-01#name-the-id-keyword
|
||||
|
||||
.. testsetup::
|
||||
|
||||
from amaranth import *
|
||||
from amaranth.lib import wiring, meta
|
||||
from amaranth.lib.wiring import In, Out
|
||||
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
Many Amaranth designs stay entirely within the Amaranth ecosystem, using the facilities it provides to define, test, and build hardware. In this case, the design is available for exploration using Python code, and metadata is not necessary. However, if an Amaranth design needs to fit into an existing ecosystem, or, conversely, to integrate components developed for another ecosystem, metadata can be used to exchange structured information about the design.
|
||||
|
||||
Consider a simple :ref:`component <wiring>`:
|
||||
|
||||
.. testcode::
|
||||
|
||||
class Adder(wiring.Component):
|
||||
a: In(unsigned(32))
|
||||
b: In(unsigned(32))
|
||||
o: Out(unsigned(33))
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
m.d.comb += self.o.eq(self.a + self.b)
|
||||
return m
|
||||
|
||||
..
|
||||
TODO: link to Verilog backend doc when we have it
|
||||
|
||||
While it can be easily converted to Verilog, external tools will find the interface of the resulting module opaque unless they parse its Verilog source (a difficult and unrewarding task), or are provided with a description of it. Components can describe their signature with JSON-based metadata:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> adder = Adder()
|
||||
>>> adder.metadata # doctest: +ELLIPSIS
|
||||
<amaranth.lib.wiring.ComponentMetadata for ...Adder object at ...>
|
||||
>>> adder.metadata.as_json() # doctest: +SKIP
|
||||
{
|
||||
'interface': {
|
||||
'members': {
|
||||
'a': {
|
||||
'type': 'port',
|
||||
'name': 'a',
|
||||
'dir': 'in',
|
||||
'width': 32,
|
||||
'signed': False,
|
||||
'init': '0'
|
||||
},
|
||||
'b': {
|
||||
'type': 'port',
|
||||
'name': 'b',
|
||||
'dir': 'in',
|
||||
'width': 32,
|
||||
'signed': False,
|
||||
'init': '0'
|
||||
},
|
||||
'o': {
|
||||
'type': 'port',
|
||||
'name': 'o',
|
||||
'dir': 'out',
|
||||
'width': 33,
|
||||
'signed': False,
|
||||
'init': '0'
|
||||
}
|
||||
},
|
||||
'annotations': {}
|
||||
}
|
||||
}
|
||||
|
||||
.. testcode::
|
||||
:hide:
|
||||
|
||||
# The way doctest requires this object to be formatted is truly hideous, even with +NORMALIZE_WHITESPACE.
|
||||
assert adder.metadata.as_json() == {'interface': {'members': {'a': {'type': 'port', 'name': 'a', 'dir': 'in', 'width': 32, 'signed': False, 'init': '0'}, 'b': {'type': 'port', 'name': 'b', 'dir': 'in', 'width': 32, 'signed': False, 'init': '0'}, 'o': {'type': 'port', 'name': 'o', 'dir': 'out', 'width': 33, 'signed': False, 'init': '0'}}, 'annotations': {}}}
|
||||
|
||||
|
||||
All metadata in Amaranth must adhere to a schema in the `JSON Schema`_ language, which is integral to its definition, and can be used to validate the generated JSON:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> wiring.ComponentMetadata.validate(adder.metadata.as_json())
|
||||
|
||||
The built-in component metadata can be extended to provide arbitrary information about an interface through user-defined annotations. For example, a memory bus interface could provide the layout of any memory-mapped peripherals accessible through that bus.
|
||||
|
||||
|
||||
Defining annotations
|
||||
--------------------
|
||||
|
||||
Consider a simple control and status register (CSR) bus that provides the memory layout of the accessible registers via an annotation:
|
||||
|
||||
.. testcode::
|
||||
|
||||
class CSRLayoutAnnotation(meta.Annotation):
|
||||
schema = {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://amaranth-lang.org/schema/example/0/csr-layout.json",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"registers": {
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^.+$": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"requiredProperties": [
|
||||
"registers",
|
||||
],
|
||||
}
|
||||
|
||||
def __init__(self, origin):
|
||||
self._origin = origin
|
||||
|
||||
@property
|
||||
def origin(self):
|
||||
return self._origin
|
||||
|
||||
def as_json(self):
|
||||
instance = {
|
||||
"registers": self.origin.registers,
|
||||
}
|
||||
# Validating the value returned by `as_json()` ensures its conformance.
|
||||
self.validate(instance)
|
||||
return instance
|
||||
|
||||
|
||||
class CSRSignature(wiring.Signature):
|
||||
def __init__(self):
|
||||
super().__init__({
|
||||
"addr": Out(16),
|
||||
"w_en": Out(1),
|
||||
"w_data": Out(32),
|
||||
"r_en": Out(1),
|
||||
"r_data": In(32),
|
||||
})
|
||||
|
||||
def annotations(self, obj, /):
|
||||
# Unfortunately `super()` cannot be used in `wiring.Signature` subclasses;
|
||||
# instead, use a direct call to a superclass method. In this case that is
|
||||
# `wiring.Signature` itself, but in a more complex class hierarchy it could
|
||||
# be different.
|
||||
return wiring.Signature.annotations(self, obj) + (CSRLayoutAnnotation(obj),)
|
||||
|
||||
A component that embeds a few CSR registers would define their addresses:
|
||||
|
||||
.. testcode::
|
||||
|
||||
class MyPeripheral(wiring.Component):
|
||||
csr_bus: In(CSRSignature())
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.csr_bus.registers = {
|
||||
"control": 0x0000,
|
||||
"status": 0x0004,
|
||||
"data": 0x0008,
|
||||
}
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> peripheral = MyPeripheral()
|
||||
>>> peripheral.metadata.as_json() # doctest: +SKIP
|
||||
{
|
||||
'interface': {
|
||||
'members': {
|
||||
'csr_bus': {
|
||||
'type': 'interface',
|
||||
'members': {
|
||||
'addr': {
|
||||
'type': 'port',
|
||||
'name': 'csr_bus__addr',
|
||||
'dir': 'in',
|
||||
'width': 16,
|
||||
'signed': False,
|
||||
'init': '0'
|
||||
},
|
||||
'w_en': {
|
||||
'type': 'port',
|
||||
'name': 'csr_bus__w_en',
|
||||
'dir': 'in',
|
||||
'width': 1,
|
||||
'signed': False,
|
||||
'init': '0'
|
||||
},
|
||||
'w_data': {
|
||||
'type': 'port',
|
||||
'name': 'csr_bus__w_data',
|
||||
'dir': 'in',
|
||||
'width': 32,
|
||||
'signed': False,
|
||||
'init': '0'
|
||||
},
|
||||
'r_en': {
|
||||
'type': 'port',
|
||||
'name': 'csr_bus__r_en',
|
||||
'dir': 'in',
|
||||
'width': 1,
|
||||
'signed': False,
|
||||
'init': '0'
|
||||
},
|
||||
'r_data': {
|
||||
'type': 'port',
|
||||
'name': 'csr_bus__r_data',
|
||||
'dir': 'out',
|
||||
'width': 32,
|
||||
'signed': False,
|
||||
'init': '0'
|
||||
},
|
||||
},
|
||||
'annotations': {
|
||||
'https://amaranth-lang.org/schema/example/0/csr-layout.json': {
|
||||
'registers': {
|
||||
'control': 0,
|
||||
'status': 4,
|
||||
'data': 8
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'annotations': {}
|
||||
}
|
||||
}
|
||||
|
||||
.. testcode::
|
||||
:hide:
|
||||
|
||||
# The way doctest requires this object to be formatted is truly hideous, even with +NORMALIZE_WHITESPACE.
|
||||
assert peripheral.metadata.as_json() == {'interface': {'members': {'csr_bus': {'type': 'interface', 'members': {'addr': {'type': 'port', 'name': 'csr_bus__addr', 'dir': 'in', 'width': 16, 'signed': False, 'init': '0'}, 'w_en': {'type': 'port', 'name': 'csr_bus__w_en', 'dir': 'in', 'width': 1, 'signed': False, 'init': '0'}, 'w_data': {'type': 'port', 'name': 'csr_bus__w_data', 'dir': 'in', 'width': 32, 'signed': False, 'init': '0'}, 'r_en': {'type': 'port', 'name': 'csr_bus__r_en', 'dir': 'in', 'width': 1, 'signed': False, 'init': '0'}, 'r_data': {'type': 'port', 'name': 'csr_bus__r_data', 'dir': 'out', 'width': 32, 'signed': False, 'init': '0'}}, 'annotations': {'https://amaranth-lang.org/schema/example/0/csr-layout.json': {'registers': {'control': 0, 'status': 4, 'data': 8}}}}}, 'annotations': {}}}
|
||||
|
||||
|
||||
Identifying schemas
|
||||
-------------------
|
||||
|
||||
An :class:`Annotation` schema must have a ``"$id"`` property, whose value is a URL that serves as its globally unique identifier. The suggested format of this URL is:
|
||||
|
||||
.. code::
|
||||
|
||||
<protocol>://<domain>/schema/<package>/<version>/<path>.json
|
||||
|
||||
where:
|
||||
|
||||
* ``<domain>`` is a domain name registered to the person or entity defining the annotation;
|
||||
* ``<package>`` is the name of the Python package providing the :class:`Annotation` subclass;
|
||||
* ``<version>`` is the version of that package;
|
||||
* ``<path>`` is a non-empty string specific to the annotation.
|
||||
|
||||
.. note::
|
||||
|
||||
Annotations used in the Amaranth project packages are published under https://amaranth-lang.org/schema/ according to this URL format, and are covered by the usual compatibility commitment.
|
||||
|
||||
Other projects that define additional Amaranth annotations are encouraged, but not required, to make their schemas publicly accessible; the only requirement is for the URL to be globally unique.
|
||||
|
||||
|
||||
Reference
|
||||
---------
|
||||
|
||||
.. autoexception:: InvalidSchema
|
||||
|
||||
.. autoexception:: InvalidAnnotation
|
||||
|
||||
.. autoclass:: Annotation
|
||||
:no-members:
|
||||
:members: validate, origin, as_json
|
||||
|
||||
.. automethod:: __init_subclass__()
|
||||
|
||||
.. autoattribute:: schema
|
||||
:annotation: = { "$id": "...", ... }
|
||||
|
|
@ -599,4 +599,19 @@ Making connections
|
|||
Components
|
||||
==========
|
||||
|
||||
.. _JSON Schema: https://json-schema.org
|
||||
|
||||
.. autoclass:: Component
|
||||
|
||||
|
||||
Component metadata
|
||||
==================
|
||||
|
||||
.. autoexception:: InvalidMetadata
|
||||
|
||||
.. autoclass:: ComponentMetadata
|
||||
:no-members:
|
||||
:members: validate, origin, as_json
|
||||
|
||||
.. autoattribute:: schema
|
||||
:annotation: = { "$id": "https://amaranth-lang.org/schema/amaranth/0.5/component.json", ... }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue