Repository: amaranth-lang/amaranth
Pull request #1088 (2024-02-06 22:56:46)

Implement RFC 27: Testbench processes for the simulator.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1088?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang) Report
Attention: `9 lines` in your changes are missing coverage. Please review.
> Comparison is base [(`f48b865`)](https://app.codecov.io/gh/amaranth-lang/amaranth/commit/f48b8650c4df42b3260c4791b18fad7991af0541?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang) 86.86% compared to head [(`814ad4e`)](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1088?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang) 86.44%.

| [Files](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1088?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang) | Patch % | Lines |
|---|---|---|
| [amaranth/sim/core.py](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1088?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang#diff-YW1hcmFudGgvc2ltL2NvcmUucHk=) | 82.00% | [7 Missing and 2 partials :warning: ](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1088?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1088      +/-   ##
==========================================
- Coverage   86.86%   86.44%   -0.42%     
==========================================
  Files          41       41              
  Lines        7109     7150      +41     
  Branches     1699     1487     -212     
==========================================
+ Hits         6175     6181       +6     
- Misses        772      806      +34     
- Partials      162      163       +1     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1088?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang).


Patch:
diff --git a/amaranth/sim/core.py b/amaranth/sim/core.py
index 64686ce..a50e468 100644
--- a/amaranth/sim/core.py
+++ b/amaranth/sim/core.py
@@ -15,6 +15,12 @@ class Command:
 
 
 class Settle(Command):
+    @deprecated("The `Settle` command is deprecated per RFC 27. Use `add_testbench` to write "
+                "testbenches; in them, an equivalent of `yield Settle()` is performed "
+                "automatically.")
+    def __init__(self):
+        pass
+
     def __repr__(self):
         return "(settle)"
 
@@ -74,11 +80,12 @@ class Simulator:
                             .format(process))
         return process
 
+    @deprecated("The `add_process` method is deprecated per RFC 27. Use `add_testbench` instead.")
     def add_process(self, process):
         process = self._check_process(process)
         def wrapper():
             # Only start a bench process after comb settling, so that the reset values are correct.
-            yield Settle()
+            yield object.__new__(Settle)
             yield from process()
         self._engine.add_coroutine_process(wrapper, default_cmd=None)
 
@@ -87,10 +94,74 @@ class Simulator:
         def wrapper():
             # Only start a sync process after the first clock edge (or reset edge, if the domain
             # uses an asynchronous reset). This matches the behavior of synchronous FFs.
+            generator = process()
+            result = None
+            exception = None
             yield Tick(domain)
-            yield from process()
+            while True:
+                try:
+                    if exception is None:
+                        command = generator.send(result)
+                    else:
+                        command = generator.throw(exception)
+                except StopIteration:
+                    break
+                try:
+                    if isinstance(command, (Settle, Delay, Tick)):
+                        frame = generator.gi_frame
+                        module_globals = frame.f_globals
+                        if '__name__' in module_globals:
+                            module = module_globals['__name__']
+                        else:
+                            module = "<string>"
+                        # If the warning action is "error", this call will throw the warning, and
+                        # the try block will redirect it into the generator.
+                        warnings.warn_explicit(
+                            f"Using `{command.__class__.__name__}` is deprecated within "
+                            f"`add_sync_process` per RFC 27; use `add_testbench` instead.",
+                            DeprecationWarning,
+                            filename=frame.f_code.co_filename,
+                            lineno=frame.f_lineno,
+                            module=module,
+                            registry=module_globals.setdefault("__warningregistry__", {}),
+                            module_globals=module_globals,
+                        )
+                    result = yield command
+                    exception = None
+                except Exception as e:
+                    result = None
+                    exception = e
         self._engine.add_coroutine_process(wrapper, default_cmd=Tick(domain))
 
+    def add_testbench(self, process):
+        process = self._check_process(process)
+        def wrapper():
+            generator = process()
+            # Only start a bench process after power-on reset finishes. Use object.__new__ to
+            # avoid deprecation warning.
+            yield object.__new__(Settle)
+            result = None
+            exception = None
+            while True:
+                try:
+                    if exception is None:
+                        command = generator.send(result)
+                    else:
+                        command = generator.throw(exception)
+                except StopIteration:
+                    break
+                if command is None or isinstance(command, Settle):
+                    exception = TypeError(f"Command {command!r} is not allowed in testbenches")
+                else:
+                    try:
+                        result = yield command
+                        exception = None
+                        yield object.__new__(Settle)
+                    except Exception as e:
+                        result = None
+                        exception = e
+        self._engine.add_coroutine_process(wrapper, default_cmd=None)
+
     def add_clock(self, period, *, phase=None, domain="sync", if_exists=False):
         """Add a clock process.
 
diff --git a/docs/changes.rst b/docs/changes.rst
index bc25118..9b7d288 100644
--- a/docs/changes.rst
+++ b/docs/changes.rst
@@ -29,16 +29,21 @@ Apply the following changes to code written against Amaranth 0.4 to migrate it t
 * Replace uses of ``Value.matches()`` with no patterns with ``Const(1)``
 * Update uses of ``amaranth.utils.log2_int(need_pow2=False)`` to :func:`amaranth.utils.ceil_log2`
 * Update uses of ``amaranth.utils.log2_int(need_pow2=True)`` to :func:`amaranth.utils.exact_log2`
+* Update uses of ``Simulator.add_process`` to ``Simulator.add_testbench``
+* Convert uses of ``Simulator.add_sync_process`` used as testbenches to ``Simulator.add_testbench``
+* Convert uses of ``yield Tick()`` within remaining ``Simulator.add_sync_process`` to plain ``yield``
 
 
 Implemented RFCs
 ----------------
 
 .. _RFC 17: https://amaranth-lang.org/rfcs/0017-remove-log2-int.html
+.. _RFC 27: https://amaranth-lang.org/rfcs/0027-simulator-testbenches.html
 .. _RFC 39: https://amaranth-lang.org/rfcs/0039-empty-case.html
 .. _RFC 46: https://amaranth-lang.org/rfcs/0046-shape-range-1.html
 
 * `RFC 17`_: Remove ``log2_int``
+* `RFC 27`_: Testbench processes for the simulator
 * `RFC 39`_: Change semantics of no-argument ``m.Case()``
 * `RFC 46`_: Change ``Shape.cast(range(1))`` to ``unsigned(0)``
 
@@ -71,6 +76,14 @@ Standard library changes
 * Removed: (deprecated in 0.4) :class:`amaranth.lib.fifo.SyncFIFO` with ``fwft=False``. (`RFC 20`_)
 
 
+Toolchain changes
+-----------------
+
+* Added: ``Simulator.add_testbench``. (`RFC 27`_)
+* Deprecated: ``Settle`` simulation command. (`RFC 27`_)
+* Deprecated: ``Simulator.add_process``. (`RFC 27`_)
+
+
 Platform integration changes
 ----------------------------
 


Test patch:
diff --git a/tests/test_lib_cdc.py b/tests/test_lib_cdc.py
index 0540be2..a271032 100644
--- a/tests/test_lib_cdc.py
+++ b/tests/test_lib_cdc.py
@@ -26,13 +26,13 @@ class FFSynchronizerTestCase(FHDLTestCase):
         def process():
             self.assertEqual((yield o), 0)
             yield i.eq(1)
-            yield Tick()
+            yield
             self.assertEqual((yield o), 0)
-            yield Tick()
+            yield
             self.assertEqual((yield o), 0)
-            yield Tick()
+            yield
             self.assertEqual((yield o), 1)
-        sim.add_process(process)
+        sim.add_sync_process(process)
         sim.run()
 
     def test_reset_value(self):
@@ -45,13 +45,13 @@ class FFSynchronizerTestCase(FHDLTestCase):
         def process():
             self.assertEqual((yield o), 1)
             yield i.eq(0)
-            yield Tick()
+            yield
             self.assertEqual((yield o), 1)
-            yield Tick()
+            yield
             self.assertEqual((yield o), 1)
-            yield Tick()
+            yield
             self.assertEqual((yield o), 0)
-        sim.add_process(process)
+        sim.add_sync_process(process)
         sim.run()
 
 
@@ -90,28 +90,27 @@ class AsyncFFSynchronizerTestCase(FHDLTestCase):
             # initial reset
             self.assertEqual((yield i), 0)
             self.assertEqual((yield o), 1)
-            yield Tick(); yield Delay(1e-8)
+            yield Tick()
             self.assertEqual((yield o), 1)
-            yield Tick(); yield Delay(1e-8)
+            yield Tick()
             self.assertEqual((yield o), 0)
-            yield Tick(); yield Delay(1e-8)
+            yield Tick()
             self.assertEqual((yield o), 0)
-            yield Tick(); yield Delay(1e-8)
+            yield Tick()
 
             yield i.eq(1)
-            yield Delay(1e-8)
             self.assertEqual((yield o), 1)
-            yield Tick(); yield Delay(1e-8)
+            yield Tick()
             self.assertEqual((yield o), 1)
             yield i.eq(0)
-            yield Tick(); yield Delay(1e-8)
+            yield Tick()
             self.assertEqual((yield o), 1)
-            yield Tick(); yield Delay(1e-8)
+            yield Tick()
             self.assertEqual((yield o), 0)
-            yield Tick(); yield Delay(1e-8)
+            yield Tick()
             self.assertEqual((yield o), 0)
-            yield Tick(); yield Delay(1e-8)
-        sim.add_process(process)
+            yield Tick()
+        sim.add_testbench(process)
         with sim.write_vcd("test.vcd"):
             sim.run()
 
@@ -128,28 +127,27 @@ class AsyncFFSynchronizerTestCase(FHDLTestCase):
             # initial reset
             self.assertEqual((yield i), 1)
             self.assertEqual((yield o), 1)
-            yield Tick(); yield Delay(1e-8)
+            yield Tick()
             self.assertEqual((yield o), 1)
-            yield Tick(); yield Delay(1e-8)
+            yield Tick()
             self.assertEqual((yield o), 0)
-            yield Tick(); yield Delay(1e-8)
+            yield Tick()
             self.assertEqual((yield o), 0)
-            yield Tick(); yield Delay(1e-8)
+            yield Tick()
 
             yield i.eq(0)
-            yield Delay(1e-8)
             self.assertEqual((yield o), 1)
-            yield Tick(); yield Delay(1e-8)
+            yield Tick()
             self.assertEqual((yield o), 1)
             yield i.eq(1)
-            yield Tick(); yield Delay(1e-8)
+            yield Tick()
             self.assertEqual((yield o), 1)
-            yield Tick(); yield Delay(1e-8)
+            yield Tick()
             self.assertEqual((yield o), 0)
-            yield Tick(); yield Delay(1e-8)
+            yield Tick()
             self.assertEqual((yield o), 0)
-            yield Tick(); yield Delay(1e-8)
-        sim.add_process(process)
+            yield Tick()
+        sim.add_testbench(process)
         with sim.write_vcd("test.vcd"):
             sim.run()
 
@@ -176,28 +174,28 @@ class ResetSynchronizerTestCase(FHDLTestCase):
         def process():
             # initial reset
             self.assertEqual((yield s), 1)
-            yield Tick(); yield Delay(1e-8)
+            yield Tick()
             self.assertEqual((yield s), 1)
-            yield Tick(); yield Delay(1e-8)
+            yield Tick()
             self.assertEqual((yield s), 1)
-            yield Tick(); yield Delay(1e-8)
+            yield Tick()
             self.assertEqual((yield s), 0)
-            yield Tick(); yield Delay(1e-8)
+            yield Tick()
 
             yield arst.eq(1)
             yield Delay(1e-8)
             self.assertEqual((yield s), 0)
-            yield Tick(); yield Delay(1e-8)
+            yield Tick()
             self.assertEqual((yield s), 1)
             yield arst.eq(0)
-            yield Tick(); yield Delay(1e-8)
+            yield Tick()
             self.assertEqual((yield s), 1)
-            yield Tick(); yield Delay(1e-8)
+            yield Tick()
             self.assertEqual((yield s), 1)
-            yield Tick(); yield Delay(1e-8)
+            yield Tick()
             self.assertEqual((yield s), 0)
-            yield Tick(); yield Delay(1e-8)
-        sim.add_process(process)
+            yield Tick()
+        sim.add_testbench(process)
         with sim.write_vcd("test.vcd"):
             sim.run()
 
@@ -223,17 +221,17 @@ class PulseSynchronizerTestCase(FHDLTestCase):
             yield ps.i.eq(0)
             # TODO: think about reset
             for n in range(5):
-                yield Tick()
+                yield
             # Make sure no pulses are generated in quiescent state
             for n in range(3):
-                yield Tick()
+                yield
                 self.assertEqual((yield ps.o), 0)
             # Check conservation of pulses
             accum = 0
             for n in range(10):
                 yield ps.i.eq(1 if n < 4 else 0)
-                yield Tick()
+                yield
                 accum += yield ps.o
             self.assertEqual(accum, 4)
-        sim.add_process(process)
+        sim.add_sync_process(process)
         sim.run()
diff --git a/tests/test_lib_coding.py b/tests/test_lib_coding.py
index e2f875d..41f77fe 100644
--- a/tests/test_lib_coding.py
+++ b/tests/test_lib_coding.py
@@ -14,22 +14,19 @@ class EncoderTestCase(FHDLTestCase):
             self.assertEqual((yield enc.o), 0)
 
             yield enc.i.eq(0b0001)
-            yield Settle()
             self.assertEqual((yield enc.n), 0)
             self.assertEqual((yield enc.o), 0)
 
             yield enc.i.eq(0b0100)
-            yield Settle()
             self.assertEqual((yield enc.n), 0)
             self.assertEqual((yield enc.o), 2)
 
             yield enc.i.eq(0b0110)
-            yield Settle()
             self.assertEqual((yield enc.n), 1)
             self.assertEqual((yield enc.o), 0)
 
         sim = Simulator(enc)
-        sim.add_process(process)
+        sim.add_testbench(process)
         sim.run()
 
 
@@ -41,22 +38,19 @@ class PriorityEncoderTestCase(FHDLTestCase):
             self.assertEqual((yield enc.o), 0)
 
             yield enc.i.eq(0b0001)
-            yield Settle()
             self.assertEqual((yield enc.n), 0)
             self.assertEqual((yield enc.o), 0)
 
             yield enc.i.eq(0b0100)
-            yield Settle()
             self.assertEqual((yield enc.n), 0)
             self.assertEqual((yield enc.o), 2)
 
             yield enc.i.eq(0b0110)
-            yield Settle()
             self.assertEqual((yield enc.n), 0)
             self.assertEqual((yield enc.o), 1)
 
         sim = Simulator(enc)
-        sim.add_process(process)
+        sim.add_testbench(process)
         sim.run()
 
 
@@ -67,19 +61,16 @@ class DecoderTestCase(FHDLTestCase):
             self.assertEqual((yield dec.o), 0b0001)
 
             yield dec.i.eq(1)
-            yield Settle()
             self.assertEqual((yield dec.o), 0b0010)
 
             yield dec.i.eq(3)
-            yield Settle()
             self.assertEqual((yield dec.o), 0b1000)
 
             yield dec.n.eq(1)
-            yield Settle()
             self.assertEqual((yield dec.o), 0b0000)
 
         sim = Simulator(dec)
-        sim.add_process(process)
+        sim.add_testbench(process)
         sim.run()
 
 
diff --git a/tests/test_lib_data.py b/tests/test_lib_data.py
index 42f2aca..486e1e0 100644
--- a/tests/test_lib_data.py
+++ b/tests/test_lib_data.py
@@ -870,7 +870,7 @@ class RFCExamplesTestCase(TestCase):
     def simulate(m):
         def wrapper(fn):
             sim = Simulator(m)
-            sim.add_process(fn)
+            sim.add_testbench(fn)
             sim.run()
         return wrapper
 
diff --git a/tests/test_lib_fifo.py b/tests/test_lib_fifo.py
index 004b7f3..e35c978 100644
--- a/tests/test_lib_fifo.py
+++ b/tests/test_lib_fifo.py
@@ -315,10 +315,10 @@ class AsyncFIFOSimCase(FHDLTestCase):
             for i in range(fill_in):
                 yield fifo.w_data.eq(i)
                 yield fifo.w_en.eq(1)
-                yield Tick("write")
+                yield
             yield fifo.w_en.eq(0)
-            yield Tick("write")
-            yield Tick("write")
+            yield
+            yield
             self.assertEqual((yield fifo.w_level), expected_level)
             yield write_done.eq(1)
 
@@ -326,7 +326,7 @@ class AsyncFIFOSimCase(FHDLTestCase):
             if read:
                 yield fifo.r_en.eq(1)
             while not (yield write_done):
-                yield Tick("read")
+                yield
             self.assertEqual((yield fifo.r_level), expected_level)
 
         simulator = Simulator(fifo)
diff --git a/tests/test_sim.py b/tests/test_sim.py
index 5eaf6df..ce776b0 100644
--- a/tests/test_sim.py
+++ b/tests/test_sim.py
@@ -35,9 +35,8 @@ class SimulatorUnitTestCase(FHDLTestCase):
         def process():
             for isig, input in zip(isigs, inputs):
                 yield isig.eq(input)
-            yield Settle()
             self.assertEqual((yield osig), output.value)
-        sim.add_process(process)
+        sim.add_testbench(process)
         with sim.write_vcd("test.vcd", "test.gtkw", traces=[*isigs, osig]):
             sim.run()
 
@@ -436,28 +435,29 @@ class SimulatorIntegrationTestCase(FHDLTestCase):
 
     def test_counter_process(self):
         self.setUp_counter()
-        with self.assertSimulation(self.m) as sim:
-            def process():
-                self.assertEqual((yield self.count), 4)
-                yield Delay(1e-6)
-                self.assertEqual((yield self.count), 4)
-                yield self.sync.clk.eq(1)
-                self.assertEqual((yield self.count), 4)
-                yield Settle()
-                self.assertEqual((yield self.count), 5)
-                yield Delay(1e-6)
-                self.assertEqual((yield self.count), 5)
-                yield self.sync.clk.eq(0)
-                self.assertEqual((yield self.count), 5)
-                yield Settle()
-                self.assertEqual((yield self.count), 5)
-                for _ in range(3):
+        with _ignore_deprecated():
+            with self.assertSimulation(self.m) as sim:
+                def process():
+                    self.assertEqual((yield self.count), 4)
                     yield Delay(1e-6)
+                    self.assertEqual((yield self.count), 4)
                     yield self.sync.clk.eq(1)
+                    self.assertEqual((yield self.count), 4)
+                    yield Settle()
+                    self.assertEqual((yield self.count), 5)
                     yield Delay(1e-6)
+                    self.assertEqual((yield self.count), 5)
                     yield self.sync.clk.eq(0)
-                self.assertEqual((yield self.count), 0)
-            sim.add_process(process)
+                    self.assertEqual((yield self.count), 5)
+                    yield Settle()
+                    self.assertEqual((yield self.count), 5)
+                    for _ in range(3):
+                        yield Delay(1e-6)
+                        yield self.sync.clk.eq(1)
+                        yield Delay(1e-6)
+                        yield self.sync.clk.eq(0)
+                    self.assertEqual((yield self.count), 0)
+                sim.add_process(process)
 
     def test_counter_clock_and_sync_process(self):
         self.setUp_counter()
@@ -611,10 +611,9 @@ class SimulatorIntegrationTestCase(FHDLTestCase):
             def process():
                 yield self.i.eq(0b10101010)
                 yield self.i[:4].eq(-1)
-                yield Settle()
                 self.assertEqual((yield self.i[:4]), 0b1111)
                 self.assertEqual((yield self.i), 0b10101111)
-            sim.add_process(process)
+            sim.add_testbench(process)
 
     def test_run_until(self):
         m = Module()
@@ -626,7 +625,7 @@ class SimulatorIntegrationTestCase(FHDLTestCase):
                 for _ in range(101):
                     yield Delay(1e-6)
                 self.fail()
-            sim.add_process(process)
+            sim.add_testbench(process)
 
     def test_run_until_fail(self):
         m = Module()
@@ -639,13 +638,13 @@ class SimulatorIntegrationTestCase(FHDLTestCase):
                         for _ in range(99):
                             yield Delay(1e-6)
                         self.fail()
-                    sim.add_process(process)
+                    sim.add_testbench(process)
 
     def test_add_process_wrong(self):
         with self.assertSimulation(Module()) as sim:
             with self.assertRaisesRegex(TypeError,
                     r"^Cannot add a process 1 because it is not a generator function$"):
-                sim.add_process(1)
+                sim.add_sync_process(1)
 
     def test_add_process_wrong_generator(self):
         with self.assertSimulation(Module()) as sim:
@@ -653,7 +652,7 @@ class SimulatorIntegrationTestCase(FHDLTestCase):
                     r"^Cannot add a process <.+?> because it is not a generator function$"):
                 def process():
                     yield Delay()
-                sim.add_process(process())
+                sim.add_sync_process(process())
 
     def test_add_clock_wrong_twice(self):
         m = Module()
@@ -679,14 +678,47 @@ class SimulatorIntegrationTestCase(FHDLTestCase):
 
     def test_command_wrong(self):
         survived = False
-        with self.assertSimulation(Module()) as sim:
+        with _ignore_deprecated():
+            with self.assertSimulation(Module()) as sim:
+                def process():
+                    nonlocal survived
+                    with self.assertRaisesRegex(TypeError,
+                            r"Received unsupported command 1 from process .+?"):
+                        yield 1
+                    survived = True
+                sim.add_process(process)
+        self.assertTrue(survived)
+
+    def test_sync_command_deprecated(self):
+        survived = False
+        m = Module()
+        dummy = Signal()
+        m.d.sync += dummy.eq(1)
+        with self.assertSimulation(m) as sim:
+            def process():
+                nonlocal survived
+                with self.assertWarnsRegex(DeprecationWarning,
+                        r"Using `Delay` is deprecated within `add_sync_process`"):
+                    yield Delay(1e-8)
+                survived = True
+            sim.add_sync_process(process)
+            sim.add_clock(1e-6)
+        self.assertTrue(survived)
+
+    def test_sync_command_wrong(self):
+        survived = False
+        m = Module()
+        dummy = Signal()
+        m.d.sync += dummy.eq(1)
+        with self.assertSimulation(m) as sim:
             def process():
                 nonlocal survived
                 with self.assertRaisesRegex(TypeError,
                         r"Received unsupported command 1 from process .+?"):
                     yield 1
                 survived = True
-            sim.add_process(process)
+            sim.add_sync_process(process)
+            sim.add_clock(1e-6)
         self.assertTrue(survived)
 
     def test_value_castable(self):
@@ -701,13 +733,19 @@ class SimulatorIntegrationTestCase(FHDLTestCase):
         a = Array([1,2,3])
         a[MyValue()]
 
+    def test_bench_command_wrong(self):
         survived = False
         with self.assertSimulation(Module()) as sim:
             def process():
                 nonlocal survived
-                yield MyValue()
+                with self.assertWarnsRegex(DeprecationWarning,
+                        r"The `Settle` command is deprecated"):
+                    settle = Settle()
+                with self.assertRaisesRegex(TypeError,
+                        r"Command \(settle\) is not allowed in testbenches"):
+                    yield settle
                 survived = True
-            sim.add_process(process)
+            sim.add_testbench(process)
         self.assertTrue(survived)
 
     def setUp_memory(self, rd_synchronous=True, rd_transparent=True, wr_granularity=None):
@@ -755,23 +793,23 @@ class SimulatorIntegrationTestCase(FHDLTestCase):
             def process():
                 yield self.wrport.data.eq(0x50)
                 yield self.wrport.en.eq(0b00)
-                yield
+                yield Tick()
                 yield self.wrport.en.eq(0)
-                yield
+                yield Tick()
                 self.assertEqual((yield self.rdport.data), 0xaa)
                 yield self.wrport.en.eq(0b10)
-                yield
+                yield Tick()
                 yield self.wrport.en.eq(0)
-                yield
+                yield Tick()
                 self.assertEqual((yield self.rdport.data), 0x5a)
                 yield self.wrport.data.eq(0x33)
                 yield self.wrport.en.eq(0b01)
-                yield
+                yield Tick()
                 yield self.wrport.en.eq(0)
-                yield
+                yield Tick()
                 self.assertEqual((yield self.rdport.data), 0x53)
             sim.add_clock(1e-6)
-            sim.add_sync_process(process)
+            sim.add_testbench(process)
 
     def test_memory_read_before_write(self):
         self.setUp_memory(rd_transparent=False)
@@ -779,52 +817,46 @@ class SimulatorIntegrationTestCase(FHDLTestCase):
             def process():
                 yield self.wrport.data.eq(0x33)
                 yield self.wrport.en.eq(1)
-                yield
+                yield Tick()
                 self.assertEqual((yield self.rdport.data), 0xaa)
-                yield
-                self.assertEqual((yield self.rdport.data), 0xaa)
-                yield Settle()
+                yield Tick()
                 self.assertEqual((yield self.rdport.data), 0x33)
             sim.add_clock(1e-6)
-            sim.add_sync_process(process)
+            sim.add_testbench(process)
 
     def test_memory_write_through(self):
         self.setUp_memory(rd_transparent=True)
         with self.assertSimulation(self.m) as sim:
             def process():
+                yield Tick()
                 yield self.wrport.data.eq(0x33)
                 yield self.wrport.en.eq(1)
-                yield
                 self.assertEqual((yield self.rdport.data), 0xaa)
-                yield Settle()
+                yield Tick()
                 self.assertEqual((yield self.rdport.data), 0x33)
-                yield
+                yield Tick()
                 yield self.rdport.addr.eq(1)
-                yield Settle()
                 self.assertEqual((yield self.rdport.data), 0x33)
             sim.add_clock(1e-6)
-            sim.add_sync_process(process)
+            sim.add_testbench(process)
 
     def test_memory_async_read_write(self):
         self.setUp_memory(rd_synchronous=False)
         with self.assertSimulation(self.m) as sim:
             def process():
                 yield self.rdport.addr.eq(0)
-                yield Settle()
                 self.assertEqual((yield self.rdport.data), 0xaa)
                 yield self.rdport.addr.eq(1)
-                yield Settle()
                 self.assertEqual((yield self.rdport.data), 0x55)
                 yield self.rdport.addr.eq(0)
                 yield self.wrport.addr.eq(0)
                 yield self.wrport.data.eq(0x33)
                 yield self.wrport.en.eq(1)
-                yield Tick("sync")
                 self.assertEqual((yield self.rdport.data), 0xaa)
-                yield Settle()
+                yield Tick("sync")
                 self.assertEqual((yield self.rdport.data), 0x33)
             sim.add_clock(1e-6)
-            sim.add_process(process)
+            sim.add_testbench(process)
 
     def test_memory_read_only(self):
         self.m = Module()
@@ -841,6 +873,49 @@ class SimulatorIntegrationTestCase(FHDLTestCase):
             sim.add_clock(1e-6)
             sim.add_sync_process(process)
 
+    def test_comb_bench_process(self):
+        m = Module()
+        a = Signal(reset=1)
+        b = Signal()
+        m.d.comb += b.eq(a)
+        with self.assertSimulation(m) as sim:
+            def process():
+                self.assertEqual((yield a), 1)
+                self.assertEqual((yield b), 1)
+                yield a.eq(0)
+                self.assertEqual((yield a), 0)
+                self.assertEqual((yield b), 0)
+            sim.add_testbench(process)
+
+    def test_sync_bench_process(self):
+        m = Module()
+        a = Signal(reset=1)
+        b = Signal()
+        m.d.sync += b.eq(a)
+        t = Signal()
+        m.d.sync += t.eq(~t)
+        with self.assertSimulation(m) as sim:
+            def process():
+                self.assertEqual((yield a), 1)
+                self.assertEqual((yield b), 0)
+                self.assertEqual((yield t), 0)
+                yield Tick()
+                self.assertEqual((yield a), 1)
+                self.assertEqual((yield b), 1)
+                self.assertEqual((yield t), 1)
+                yield Tick()
+                self.assertEqual((yield a), 1)
+                self.assertEqual((yield b), 1)
+                self.assertEqual((yield t), 0)
+                yield a.eq(0)
+                self.assertEqual((yield a), 0)
+                self.assertEqual((yield b), 1)
+                yield Tick()
+                self.assertEqual((yield a), 0)
+                self.assertEqual((yield b), 0)
+            sim.add_clock(1e-6)
+            sim.add_testbench(process)
+
     def test_memory_transparency_simple(self):
         m = Module()
         init = [0x11, 0x22, 0x33, 0x44]
@@ -850,41 +925,35 @@ class SimulatorIntegrationTestCase(FHDLTestCase):
         with self.assertSimulation(m) as sim:
             def process():
                 yield rdport.addr.eq(0)
-                yield
-                yield Settle()
+                yield Tick()
                 self.assertEqual((yield rdport.data), 0x11)
                 yield rdport.addr.eq(1)
-                yield
-                yield Settle()
+                yield Tick()
                 self.assertEqual((yield rdport.data), 0x22)
                 yield wrport.addr.eq(0)
                 yield wrport.data.eq(0x44444444)
                 yield wrport.en.eq(1)
-                yield
-                yield Settle()
+                yield Tick()
                 self.assertEqual((yield rdport.data), 0x22)
                 yield wrport.addr.eq(1)
                 yield wrport.data.eq(0x55)
                 yield wrport.en.eq(1)
-                yield
-                yield Settle()
+                yield Tick()
                 self.assertEqual((yield rdport.data), 0x55)
                 yield wrport.addr.eq(1)
                 yield wrport.data.eq(0x66)
                 yield wrport.en.eq(1)
                 yield rdport.en.eq(0)
-                yield
-                yield Settle()
+                yield Tick()
                 self.assertEqual((yield rdport.data), 0x55)
                 yield wrport.addr.eq(2)
                 yield wrport.data.eq(0x77)
                 yield wrport.en.eq(1)
                 yield rdport.en.eq(1)
-                yield
-                yield Settle()
+                yield Tick()
                 self.assertEqual((yield rdport.data), 0x66)
             sim.add_clock(1e-6)
-            sim.add_sync_process(process)
+            sim.add_testbench(process)
 
     def test_memory_transparency_multibit(self):
         m = Module()
@@ -895,41 +964,35 @@ class SimulatorIntegrationTestCase(FHDLTestCase):
         with self.assertSimulation(m) as sim:
             def process():
                 yield rdport.addr.eq(0)
-                yield
-                yield Settle()
+                yield Tick()
                 self.assertEqual((yield rdport.data), 0x11111111)
                 yield rdport.addr.eq(1)
-                yield
-                yield Settle()
+                yield Tick()
                 self.assertEqual((yield rdport.data), 0x22222222)
                 yield wrport.addr.eq(0)
                 yield wrport.data.eq(0x44444444)
                 yield wrport.en.eq(1)
-                yield
-                yield Settle()
+                yield Tick()
                 self.assertEqual((yield rdport.data), 0x22222222)
                 yield wrport.addr.eq(1)
                 yield wrport.data.eq(0x55555555)
                 yield wrport.en.eq(1)
-                yield
-                yield Settle()
+                yield Tick()
                 self.assertEqual((yield rdport.data), 0x22222255)
                 yield wrport.addr.eq(1)
                 yield wrport.data.eq(0x66666666)
                 yield wrport.en.eq(2)
                 yield rdport.en.eq(0)
-                yield
-                yield Settle()
+                yield Tick()
                 self.assertEqual((yield rdport.data), 0x22222255)
                 yield wrport.addr.eq(1)
                 yield wrport.data.eq(0x77777777)
                 yield wrport.en.eq(4)
                 yield rdport.en.eq(1)
-                yield
-                yield Settle()
+                yield Tick()
                 self.assertEqual((yield rdport.data), 0x22776655)
             sim.add_clock(1e-6)
-            sim.add_sync_process(process)
+            sim.add_testbench(process)
 
     def test_vcd_wrong_nonzero_time(self):
         s = Signal()
@@ -977,7 +1040,7 @@ class SimulatorRegressionTestCase(FHDLTestCase):
         sim = Simulator(Module())
         def process():
             self.assertEqual((yield -(Const(0b11, 2).as_signed())), 1)
-        sim.add_process(process)
+        sim.add_testbench(process)
         sim.run()
 
     def test_bug_595(self):
@@ -1018,5 +1081,5 @@ class SimulatorRegressionTestCase(FHDLTestCase):
             self.assertEqual((yield C(0b0000, 4) | ~C(1, 1)), 0b0000)
             self.assertEqual((yield C(0b1111, 4) & ~C(1, 1)), 0b0000)
             self.assertEqual((yield C(0b1111, 4) ^ ~C(1, 1)), 0b1111)
-        sim.add_process(process)
+        sim.add_testbench(process)
         sim.run()