Skip to content

Commit 9e75962

Browse files
whitequarkwanda-phi
andcommitted
Implement RFC 27: Testbench processes for the simulator.
Co-authored-by: Wanda <wanda@phinode.net>
1 parent f48b865 commit 9e75962

File tree

7 files changed

+279
-143
lines changed

7 files changed

+279
-143
lines changed

amaranth/sim/core.py

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ class Command:
1515

1616

1717
class Settle(Command):
18+
@deprecated("The `Settle` command is deprecated per RFC 27. Use `add_testbench` to write "
19+
"testbenches; in them, an equivalent of `yield Settle()` is performed "
20+
"automatically.")
21+
def __init__(self):
22+
pass
23+
1824
def __repr__(self):
1925
return "(settle)"
2026

@@ -74,11 +80,12 @@ def _check_process(self, process):
7480
.format(process))
7581
return process
7682

83+
@deprecated("The `add_process` method is deprecated per RFC 27. Use `add_testbench` instead.")
7784
def add_process(self, process):
7885
process = self._check_process(process)
7986
def wrapper():
8087
# Only start a bench process after comb settling, so that the reset values are correct.
81-
yield Settle()
88+
yield object.__new__(Settle)
8289
yield from process()
8390
self._engine.add_coroutine_process(wrapper, default_cmd=None)
8491

@@ -87,10 +94,74 @@ def add_sync_process(self, process, *, domain="sync"):
8794
def wrapper():
8895
# Only start a sync process after the first clock edge (or reset edge, if the domain
8996
# uses an asynchronous reset). This matches the behavior of synchronous FFs.
97+
generator = process()
98+
result = None
99+
exception = None
90100
yield Tick(domain)
91-
yield from process()
101+
while True:
102+
try:
103+
if exception is None:
104+
command = generator.send(result)
105+
else:
106+
command = generator.throw(exception)
107+
except StopIteration:
108+
break
109+
try:
110+
if isinstance(command, (Settle, Delay, Tick)):
111+
frame = generator.gi_frame
112+
module_globals = frame.f_globals
113+
if '__name__' in module_globals:
114+
module = module_globals['__name__']
115+
else:
116+
module = "<string>"
117+
# If the warning action is "error", this call will throw the warning, and
118+
# the try block will redirect it into the generator.
119+
warnings.warn_explicit(
120+
f"Using `{command.__class__.__name__}` is deprecated within "
121+
f"`add_sync_process` per RFC 27; use `add_testbench` instead.",
122+
DeprecationWarning,
123+
filename=frame.f_code.co_filename,
124+
lineno=frame.f_lineno,
125+
module=module,
126+
registry=module_globals.setdefault("__warningregistry__", {}),
127+
module_globals=module_globals,
128+
)
129+
result = yield command
130+
exception = None
131+
except Exception as e:
132+
result = None
133+
exception = e
92134
self._engine.add_coroutine_process(wrapper, default_cmd=Tick(domain))
93135

136+
def add_testbench(self, process):
137+
process = self._check_process(process)
138+
def wrapper():
139+
generator = process()
140+
# Only start a bench process after power-on reset finishes. Use object.__new__ to
141+
# avoid deprecation warning.
142+
yield object.__new__(Settle)
143+
result = None
144+
exception = None
145+
while True:
146+
try:
147+
if exception is None:
148+
command = generator.send(result)
149+
else:
150+
command = generator.throw(exception)
151+
except StopIteration:
152+
break
153+
if command is None or isinstance(command, Settle):
154+
exception = TypeError(f"Command {command!r} is not allowed in testbenches")
155+
else:
156+
try:
157+
result = yield command
158+
exception = None
159+
yield object.__new__(Settle)
160+
except Exception as e:
161+
result = None
162+
exception = e
163+
self._engine.add_coroutine_process(wrapper, default_cmd=None)
164+
94165
def add_clock(self, period, *, phase=None, domain="sync", if_exists=False):
95166
"""Add a clock process.
96167

docs/changes.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,21 @@ Apply the following changes to code written against Amaranth 0.4 to migrate it t
2929
* Replace uses of ``Value.matches()`` with no patterns with ``Const(1)``
3030
* Update uses of ``amaranth.utils.log2_int(need_pow2=False)`` to :func:`amaranth.utils.ceil_log2`
3131
* Update uses of ``amaranth.utils.log2_int(need_pow2=True)`` to :func:`amaranth.utils.exact_log2`
32+
* Update uses of ``Simulator.add_process`` to ``Simulator.add_testbench``
33+
* Convert uses of ``Simulator.add_sync_process`` used as testbenches to ``Simulator.add_testbench``
34+
* Convert uses of ``yield Tick()`` within remaining ``Simulator.add_sync_process`` to plain ``yield``
3235

3336

3437
Implemented RFCs
3538
----------------
3639

3740
.. _RFC 17: https://amaranth-lang.org/rfcs/0017-remove-log2-int.html
41+
.. _RFC 27: https://amaranth-lang.org/rfcs/0027-simulator-testbenches.html
3842
.. _RFC 39: https://amaranth-lang.org/rfcs/0039-empty-case.html
3943
.. _RFC 46: https://amaranth-lang.org/rfcs/0046-shape-range-1.html
4044

4145
* `RFC 17`_: Remove ``log2_int``
46+
* `RFC 27`_: Testbench processes for the simulator
4247
* `RFC 39`_: Change semantics of no-argument ``m.Case()``
4348
* `RFC 46`_: Change ``Shape.cast(range(1))`` to ``unsigned(0)``
4449

@@ -71,6 +76,14 @@ Standard library changes
7176
* Removed: (deprecated in 0.4) :class:`amaranth.lib.fifo.SyncFIFO` with ``fwft=False``. (`RFC 20`_)
7277

7378

79+
Toolchain changes
80+
-----------------
81+
82+
* Added: ``Simulator.add_testbench``. (`RFC 27`_)
83+
* Deprecated: ``Settle`` simulation command. (`RFC 27`_)
84+
* Deprecated: ``Simulator.add_process``. (`RFC 27`_)
85+
86+
7487
Platform integration changes
7588
----------------------------
7689

tests/test_lib_cdc.py

Lines changed: 42 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ def test_basic(self):
2626
def process():
2727
self.assertEqual((yield o), 0)
2828
yield i.eq(1)
29-
yield Tick()
29+
yield
3030
self.assertEqual((yield o), 0)
31-
yield Tick()
31+
yield
3232
self.assertEqual((yield o), 0)
33-
yield Tick()
33+
yield
3434
self.assertEqual((yield o), 1)
35-
sim.add_process(process)
35+
sim.add_sync_process(process)
3636
sim.run()
3737

3838
def test_reset_value(self):
@@ -45,13 +45,13 @@ def test_reset_value(self):
4545
def process():
4646
self.assertEqual((yield o), 1)
4747
yield i.eq(0)
48-
yield Tick()
48+
yield
4949
self.assertEqual((yield o), 1)
50-
yield Tick()
50+
yield
5151
self.assertEqual((yield o), 1)
52-
yield Tick()
52+
yield
5353
self.assertEqual((yield o), 0)
54-
sim.add_process(process)
54+
sim.add_sync_process(process)
5555
sim.run()
5656

5757

@@ -90,28 +90,27 @@ def process():
9090
# initial reset
9191
self.assertEqual((yield i), 0)
9292
self.assertEqual((yield o), 1)
93-
yield Tick(); yield Delay(1e-8)
93+
yield Tick()
9494
self.assertEqual((yield o), 1)
95-
yield Tick(); yield Delay(1e-8)
95+
yield Tick()
9696
self.assertEqual((yield o), 0)
97-
yield Tick(); yield Delay(1e-8)
97+
yield Tick()
9898
self.assertEqual((yield o), 0)
99-
yield Tick(); yield Delay(1e-8)
99+
yield Tick()
100100

101101
yield i.eq(1)
102-
yield Delay(1e-8)
103102
self.assertEqual((yield o), 1)
104-
yield Tick(); yield Delay(1e-8)
103+
yield Tick()
105104
self.assertEqual((yield o), 1)
106105
yield i.eq(0)
107-
yield Tick(); yield Delay(1e-8)
106+
yield Tick()
108107
self.assertEqual((yield o), 1)
109-
yield Tick(); yield Delay(1e-8)
108+
yield Tick()
110109
self.assertEqual((yield o), 0)
111-
yield Tick(); yield Delay(1e-8)
110+
yield Tick()
112111
self.assertEqual((yield o), 0)
113-
yield Tick(); yield Delay(1e-8)
114-
sim.add_process(process)
112+
yield Tick()
113+
sim.add_testbench(process)
115114
with sim.write_vcd("test.vcd"):
116115
sim.run()
117116

@@ -128,28 +127,27 @@ def process():
128127
# initial reset
129128
self.assertEqual((yield i), 1)
130129
self.assertEqual((yield o), 1)
131-
yield Tick(); yield Delay(1e-8)
130+
yield Tick()
132131
self.assertEqual((yield o), 1)
133-
yield Tick(); yield Delay(1e-8)
132+
yield Tick()
134133
self.assertEqual((yield o), 0)
135-
yield Tick(); yield Delay(1e-8)
134+
yield Tick()
136135
self.assertEqual((yield o), 0)
137-
yield Tick(); yield Delay(1e-8)
136+
yield Tick()
138137

139138
yield i.eq(0)
140-
yield Delay(1e-8)
141139
self.assertEqual((yield o), 1)
142-
yield Tick(); yield Delay(1e-8)
140+
yield Tick()
143141
self.assertEqual((yield o), 1)
144142
yield i.eq(1)
145-
yield Tick(); yield Delay(1e-8)
143+
yield Tick()
146144
self.assertEqual((yield o), 1)
147-
yield Tick(); yield Delay(1e-8)
145+
yield Tick()
148146
self.assertEqual((yield o), 0)
149-
yield Tick(); yield Delay(1e-8)
147+
yield Tick()
150148
self.assertEqual((yield o), 0)
151-
yield Tick(); yield Delay(1e-8)
152-
sim.add_process(process)
149+
yield Tick()
150+
sim.add_testbench(process)
153151
with sim.write_vcd("test.vcd"):
154152
sim.run()
155153

@@ -176,28 +174,28 @@ def test_basic(self):
176174
def process():
177175
# initial reset
178176
self.assertEqual((yield s), 1)
179-
yield Tick(); yield Delay(1e-8)
177+
yield Tick()
180178
self.assertEqual((yield s), 1)
181-
yield Tick(); yield Delay(1e-8)
179+
yield Tick()
182180
self.assertEqual((yield s), 1)
183-
yield Tick(); yield Delay(1e-8)
181+
yield Tick()
184182
self.assertEqual((yield s), 0)
185-
yield Tick(); yield Delay(1e-8)
183+
yield Tick()
186184

187185
yield arst.eq(1)
188186
yield Delay(1e-8)
189187
self.assertEqual((yield s), 0)
190-
yield Tick(); yield Delay(1e-8)
188+
yield Tick()
191189
self.assertEqual((yield s), 1)
192190
yield arst.eq(0)
193-
yield Tick(); yield Delay(1e-8)
191+
yield Tick()
194192
self.assertEqual((yield s), 1)
195-
yield Tick(); yield Delay(1e-8)
193+
yield Tick()
196194
self.assertEqual((yield s), 1)
197-
yield Tick(); yield Delay(1e-8)
195+
yield Tick()
198196
self.assertEqual((yield s), 0)
199-
yield Tick(); yield Delay(1e-8)
200-
sim.add_process(process)
197+
yield Tick()
198+
sim.add_testbench(process)
201199
with sim.write_vcd("test.vcd"):
202200
sim.run()
203201

@@ -223,17 +221,17 @@ def process():
223221
yield ps.i.eq(0)
224222
# TODO: think about reset
225223
for n in range(5):
226-
yield Tick()
224+
yield
227225
# Make sure no pulses are generated in quiescent state
228226
for n in range(3):
229-
yield Tick()
227+
yield
230228
self.assertEqual((yield ps.o), 0)
231229
# Check conservation of pulses
232230
accum = 0
233231
for n in range(10):
234232
yield ps.i.eq(1 if n < 4 else 0)
235-
yield Tick()
233+
yield
236234
accum += yield ps.o
237235
self.assertEqual(accum, 4)
238-
sim.add_process(process)
236+
sim.add_sync_process(process)
239237
sim.run()

tests/test_lib_coding.py

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,19 @@ def process():
1414
self.assertEqual((yield enc.o), 0)
1515

1616
yield enc.i.eq(0b0001)
17-
yield Settle()
1817
self.assertEqual((yield enc.n), 0)
1918
self.assertEqual((yield enc.o), 0)
2019

2120
yield enc.i.eq(0b0100)
22-
yield Settle()
2321
self.assertEqual((yield enc.n), 0)
2422
self.assertEqual((yield enc.o), 2)
2523

2624
yield enc.i.eq(0b0110)
27-
yield Settle()
2825
self.assertEqual((yield enc.n), 1)
2926
self.assertEqual((yield enc.o), 0)
3027

3128
sim = Simulator(enc)
32-
sim.add_process(process)
29+
sim.add_testbench(process)
3330
sim.run()
3431

3532

@@ -41,22 +38,19 @@ def process():
4138
self.assertEqual((yield enc.o), 0)
4239

4340
yield enc.i.eq(0b0001)
44-
yield Settle()
4541
self.assertEqual((yield enc.n), 0)
4642
self.assertEqual((yield enc.o), 0)
4743

4844
yield enc.i.eq(0b0100)
49-
yield Settle()
5045
self.assertEqual((yield enc.n), 0)
5146
self.assertEqual((yield enc.o), 2)
5247

5348
yield enc.i.eq(0b0110)
54-
yield Settle()
5549
self.assertEqual((yield enc.n), 0)
5650
self.assertEqual((yield enc.o), 1)
5751

5852
sim = Simulator(enc)
59-
sim.add_process(process)
53+
sim.add_testbench(process)
6054
sim.run()
6155

6256

@@ -67,19 +61,16 @@ def process():
6761
self.assertEqual((yield dec.o), 0b0001)
6862

6963
yield dec.i.eq(1)
70-
yield Settle()
7164
self.assertEqual((yield dec.o), 0b0010)
7265

7366
yield dec.i.eq(3)
74-
yield Settle()
7567
self.assertEqual((yield dec.o), 0b1000)
7668

7769
yield dec.n.eq(1)
78-
yield Settle()
7970
self.assertEqual((yield dec.o), 0b0000)
8071

8172
sim = Simulator(dec)
82-
sim.add_process(process)
73+
sim.add_testbench(process)
8374
sim.run()
8475

8576

tests/test_lib_data.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -870,7 +870,7 @@ class RFCExamplesTestCase(TestCase):
870870
def simulate(m):
871871
def wrapper(fn):
872872
sim = Simulator(m)
873-
sim.add_process(fn)
873+
sim.add_testbench(fn)
874874
sim.run()
875875
return wrapper
876876

0 commit comments

Comments
 (0)