Skip to content

Commit 207f841

Browse files
committed
RFC 50: Print statement and string formatting.
1 parent b31896d commit 207f841

File tree

1 file changed

+200
-0
lines changed

1 file changed

+200
-0
lines changed

text/0050-print.md

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
- Start Date: 2024-03-04
2+
- RFC PR: [amaranth-lang/rfcs#50](https://github.com/amaranth-lang/rfcs/pull/50)
3+
- Amaranth Issue: [amaranth-lang/amaranth#0000](https://github.com/amaranth-lang/amaranth/issues/0000)
4+
5+
# `Print` statement and string formatting
6+
7+
## Summary
8+
[summary]: #summary
9+
10+
A `Print` statement is added, providing for simulation-time printing. A `Format` object, based on Python format strings, is added to support the `Print` statement. The existing `Assert`, `Assume`, and `Cover` statements are modified to make use of `Format` functionality as well.
11+
12+
## Motivation
13+
[motivation]: #motivation
14+
15+
This functionality has been requested multiple times, for debugging purposes. While debug printing can be provided externally via Python testbenches, having it as a statement allows it to be embedded directly into the module, without having to carefully plumb the required state into the simulator process. Since it can be passed to yosys, it is also usable for cross-language simulation.
16+
17+
## Guide-level explanation
18+
[guide-level-explanation]: #guide-level-explanation
19+
20+
A `Print` statement can be used to print design state during simulation:
21+
22+
```
23+
ctr = Signal(16)
24+
m.d.sync += [
25+
ctr.eq(ctr + 1),
26+
Print("counter:", ctr),
27+
]
28+
```
29+
30+
```
31+
counter: 0
32+
counter: 1
33+
counter: 2
34+
...
35+
```
36+
37+
The `Print` statement is modeled after Python's `print` function and likewise supports `sep=` and `end=` keyword arguments.
38+
39+
For more advanced formatting, `Print` can be paired with `Format`, which corresponds to Python string formatting and uses a subset of the same syntax:
40+
41+
```
42+
ctr = Signal(16)
43+
m.d.sync += [
44+
ctr.eq(ctr + 1),
45+
Print(Format("Counter: {ctr:04x}", ctr=ctr)),
46+
]
47+
```
48+
49+
```
50+
...
51+
Counter: fffe
52+
Counter: ffff
53+
Counter: 0000
54+
Counter: 0001
55+
Counter: 0002
56+
...
57+
```
58+
59+
The `Format` functionality supports printing `Value`s as integers, in `b`, `d`, `o`, `x`, `X` format types, with most of the relevant formatting options. The `c` format, which prints the `Value` treating it as a Unicode code pointer, is also supported.
60+
61+
In addition, the Amaranth-specific `s` format is supported, which prints a `Value` as a Verilog-style string: the `Value` is treated as a string of octets starting from LSB, trailing NUL bytes are trimmed, and the octets are interpreted as UTF-8.
62+
63+
Formatting `ValueCastable` is not supported, and is left for a future RFC. Formatting anything other than `Value` and `ValueCastable` will be done at elaboration time by defering to normal Python string formatting.
64+
65+
The `Assert`, `Assume`, and `Cover` statements are extended to take an optional message (which can be a `Format` object) that will be printed when the statement is triggered:
66+
67+
```
68+
m.d.sync += Assert(ctr < 10, message=Format("ctr value {} is out of bounds", ctr))
69+
```
70+
71+
```
72+
assertion failed at foo.py:13: ctr value 17 is out of bounds
73+
```
74+
75+
When message is not included, `Assert` and `Assume` will get a default message, while `Cover` will execute silently.
76+
77+
The `Print`, `Assert`, `Assume`, and `Cover` statements are supported in the Python simulator, in CXXRTL simulator, and (to the best of target language's ability) in Verilog output.
78+
79+
In pysim, `Print` prints to standard output. `Assert` and `Assume` will throw an `AssertionError` when the test is false, with the formatted message included in the exception message. The `Cover` statement prints the message to standard output together with its source location, and the actual coverage tracking functionality is not implemented yet.
80+
81+
While `Assert` executes identically to `Assume` (with the exception of failure message), `Assert` is meant for checking post-conditions, while `Assume` is meant for checking pre-conditions.
82+
83+
84+
## Reference-level explanation
85+
[reference-level-explanation]: #reference-level-explanation
86+
87+
### `Format` objects
88+
89+
A new class is introduced for the formatting functionality:
90+
91+
- `amaranth.hdl.Format(format_string, /, *args, **kwargs)`
92+
93+
The format string uses exactly the same format string grammar as `str.format`. Any arguments that are not `Value` or `ValueCastable` will be formatted immediately when the `Format` object is constructed and essentially inlined into the format string. Any `ValueCastable` arguments are an error (as a placeholder for a future RFC which will specify `ValueCastable` formatting). Any `Value` arguments will be stored, to be formatted at simulation time.
94+
95+
For `Value`, the following subset of standard format specifiers is supported:
96+
97+
- fill character: supported
98+
- alignment: `<`, `>`, `=` (but *not* `^`)
99+
- sign: `-`, `+`, ` `
100+
- `#` and `0` options: supported
101+
- width: supported, but must be an elaboration-time constant (ie. cannot be another `Value`)
102+
- grouping option: `_` is supported, `,` is *not* supported
103+
- type:
104+
- `b`, `c`, `d`, `o`, `x`, `X`: supported, with Python semantics
105+
- `s`: the corresponding `Value` must be a multiple of 8 bits wide; the value is converted into an UTF-8 string LSB-first, any 0 bytes present in the string are removed, and the result is printed as a string
106+
107+
Two `Format` objects can be concatenated together with the `+` operator.
108+
109+
The `Format` class is added to the prelude.
110+
111+
### The `Print` statement
112+
113+
A `Print` statement is added:
114+
115+
- `amaranth.hdl.Print(*args, sep=" ", end="\n")`
116+
117+
Any argument that is not a `Format` instance will be implicitly converted by wrapping it in `Format("{}", arg)`.
118+
119+
When the statement is executed in simulation, all `Format` objects will be evaluated to strings, the results will be joined together with the `sep` argument, the `end` argument will be appended at the end, and the resulting string will be printed directly to standard output.
120+
121+
A `Print` statement is considered active iff all `If`, `Case`, and `State` constructs in which it is contained are active.
122+
123+
`Print` statements contained in `comb` domains will be executed:
124+
125+
- at the beginning of the simulation, if active at that point
126+
- whenever they become active after being previously inactive
127+
- whenever any referenced `Value` changes while they are active
128+
129+
`Print` statements contained in non-`comb` domains will be executed whenever they are active on the relevant clock edge.
130+
131+
The `Print` statement is added to the prelude.
132+
133+
### `Assert`, `Assume`, `Cover`
134+
135+
The statements become:
136+
137+
- `amaranth.hdl.Assert(test, message=None)`
138+
- `amaranth.hdl.Assume(test, message=None)`
139+
- `amaranth.hdl.Cover(test, message=None)`
140+
141+
The `name` argument is deprecated and removed.
142+
143+
The `message` argument can be either `None`, a string, or a `Format` object. If it is a string, it's equivalent to passing `Format("{}", s)`.
144+
145+
Whenever an `Assert` or `Assume` is active and `test` evaluates to false, the simulator throws an `AssertionError` and includes the formatted `message`, if any, in the payload.
146+
147+
Whenever a `Cover` is active and `test` evaluates to true, and the `Cover` has a message attached, the formatted message is printed to standard output along with the source location.
148+
149+
The `Assert` statement is added to the prelude. The `Assume` and `Cover` statements need to be imported manually from `amaranth.hdl`. Their old location in `amaranth.asserts` is deprecated.
150+
151+
## Drawbacks
152+
[drawbacks]: #drawbacks
153+
154+
This is some quite complex functionality. It cannot be fully represented in Verilog output, since Verilog is quite limitted in its formatting capabilities.
155+
156+
It is not fully clear what `Assume` and especially `Cover` semantics should be, and they are very much formal verification oriented.
157+
158+
Unfortunately the `f""` syntax is not supported. The hacks that would be necessary to make this work are too horrifying even for the authors of this RFC.
159+
160+
## Rationale and alternatives
161+
[rationale-and-alternatives]: #rationale-and-alternatives
162+
163+
The design follows Python prior art, not leaving much space for discussion. The exact subset of supported format specifiers is, of course, to be bikeshedded.
164+
165+
The `s` format specifier is included to provide a way to implement data-dependent string printing, eg. for printing an enum value. It is expected this may be used for lowering future `ValueCastable` printing.
166+
167+
Signals that are enum-typed could get special default formatting. However, this will be solved by future `ValueCastable` hooks for Amaranth enums.
168+
169+
## Prior art
170+
[prior-art]: #prior-art
171+
172+
The design of `Print`, `Format` and `Assert` closely follows Python, as much as is easily possible.
173+
174+
The `s` format specifier is based directly on Verilog strings.
175+
176+
The `Print` statement corresponds and synthesizes to RTLIL `$print` cell, aligning closely to its capabilities.
177+
178+
The `Print` statement is loosely modelled on Verilog's `$display` and related task. The `Assert`, `Assume`, and `Cover` statements are loosely based on corresponding SystemVerilog statements.
179+
180+
## Unresolved questions
181+
[unresolved-questions]: #unresolved-questions
182+
183+
- What exact format specifiers should be supported?
184+
185+
## Future possibilities
186+
[future-possibilities]: #future-possibilities
187+
188+
`ValueCastable` formatting can and should be implemented by an extension hook on the `ValueCastable` class.
189+
190+
`Format` could be made into a (non-synthesizable) `Value` itself, corresponding to `$sformat` in Verilog. This would be useful for creating more complex formatting for `ValueCastables`.
191+
192+
Some kind of output redirection or output hook could be implemented in pysim.
193+
194+
Likewise, a hook for assertion failure could be desirable.
195+
196+
Actual coverage tracking could be implemented in pysim for `Cover`.
197+
198+
More exotic formatting could be useful (eg. for the proposed fixed point numbers).
199+
200+
Some way to print the current simulation time could be useful.

0 commit comments

Comments
 (0)