Skip to content

Commit 465657c

Browse files
authored
RFC #65: Special formatting for structures and enums
2 parents ecfcd6f + e4af1ad commit 465657c

File tree

1 file changed

+165
-0
lines changed

1 file changed

+165
-0
lines changed

text/0065-format-struct-enum.md

+165
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
- Start Date: 2024-04-08
2+
- RFC PR: [amaranth-lang/rfcs#65](https://github.com/amaranth-lang/rfcs/pull/65)
3+
- Amaranth Issue: [amaranth-lang/amaranth#1293](https://github.com/amaranth-lang/amaranth/issues/1293)
4+
5+
# Special formatting for structures and enums
6+
7+
## Summary
8+
[summary]: #summary
9+
10+
Extend `Format` machinery to support structured description of aggregate and enumerated data types.
11+
12+
## Motivation
13+
[motivation]: #motivation
14+
15+
When a `lib.data.Layout`-typed signal is included in the design, it is often useful to treat it as multiple signals for debug purposes:
16+
17+
- in pysim VCD output, it is useful to have each field as its own trace
18+
- in RTLIL output, it is useful to include a wire for each field (in particular, for CXXRTL simulation purposes)
19+
20+
Likewise, `lib.enum.Enum`-typed signals benefit from being marked as enum-typed in RTLIL output.
21+
22+
Currently, both of the above usecases are covered by the private `ShapeCastable._value_repr` interface. This interface has been added in a rush for the Amaranth 0.4 release as a stopgap measure, to ensure switching from `hdl.rec` to `lib.data` doesn't cause a functionality regression ([amaranth-lang/amaranth#790](https://github.com/amaranth-lang/amaranth/issues/790)). Such forbidden coupling between `lib` and `hdl` via private interfaces is frowned upon, and the time has come to atone for our sins and create a public interface that can be used for all sorts of aggregate and enumerated data types.
23+
24+
Since both needs relate to presentation of shape-castables, we propose piggybacking the functionality on top of `ShapeCastable.format` machinery. At the same time, we propose implementing formatting for `lib.data` and `lib.enum`.
25+
26+
## Guide-level explanation
27+
[guide-level-explanation]: #guide-level-explanation
28+
29+
Shape-castables implementing enumerated types can return a `Format.Enum` instance instead of a `Format` from their `format` method:
30+
31+
```py
32+
class MyEnum(ShapeCastable):
33+
...
34+
35+
def format(self, obj, spec):
36+
assert spec == ""
37+
return Format.Enum(Value.cast(obj), {
38+
0: "ABC",
39+
1: "DEF",
40+
2: "GHI",
41+
})
42+
```
43+
44+
For formatting purposes, this is equivalent to:
45+
46+
```py
47+
class MyEnum(ShapeCastable):
48+
...
49+
50+
def format(self, obj, spec):
51+
assert spec == ""
52+
return Format(
53+
"{:s}",
54+
Mux(Value.cast(obj) == 0, int.from_bytes("ABC".encode(), "little"),
55+
Mux(Value.cast(obj) == 1, int.from_bytes("DEF".encode(), "little"),
56+
Mux(Value.cast(obj) == 2, int.from_bytes("GHI".encode(), "little"),
57+
int.from_bytes("[unknown]".encode(), "little")
58+
)))
59+
)
60+
```
61+
62+
with the added benefit that any `MyEnum`-shaped signals will be automatically marked as enums in RTLIL output.
63+
64+
Likewise, shape-castables implementing aggregate types can return a `Format.Struct` instance instead of a `Format`:
65+
66+
```py
67+
class MyStruct(ShapeCastable):
68+
...
69+
70+
def format(self, obj, spec):
71+
assert spec == ""
72+
return Format.Struct({
73+
# Assume obj.a, obj.b, obj.c are accessors that return the struct fields as ValueLike.
74+
"a": Format("{}", obj.a),
75+
"b": Format("{}", obj.b),
76+
"c": Format("{}", obj.c),
77+
})
78+
```
79+
80+
For formatting purposes, this is equivalent to:
81+
82+
```py
83+
class MyStruct(ShapeCastable):
84+
...
85+
86+
def format(self, obj, spec):
87+
assert spec == ""
88+
return Format("{{a: {}, b: {}, c: {}}}", obj.a, obj.b, obj.c)
89+
return Format.Struct(Value.cast(obj), {
90+
# Assume obj.a, obj.b, obj.c are accessors that return the struct fields as ValueLike.
91+
"a": Format("{}", obj.a),
92+
"b": Format("{}", obj.b),
93+
"c": Format("{}", obj.c),
94+
})
95+
```
96+
97+
with the added benefit that any `MyStruct`-shaped signal will automatically have per-field traces included in VCD output and per-field wires included in RTLIL output.
98+
99+
Implementations of `format` are added as appropriate to `lib.enum` and `lib.data`, making use of the above features.
100+
101+
## Reference-level explanation
102+
[reference-level-explanation]: #reference-level-explanation
103+
104+
Three new classes are added:
105+
106+
- `amaranth.hdl.Format.Enum(value: Value, /, variants: EnumType | dict[int, str])`
107+
- `amaranth.hdl.Format.Struct(value: Value, /, fields: dict[str, Format])`
108+
- `amaranth.hdl.Format.Array(value: Value, /, fields: list[Format])`
109+
110+
Instances of these classes can be used wherever a `Format` can be used:
111+
112+
- as an argument to `Print`, `Assert`, ...
113+
- included within another `Format` via substitution
114+
- returned from `ShapeCastable.format`
115+
- as a field format within `Format.Struct` or `Format.Array`
116+
117+
When used for formatting:
118+
119+
- `Format.Enum` will display as whichever string corresponds to current value of `value`, or as `"[unknown]"` otherwise.
120+
- `Format.Struct` will display as `"{a: <formatted field a>, b: <formatted field b>}"`.
121+
- `Format.Array` will display as `"[<formatted field 0>, <formatted field 1>]"`
122+
123+
Whenever a signal is created with a shape-castable as its shape, its `format` method is called with `spec=""` and the result is stashed away.
124+
125+
VCD output is done as follows:
126+
127+
1. When a signal or field's format is just `Format("{}", value)`: value is emitted as a bitvector to VCD.
128+
2. Otherwise, when a signal or field's custom format is not `Format.Struct` nor `Format.Array`: the format is evaluated every time the value changes, and the result is emitted as a string to VCD
129+
3. When the custom format is `Format.Struct` or `Format.Array`:
130+
- the `value` as a whole is emitted as a bitvector
131+
- each field is emitted recursively (as a separate trace, perhaps with subtraces)
132+
133+
RTLIL output is done as follows:
134+
135+
1. When a signal or field's format is a plain `Format` and contains exactly one format specifier: a wire is created and assigned with the specifier's value.
136+
2. When a signal or field's format is a plain `Format` that doesn't conform to the above rule: no wire is created.
137+
3. When a signal or field's format is a `Format.Enum`: a wire is created and assigned with the format's `value`. RTLIL attributes are emitted on it, describing the enumeration values.
138+
4. When a signal or field's format is a `Format.Struct` or `Format.Array`: a wire is created and assigned with the format's `value`, representing the struct as a whole. For every field of the aggregate, the rules are applied recursively.
139+
140+
## Drawbacks
141+
[drawbacks]: #drawbacks
142+
143+
More language complexity.
144+
145+
A shape-castable using `Format.Struct` to mark itself as aggregate is forced to use the fixed `Format.Struct` display when formatted.
146+
147+
## Rationale and alternatives
148+
[rationale-and-alternatives]: #rationale-and-alternatives
149+
150+
This design ties together concerns of formatting with structural description. An alternative would be to have separate hooks for those, like the current `_value_repr` interface.
151+
152+
## Prior art
153+
[prior-art]: #prior-art
154+
155+
None.
156+
157+
## Unresolved questions
158+
[unresolved-questions]: #unresolved-questions
159+
160+
None.
161+
162+
## Future possibilities
163+
[future-possibilities]: #future-possibilities
164+
165+
The current `decoder` interface on `Signal` could be deprecated and retired.

0 commit comments

Comments
 (0)