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
@@ -63,13 +63,13 @@ The code example above is rewritten as:
63
63
dut = DUT()
64
64
deftestbench():
65
65
yield dut.out.eq(1)
66
-
yield
66
+
yield Tick()
67
67
print((yield dut.out))
68
68
print((yield dut.outn))
69
69
70
70
sim = Simulator(dut)
71
71
sim.add_clock(1e-6)
72
-
sim.add_testbench(testbench, domain="sync")
72
+
sim.add_testbench(testbench)
73
73
sim.run()
74
74
```
75
75
@@ -80,29 +80,134 @@ When run, it prints:
80
80
0
81
81
```
82
82
83
-
Existing testbenches can be ported to use `Simulator.add_testbench` by removing extraneous `yield` or `yield Settle()` calls.
83
+
Existing testbenches can be ported to use `Simulator.add_testbench` by removing extraneous `yield` or `yield Settle()` calls (and, in some cases, shifting other `yield` calls around).
84
84
85
85
Reusable abstractions can be built by defining generator functions on interfaces or components.
86
86
87
+
### Guidance on simulator modalities
88
+
89
+
There are two main simulator modalities: `add_testbench` and `add_sync_process`. They have completely disjoint purposes:
90
+
91
+
-`add_testbench` is used for testing logic (asynchronous or synchronous). It is not used for behavioral replacement of synchronous logic.
92
+
-`add_sync_process` is used for behavioral replacement of synchronous logic. It is not for testing logic (except for legacy code), and a deprecation warning is shown when `yield Settle()` is executed in such a process.
93
+
94
+
Example of using `add_testbench` to test combinatorial logic:
95
+
96
+
```python
97
+
m = Module()
98
+
m.d.comb += a.eq(~b)
99
+
100
+
deftestbench():
101
+
yield b.eq(1)
102
+
print((yield a)) # => 0
103
+
104
+
sim = Simulator(m)
105
+
# No clock is required
106
+
sim.add_testbench(testbench)
107
+
sim.run()
108
+
```
109
+
110
+
Example of using `add_testbench` to test synchronous logic:
111
+
112
+
```python
113
+
m = Module()
114
+
m.d.sync += a.eq(~b)
115
+
116
+
deftestbench():
117
+
yield b.eq(1)
118
+
yield Tick() # same as Tick("sync")
119
+
print((yield a)) # => 0
120
+
121
+
sim = Simulator(m)
122
+
sim.add_clock(1e-6)
123
+
sim.add_testbench(testbench)
124
+
sim.run()
125
+
```
126
+
127
+
Example of using `add_sync_process` to replace the flop above, and `add_testbench` to test the flop:
128
+
129
+
```python
130
+
m = Module()
131
+
132
+
defflop():
133
+
whileTrue:
134
+
yield b.eq(~(yield a))
135
+
yield Tick()
136
+
137
+
deftestbench():
138
+
yield b.eq(1)
139
+
yield Tick() # same as Tick("sync")
140
+
print((yield a)) # => 0
141
+
142
+
sim = Simulator(m)
143
+
sim.add_clock(1e-6)
144
+
sim.add_sync_process(flop)
145
+
sim.add_testbench(testbench)
146
+
sim.run()
147
+
```
148
+
149
+
### Why not replace `add_sync_process` with `add_testbench` entirely?
150
+
151
+
It is not possible to use `add_testbench` processes that drive signals in a race-free way. Consider this (behaviorally defined) circuit:
152
+
153
+
```python
154
+
x = Signal(reset=1)
155
+
y = Signal()
156
+
157
+
defproc_flop():
158
+
yield Tick()
159
+
yield y.eq(x)
160
+
161
+
defproc2():
162
+
yield Tick()
163
+
xv =yield x
164
+
yv =yield y
165
+
print(f"proc2 x={xv} y={yv}")
166
+
167
+
defproc3():
168
+
yield Tick()
169
+
yv =yield y
170
+
xv =yield x
171
+
print(f"proc3 x={xv} y={yv}")
172
+
```
173
+
174
+
If these processes are added using `add_testbench`, the output is:
175
+
176
+
```
177
+
proc3 x=1 y=0
178
+
proc2 x=1 y=1
179
+
```
180
+
181
+
If they are added using `add_sync_process`, the output is:
182
+
183
+
```
184
+
proc2 x=1 y=0
185
+
proc3 x=1 y=0
186
+
```
187
+
188
+
Clearly, if `proc2` and `proc3` are other flops in the circuit, perhaps performing a computation on `x` and `y`, they must be simulated using `add_sync_process`.
A new `Simulator.add_testbench(process, *, domain=None)` is added. This function schedules `process` similarly to `add_process`, except that before returning control to the coroutine `process` it performs the equivalent of `yield Settle()`. If `domain` is not `None`, then calling `yield` within `add_testbench` performs the equivalent of `yield Tick(domain)`.
193
+
A new `Simulator.add_testbench(process)` is added. This function schedules `process` similarly to `add_process`, except that before returning control to the coroutine `process` it performs the equivalent of `yield Settle()`.
194
+
195
+
`add_process` and `Settle` are deprecated and removed in a future version.
91
196
92
-
`Settle` is deprecated and removed in a future version.
197
+
`yield Tick()` is deprecated within `add_sync_process`and the ability to use it as well as `yield Settle()` is removed in a future version.
93
198
94
199
## Drawbacks
95
200
[drawbacks]: #drawbacks
96
201
97
-
Increase in API surface area and complexity. Churn.
202
+
- Churn.
203
+
- Testbench processes can race with each other, and it is not trivial to use multiple testbench processes in a design in a race-free way.
The motivating issue has no known alternative resolution besides introducing this (or a very similar) API. The status quo has proved deeply unsatisfactory over many years, and the `add_testbench` process has been trialed in 2020 and found usable.
103
210
104
-
The `domain` argument of `add_testbench` could default to "sync", as for `add_sync_process`. Since a testbench does not inherently have a "default" domain (unlike a behavioral replacement for a register transfer level module, where `sync` is the default), this does not appear appropriate.
105
-
106
211
## Prior art
107
212
[prior-art]: #prior-art
108
213
@@ -116,4 +221,10 @@ None.
116
221
## Future possibilities
117
222
[future-possibilities]: #future-possibilities
118
223
119
-
In the standard library, `fifo.read()` and `fifo.write()` functions could be defined that aid in testing designs with FIFOs. Such functions will only work correctly within testbench processes.
224
+
In the standard library, `fifo.read()` and `fifo.write()` functions could be defined that aid in testing designs with FIFOs. Such functions will only work correctly within testbench processes.
225
+
226
+
As it is, every such helper function would have to take a `domain` argument, which can quickly get out of hand. We have `DomainRenamer` in the RTL sub-language and we may want to have something like that in the simulation sub-language. (@zyp)
227
+
228
+
A new `add_comb_process` function could be added, to replace combinatorial logic. This function would have to accept a list of all signals driven by the process, so that combinatorial loops could be detected. (The demand for this has not been high; as of right now, this is not possible anyway.)
229
+
230
+
The existing `add_sync_process` function could accept a list of all signals driven by the process. This could aid in error detection, especially as CXXRTL is integrated into the design, because if a simulator process is driving a signal at the same time as an RTL process, a silent race condition occurs.
0 commit comments