back.pysim: explain how delta cycles work.
This commit is contained in:
parent
e4d08d2855
commit
cb998d891b
|
@ -184,9 +184,11 @@ class _StatementCompiler(StatementTransformer):
|
|||
class Simulator:
|
||||
def __init__(self, fragment=None, vcd_file=None):
|
||||
self._fragments = {} # fragment -> hierarchy
|
||||
|
||||
self._domains = {} # str/domain -> ClockDomain
|
||||
self._domain_triggers = ValueDict() # Signal -> str/domain
|
||||
self._domain_signals = {} # str/domain -> {Signal}
|
||||
|
||||
self._signals = ValueSet() # {Signal}
|
||||
self._comb_signals = ValueSet() # {Signal}
|
||||
self._sync_signals = ValueSet() # {Signal}
|
||||
|
@ -194,6 +196,7 @@ class Simulator:
|
|||
|
||||
self._started = False
|
||||
self._timestamp = 0.
|
||||
self._epsilon = 1e-10
|
||||
self._state = _State()
|
||||
|
||||
self._processes = set() # {process}
|
||||
|
@ -202,7 +205,7 @@ class Simulator:
|
|||
self._wait_deadline = {} # process -> float/timestamp
|
||||
self._wait_tick = {} # process -> str/domain
|
||||
|
||||
self._handlers = ValueDict() # Signal -> set(lambda)
|
||||
self._funclets = ValueDict() # Signal -> set(lambda)
|
||||
|
||||
self._vcd_file = vcd_file
|
||||
self._vcd_writer = None
|
||||
|
@ -255,10 +258,10 @@ class Simulator:
|
|||
return "{}_{}".format(name, signal.name)
|
||||
return signal.name
|
||||
|
||||
def _add_handler(self, signal, handler):
|
||||
if signal not in self._handlers:
|
||||
self._handlers[signal] = set()
|
||||
self._handlers[signal].add(handler)
|
||||
def _add_funclet(self, signal, funclet):
|
||||
if signal not in self._funclets:
|
||||
self._funclets[signal] = set()
|
||||
self._funclets[signal].add(funclet)
|
||||
|
||||
def __enter__(self):
|
||||
if self._vcd_file:
|
||||
|
@ -298,51 +301,92 @@ class Simulator:
|
|||
self._domain_signals[domain].update(signals)
|
||||
|
||||
compiler = _StatementCompiler()
|
||||
handler = compiler(fragment.statements)
|
||||
funclet = compiler(fragment.statements)
|
||||
for signal in compiler.sensitivity:
|
||||
self._add_handler(signal, handler)
|
||||
self._add_funclet(signal, funclet)
|
||||
for domain, cd in fragment.domains.items():
|
||||
self._add_handler(cd.clk, handler)
|
||||
self._add_funclet(cd.clk, funclet)
|
||||
if cd.rst is not None:
|
||||
self._add_handler(cd.rst, handler)
|
||||
self._add_funclet(cd.rst, funclet)
|
||||
|
||||
self._user_signals = self._signals - self._comb_signals - self._sync_signals
|
||||
|
||||
def _commit_signal(self, signal):
|
||||
old, new = self._state.commit(signal)
|
||||
if (old, new) == (0, 1) and signal in self._domain_triggers:
|
||||
domain = self._domain_triggers[signal]
|
||||
for sync_signal in self._state.next_dirty:
|
||||
if sync_signal in self._domain_signals[domain]:
|
||||
self._commit_signal(sync_signal)
|
||||
def _update_dirty_signals(self):
|
||||
"""Perform the statement part of IR processes (aka RTLIL case)."""
|
||||
# First, for all dirty signals, use sensitivity lists to determine the set of fragments
|
||||
# that need their statements to be reevaluated because the signals changed at the previous
|
||||
# delta cycle.
|
||||
funclets = set()
|
||||
while self._state.curr_dirty:
|
||||
signal = self._state.curr_dirty.pop()
|
||||
if signal in self._funclets:
|
||||
funclets.update(self._funclets[signal])
|
||||
|
||||
# Second, compute the values of all signals at the start of the next delta cycle, by
|
||||
# running precompiled statements.
|
||||
for funclet in funclets:
|
||||
funclet(self._state)
|
||||
|
||||
def _commit_signal(self, signal, domains):
|
||||
"""Perform the driver part of IR processes (aka RTLIL sync), for individual signals."""
|
||||
# Take the computed value (at the start of this delta cycle) of a signal (that could have
|
||||
# come from an IR process that ran earlier, or modified by a simulator process) and update
|
||||
# the value for this delta cycle.
|
||||
old, new = self._state.commit(signal)
|
||||
|
||||
# If the signal is a clock that triggers synchronous logic, record that fact.
|
||||
if (old, new) == (0, 1) and signal in self._domain_triggers:
|
||||
domains.add(self._domain_triggers[signal])
|
||||
|
||||
if self._vcd_writer:
|
||||
# Finally, dump the new value to the VCD file.
|
||||
for vcd_signal in self._vcd_signals[signal]:
|
||||
self._vcd_writer.change(vcd_signal, self._timestamp / self._epsilon, new)
|
||||
|
||||
def _commit_comb_signals(self, domains):
|
||||
"""Perform the comb part of IR processes (aka RTLIL always)."""
|
||||
# Take the computed value (at the start of this delta cycle) of every comb signal and
|
||||
# update the value for this delta cycle.
|
||||
for signal in self._state.next_dirty:
|
||||
if signal in self._comb_signals or signal in self._user_signals:
|
||||
self._commit_signal(signal, domains)
|
||||
|
||||
def _commit_sync_signals(self, domains):
|
||||
"""Perform the sync part of IR processes (aka RTLIL posedge)."""
|
||||
# At entry, `domains` contains a list of every simultaneously triggered sync update.
|
||||
while domains:
|
||||
# Advance the timeline a bit (purely for observational purposes) and commit all of them
|
||||
# at the same timestamp.
|
||||
self._timestamp += self._epsilon
|
||||
curr_domains, domains = domains, set()
|
||||
|
||||
while curr_domains:
|
||||
domain = curr_domains.pop()
|
||||
|
||||
# Take the computed value (at the start of this delta cycle) of every sync signal
|
||||
# in this domain and update the value for this delta cycle. This can trigger more
|
||||
# synchronous logic, so record that.
|
||||
for signal in self._state.next_dirty:
|
||||
if signal in self._domain_signals[domain]:
|
||||
self._commit_signal(signal, domains)
|
||||
|
||||
# Wake up any simulator processes that wait for a domain tick.
|
||||
for proc, wait_domain in list(self._wait_tick.items()):
|
||||
if domain == wait_domain:
|
||||
del self._wait_tick[proc]
|
||||
self._suspended.remove(proc)
|
||||
|
||||
if self._vcd_writer:
|
||||
for vcd_signal in self._vcd_signals[signal]:
|
||||
self._vcd_writer.change(vcd_signal, self._timestamp * 1e10, new)
|
||||
|
||||
def _handle_event(self):
|
||||
handlers = set()
|
||||
while self._state.curr_dirty:
|
||||
signal = self._state.curr_dirty.pop()
|
||||
if signal in self._handlers:
|
||||
handlers.update(self._handlers[signal])
|
||||
|
||||
for handler in handlers:
|
||||
handler(self._state)
|
||||
|
||||
for signal in self._state.next_dirty:
|
||||
if signal in self._comb_signals or signal in self._user_signals:
|
||||
self._commit_signal(signal)
|
||||
# Unless handling synchronous logic above has triggered more synchronous logic (which
|
||||
# can happen e.g. if a domain is clocked off a clock divisor in fabric), we're done.
|
||||
# Otherwise, do one more round of updates.
|
||||
|
||||
def _force_signal(self, signal, value):
|
||||
assert signal in self._user_signals
|
||||
self._state.set_next(signal, value)
|
||||
self._commit_signal(signal)
|
||||
|
||||
domains = set()
|
||||
self._commit_signal(signal, domains)
|
||||
self._commit_sync_signals(domains)
|
||||
|
||||
def _run_process(self, proc):
|
||||
try:
|
||||
|
@ -371,8 +415,12 @@ class Simulator:
|
|||
def step(self, run_passive=False):
|
||||
# Are there any delta cycles we should run?
|
||||
while self._state.curr_dirty:
|
||||
self._timestamp += 1e-10
|
||||
self._handle_event()
|
||||
self._timestamp += self._epsilon
|
||||
|
||||
domains = set()
|
||||
self._update_dirty_signals()
|
||||
self._commit_comb_signals(domains)
|
||||
self._commit_sync_signals(domains)
|
||||
|
||||
# Are there any processes that haven't had a chance to run yet?
|
||||
if len(self._processes) > len(self._suspended):
|
||||
|
@ -400,8 +448,9 @@ class Simulator:
|
|||
while self._timestamp < deadline:
|
||||
if not self.step(run_passive):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def __exit__(self, *args):
|
||||
if self._vcd_writer:
|
||||
self._vcd_writer.close(self._timestamp * 1e10)
|
||||
self._vcd_writer.close(self._timestamp / self._epsilon)
|
||||
|
|
Loading…
Reference in a new issue