Skip to content

Commit 0fd8e10

Browse files
committed
wip
1 parent a9e7b03 commit 0fd8e10

File tree

4 files changed

+163
-43
lines changed

4 files changed

+163
-43
lines changed

amaranth/lib/meta.py

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,45 @@
55
__all__ = ["InvalidSchema", "InvalidAnnotation", "Annotation"]
66

77

8-
def _create_catalog():
9-
return jschon.create_catalog('2020-12')
10-
11-
128
class InvalidSchema(Exception):
13-
"""Exception raised when an annotation with a non-conformant schema is defined."""
9+
"""Exception raised when a subclass of :class:`Annotation` is defined with a non-conformant
10+
:data:`~Annotation.schema`."""
1411

1512

1613
class InvalidAnnotation(Exception):
17-
"""Exception raised when an annotation."""
14+
"""Exception raised by :meth:`Annotation.validate` when the JSON representation of
15+
an annotation does not conform to its schema."""
1816

1917

2018
class Annotation(metaclass=ABCMeta):
21-
"""Signature annotation.
19+
"""Interface annotation.
20+
21+
A container for metadata that can be retrieved from an interface object using
22+
the :meth:`Signature.annotations <.wiring.Signature.annotations>` method.
2223
23-
A container for metadata that can be retrieved from a :class:`~amaranth.lib.wiring.Signature`
24-
object. Annotation instances can be exported as JSON objects, whose structure is defined using
24+
Annotation instances can be exported as JSON objects, whose structure is defined using
2525
the `JSON Schema`_ language.
2626
"""
2727

2828
#: :class:`dict`: Schema of this annotation, expressed in the `JSON Schema`_ language.
2929
#:
30-
#: Subclasses of :class:`Annotation` must implement this class attribute. If the value of
31-
#: the attribute is not a valid Amaranth annotation JSON Schema, :exc:`InvalidSchema` is raised.
30+
#: Subclasses of :class:`Annotation` must implement this class attribute.
3231
schema = {}
3332

33+
@classmethod
34+
def __schema(cls):
35+
catalog = jschon.create_catalog('2020-12')
36+
return jschon.JSONSchema(cls.schema, catalog=catalog)
37+
3438
def __init_subclass__(cls, **kwargs):
39+
"""
40+
Defining a subclass of :class:`Annotation` causes its :data:`schema` to be validated.
41+
42+
Raises
43+
------
44+
:exc:`InvalidSchema`
45+
If :data:`schema` doesn't conform to the `2020-12` draft of `JSON Schema`_.
46+
"""
3547
super().__init_subclass__(**kwargs)
3648

3749
if not isinstance(cls.schema, dict):
@@ -41,8 +53,8 @@ def __init_subclass__(cls, **kwargs):
4153
if "$id" not in cls.schema:
4254
raise ValueError(f"'$id' keyword is missing from Annotation schema: {cls.schema}")
4355

44-
schema_validity = jschon.JSONSchema(cls.schema, catalog=_create_catalog()).validate()
45-
# TODO
56+
schema_validity = cls.__schema().validate()
57+
assert schema_validity.valid # TODO
4658

4759
@property
4860
@abstractmethod
@@ -83,10 +95,10 @@ def validate(cls, instance):
8395
Raises
8496
------
8597
:exc:`InvalidAnnotation`
86-
If :py:`instance` doesn't comply with :attr:`Annotation.schema`.
98+
If :py:`instance` doesn't conform to :attr:`Annotation.schema`.
8799
"""
88-
validity = JSONSchema(cls.schema, catalog=_create_catalog()).evaluate(instance)
89-
# TODO
100+
validity = cls.__schema().evaluate(jschon.JSON(instance))
101+
assert validity.valid # TODO
90102

91103
def __repr__(self):
92104
return f"<{type(self).__module__}.{type(self).__qualname__} for {self.origin!r}>"

amaranth/lib/wiring.py

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -723,18 +723,6 @@ def members(self):
723723
"""
724724
return self.__members
725725

726-
def annotations(self, obj, /):
727-
"""Get annotations of an interface object.
728-
729-
Subclasses of :class:`Signature` may override this method to return annotations attached
730-
to an interface object compatible with this signature.
731-
732-
Returns
733-
-------
734-
iterable of :class:`Annotation`
735-
"""
736-
return ()
737-
738726
def __eq__(self, other):
739727
"""Compare this signature with another.
740728
@@ -997,6 +985,21 @@ def my_property(self):
997985
"""
998986
return PureInterface(self, path=path, src_loc_at=1 + src_loc_at)
999987

988+
def annotations(self, obj, /):
989+
"""Annotate an interface object.
990+
991+
Subclasses of :class:`Signature` may override this method to provide annotations for
992+
a corresponding interface object. The default implementation provides none.
993+
994+
See :mod:`amaranth.lib.meta` for details.
995+
996+
Returns
997+
-------
998+
iterable of :class:`~.meta.Annotation`
999+
:py:`tuple()`
1000+
"""
1001+
return tuple()
1002+
10001003
def __repr__(self):
10011004
if type(self) is Signature:
10021005
return f"Signature({dict(self.members.items())})"
@@ -1257,7 +1260,7 @@ def signature(self):
12571260
12581261
Returns
12591262
-------
1260-
Signature
1263+
:class:`Signature`
12611264
:py:`unflipped.signature.flip()`
12621265
"""
12631266
return self.__unflipped.signature.flip()
@@ -1267,7 +1270,7 @@ def __eq__(self, other):
12671270
12681271
Returns
12691272
-------
1270-
bool
1273+
:class:`bool`
12711274
:py:`True` if :py:`other` is an instance :py:`FlippedInterface(other_unflipped)` where
12721275
:py:`unflipped == other_unflipped`, :py:`False` otherwise.
12731276
"""

docs/stdlib.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ The Amaranth standard library is separate from the Amaranth language: everything
1717
stdlib/enum
1818
stdlib/data
1919
stdlib/wiring
20-
stdlib/memory
2120
stdlib/meta
21+
stdlib/memory
2222
stdlib/cdc
2323
stdlib/coding
2424
stdlib/fifo

docs/stdlib/meta.rst

Lines changed: 117 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
Metadata
2-
########
1+
Interface metadata
2+
##################
33

44
.. py:module:: amaranth.lib.meta
55
@@ -10,7 +10,7 @@ The :mod:`amaranth.lib.meta` module provides a way to annotate objects in an Ama
1010
.. testsetup::
1111

1212
from amaranth import *
13-
from amaranth.lib import wiring
13+
from amaranth.lib import wiring, meta
1414
from amaranth.lib.wiring import In, Out
1515

1616

@@ -92,40 +92,145 @@ All metadata in Amaranth must adhere to a schema in the `JSON Schema`_ language,
9292

9393
>>> wiring.ComponentMetadata.validate(adder.metadata.as_json())
9494

95+
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.
96+
9597

9698
Defining annotations
9799
--------------------
98100

101+
Consider a simple control and status register (CSR) bus that provides the memory layout of the accessible registers via an annotation:
102+
103+
.. testcode::
104+
105+
class CSRLayoutAnnotation(meta.Annotation):
106+
schema = {
107+
"$schema": "https://json-schema.org/draft/2020-12/schema",
108+
"$id": "https://amaranth-lang.org/schema/example/0/csr-layout.json",
109+
"type": "object",
110+
"properties": {
111+
"registers": {
112+
"type": "object",
113+
"patternProperties": {
114+
"^.+$": {
115+
"type": "integer",
116+
"minimum": 0,
117+
},
118+
},
119+
},
120+
},
121+
"requiredProperties": [
122+
"registers",
123+
],
124+
}
125+
126+
def __init__(self, origin):
127+
self._origin = origin
128+
129+
@property
130+
def origin(self):
131+
return self._origin
132+
133+
def as_json(self):
134+
print(self.origin)
135+
print(self.origin.registers)
136+
instance = {
137+
"registers": self.origin.registers,
138+
}
139+
# Validating the value returned by `as_json()` ensures its conformance.
140+
self.validate(instance)
141+
return instance
142+
143+
144+
class CSRSignature(wiring.Signature):
145+
def __init__(self):
146+
super().__init__({
147+
"addr": Out(16),
148+
"w_en": Out(1),
149+
"w_data": Out(32),
150+
"r_en": Out(1),
151+
"r_data": In(32),
152+
})
153+
154+
def annotations(self, obj, /):
155+
# Unfortunately `super()` cannot be used in `wiring.Signature` subclasses;
156+
# instead, use a direct call to a superclass method. In this case that is
157+
# `wiring.Signature` itself, but in a more complex class hierarchy it could
158+
# be different.
159+
return wiring.Signature.annotations(self, obj) + (CSRLayoutAnnotation(obj),)
160+
161+
A component that embeds a few CSR registers would define their addresses:
162+
163+
.. testcode::
164+
165+
class MyPeripheral(wiring.Component):
166+
csr_bus: In(CSRSignature())
167+
168+
def __init__(self):
169+
super().__init__()
170+
self.csr_bus.registers = {
171+
"control": 0x0000,
172+
"status": 0x0004,
173+
"data": 0x0008,
174+
}
175+
176+
.. doctest::
177+
178+
>>> peripheral = MyPeripheral()
179+
>>> peripheral.metadata.as_json()
180+
{
181+
'interface': {
182+
'members': { ... },
183+
'annotations': {
184+
'https://amaranth-lang.org/schema/example/0/csr-layout.json': {
185+
'registers': {
186+
'control': 0,
187+
'status': 4,
188+
'data': 8
189+
}
190+
}
191+
}
192+
}
193+
}
194+
195+
99196
.. todo:: Write this.
100197

101198

102-
Publishing schemas
103-
------------------
199+
Identifying schemas
200+
-------------------
104201

105-
.. todo:: Write this
202+
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:
106203

107-
An ``Annotation`` schema must have a ``"$id"`` property, which holds an URL that serves as its
108-
unique identifier. The suggested format of this URL is:
204+
.. code::
109205
110206
<protocol>://<domain>/schema/<package>/<version>/<path>.json
111207
112208
where:
209+
113210
* ``<domain>`` is a domain name registered to the person or entity defining the annotation;
114-
* ``<package>`` is the name of the Python package providing the ``Annotation`` subclass;
115-
* ``<version>`` is the version of the aforementioned package;
211+
* ``<package>`` is the name of the Python package providing the :class:`Annotation` subclass;
212+
* ``<version>`` is the version of that package;
116213
* ``<path>`` is a non-empty string specific to the annotation.
117214

215+
.. note::
216+
217+
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.
218+
219+
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.
220+
118221

119222
Reference
120223
---------
121224

122-
.. todo::
225+
.. autoexception:: InvalidSchema
123226

124-
Write this.
227+
.. autoexception:: InvalidAnnotation
125228

126229
.. autoclass:: Annotation
127230
:no-members:
128231
:members: validate, origin, as_json
129232

233+
.. automethod:: __init_subclass__()
234+
130235
.. autoattribute:: schema
131236
:annotation: = { "$id": "...", ... }

0 commit comments

Comments
 (0)