Skip to content

Commit

Permalink
sim._pyclock: new type of process.
Browse files Browse the repository at this point in the history
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 2398b79 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.
  • Loading branch information
whitequark committed Aug 27, 2020
1 parent c00219d commit 9bc42cb
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 17 deletions.
35 changes: 35 additions & 0 deletions nmigen/sim/_pyclock.py
@@ -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
20 changes: 3 additions & 17 deletions nmigen/sim/pysim.py
Expand Up @@ -11,6 +11,7 @@
from ._core import *
from ._pyrtl import _FragmentCompiler
from ._pycoro import PyCoroProcess
from ._pyclock import PyClockProcess


__all__ = ["Settle", "Delay", "Tick", "Passive", "Active", "Simulator"]
Expand Down Expand Up @@ -299,27 +300,12 @@ def add_clock(self, period, *, phase=None, domain="sync", if_exists=False):
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):
Expand Down

0 comments on commit 9bc42cb

Please sign in to comment.