Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: m-labs/nmigen
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: d791b77cc868
Choose a base ref
...
head repository: m-labs/nmigen
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: cb998d891b73
Choose a head ref
  • 3 commits
  • 2 files changed
  • 1 contributor

Commits on Dec 14, 2018

  1. Copy the full SHA
    3bb7a87 View commit details
  2. back.pysim: delay clock processes by one half period.

    Makes it easier to see initial delta cycles.
    whitequark committed Dec 14, 2018
    Copy the full SHA
    e4d08d2 View commit details
  3. back.pysim: explain how delta cycles work.

    whitequark committed Dec 14, 2018
    Copy the full SHA
    cb998d8 View commit details
Showing with 105 additions and 44 deletions.
  1. +8 −7 examples/ctrl.py
  2. +97 −37 nmigen/back/pysim.py
15 changes: 8 additions & 7 deletions examples/ctrl.py
Original file line number Diff line number Diff line change
@@ -23,11 +23,12 @@ def get_fragment(self, platform):

sim = pysim.Simulator(frag, vcd_file=open("ctrl.vcd", "w"))
sim.add_clock("sync", 1e-6)
def sim_proc():
yield pysim.Delay(15.25e-6)
yield ctr.ce.eq(Const(1))
yield pysim.Delay(15.25e-6)
yield pysim.Tick("sync")
yield ctr.ce.eq(Const(0))
sim.add_process(sim_proc())
def ce_proc():
yield; yield; yield
yield ctr.ce.eq(1)
yield; yield; yield
yield ctr.ce.eq(0)
yield; yield; yield
yield ctr.ce.eq(1)
sim.add_sync_process(ce_proc())
with sim: sim.run_until(100e-6, run_passive=True)
134 changes: 97 additions & 37 deletions nmigen/back/pysim.py
Original file line number Diff line number Diff line change
@@ -184,16 +184,19 @@ def run(state):
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}
self._user_signals = ValueSet() # {Signal}

self._started = False
self._timestamp = 0.
self._epsilon = 1e-10
self._state = _State()

self._processes = set() # {process}
@@ -202,7 +205,7 @@ def __init__(self, fragment=None, vcd_file=None):
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
@@ -223,31 +226,42 @@ def _add_fragment(self, fragment, hierarchy=("top",)):
for subfragment, name in fragment.subfragments:
self._add_fragment(subfragment, (*hierarchy, name))

def add_process(self, fn):
self._processes.add(fn)
def add_process(self, process):
self._processes.add(process)

def add_clock(self, domain, period):
clk = self._domains[domain].clk
half_period = period / 2
def clk_process():
yield Passive()
yield Delay(half_period)
while True:
yield clk.eq(1)
yield Delay(half_period)
yield clk.eq(0)
yield Delay(half_period)
self.add_process(clk_process())

def add_sync_process(self, process, domain="sync"):
def sync_process():
try:
result = process.send(None)
while True:
result = process.send((yield (result or Tick(domain))))
except StopIteration:
pass
self.add_process(sync_process())

def _signal_name_in_fragment(self, fragment, signal):
for subfragment, name in fragment.subfragments:
if signal in subfragment.ports:
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:
@@ -287,51 +301,92 @@ def __enter__(self):
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):
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 (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)

for proc, wait_domain in list(self._wait_tick.items()):
if domain == wait_domain:
del self._wait_tick[proc]
self._suspended.remove(proc)
# 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 * 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)
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)
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)

# 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:
@@ -360,8 +415,12 @@ def _run_process(self, proc):
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):
@@ -389,8 +448,9 @@ def run_until(self, deadline, run_passive=False):
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)