Skip to content

Commit 14e0a61

Browse files
authored
Merge RFC #38: Component signature immutability
2 parents 7cf237d + 507b600 commit 14e0a61

File tree

1 file changed

+81
-0
lines changed

1 file changed

+81
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
- Start Date: 2023-12-11
2+
- RFC PR: [amaranth-lang/rfcs#38](https://github.com/amaranth-lang/rfcs/pull/38)
3+
- Amaranth Issue: [amaranth-lang/amaranth#996](https://github.com/amaranth-lang/amaranth/issues/996)
4+
5+
# `Component.signature` immutability
6+
7+
## Summary
8+
[summary]: #summary
9+
10+
Clearly define the contract for `amaranth.lib.wiring.Component.signature`: an `amaranth.lib.wiring.Signature` object is assigned in the constructor to the read-only property `.signature`.
11+
12+
## Motivation
13+
[motivation]: #motivation
14+
15+
It is important that the signature of an interface object never change. `connect` relies on the signature to check that the connection being made is valid; if the signature changes after the connection is made (intentionally or accidentally), the design would silently break in a difficult to debug ways. For an ASIC this may mean a respin.
16+
17+
Right now, the guidance for the case where the default behavior of `Component.signature` is insufficient (i.e. for parametric components) and the generation of the signature must be customized, is to override the `signature` property, or to assign `self.signature =` in the constructor. This is clearly wrong: both of these approaches can easily result in the signature changing after construction of the component.
18+
19+
Moreover, at the moment, the implementation of the `Component.signature` property creates a new `Signature` object every time it is called. If the identity of the `Signature` object is important (i.e. if it has interior mutability, which is currently the case in Amaranth SoC), this is unsound. (It is unlikely, though not impossible, that this implementation would return an object with different members.)
20+
21+
## Guide-level explanation
22+
[guide-level-explanation]: #guide-level-explanation
23+
24+
To define a simple component whose signature does not change, use variable annotations:
25+
26+
```python
27+
class SimpleComponent(wiring.Component):
28+
en: In(1)
29+
data: Out(8)
30+
```
31+
32+
To define a component whose signature is parameterized by constructor arguments, call the superclass constructor with the signature that should be applied to the component:
33+
34+
```python
35+
class ParametricComponent(wiring.Component):
36+
def __init__(self, data_width):
37+
super().__init__({
38+
"en": In(1),
39+
"data": Out(data_width)
40+
})
41+
```
42+
43+
Do not override the `signature` property, as both Amaranth and third-party code relies on the fact that it is assigned in the constructor and never changes.
44+
45+
## Reference-level explanation
46+
[reference-level-explanation]: #reference-level-explanation
47+
48+
The constructor of `Component` is updated to take one argument: `def __init__(self, signature=None)`.
49+
- If the `signature` argument is not provided, the signature is derived from class variable annotations, as in RFC 2, and assigned to an internal attribute.
50+
- If the `signature` argument is provided, it is type-checked/type-cast and assigned to the internal attribute.
51+
- If a `Signature` is provided as a value, it is used as-is.
52+
- If a `dict` is provided, it is cast by calling `wiring.Signature(signature)`.
53+
- No other types are accepted.
54+
55+
If both the `signature` argument is provided and variable annotations are present, the constructor raises a `TypeError`. This is to guard against accidentally passing a `Signature` as an argument when constructing a component that is not parametric. (If this behavior is undesirable for some reason, it is always possible to implement a constructor that does not pass superclasses' constructor, and redefine the `signature` property, but this should be done as last resort only. We should cleary document that the `signature` property should return the exact same value at all times for any given `Component` instance.)
56+
57+
The `signature` property is redefined to return the value of this internal attribute.
58+
59+
No access to this attribute is provided besides the means above, and the name of the attribute is not defined in the documentation.
60+
61+
After assigning the internal attribute, the constructor creates the members as in RFC 2.
62+
63+
## Drawbacks
64+
[drawbacks]: #drawbacks
65+
66+
None. This properly enforces an invariant that is already relied upon.
67+
68+
## Rationale and alternatives
69+
[rationale-and-alternatives]: #rationale-and-alternatives
70+
71+
Some other behaviors were considered for `Component`: those which would made `signature` a class attribute, assigned in `__init_subclass__`. However, this option would leave the `signature` attribute mutable (as class-level properties are not reasonably supported in Python), which is undesirable, and significantly complicated the case of signatures with interior mutability.
72+
73+
## Unresolved questions
74+
[unresolved-questions]: #unresolved-questions
75+
76+
None.
77+
78+
## Future possibilities
79+
[future-possibilities]: #future-possibilities
80+
81+
None.

0 commit comments

Comments
 (0)