Skip to content

Commit 54e6a60

Browse files
committed
RFC #36: Updated to address feedback.
1 parent 4de07a2 commit 54e6a60

File tree

1 file changed

+31
-16
lines changed

1 file changed

+31
-16
lines changed

text/0036-async-testbench-functions.md

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ A more expressive way to specify trigger/wait conditions allows the condition ch
1818

1919
Passing a simulator context to the testbench function provides a convenient place to gather all simulator operations.
2020

21-
Having `.get()` and `.set()` methods provides a convenient way for value castables to implement these in a type-specific manner.
21+
~~Having `.get()` and `.set()` methods provides a convenient way for value castables to implement these in a type-specific manner.~~
2222

2323
## Guide-level explanation
2424
[guide-level-explanation]: #guide-level-explanation
@@ -29,26 +29,28 @@ On the interface class, we can then implement `.send()` and `.recv()` methods li
2929
```python
3030
class StreamInterface(PureInterface):
3131
async def recv(self, sim):
32-
await self.ready.set(1)
32+
await sim.set(self.ready, 1)
3333
await sim.tick().until(self.valid)
3434

35-
value = await self.data.get()
35+
value = await sim.get(self.data)
3636

3737
await sim.tick()
38-
await self.ready.set(0)
38+
await sim.set(self.ready, 0)
3939

4040
return value
4141

4242
async def send(self, sim, value):
43-
await self.data.set(value)
43+
await sim.set(self.data, value)
4444

45-
await self.valid.set(1)
45+
await sim.set(self.valid, 1)
4646
await sim.tick().until(self.ready)
4747

4848
await sim.tick()
49-
await self.valid.set(0)
49+
await sim.set(self.valid, 0)
5050
```
5151

52+
`await sim.get()` and `await sim.set()` replaces the existing operations `yield signal` and `yield signal.eq()` respectively.
53+
5254
`sim.tick()` replaces the existing `Tick()`. It returns a trigger object that either can be awaited directly, or made conditional through `.until()`.
5355

5456
Using this stream interface, let's consider a colorspace converter accepting a stream of RGB values and outputting a stream of YUV values:
@@ -77,7 +79,7 @@ async def testbench(sim):
7779
await test_rgb(sim, 255, 255, 255)
7880
```
7981

80-
Since `.send()` and `.recv()` invokes `.get()` and `.set()` that a value castable (here `data.View`) can implement in a suitable manner, it is general enough to work for streams with arbitrary shapes.
82+
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.
8183

8284
`Tick()` and `Delay()` are replaced by `sim.tick()` and `sim.delay()` respectively.
8385
In addition, `sim.changed()` is introduced that allows creating triggers from arbitrary signals.
@@ -108,13 +110,21 @@ The following `Simulator` methods have their signatures updated:
108110

109111
The new optional named argument `passive` registers the testbench as passive when true.
110112

111-
Both methods are updated to accept an async function passed as `process`. When the function passed to `process` accepts an argument named `sim`, it will be passed a simulator context.
113+
Both methods are updated to accept an async function passed as `process`.
114+
The async function must accept a named argument `sim`, which will be passed a simulator context.
112115

113116
The simulator context have the following methods:
114-
- `delay(interval=None)`
117+
- `get(signal)`
118+
- Returns the value of `signal` when awaited.
119+
When `signal` is a value-castable, the value will be converted through `.from_bits()`. (Pending RFC #51)
120+
- `set(signal, value)`
121+
- Set `signal` to `value` when awaited.
122+
When `signal` is a value-castable, the value will be converted through `.const()`.
123+
- `delay(interval)`
115124
- Return a trigger object for advancing simulation by `interval` seconds.
116-
- `tick(domain="sync")`
125+
- `tick(domain="sync", *, context=None)`
117126
- Return a trigger object for advancing simulation by one tick of `domain`.
127+
When an elaboratable is passed to `context`, `domain` will be resolved from its perspective.
118128
- `changed(signal, value=None)`
119129
- Return a trigger object for advancing simulation until `signal` is changed to `value`. `None` is a wildcard and will trigger on any change.
120130
- `active()`
@@ -124,24 +134,27 @@ The simulator context have the following methods:
124134

125135
A trigger object has the following methods:
126136
- `until(condition)`
127-
- Repeat the trigger until `condition` is true. If `condition` is initially true, `await` will return immediately without advancing simulation.
137+
- Repeat the trigger until `condition` is true.
138+
`condition` is an arbitrary Amaranth expression.
139+
If `condition` is initially true, `await` will return immediately without advancing simulation.
128140

129-
`Value`, `data.View` and `enum.EnumView` have `.get()` and `.set()` methods added.
141+
~~`Value`, `data.View` and `enum.EnumView` have `.get()` and `.set()` methods added.~~
130142

131143
`Tick()`, `Delay()`, `Active()` and `Passive()` as well as the ability to pass generator coroutines as `process` are deprecated and removed in a future version.
132144

133145
## Drawbacks
134146
[drawbacks]: #drawbacks
135147

136-
Reserves two new names on `Value` and value castables. Increase in API surface area and complexity. Churn.
148+
- ~~Reserves two new names on `Value` and value castables~~
149+
- Increase in API surface area and complexity.
150+
- Churn.
137151

138152
## Rationale and alternatives
139153
[rationale-and-alternatives]: #rationale-and-alternatives
140154

141155
- Do nothing. Keep the existing interface, add `Changed()` alongside `Delay()` and `Tick()`, use `yield from` when calling functions.
142156

143-
- 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.
144-
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.~~
145158

146159
## Prior art
147160
[prior-art]: #prior-art
@@ -156,9 +169,11 @@ Other python libraries like [cocotb](https://docs.cocotb.org/en/stable/coroutine
156169
Simulating sync logic with async reset could be another.
157170
What would be a good syntax to combine triggers?
158171
- Is there any other functionality that's natural to have on the simulator context?
172+
- (@wanda-phi) `sim.memory_read(memory, address)`, `sim.memory_write(memory, address, value[, mask])`?
159173
- Is there any other functionality that's natural to have on the trigger object?
160174
- Maybe a way to skip a given number of triggers? We still lack a way to say «advance by n cycles».
161175
- Bikeshed all the names.
176+
- (@whitequark) We should consider different naming for `active`/`passive`.
162177

163178
## Future possibilities
164179
[future-possibilities]: #future-possibilities

0 commit comments

Comments
 (0)