You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -53,6 +51,10 @@ class StreamInterface(PureInterface):
53
51
54
52
`sim.tick()` replaces the existing `Tick()`. It returns a trigger object that either can be awaited directly, or made conditional through `.until()`.
55
53
54
+
> **Note**
55
+
> This simplified example does not include any way of specifying the clock domain of the interface and as such is only directly applicable to single domain simulations.
56
+
> A way to attach clock domain information to interfaces is desireable, but out of scope for this RFC.
57
+
56
58
Using this stream interface, let's consider a colorspace converter accepting a stream of RGB values and outputting a stream of YUV values:
57
59
58
60
```python
@@ -82,70 +84,131 @@ async def testbench(sim):
82
84
Since `.send()` and `.recv()` invokes `sim.get()` and `sim.set()` that in turn will invoke the appropriate value conversions for a value castable (here `data.View`), it is general enough to work for streams with arbitrary shapes.
83
85
84
86
`Tick()` and `Delay()` are replaced by `sim.tick()` and `sim.delay()` respectively.
85
-
In addition, `sim.changed()` is introduced that allows creating triggers from arbitrary signals.
87
+
In addition, `sim.changed()`and `sim.edge()`is introduced that allows creating triggers from arbitrary signals.
86
88
These all return a trigger object that can be made conditional through `.until()`.
87
89
88
-
`Active()` and `Passive()` are replaced by an `passive=False` keyword argument to `.add_process()` and `.add_testbench()`.
89
-
To mark a passive testbench temporarily active, `sim.active()` is introduced, which is used as a context manager:
90
+
`Active()` and `Passive()` are replaced by an `background=False` keyword argument to `.add_testbench()`.
91
+
Processes created through `.add_process()` are always created as background processes.
92
+
To allow a background process to ensure an operation is finished before end of simulation, `sim.critical()` is introduced, which is used as a context manager:
90
93
91
94
```python
92
95
asyncdefpacket_reader(sim, stream):
93
96
whileTrue
94
97
# Wait until stream has valid data.
95
98
await sim.tick().until(stream.valid)
96
99
97
-
#Go active to ensure simulation doesn't end in the middle of a packet.
98
-
asyncwith sim.active():
100
+
#Ensure simulation doesn't end in the middle of a packet.
101
+
asyncwith sim.critical():
99
102
packet =await stream.read_packet()
100
103
print('Received packet:', packet.hex(''))
101
104
```
102
105
106
+
When a trigger object is awaited, it'll return the value(s) of the trigger(s), and it can also be used as an async generator to repeatedly await the same trigger.
107
+
Multiple triggers can be combined.
108
+
Consider the following examples:
109
+
110
+
Combinational adder as a process:
111
+
```python
112
+
a = Signal(); b = Signal(); o = Signal()
113
+
asyncdefadder(sim):
114
+
asyncfor a_val, b_val in sim.changed(a, b):
115
+
await sim.set(o, a_val + b_val)
116
+
sim.add_process(adder)
117
+
```
118
+
119
+
DDR IO buffer as a process:
120
+
```python
121
+
o = Signal(2); pin = Signal()
122
+
asyncdefddr_buffer(sim):
123
+
whileTrue: # could be extended to pre-capture next `o` on posedge
124
+
await sim.negedge()
125
+
await sim.set(pin, o[0])
126
+
await sim.posedge()
127
+
await sim.set(pin, o[1])
128
+
sim.add_process(ddr_buffer)
129
+
```
130
+
131
+
Flop with configurable edge reset and posedge clock as a process:
- Write `value` to `address` in `instance` when awaited. If `mask` is given, only the corresponding bits are written.
123
169
-`delay(interval)`
124
-
-Return a trigger object for advancing simulation by `interval` seconds.
170
+
-Create a trigger object for advancing simulation by `interval` seconds.
125
171
-`tick(domain="sync", *, context=None)`
126
-
-Return a trigger object for advancing simulation by one tick of `domain`.
172
+
-Create a trigger object for advancing simulation by one tick of `domain`.
127
173
When an elaboratable is passed to `context`, `domain` will be resolved from its perspective.
128
-
-`changed(signal, value=None)`
129
-
- Return a trigger object for advancing simulation until `signal` is changed to `value`. `None` is a wildcard and will trigger on any change.
130
-
-`active()`
131
-
- Return a context manager that temporarily marks the testbench as active for the duration.
132
-
-`time()`
133
-
- Return the current simulation time.
174
+
- If `domain` is asynchronously reset while this is being awaited, `AsyncReset` is raised.
175
+
-`changed(*signals)`
176
+
- Create a trigger object for advancing simulation until any signal in `signals` changes.
177
+
-`edge(signal, value)`
178
+
- Create a trigger object for advancing simulation until `signal` is changed to `value`.
179
+
`signal` must be a 1-bit signal or a 1-bit slice of a signal.
180
+
-`posedge(signal)`
181
+
-`negedge(signal)`
182
+
- Aliases for `edge(signal, 1)` and `edge(signal, 0)` respectively.
183
+
184
+
`signal` is changed to `value`. `None` is a wildcard and will trigger on any change.
185
+
-`critical()`
186
+
- Return a context manager that ensures simulation won't terminate in the middle of the enclosed scope.
134
187
135
188
A trigger object has the following methods:
189
+
-`__await__()`
190
+
- Advance simulation and return the value(s) of the trigger(s).
191
+
-`delay` and `tick` triggers return `True` when they are hit, otherwise `False`.
192
+
-`changed` and `edge` triggers return the current value of the signals they are monitoring.
193
+
-`__aiter__()`
194
+
- Return an async generator that repeatedly invokes `__await__()` and yields the returned values.
195
+
-`delay(interval)`
196
+
-`tick(domain="sync", *, context=None)`
197
+
-`changed(*signals)`
198
+
-`edge(signal, value)`
199
+
-`posedge(signal)`
200
+
-`negedge(signal)`
201
+
- Create a new trigger object by copying the current object and appending another trigger.
136
202
-`until(condition)`
137
203
- Repeat the trigger until `condition` is true.
138
204
`condition` is an arbitrary Amaranth expression.
139
205
If `condition` is initially true, `await` will return immediately without advancing simulation.
140
206
141
-
~~`Value`, `data.View` and `enum.EnumView` have `.get()` and `.set()` methods added.~~
142
-
143
207
`Tick()`, `Delay()`, `Active()` and `Passive()` as well as the ability to pass generator coroutines as `process` are deprecated and removed in a future version.
144
208
145
209
## Drawbacks
146
210
[drawbacks]: #drawbacks
147
211
148
-
-~~Reserves two new names on `Value` and value castables~~
149
212
- Increase in API surface area and complexity.
150
213
- Churn.
151
214
@@ -154,8 +217,6 @@ A trigger object has the following methods:
154
217
155
218
- Do nothing. Keep the existing interface, add `Changed()` alongside `Delay()` and `Tick()`, use `yield from` when calling functions.
156
219
157
-
-~~Don't introduce `.get()` and `.set()`. Instead require a value castable and the return value of its `.eq()` to be awaitable so `await value` and `await value.eq(foo)` is possible.~~
158
-
159
220
## Prior art
160
221
[prior-art]: #prior-art
161
222
@@ -164,18 +225,11 @@ Other python libraries like [cocotb](https://docs.cocotb.org/en/stable/coroutine
164
225
## Unresolved questions
165
226
[unresolved-questions]: #unresolved-questions
166
227
167
-
- It should be possible to combine triggers, e.g. when we have a set of signals and are waiting for either of them to change.
168
-
Simulating combinational logic with `add_process` would be one use case for this.
169
-
Simulating sync logic with async reset could be another.
170
-
What would be a good syntax to combine triggers?
171
-
- Is there any other functionality that's natural to have on the simulator context?
- Is there any other functionality that's natural to have on the trigger object?
174
-
- Maybe a way to skip a given number of triggers? We still lack a way to say «advance by n cycles».
175
228
- Bikeshed all the names.
176
-
- (@whitequark) We should consider different naming for `active`/`passive`.
177
229
178
230
## Future possibilities
179
231
[future-possibilities]: #future-possibilities
180
232
181
-
Add simulation helpers in the manner of `.send()` and `.recv()` to standard interfaces where it makes sense.
233
+
- Add simulation helpers in the manner of `.send()` and `.recv()` to standard interfaces where it makes sense.
234
+
- There is a desire for a `sim.time()` method that returns the current simulation time, but it needs a suitable return type to represent seconds with femtosecond resolution and that is out of the scope for this RFC.
235
+
- We ought to have a way to skip a given number of triggers, so that we can tell the simulation engine to e.g. «advance by n cycles».
0 commit comments