sim._pyclock: new type of process.

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.
This commit is contained in:
whitequark 2020-08-27 07:54:27 +00:00
parent c00219d9f3
commit 9bc42cb8c5
2 changed files with 38 additions and 17 deletions

35
nmigen/sim/_pyclock.py Normal file
View file

@ -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

View 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):