sim._pyclock: new type of process.
authorwhitequark <whitequark@whitequark.org>
Thu, 27 Aug 2020 07:54:27 +0000 (07:54 +0000)
committerwhitequark <whitequark@whitequark.org>
Thu, 27 Aug 2020 07:56:47 +0000 (07:56 +0000)
The overhead of coroutine processes is fairly high. A clock driver
implemented through a coroutine process is mostly overhead. This was
partially addressed in commit 2398b792 by microoptimizing yielding.

This commit eliminates the coroutine process overhead completely by
introducing dedicated clock processes. It also simplifies the logic
to a simple toggle.

This change improves runtime by about 12% on Minerva SRAM SoC.

nmigen/sim/_pyclock.py [new file with mode: 0644]
nmigen/sim/pysim.py

diff --git a/nmigen/sim/_pyclock.py b/nmigen/sim/_pyclock.py
new file mode 100644 (file)
index 0000000..3e1e6ab
--- /dev/null
@@ -0,0 +1,35 @@
+import inspect
+
+from ._core import Process
+
+
+__all__ = ["PyClockProcess"]
+
+
+class PyClockProcess(Process):
+    def __init__(self, state, signal, *, phase, period):
+        assert len(signal) == 1
+
+        self.state  = state
+        self.slot   = self.state.get_signal(signal)
+        self.phase  = phase
+        self.period = period
+
+        self.reset()
+
+    def reset(self):
+        self.runnable = True
+        self.passive = True
+        self.initial = True
+
+    def run(self):
+        if self.initial:
+            self.initial = False
+            self.state.timeline.delay(self.phase, self)
+
+        else:
+            clk_state = self.state.slots[self.slot]
+            clk_state.set(not clk_state.curr)
+            self.state.timeline.delay(self.period / 2, self)
+
+        self.runnable = False
index c50b7423fdbd1592d10324f076c5bac324092831..5f30816767e51066f36033d40f073173353b6105 100644 (file)
@@ -11,6 +11,7 @@ from ._cmds import *
 from ._core import *
 from ._pyrtl import _FragmentCompiler
 from ._pycoro import PyCoroProcess
+from ._pyclock import PyClockProcess
 
 
 __all__ = ["Settle", "Delay", "Tick", "Passive", "Active", "Simulator"]
@@ -299,27 +300,12 @@ class Simulator:
             raise ValueError("Domain {!r} already has a clock driving it"
                              .format(domain.name))
 
-        half_period = period / 2
         if phase is None:
             # By default, delay the first edge by half period. This causes any synchronous activity
             # to happen at a non-zero time, distinguishing it from the reset values in the waveform
             # viewer.
-            phase = half_period
-        def clk_process():
-            yield Passive()
-            yield Delay(phase)
-            # Behave correctly if the process is added after the clock signal is manipulated, or if
-            # its reset state is high.
-            initial = (yield domain.clk)
-            steps = (
-                domain.clk.eq(~initial),
-                Delay(half_period),
-                domain.clk.eq(initial),
-                Delay(half_period),
-            )
-            while True:
-                yield from iter(steps)
-        self._add_coroutine_process(clk_process, default_cmd=None)
+            phase = period / 2
+        self._processes.add(PyClockProcess(self._state, domain.clk, phase=phase, period=period))
         self._clocked.add(domain)
 
     def reset(self):