Skip to content

Commit 844c121

Browse files
committed
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.
1 parent 1c5790b commit 844c121

File tree

1 file changed

+40
-27
lines changed

1 file changed

+40
-27
lines changed

text/0000-component-metadata.md

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ This RFC proposes a JSON-based format to describe and exchange component metadat
3030

3131
### Component metadata
3232

33-
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:
33+
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.
34+
35+
The following example defines an `AsyncSerial` component, and outputs its metadata:
3436

3537
```python3
3638
from amaranth import *
@@ -179,14 +181,14 @@ The `["interface"]["annotations"]` object, which is empty here, is explained in
179181

180182
Users can attach arbitrary annotations to an `amaranth.lib.wiring.Signature`, which are automatically collected into the metadata of components using this signature.
181183

182-
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:
184+
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:
183185

184186
```python3
185187
class AsyncSerialAnnotation(Annotation):
186-
name = "org.example.serial"
188+
name = "com.example.serial"
187189
schema = {
188190
"$schema": "https://json-schema.org/draft/2020-12/schema",
189-
"$id": "https://example.org/schema/0.1/serial",
191+
"$id": "https://example.com/schema/1.0/serial",
190192
"type": "object",
191193
"properties": {
192194
"data_bits": {
@@ -217,20 +219,18 @@ class AsyncSerialAnnotation(Annotation):
217219
return instance
218220
```
219221

220-
We can now override the `.annotations` property of `AsyncSerialSignature` to return an instance of our annotation:
222+
We can attach annotations to a `Signature` subclass by overriding its `.annotations` property:
221223

222224
```python3
223225
class AsyncSerialSignature(Signature):
224226
# ...
225227

226228
@property
227229
def annotations(self):
228-
return (AsyncSerialAnnotation(self),)
230+
return (*super().annotations, AsyncSerialAnnotation(self))
229231
```
230232

231-
Note: `Signature.annotations` can return multiple annotations, but they must have different names.
232-
233-
Printing ``json.dumps(serial.metadata.as_json(), indent=4)`` will now output this:
233+
The JSON object returned by ``serial.metadata.as_json()`` will now use this annotation:
234234

235235
```json
236236
{
@@ -318,7 +318,7 @@ Printing ``json.dumps(serial.metadata.as_json(), indent=4)`` will now output thi
318318
}
319319
},
320320
"annotations": {
321-
"org.example.serial": {
321+
"com.example.serial": {
322322
"data_bits": 8,
323323
"parity": "none"
324324
}
@@ -327,14 +327,28 @@ Printing ``json.dumps(serial.metadata.as_json(), indent=4)`` will now output thi
327327
}
328328
```
329329

330+
#### Annotation names and schema URLs
331+
332+
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: `"<protocol>://<domain>/schema/<version>/<path>"`. The version of an annotation schema should match the Python package that implements it.
333+
334+
Some examples of valid schema URLs:
335+
336+
- "https://example.com/schema/1.0/serial" for the "com.example.serial" annotation;
337+
- "https://amaranth-lang.org/schema/0.4/fifo" for "org.amaranth-lang.fifo";
338+
- "https://amaranth-lang.org/schema/0.1/soc/memory-map" for "org.amaranth-lang.soc.memory-map".
339+
330340
## Reference-level explanation
331341
[reference-level-explanation]: #reference-level-explanation
332342

333343
### Annotations
334344

335-
- add an `Annotation` base class to `amaranth.lib.annotations`, with:
345+
- add an `Annotation` base class to `amaranth.lib.meta`, with:
336346
* a `.name` "abstract" class attribute, which must be a string (e.g. "org.amaranth-lang.soc.memory-map").
337347
* a `.schema` "abstract" class attribute, which must be a JSON schema, as a dict.
348+
* a `.__init_subclass__()` class method, which raises an exception if:
349+
- `.schema` does not comply with the [2020-12 draft](https://json-schema.org/specification-links#2020-12) of the JSON Schema specification.
350+
- `.name` cannot be extracted from the `.schema["$id"]` URL (as explained [here](#annotation-names-and-schema-urls)).
351+
- a `.origin` attribute, which returns the Python object described by an annotation instance.
338352
* a `.validate()` class method, which takes a JSON instance as argument. An exception is raised if the instance does not comply with the schema.
339353
* 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.
340354

@@ -344,12 +358,13 @@ The following changes are made to `amaranth.lib.wiring`:
344358
### Component metadata
345359

346360
The following changes are made to `amaranth.lib.wiring`:
347-
- add a `ComponentMetadata` class, inheriting from `Annotation`, where:
348-
- `.name` returns "org.amaranth-lang.component".
349-
- `.schema` returns a JSON schema describing component metadata. Its definition is detailed below.
361+
- add a `ComponentMetadata` class, with:
362+
- a `.name` class attribute, which returns `"org.amaranth-lang.component"`.
363+
- a `.schema` class attribute, which returns a JSON schema of component metadata. Its definition is detailed [below](#component-metadata-schema).
364+
- a `.validate()` class method, which takes a JSON instance as argument. An exception is raised if the instance does not comply with the schema.
350365
- `.__init__()` takes a `Component` object as parameter.
351-
-`.origin` returns the component object given in `.__init__()`.
352-
- `.as_json()` returns a JSON instance of `.origin`, that complies with `.schema`. It is populated by iterating over the component's interface and annotations.
366+
- a `.origin` attribute, which returns the component object given in `.__init__()`.
367+
- 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.
353368
- add a `.metadata` property to `Component`, which returns `ComponentMetadata(self)`.
354369

355370
#### Component metadata schema
@@ -449,32 +464,30 @@ class ComponentMetadata(Annotation):
449464
# ...
450465
```
451466

452-
Notes:
453-
- 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`.
454-
- 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.
467+
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`.
455468

456469
## Drawbacks
457470
[drawbacks]: #drawbacks
458471

459-
- `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.
460-
- it is possible to define multiple `Annotation` classes with the same `.name` attribute.
472+
- Developers need to learn the JSON Schema language to define annotations.
473+
- An annotation schema URL may point to a non-existent domain, despite being well formatted.
474+
- Handling backward-incompatible changes in new versions of an annotation is left to its consumers.
461475

462476
## Rationale and alternatives
463477
[rationale-and-alternatives]: #rationale-and-alternatives
464478

479+
- As an alternative, do nothing; let tools and downstream libraries provide non-interoperable mechanisms to introspect components to and from Amaranth designs.
465480
- 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`.
466481
- JSON schema is an IETF standard that is well supported across tools and programming languages.
482+
- This metadata format can be translated into other formats, such as [IP-XACT](https://www.accellera.org/downloads/standards/ip-xact).
467483

468484
## Unresolved questions
469485
[unresolved-questions]: #unresolved-questions
470486

471-
To do before merging:
472-
- Add support for clock and reset ports of a component.
473-
474-
Out of scope:
475-
- Add support for port annotations (e.g. to describe non-trivial shapes).
487+
- 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 mixed HDL designs, single-clock designs 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.
488+
- Annotating individual ports of an interface is not out of the scope of this RFC. Port annotations may be useful to describe non-trivial signal shapes, and introduced in a later RFC.
476489

477490
## Future possibilities
478491
[future-possibilities]: #future-possibilities
479492

480-
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.
493+
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.

0 commit comments

Comments
 (0)