From b974a8d99afe99855ab118e10840f5cbbe6870a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Fri, 17 Nov 2023 19:05:59 +0100 Subject: [PATCH 01/11] Add RFC for component metadata. --- text/0000-component-metadata.md | 480 ++++++++++++++++++++++++++++++++ 1 file changed, 480 insertions(+) create mode 100644 text/0000-component-metadata.md diff --git a/text/0000-component-metadata.md b/text/0000-component-metadata.md new file mode 100644 index 0000000..0767393 --- /dev/null +++ b/text/0000-component-metadata.md @@ -0,0 +1,480 @@ +- Start Date: (fill me in with today's date, YYYY-MM-DD) +- RFC PR: [amaranth-lang/rfcs#0000](https://github.com/amaranth-lang/rfcs/pull/0000) +- Amaranth Issue: [amaranth-lang/amaranth#0000](https://github.com/amaranth-lang/amaranth/issues/0000) + +# Component metadata RFC + +## Summary +[summary]: #summary + +Add support for JSON-based introspection of an Amaranth component, describing its interface and properties. + +## Motivation +[motivation]: #motivation + +Introspection of components is an inherent feature of Amaranth. As Python objects, they make use of attributes to: +- expose the ports that compose their interface. +- communicate other kinds of metadata, such as behavioral properties or safety invariants. + +Multiple tools may consume parts of this metadata at different points in time. While the ports of an interface must be known at build time, other properties (such as a bus memory map) may be used afterwards to operate or verify the design. + +However, in a mixed HDL design, components implemented in other HDLs require ad-hoc integration: +- their netlist must be consulted in order to know their signature. +- each port must be connected individually (whereas Amaranth components can use `connect()` on compatible interfaces). +- there is no mechanism to pass metadata besides instance parameters and attributes. Any information produced by the instance itself cannot be easily passed to its parent. + +This RFC proposes a JSON-based format to describe and exchange component metadata. While building upon the concepts of [RFC 2](https://github.com/amaranth-lang/rfcs/blob/main/text/0002-interfaces.md), this metadata format tries to avoid making assumptions about its consumers (which could be other HDL frontends, block diagram design tools, etc). + +## Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +### Component metadata + +An `amaranth.lib.wiring.Component` can provide metadata about itself, represented as a JSON object. This metadata contains a hierarchical description of every port of its interface: + +```python3 +from amaranth import * +from amaranth.lib.data import StructLayout +from amaranth.lib.wiring import In, Out, Signature, Component + + +class AsyncSerialSignature(Signature): + def __init__(self, divisor_reset, divisor_bits, data_bits, parity): + self.data_bits = data_bits + self.parity = parity + + super().__init__({ + "divisor": In(divisor_bits, reset=divisor_reset), + + "rx_data": Out(data_bits), + "rx_err": Out(StructLayout({"overflow": 1, "frame": 1, "parity": 1})), + "rx_rdy": Out(1), + "rx_ack": In(1), + "rx_i": In(1), + + "tx_data": In(data_bits), + "tx_rdy": Out(1), + "tx_ack": In(1), + "tx_o": Out(1), + }) + + +class AsyncSerial(Component): + def __init__(self, *, divisor_reset, divisor_bits, data_bits=8, parity="none"): + self.divisor_reset = divisor_reset + self.divisor_bits = divisor_bits + self.data_bits = data_bits + self.parity = parity + + @property + def signature(self): + return AsyncSerialSignature(self.divisor_reset, self.divisor_bits, self.data_bits, self.parity) + + +if __name__ == "__main__": + import json + from amaranth.utils import bits_for + + divisor = int(100e6 // 115200) + serial = AsyncSerial(divisor_reset=divisor, divisor_bits=bits_for(divisor), data_bits=8, parity="none") + + print(json.dumps(serial.metadata.as_json(), indent=4)) +``` + +The `.metadata` property of a `Component` returns a `ComponentMetadata` instance describing that component. In the above example, ``serial.metadata.as_json()`` converts this metadata into a JSON object, which is then printed: + +```json +{ + "interface": { + "members": { + "divisor": { + "type": "port", + "name": "divisor", + "dir": "in", + "width": 10, + "signed": false, + "reset": 868 + }, + "rx_ack": { + "type": "port", + "name": "rx_ack", + "dir": "in", + "width": 1, + "signed": false, + "reset": 0 + }, + "rx_data": { + "type": "port", + "name": "rx_data", + "dir": "out", + "width": 8, + "signed": false, + "reset": 0 + }, + "rx_err": { + "type": "port", + "name": "rx_err", + "dir": "out", + "width": 3, + "signed": false, + "reset": 0 + }, + "rx_i": { + "type": "port", + "name": "rx_i", + "dir": "in", + "width": 1, + "signed": false, + "reset": 0 + }, + "rx_rdy": { + "type": "port", + "name": "rx_rdy", + "dir": "out", + "width": 1, + "signed": false, + "reset": 0 + }, + "tx_ack": { + "type": "port", + "name": "tx_ack", + "dir": "in", + "width": 1, + "signed": false, + "reset": 0 + }, + "tx_data": { + "type": "port", + "name": "tx_data", + "dir": "in", + "width": 8, + "signed": false, + "reset": 0 + }, + "tx_o": { + "type": "port", + "name": "tx_o", + "dir": "out", + "width": 1, + "signed": false, + "reset": 0 + }, + "tx_rdy": { + "type": "port", + "name": "tx_rdy", + "dir": "out", + "width": 1, + "signed": false, + "reset": 0 + } + }, + "annotations": {} + } +} +``` + +The `["interface"]["annotations"]` object, which is empty here, is explained in the next section. + +### Annotations + +Users can attach arbitrary annotations to an `amaranth.lib.wiring.Signature`, which are automatically collected into the metadata of components using this signature. + +An `Annotation` class has a name (e.g. `"org.amaranth-lang.soc.memory-map"`) and a [JSON schema](https://json-schema.org) defining the structure of its instances. To continue our previous example, we add an annotation to `AsyncSerialSignature` that will allow us to describe a [8-N-1](https://en.wikipedia.org/wiki/8-N-1) configuration: + +```python3 +class AsyncSerialAnnotation(Annotation): + name = "org.example.serial" + schema = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.org/schema/0.1/serial", + "type": "object", + "properties": { + "data_bits": { + "type": "integer", + "minimum": 0, + }, + "parity": { + "enum": [ "none", "mark", "space", "even", "odd" ], + }, + }, + "additionalProperties": False, + "required": [ + "data_bits", + "parity", + ], + } + + def __init__(self, origin): + assert isinstance(origin, AsyncSerialSignature) + self.origin = origin + + def as_json(self): + instance = { + "data_bits": self.origin.data_bits, + "parity": self.origin.parity, + } + self.validate(instance) + return instance +``` + +We can now override the `.annotations` property of `AsyncSerialSignature` to return an instance of our annotation: + +```python3 +class AsyncSerialSignature(Signature): + # ... + + @property + def annotations(self): + return (AsyncSerialAnnotation(self),) +``` + +Note: `Signature.annotations` can return multiple annotations, but they must have different names. + +Printing ``json.dumps(serial.metadata.as_json(), indent=4)`` will now output this: + +```json +{ + "interface": { + "members": { + "divisor": { + "type": "port", + "name": "divisor", + "dir": "in", + "width": 10, + "signed": false, + "reset": 868 + }, + "rx_ack": { + "type": "port", + "name": "rx_ack", + "dir": "in", + "width": 1, + "signed": false, + "reset": 0 + }, + "rx_data": { + "type": "port", + "name": "rx_data", + "dir": "out", + "width": 8, + "signed": false, + "reset": 0 + }, + "rx_err": { + "type": "port", + "name": "rx_err", + "dir": "out", + "width": 3, + "signed": false, + "reset": 0 + }, + "rx_i": { + "type": "port", + "name": "rx_i", + "dir": "in", + "width": 1, + "signed": false, + "reset": 0 + }, + "rx_rdy": { + "type": "port", + "name": "rx_rdy", + "dir": "out", + "width": 1, + "signed": false, + "reset": 0 + }, + "tx_ack": { + "type": "port", + "name": "tx_ack", + "dir": "in", + "width": 1, + "signed": false, + "reset": 0 + }, + "tx_data": { + "type": "port", + "name": "tx_data", + "dir": "in", + "width": 8, + "signed": false, + "reset": 0 + }, + "tx_o": { + "type": "port", + "name": "tx_o", + "dir": "out", + "width": 1, + "signed": false, + "reset": 0 + }, + "tx_rdy": { + "type": "port", + "name": "tx_rdy", + "dir": "out", + "width": 1, + "signed": false, + "reset": 0 + } + }, + "annotations": { + "org.example.serial": { + "data_bits": 8, + "parity": "none" + } + } + } +} +``` + +## Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +### Annotations + +- add an `Annotation` base class to `amaranth.lib.annotations`, with: + * a `.name` "abstract" class attribute, which must be a string (e.g. "org.amaranth-lang.soc.memory-map"). + * a `.schema` "abstract" class attribute, which must be a JSON schema, as a dict. + * a `.validate()` class method, which takes a JSON instance as argument. An exception is raised if the instance does not comply with the schema. + * a `.as_json()` abstract method, which must return a JSON instance, as a dict. This instance must be compliant with `.schema`, i.e. `self.validate(self.as_json())` must succeed. + +The following changes are made to `amaranth.lib.wiring`: +- add a `.annotations` property to `Signature`, which returns an empty tuple. If overriden, it must return an iterable of `Annotation` objects. + +### Component metadata + +The following changes are made to `amaranth.lib.wiring`: +- add a `ComponentMetadata` class, inheriting from `Annotation`, where: + - `.name` returns "org.amaranth-lang.component". + - `.schema` returns a JSON schema describing component metadata. Its definition is detailed below. + - `.__init__()` takes a `Component` object as parameter. + -`.origin` returns the component object given in `.__init__()`. + - `.as_json()` returns a JSON instance of `.origin`, that complies with `.schema`. It is populated by iterating over the component's interface and annotations. +- add a `.metadata` property to `Component`, which returns `ComponentMetadata(self)`. + +#### Component metadata schema + +```python3 +class ComponentMetadata(Annotation): + name = "org.amaranth-lang.component" + schema = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://amaranth-lang.org/schema/amaranth/0.4/component", + "type": "object", + "properties": { + "interface": { + "type": "object", + "properties": { + "members": { + "type": "object", + "patternProperties": { + "^[A-Za-z][0-9A-Za-z_]*$": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "enum": ["port"], + }, + "name": { + "type": "string", + }, + "dir": { + "enum": ["in", "out"], + }, + "width": { + "type": "integer", + "minimum": 0, + }, + "signed": { + "type": "boolean", + }, + "reset": { + "type": "integer", + "minimum": 0, + }, + }, + "additionalProperties": False, + "required": [ + "type", + "name", + "dir", + "width", + "signed", + "reset", + ], + }, + { + "type": "object", + "properties": { + "type": { + "enum": ["interface"], + }, + "members": { + "$ref": "#/properties/interface/properties/members", + }, + "annotations": { + "type": "object", + }, + }, + "additionalProperties": False, + "required": [ + "type", + "members", + "annotations", + ], + }, + ], + }, + }, + "additionalProperties": False, + }, + "annotations": { + "type": "object", + }, + }, + "additionalProperties": False, + "required": [ + "members", + "annotations", + ], + }, + }, + "additionalProperties": False, + "required": [ + "interface", + ] + } + + # ... +``` + +Notes: +- Reset values are represented by their two's complement. For example, the reset value of `Member(Out, signed(2), reset=-1)` would be given as `3`. +- Despite not being enforced in this schema, annotations must be uniquely identified by their name. For example, an `"org.example.serial"` annotation may have only one possible schema. + +## Drawbacks +[drawbacks]: #drawbacks + +- `Annotation` class definitions must be kept in sync with their associated `Signature`. Using `Annotation.validate()` can catch some mismatches, but won't help if one forgets to add a new attribute to the JSON schema. +- it is possible to define multiple `Annotation` classes with the same `.name` attribute. + +## Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +- Usage of this feature is entirely optional. It has a limited impact on the `amaranth.lib.wiring`, by reserving only two attributes: `Signature.annotations` and `Component.metadata`. +- JSON schema is an IETF standard that is well supported across tools and programming languages. + +## Unresolved questions +[unresolved-questions]: #unresolved-questions + +To do before merging: +- Add support for clock and reset ports of a component. + +Out of scope: +- Add support for port annotations (e.g. to describe non-trivial shapes). + +## Future possibilities +[future-possibilities]: #future-possibilities + +While this RFC can apply to any Amaranth component, one of its motivating use cases is the ability to export the interface and behavioral properties of SoC peripherals. From 012304e0c9bd83c939373ff3efff3dd120460965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Fri, 24 Nov 2023 18:29:58 +0100 Subject: [PATCH 02/11] RFC #30: Address feedback from the 2023-11-20 Amaranth meeting. - clarify how Signature subclasses can have multiple annotations. - add a naming policy for annotations. - rename lib.annotation to lib.meta. - remove mention that ComponentMetadata inherits from Annotation. - expand drawbacks and rationale sections. - move support for clock and reset signals to a later RFC. --- text/0000-component-metadata.md | 67 ++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/text/0000-component-metadata.md b/text/0000-component-metadata.md index 0767393..27c97c8 100644 --- a/text/0000-component-metadata.md +++ b/text/0000-component-metadata.md @@ -30,7 +30,9 @@ This RFC proposes a JSON-based format to describe and exchange component metadat ### Component metadata -An `amaranth.lib.wiring.Component` can provide metadata about itself, represented as a JSON object. This metadata contains a hierarchical description of every port of its interface: +An `amaranth.lib.wiring.Component` can provide metadata about itself, represented as a JSON object. This metadata contains a hierarchical description of every port of its interface. + +The following example defines an `AsyncSerial` component, and outputs its metadata: ```python3 from amaranth import * @@ -179,14 +181,14 @@ The `["interface"]["annotations"]` object, which is empty here, is explained in Users can attach arbitrary annotations to an `amaranth.lib.wiring.Signature`, which are automatically collected into the metadata of components using this signature. -An `Annotation` class has a name (e.g. `"org.amaranth-lang.soc.memory-map"`) and a [JSON schema](https://json-schema.org) defining the structure of its instances. To continue our previous example, we add an annotation to `AsyncSerialSignature` that will allow us to describe a [8-N-1](https://en.wikipedia.org/wiki/8-N-1) configuration: +An `Annotation` class has a name (e.g. `"org.amaranth-lang.soc.memory-map"`) and a [JSON schema](https://json-schema.org) defining the structure of its instances. To continue our `AsyncSerial` example, we add an annotation to `AsyncSerialSignature` that will allow us to describe a [8-N-1](https://en.wikipedia.org/wiki/8-N-1) configuration: ```python3 class AsyncSerialAnnotation(Annotation): - name = "org.example.serial" + name = "com.example.serial" schema = { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://example.org/schema/0.1/serial", + "$id": "https://example.com/schema/1.0/serial", "type": "object", "properties": { "data_bits": { @@ -217,7 +219,7 @@ class AsyncSerialAnnotation(Annotation): return instance ``` -We can now override the `.annotations` property of `AsyncSerialSignature` to return an instance of our annotation: +We can attach annotations to a `Signature` subclass by overriding its `.annotations` property: ```python3 class AsyncSerialSignature(Signature): @@ -225,12 +227,10 @@ class AsyncSerialSignature(Signature): @property def annotations(self): - return (AsyncSerialAnnotation(self),) + return (*super().annotations, AsyncSerialAnnotation(self)) ``` -Note: `Signature.annotations` can return multiple annotations, but they must have different names. - -Printing ``json.dumps(serial.metadata.as_json(), indent=4)`` will now output this: +The JSON object returned by ``serial.metadata.as_json()`` will now use this annotation: ```json { @@ -318,7 +318,7 @@ Printing ``json.dumps(serial.metadata.as_json(), indent=4)`` will now output thi } }, "annotations": { - "org.example.serial": { + "com.example.serial": { "data_bits": 8, "parity": "none" } @@ -327,14 +327,28 @@ Printing ``json.dumps(serial.metadata.as_json(), indent=4)`` will now output thi } ``` +#### Annotation names and schema URLs + +Annotation names must be prefixed by a reversed second-level domain name (e.g. "com.example") that belongs to the person or entity defining the annotation. Annotation names are obtainable from their schema URL (provided by the `"$id"` key of `Annotation.schema`). To ensure this, schema URLs must have the following structure: `":///schema//"`. The version of an annotation schema should match the Python package that implements it. + +Some examples of valid schema URLs: + +- "https://example.com/schema/1.0/serial" for the "com.example.serial" annotation; +- "https://amaranth-lang.org/schema/0.4/fifo" for "org.amaranth-lang.fifo"; +- "https://amaranth-lang.org/schema/0.1/soc/memory-map" for "org.amaranth-lang.soc.memory-map". + ## Reference-level explanation [reference-level-explanation]: #reference-level-explanation ### Annotations -- add an `Annotation` base class to `amaranth.lib.annotations`, with: +- add an `Annotation` base class to `amaranth.lib.meta`, with: * a `.name` "abstract" class attribute, which must be a string (e.g. "org.amaranth-lang.soc.memory-map"). * a `.schema` "abstract" class attribute, which must be a JSON schema, as a dict. + * a `.__init_subclass__()` class method, which raises an exception if: + - `.schema` does not comply with the [2020-12 draft](https://json-schema.org/specification-links#2020-12) of the JSON Schema specification. + - `.name` cannot be extracted from the `.schema["$id"]` URL (as explained [here](#annotation-names-and-schema-urls)). + - a `.origin` attribute, which returns the Python object described by an annotation instance. * a `.validate()` class method, which takes a JSON instance as argument. An exception is raised if the instance does not comply with the schema. * a `.as_json()` abstract method, which must return a JSON instance, as a dict. This instance must be compliant with `.schema`, i.e. `self.validate(self.as_json())` must succeed. @@ -344,12 +358,13 @@ The following changes are made to `amaranth.lib.wiring`: ### Component metadata The following changes are made to `amaranth.lib.wiring`: -- add a `ComponentMetadata` class, inheriting from `Annotation`, where: - - `.name` returns "org.amaranth-lang.component". - - `.schema` returns a JSON schema describing component metadata. Its definition is detailed below. +- add a `ComponentMetadata` class, with: + - a `.name` class attribute, which returns `"org.amaranth-lang.component"`. + - a `.schema` class attribute, which returns a JSON schema of component metadata. Its definition is detailed [below](#component-metadata-schema). + - a `.validate()` class method, which takes a JSON instance as argument. An exception is raised if the instance does not comply with the schema. - `.__init__()` takes a `Component` object as parameter. - -`.origin` returns the component object given in `.__init__()`. - - `.as_json()` returns a JSON instance of `.origin`, that complies with `.schema`. It is populated by iterating over the component's interface and annotations. + - a `.origin` attribute, which returns the component object given in `.__init__()`. + - a `.as_json()` method, which returns a JSON instance of `.origin` that complies with `.schema`. It is populated by iterating over the component's interface and annotations. - add a `.metadata` property to `Component`, which returns `ComponentMetadata(self)`. #### Component metadata schema @@ -449,32 +464,30 @@ class ComponentMetadata(Annotation): # ... ``` -Notes: -- Reset values are represented by their two's complement. For example, the reset value of `Member(Out, signed(2), reset=-1)` would be given as `3`. -- Despite not being enforced in this schema, annotations must be uniquely identified by their name. For example, an `"org.example.serial"` annotation may have only one possible schema. +Reset values are represented by their two's complement. For example, the reset value of `Member(Out, signed(2), reset=-1)` would be given as `3`. ## Drawbacks [drawbacks]: #drawbacks -- `Annotation` class definitions must be kept in sync with their associated `Signature`. Using `Annotation.validate()` can catch some mismatches, but won't help if one forgets to add a new attribute to the JSON schema. -- it is possible to define multiple `Annotation` classes with the same `.name` attribute. +- Developers need to learn the JSON Schema language to define annotations. +- An annotation schema URL may point to a non-existent domain, despite being well formatted. +- Handling backward-incompatible changes in new versions of an annotation is left to its consumers. ## Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives +- As an alternative, do nothing; let tools and downstream libraries provide non-interoperable mechanisms to introspect components to and from Amaranth designs. - Usage of this feature is entirely optional. It has a limited impact on the `amaranth.lib.wiring`, by reserving only two attributes: `Signature.annotations` and `Component.metadata`. - JSON schema is an IETF standard that is well supported across tools and programming languages. +- This metadata format can be translated into other formats, such as [IP-XACT](https://www.accellera.org/downloads/standards/ip-xact). ## Unresolved questions [unresolved-questions]: #unresolved-questions -To do before merging: -- Add support for clock and reset ports of a component. - -Out of scope: -- Add support for port annotations (e.g. to describe non-trivial shapes). +- The clock and reset ports of a component are omitted from this metadata format. Currently, the clock domains of an Amaranth component are only known at elaboration, whereas this RFC requires metadata to be accessible at any time. While this is a significant limitation for multi-clock components, single-clock components may be assumed to have a positive edge clock `"clk"` and a synchronous reset `"rst"`. Support for arbitrary clock domains should be introduced in later RFCs. +- Annotating individual ports of an interface is out of the scope of this RFC. Port annotations may be useful to describe non-trivial signal shapes, and introduced in a later RFC. ## Future possibilities [future-possibilities]: #future-possibilities -While this RFC can apply to any Amaranth component, one of its motivating use cases is the ability to export the interface and behavioral properties of SoC peripherals. +While this RFC can apply to any Amaranth component, one of its motivating use cases is the ability to export the interface and behavioral properties of SoC peripherals in various formats, such as SVD. From 835269ed793481a527e5604643e90606cb871bc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Mon, 27 Nov 2023 17:36:45 +0100 Subject: [PATCH 03/11] RFC #30: change the format of annotation schema URLs. * Remove restriction to second-level domains to allow providers such as Github Pages. * Move the schema version at the end of its URL, as project versions may not necessarily be synchronized within an organization. * Add a .json suffix to schema URLs, to help servers report the correct MIME type. --- text/0000-component-metadata.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/text/0000-component-metadata.md b/text/0000-component-metadata.md index 27c97c8..b3ad275 100644 --- a/text/0000-component-metadata.md +++ b/text/0000-component-metadata.md @@ -1,5 +1,5 @@ -- Start Date: (fill me in with today's date, YYYY-MM-DD) -- RFC PR: [amaranth-lang/rfcs#0000](https://github.com/amaranth-lang/rfcs/pull/0000) +- Start Date: 2024-11-20 +- RFC PR: [amaranth-lang/rfcs#30](https://github.com/amaranth-lang/rfcs/pull/30) - Amaranth Issue: [amaranth-lang/amaranth#0000](https://github.com/amaranth-lang/amaranth/issues/0000) # Component metadata RFC @@ -181,14 +181,14 @@ The `["interface"]["annotations"]` object, which is empty here, is explained in Users can attach arbitrary annotations to an `amaranth.lib.wiring.Signature`, which are automatically collected into the metadata of components using this signature. -An `Annotation` class has a name (e.g. `"org.amaranth-lang.soc.memory-map"`) and a [JSON schema](https://json-schema.org) defining the structure of its instances. To continue our `AsyncSerial` example, we add an annotation to `AsyncSerialSignature` that will allow us to describe a [8-N-1](https://en.wikipedia.org/wiki/8-N-1) configuration: +An `Annotation` class has a name (e.g. `"org.amaranth-lang.amaranth-soc.memory-map"`) and a [JSON schema](https://json-schema.org) defining the structure of its instances. To continue our `AsyncSerial` example, we add an annotation to `AsyncSerialSignature` that will allow us to describe a [8-N-1](https://en.wikipedia.org/wiki/8-N-1) configuration: ```python3 class AsyncSerialAnnotation(Annotation): name = "com.example.serial" schema = { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://example.com/schema/1.0/serial", + "$id": "https://example.com/schema/serial/1.0.json", "type": "object", "properties": { "data_bits": { @@ -329,13 +329,13 @@ The JSON object returned by ``serial.metadata.as_json()`` will now use this anno #### Annotation names and schema URLs -Annotation names must be prefixed by a reversed second-level domain name (e.g. "com.example") that belongs to the person or entity defining the annotation. Annotation names are obtainable from their schema URL (provided by the `"$id"` key of `Annotation.schema`). To ensure this, schema URLs must have the following structure: `":///schema//"`. The version of an annotation schema should match the Python package that implements it. +Annotation names must be prefixed by a reversed domain name (e.g. "com.example") that belongs to the person or entity defining the annotation. Annotation names are obtainable from their schema URL (provided by the `"$id"` key of `Annotation.schema`). To ensure this, schema URLs must have the following structure: `":///schema//.json"`. The version of an annotation schema should match the Python package that implements it. Some examples of valid schema URLs: -- "https://example.com/schema/1.0/serial" for the "com.example.serial" annotation; -- "https://amaranth-lang.org/schema/0.4/fifo" for "org.amaranth-lang.fifo"; -- "https://amaranth-lang.org/schema/0.1/soc/memory-map" for "org.amaranth-lang.soc.memory-map". +- "https://example.github.io/schema/serial/1.0.json" for the "io.github.example.serial" annotation; +- "https://amaranth-lang.org/schema/amaranth/fifo/4.0.json" for "org.amaranth-lang.amaranth.fifo"; +- "https://amaranth-lang.org/schema/amaranth-soc/memory-map/1.0.json" for "org.amaranth-lang.amaranth-soc.memory-map". ## Reference-level explanation [reference-level-explanation]: #reference-level-explanation @@ -343,7 +343,7 @@ Some examples of valid schema URLs: ### Annotations - add an `Annotation` base class to `amaranth.lib.meta`, with: - * a `.name` "abstract" class attribute, which must be a string (e.g. "org.amaranth-lang.soc.memory-map"). + * a `.name` "abstract" class attribute, which must be a string (e.g. "org.amaranth-lang.amaranth-soc.memory-map"). * a `.schema` "abstract" class attribute, which must be a JSON schema, as a dict. * a `.__init_subclass__()` class method, which raises an exception if: - `.schema` does not comply with the [2020-12 draft](https://json-schema.org/specification-links#2020-12) of the JSON Schema specification. @@ -359,7 +359,7 @@ The following changes are made to `amaranth.lib.wiring`: The following changes are made to `amaranth.lib.wiring`: - add a `ComponentMetadata` class, with: - - a `.name` class attribute, which returns `"org.amaranth-lang.component"`. + - a `.name` class attribute, which returns `"org.amaranth-lang.amaranth.component"`. - a `.schema` class attribute, which returns a JSON schema of component metadata. Its definition is detailed [below](#component-metadata-schema). - a `.validate()` class method, which takes a JSON instance as argument. An exception is raised if the instance does not comply with the schema. - `.__init__()` takes a `Component` object as parameter. @@ -371,10 +371,10 @@ The following changes are made to `amaranth.lib.wiring`: ```python3 class ComponentMetadata(Annotation): - name = "org.amaranth-lang.component" + name = "org.amaranth-lang.amaranth.component" schema = { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://amaranth-lang.org/schema/amaranth/0.4/component", + "$id": "https://amaranth-lang.org/schema/amaranth/component/0.4.json", "type": "object", "properties": { "interface": { From b92fe39a28a869f745356debfd034a6727a04f5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Mon, 27 Nov 2023 18:14:33 +0100 Subject: [PATCH 04/11] RFC #30: fix start date. --- text/0000-component-metadata.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-component-metadata.md b/text/0000-component-metadata.md index b3ad275..65feca5 100644 --- a/text/0000-component-metadata.md +++ b/text/0000-component-metadata.md @@ -1,4 +1,4 @@ -- Start Date: 2024-11-20 +- Start Date: 2023-11-20 - RFC PR: [amaranth-lang/rfcs#30](https://github.com/amaranth-lang/rfcs/pull/30) - Amaranth Issue: [amaranth-lang/amaranth#0000](https://github.com/amaranth-lang/amaranth/issues/0000) From 511cf1c701c5181baf0628ba55e9e9d39a63e7ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Mon, 15 Jan 2024 17:23:12 +0100 Subject: [PATCH 05/11] RFC #30: use strings to represent reset values. --- text/0000-component-metadata.md | 53 ++++++++++++++------------------- 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/text/0000-component-metadata.md b/text/0000-component-metadata.md index 65feca5..51acc16 100644 --- a/text/0000-component-metadata.md +++ b/text/0000-component-metadata.md @@ -63,14 +63,7 @@ class AsyncSerialSignature(Signature): class AsyncSerial(Component): def __init__(self, *, divisor_reset, divisor_bits, data_bits=8, parity="none"): - self.divisor_reset = divisor_reset - self.divisor_bits = divisor_bits - self.data_bits = data_bits - self.parity = parity - - @property - def signature(self): - return AsyncSerialSignature(self.divisor_reset, self.divisor_bits, self.data_bits, self.parity) + super().__init__(AsyncSerialSignature(divisor_reset, divisor_bits, data_bits, parity)) if __name__ == "__main__": @@ -95,7 +88,7 @@ The `.metadata` property of a `Component` returns a `ComponentMetadata` instance "dir": "in", "width": 10, "signed": false, - "reset": 868 + "reset": "868" }, "rx_ack": { "type": "port", @@ -103,7 +96,7 @@ The `.metadata` property of a `Component` returns a `ComponentMetadata` instance "dir": "in", "width": 1, "signed": false, - "reset": 0 + "reset": "0" }, "rx_data": { "type": "port", @@ -111,7 +104,7 @@ The `.metadata` property of a `Component` returns a `ComponentMetadata` instance "dir": "out", "width": 8, "signed": false, - "reset": 0 + "reset": "0" }, "rx_err": { "type": "port", @@ -119,7 +112,7 @@ The `.metadata` property of a `Component` returns a `ComponentMetadata` instance "dir": "out", "width": 3, "signed": false, - "reset": 0 + "reset": "0" }, "rx_i": { "type": "port", @@ -127,7 +120,7 @@ The `.metadata` property of a `Component` returns a `ComponentMetadata` instance "dir": "in", "width": 1, "signed": false, - "reset": 0 + "reset": "0" }, "rx_rdy": { "type": "port", @@ -135,7 +128,7 @@ The `.metadata` property of a `Component` returns a `ComponentMetadata` instance "dir": "out", "width": 1, "signed": false, - "reset": 0 + "reset": "0" }, "tx_ack": { "type": "port", @@ -143,7 +136,7 @@ The `.metadata` property of a `Component` returns a `ComponentMetadata` instance "dir": "in", "width": 1, "signed": false, - "reset": 0 + "reset": "0" }, "tx_data": { "type": "port", @@ -151,7 +144,7 @@ The `.metadata` property of a `Component` returns a `ComponentMetadata` instance "dir": "in", "width": 8, "signed": false, - "reset": 0 + "reset": "0" }, "tx_o": { "type": "port", @@ -159,7 +152,7 @@ The `.metadata` property of a `Component` returns a `ComponentMetadata` instance "dir": "out", "width": 1, "signed": false, - "reset": 0 + "reset": "0" }, "tx_rdy": { "type": "port", @@ -167,7 +160,7 @@ The `.metadata` property of a `Component` returns a `ComponentMetadata` instance "dir": "out", "width": 1, "signed": false, - "reset": 0 + "reset": "0" } }, "annotations": {} @@ -242,7 +235,7 @@ The JSON object returned by ``serial.metadata.as_json()`` will now use this anno "dir": "in", "width": 10, "signed": false, - "reset": 868 + "reset": "868" }, "rx_ack": { "type": "port", @@ -250,7 +243,7 @@ The JSON object returned by ``serial.metadata.as_json()`` will now use this anno "dir": "in", "width": 1, "signed": false, - "reset": 0 + "reset": "0" }, "rx_data": { "type": "port", @@ -258,7 +251,7 @@ The JSON object returned by ``serial.metadata.as_json()`` will now use this anno "dir": "out", "width": 8, "signed": false, - "reset": 0 + "reset": "0" }, "rx_err": { "type": "port", @@ -266,7 +259,7 @@ The JSON object returned by ``serial.metadata.as_json()`` will now use this anno "dir": "out", "width": 3, "signed": false, - "reset": 0 + "reset": "0" }, "rx_i": { "type": "port", @@ -274,7 +267,7 @@ The JSON object returned by ``serial.metadata.as_json()`` will now use this anno "dir": "in", "width": 1, "signed": false, - "reset": 0 + "reset": "0" }, "rx_rdy": { "type": "port", @@ -282,7 +275,7 @@ The JSON object returned by ``serial.metadata.as_json()`` will now use this anno "dir": "out", "width": 1, "signed": false, - "reset": 0 + "reset": "0" }, "tx_ack": { "type": "port", @@ -290,7 +283,7 @@ The JSON object returned by ``serial.metadata.as_json()`` will now use this anno "dir": "in", "width": 1, "signed": false, - "reset": 0 + "reset": "0" }, "tx_data": { "type": "port", @@ -298,7 +291,7 @@ The JSON object returned by ``serial.metadata.as_json()`` will now use this anno "dir": "in", "width": 8, "signed": false, - "reset": 0 + "reset": "0" }, "tx_o": { "type": "port", @@ -306,7 +299,7 @@ The JSON object returned by ``serial.metadata.as_json()`` will now use this anno "dir": "out", "width": 1, "signed": false, - "reset": 0 + "reset": "0" }, "tx_rdy": { "type": "port", @@ -405,8 +398,8 @@ class ComponentMetadata(Annotation): "type": "boolean", }, "reset": { - "type": "integer", - "minimum": 0, + "type": "string", + "pattern": "^[+-]?[0-9]+$", }, }, "additionalProperties": False, @@ -464,7 +457,7 @@ class ComponentMetadata(Annotation): # ... ``` -Reset values are represented by their two's complement. For example, the reset value of `Member(Out, signed(2), reset=-1)` would be given as `3`. +Reset values are serialized to strings (e.g. "-1"), because JSON can only represent integers up to 2^53. ## Drawbacks [drawbacks]: #drawbacks From 3683a2ca124b6c070edd03a1fca91ff56b4023a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Mon, 15 Jan 2024 17:24:15 +0100 Subject: [PATCH 06/11] RFC #30: update the $id URL format. --- text/0000-component-metadata.md | 35 ++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/text/0000-component-metadata.md b/text/0000-component-metadata.md index 51acc16..a35d3a0 100644 --- a/text/0000-component-metadata.md +++ b/text/0000-component-metadata.md @@ -178,10 +178,10 @@ An `Annotation` class has a name (e.g. `"org.amaranth-lang.amaranth-soc.memory-m ```python3 class AsyncSerialAnnotation(Annotation): - name = "com.example.serial" + name = "com.example.foo.serial" schema = { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://example.com/schema/serial/1.0.json", + "$id": "https://example.com/schema/foo/1.0/serial.json", "type": "object", "properties": { "data_bits": { @@ -307,11 +307,11 @@ The JSON object returned by ``serial.metadata.as_json()`` will now use this anno "dir": "out", "width": 1, "signed": false, - "reset": 0 + "reset": "0" } }, "annotations": { - "com.example.serial": { + "com.example.foo.serial": { "data_bits": 8, "parity": "none" } @@ -322,13 +322,30 @@ The JSON object returned by ``serial.metadata.as_json()`` will now use this anno #### Annotation names and schema URLs -Annotation names must be prefixed by a reversed domain name (e.g. "com.example") that belongs to the person or entity defining the annotation. Annotation names are obtainable from their schema URL (provided by the `"$id"` key of `Annotation.schema`). To ensure this, schema URLs must have the following structure: `":///schema//.json"`. The version of an annotation schema should match the Python package that implements it. +An `Annotation` schema must have a `"$id"` property, which holds an URL that serves as its unique identifier. This URL must have the following format: + +`:///schema///.json` + +where: + +* `` is a domain name registered to the person or entity defining the annotation; +* `` is the name of the Python package providing the `Annotation` subclass; +* `` is the version of the aforementioned package; +* `` is a non-empty string. + +An `Annotation` name should be retrievable from the `"$id"` property of its schema. Its name is the concatenation of the following parts, separated by `"."`: + +* ``, reversed (e.g. "com.example"); +* ``; +* ``, split using `"/"` as separator. Some examples of valid schema URLs: -- "https://example.github.io/schema/serial/1.0.json" for the "io.github.example.serial" annotation; -- "https://amaranth-lang.org/schema/amaranth/fifo/4.0.json" for "org.amaranth-lang.amaranth.fifo"; -- "https://amaranth-lang.org/schema/amaranth-soc/memory-map/1.0.json" for "org.amaranth-lang.amaranth-soc.memory-map". +- "https://example.github.io/schema/foo/1.0/serial.json" for the "io.github.example.foo.serial" annotation; +- "https://amaranth-lang.org/schema/amaranth/0.4/fifo.json" for "org.amaranth-lang.amaranth.fifo"; +- "https://amaranth-lang.org/schema/amaranth-soc/0.1/memory-map.json" for "org.amaranth-lang.amaranth-soc.memory-map". + +Changes to schema definitions hosted at https://amaranth-lang.org should follow the [RFC process](https://github.com/amaranth-lang/rfcs). ## Reference-level explanation [reference-level-explanation]: #reference-level-explanation @@ -367,7 +384,7 @@ class ComponentMetadata(Annotation): name = "org.amaranth-lang.amaranth.component" schema = { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://amaranth-lang.org/schema/amaranth/component/0.4.json", + "$id": "https://amaranth-lang.org/schema/amaranth/0.5/component.json", "type": "object", "properties": { "interface": { From 44d8603fdb5873f1403ff4a845ff6fd8970eee6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Mon, 15 Jan 2024 17:25:54 +0100 Subject: [PATCH 07/11] RFC #30: add a pattern constraint to interface port names. --- text/0000-component-metadata.md | 1 + 1 file changed, 1 insertion(+) diff --git a/text/0000-component-metadata.md b/text/0000-component-metadata.md index a35d3a0..81eb0a5 100644 --- a/text/0000-component-metadata.md +++ b/text/0000-component-metadata.md @@ -403,6 +403,7 @@ class ComponentMetadata(Annotation): }, "name": { "type": "string", + "pattern": "^[A-Za-z][A-Za-z0-9_]*$", }, "dir": { "enum": ["in", "out"], From a395797e35fdfc07902045cfc239d8f3e1ce5230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Mon, 15 Jan 2024 17:55:15 +0100 Subject: [PATCH 08/11] RFC #30: add link to implementation PR. --- text/0000-component-metadata.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-component-metadata.md b/text/0000-component-metadata.md index 81eb0a5..498b114 100644 --- a/text/0000-component-metadata.md +++ b/text/0000-component-metadata.md @@ -1,6 +1,6 @@ -- Start Date: 2023-11-20 +- Start Date: (fill me in with today's date, YYYY-MM-DD) - RFC PR: [amaranth-lang/rfcs#30](https://github.com/amaranth-lang/rfcs/pull/30) -- Amaranth Issue: [amaranth-lang/amaranth#0000](https://github.com/amaranth-lang/amaranth/issues/0000) +- Amaranth Issue: [amaranth-lang/amaranth#978](https://github.com/amaranth-lang/amaranth/issues/978) # Component metadata RFC From da1082e1a212aee9865710c63b665ef04a34255d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Tue, 16 Jan 2024 13:17:06 +0100 Subject: [PATCH 09/11] RFC #30: remove annotation names and use $id URLs instead. --- text/0000-component-metadata.md | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/text/0000-component-metadata.md b/text/0000-component-metadata.md index 498b114..f9662e6 100644 --- a/text/0000-component-metadata.md +++ b/text/0000-component-metadata.md @@ -178,7 +178,6 @@ An `Annotation` class has a name (e.g. `"org.amaranth-lang.amaranth-soc.memory-m ```python3 class AsyncSerialAnnotation(Annotation): - name = "com.example.foo.serial" schema = { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://example.com/schema/foo/1.0/serial.json", @@ -311,7 +310,7 @@ The JSON object returned by ``serial.metadata.as_json()`` will now use this anno } }, "annotations": { - "com.example.foo.serial": { + "https://example.com/schema/foo/1.0/serial.json": { "data_bits": 8, "parity": "none" } @@ -320,9 +319,9 @@ The JSON object returned by ``serial.metadata.as_json()`` will now use this anno } ``` -#### Annotation names and schema URLs +#### Annotation schema URLs -An `Annotation` schema must have a `"$id"` property, which holds an URL that serves as its unique identifier. This URL must have the following format: +An `Annotation` schema must have a `"$id"` property, which holds an URL that serves as its unique identifier. The following convention is required for the `"$id"` of schemas hosted at https://amaranth-lang.org, and suggested otherwise: `:///schema///.json` @@ -333,17 +332,10 @@ where: * `` is the version of the aforementioned package; * `` is a non-empty string. -An `Annotation` name should be retrievable from the `"$id"` property of its schema. Its name is the concatenation of the following parts, separated by `"."`: +For example: -* ``, reversed (e.g. "com.example"); -* ``; -* ``, split using `"/"` as separator. - -Some examples of valid schema URLs: - -- "https://example.github.io/schema/foo/1.0/serial.json" for the "io.github.example.foo.serial" annotation; -- "https://amaranth-lang.org/schema/amaranth/0.4/fifo.json" for "org.amaranth-lang.amaranth.fifo"; -- "https://amaranth-lang.org/schema/amaranth-soc/0.1/memory-map.json" for "org.amaranth-lang.amaranth-soc.memory-map". +- "https://amaranth-lang.org/schema/amaranth/0.5/fifo.json"; +- "https://amaranth-lang.org/schema/amaranth-soc/0.1/memory-map.json". Changes to schema definitions hosted at https://amaranth-lang.org should follow the [RFC process](https://github.com/amaranth-lang/rfcs). @@ -353,11 +345,9 @@ Changes to schema definitions hosted at https://amaranth-lang.org should follow ### Annotations - add an `Annotation` base class to `amaranth.lib.meta`, with: - * a `.name` "abstract" class attribute, which must be a string (e.g. "org.amaranth-lang.amaranth-soc.memory-map"). * a `.schema` "abstract" class attribute, which must be a JSON schema, as a dict. * a `.__init_subclass__()` class method, which raises an exception if: - `.schema` does not comply with the [2020-12 draft](https://json-schema.org/specification-links#2020-12) of the JSON Schema specification. - - `.name` cannot be extracted from the `.schema["$id"]` URL (as explained [here](#annotation-names-and-schema-urls)). - a `.origin` attribute, which returns the Python object described by an annotation instance. * a `.validate()` class method, which takes a JSON instance as argument. An exception is raised if the instance does not comply with the schema. * a `.as_json()` abstract method, which must return a JSON instance, as a dict. This instance must be compliant with `.schema`, i.e. `self.validate(self.as_json())` must succeed. @@ -369,7 +359,6 @@ The following changes are made to `amaranth.lib.wiring`: The following changes are made to `amaranth.lib.wiring`: - add a `ComponentMetadata` class, with: - - a `.name` class attribute, which returns `"org.amaranth-lang.amaranth.component"`. - a `.schema` class attribute, which returns a JSON schema of component metadata. Its definition is detailed [below](#component-metadata-schema). - a `.validate()` class method, which takes a JSON instance as argument. An exception is raised if the instance does not comply with the schema. - `.__init__()` takes a `Component` object as parameter. @@ -381,7 +370,6 @@ The following changes are made to `amaranth.lib.wiring`: ```python3 class ComponentMetadata(Annotation): - name = "org.amaranth-lang.amaranth.component" schema = { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://amaranth-lang.org/schema/amaranth/0.5/component.json", From 7eb2a48b9ebec1a060919dbaed0286f9168569de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Tue, 16 Jan 2024 13:21:03 +0100 Subject: [PATCH 10/11] RFC #30: give Signature.annotations() access to interface objects. In some cases such as SoC memory maps, the contents of an annotation originates from mutable objects, which cannot be attached to a signature. --- text/0000-component-metadata.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/text/0000-component-metadata.md b/text/0000-component-metadata.md index f9662e6..e5aad88 100644 --- a/text/0000-component-metadata.md +++ b/text/0000-component-metadata.md @@ -211,17 +211,18 @@ class AsyncSerialAnnotation(Annotation): return instance ``` -We can attach annotations to a `Signature` subclass by overriding its `.annotations` property: +We can attach annotations to a `Signature` subclass by overriding its `.annotations()` method: ```python3 class AsyncSerialSignature(Signature): # ... - @property - def annotations(self): - return (*super().annotations, AsyncSerialAnnotation(self)) + def annotations(self, obj): + return (*super().annotations(obj), AsyncSerialAnnotation(self)) ``` +In this case, `AsyncSerialAnnotation` depends on immutable metadata attached to `AsyncSerialSignature` (`.data_bits` and `.parity`). + The JSON object returned by ``serial.metadata.as_json()`` will now use this annotation: ```json @@ -353,7 +354,7 @@ Changes to schema definitions hosted at https://amaranth-lang.org should follow * a `.as_json()` abstract method, which must return a JSON instance, as a dict. This instance must be compliant with `.schema`, i.e. `self.validate(self.as_json())` must succeed. The following changes are made to `amaranth.lib.wiring`: -- add a `.annotations` property to `Signature`, which returns an empty tuple. If overriden, it must return an iterable of `Annotation` objects. +- add a `.annotations(self, obj)` method to `Signature`, which returns an empty tuple. If overriden, it must return an iterable of `Annotation` objects. `obj` is an interface object that complies with this signature, i.e. `self.is_compliant(obj)` must succeed. ### Component metadata From 453da4eabe74df61507f3d334847ced02d790574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Mon, 22 Jan 2024 19:04:01 +0100 Subject: [PATCH 11/11] RFC #30: update tracking issue. --- ...{0000-component-metadata.md => 0030-component-metadata.md} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename text/{0000-component-metadata.md => 0030-component-metadata.md} (99%) diff --git a/text/0000-component-metadata.md b/text/0030-component-metadata.md similarity index 99% rename from text/0000-component-metadata.md rename to text/0030-component-metadata.md index e5aad88..a1affc5 100644 --- a/text/0000-component-metadata.md +++ b/text/0030-component-metadata.md @@ -1,6 +1,6 @@ -- Start Date: (fill me in with today's date, YYYY-MM-DD) +- Start Date: 2024-01-22 - RFC PR: [amaranth-lang/rfcs#30](https://github.com/amaranth-lang/rfcs/pull/30) -- Amaranth Issue: [amaranth-lang/amaranth#978](https://github.com/amaranth-lang/amaranth/issues/978) +- Amaranth Issue: [amaranth-lang/amaranth#1047](https://github.com/amaranth-lang/amaranth/issues/1047) # Component metadata RFC