sim: split into base, core, and engines.

Before this commit, each simulation engine (which is only pysim at
the moment, but also cxxsim soon) was a subclass of SimulatorCore,
and every simulation engine module would essentially duplicate
the complete structure of a simulator, with code partially shared.

This was a really bad idea: it was inconvenient to use, with
downstream code having to branch between e.g. PySettle and CxxSettle;
it had no well-defined external interface; it had multiple virtually
identical entry points; and it had no separation between simulation
algorithms and glue code.

This commit completely rearranges simulation code.
  1. sim._base defines internal simulation interfaces. The clarity of
     these internal interfaces is important because simulation
     engines mix and match components to provide a consistent API
     regardless of the chosen engine.
  2. sim.core defines the external simulation interface: the commands
     and the simulator facade. The facade provides a single entry
     point and, when possible, validates or lowers user input.
     It also imports built-in simulation engines by their symbolic
     name, avoiding eager imports of pyvcd or ctypes.
  3. sim.xxxsim (currently, only sim.pysim) defines the simulator
     implementation: time and state management, process scheduling,
     and waveform dumping.

The new simulator structure has none of the downsides of the old one.

See #324.
This commit is contained in:
whitequark 2020-08-27 10:17:02 +00:00
parent 9bdb7accc8
commit b65e11f38f
19 changed files with 396 additions and 301 deletions

View file

@ -1,5 +1,6 @@
from nmigen import *
from nmigen.back import rtlil, verilog, pysim
from nmigen.sim import *
from nmigen.back import rtlil, verilog
class Counter(Elaboratable):
@ -19,7 +20,7 @@ ctr = Counter(width=16)
print(verilog.convert(ctr, ports=[ctr.o, ctr.en]))
sim = pysim.Simulator(ctr)
sim = Simulator(ctr)
sim.add_clock(1e-6)
def ce_proc():
yield; yield; yield

View file

@ -1,11 +1,11 @@
import warnings
from ..sim.pysim import *
from ..sim import *
__all__ = ["Settle", "Delay", "Tick", "Passive", "Active", "Simulator"]
# TODO(nmigen-0.4): remove
warnings.warn("instead of back.pysim, use sim.pysim",
warnings.warn("instead of nmigen.back.pysim.*, use nmigen.sim.*",
DeprecationWarning, stacklevel=2)

View file

@ -2,8 +2,8 @@ import functools
import inspect
from collections.abc import Iterable
from ...hdl.cd import ClockDomain
from ...back.pysim import *
from ...hdl.ir import Fragment
from ...sim import *
__all__ = ["run_simulation", "passive"]

View file

@ -0,0 +1,4 @@
from .core import *
__all__ = ["Settle", "Delay", "Tick", "Passive", "Active", "Simulator"]

67
nmigen/sim/_base.py Normal file
View file

@ -0,0 +1,67 @@
__all__ = ["BaseProcess", "BaseSignalState", "BaseSimulation", "BaseEngine"]
class BaseProcess:
__slots__ = ()
def __init__(self):
self.reset()
def reset(self):
self.runnable = False
self.passive = True
def run(self):
raise NotImplementedError
class BaseSignalState:
__slots__ = ()
signal = NotImplemented
curr = NotImplemented
next = NotImplemented
def set(self, value):
raise NotImplementedError
class BaseSimulation:
def reset(self):
raise NotImplementedError
def get_signal(self, signal):
raise NotImplementedError
slots = NotImplemented
def add_trigger(self, process, signal, *, trigger=None):
raise NotImplementedError
def remove_trigger(self, process, signal):
raise NotImplementedError
def wait_interval(self, process, interval):
raise NotImplementedError
class BaseEngine:
def add_coroutine_process(self, process, *, default_cmd):
raise NotImplementedError
def add_clock_process(self, clock, *, phase, period):
raise NotImplementedError
def reset(self):
raise NotImplementedError
@property
def now(self):
raise NotImplementedError
def advance(self):
raise NotImplementedError
def write_vcd(self, *, vcd_file, gtkw_file, traces):
raise NotImplementedError

View file

@ -1,46 +0,0 @@
from ..hdl.cd import *
__all__ = ["Settle", "Delay", "Tick", "Passive", "Active"]
class Command:
pass
class Settle(Command):
def __repr__(self):
return "(settle)"
class Delay(Command):
def __init__(self, interval=None):
self.interval = None if interval is None else float(interval)
def __repr__(self):
if self.interval is None:
return "(delay ε)"
else:
return "(delay {:.3}us)".format(self.interval * 1e6)
class Tick(Command):
def __init__(self, domain="sync"):
if not isinstance(domain, (str, ClockDomain)):
raise TypeError("Domain must be a string or a ClockDomain instance, not {!r}"
.format(domain))
assert domain != "comb"
self.domain = domain
def __repr__(self):
return "(tick {})".format(self.domain)
class Passive(Command):
def __repr__(self):
return "(passive)"
class Active(Command):
def __repr__(self):
return "(active)"

View file

@ -1,63 +0,0 @@
__all__ = ["Process", "Timeline"]
class Process:
def __init__(self, *, is_comb):
self.is_comb = is_comb
self.reset()
def reset(self):
self.runnable = self.is_comb
self.passive = True
def run(self):
raise NotImplementedError
class Timeline:
def __init__(self):
self.now = 0.0
self.deadlines = dict()
def reset(self):
self.now = 0.0
self.deadlines.clear()
def at(self, run_at, process):
assert process not in self.deadlines
self.deadlines[process] = run_at
def delay(self, delay_by, process):
if delay_by is None:
run_at = self.now
else:
run_at = self.now + delay_by
self.at(run_at, process)
def advance(self):
nearest_processes = set()
nearest_deadline = None
for process, deadline in self.deadlines.items():
if deadline is None:
if nearest_deadline is not None:
nearest_processes.clear()
nearest_processes.add(process)
nearest_deadline = self.now
break
elif nearest_deadline is None or deadline <= nearest_deadline:
assert deadline >= self.now
if nearest_deadline is not None and deadline < nearest_deadline:
nearest_processes.clear()
nearest_processes.add(process)
nearest_deadline = deadline
if not nearest_processes:
return False
for process in nearest_processes:
process.runnable = True
del self.deadlines[process]
self.now = nearest_deadline
return True

View file

@ -1,12 +1,12 @@
import inspect
from ._core import Process
from ._base import BaseProcess
__all__ = ["PyClockProcess"]
class PyClockProcess(Process):
class PyClockProcess(BaseProcess):
def __init__(self, state, signal, *, phase, period):
assert len(signal) == 1
@ -20,16 +20,17 @@ class PyClockProcess(Process):
def reset(self):
self.runnable = True
self.passive = True
self.initial = True
def run(self):
self.runnable = False
if self.initial:
self.initial = False
self.state.timeline.delay(self.phase, self)
self.state.wait_interval(self, self.phase)
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
self.state.wait_interval(self, self.period / 2)

View file

@ -2,15 +2,15 @@ import inspect
from ..hdl import *
from ..hdl.ast import Statement, SignalSet
from ._cmds import *
from ._core import Process
from .core import Tick, Settle, Delay, Passive, Active
from ._base import BaseProcess
from ._pyrtl import _ValueCompiler, _RHSValueCompiler, _StatementCompiler
__all__ = ["PyCoroProcess"]
class PyCoroProcess(Process):
class PyCoroProcess(BaseProcess):
def __init__(self, state, domains, constructor, *, default_cmd=None):
self.state = state
self.domains = domains
@ -22,6 +22,7 @@ class PyCoroProcess(Process):
def reset(self):
self.runnable = True
self.passive = False
self.coroutine = self.constructor()
self.exec_locals = {
"slots": self.state.slots,
@ -90,11 +91,11 @@ class PyCoroProcess(Process):
return
elif type(command) is Settle:
self.state.timeline.delay(None, self)
self.state.wait_interval(self, None)
return
elif type(command) is Delay:
self.state.timeline.delay(command.interval, self)
self.state.wait_interval(self, command.interval)
return
elif type(command) is Passive:

View file

@ -5,14 +5,23 @@ from contextlib import contextmanager
from ..hdl import *
from ..hdl.ast import SignalSet
from ..hdl.xfrm import ValueVisitor, StatementVisitor, LHSGroupFilter
from ._core import *
from ._base import BaseProcess
__all__ = ["PyRTLProcess"]
class PyRTLProcess(Process):
pass
class PyRTLProcess(BaseProcess):
__slots__ = ("is_comb", "runnable", "passive", "run")
def __init__(self, *, is_comb):
self.is_comb = is_comb
self.reset()
def reset(self):
self.runnable = self.is_comb
self.passive = True
class _PythonEmitter:

206
nmigen/sim/core.py Normal file
View file

@ -0,0 +1,206 @@
import inspect
from .._utils import deprecated
from ..hdl.cd import *
from ..hdl.ir import *
from ._base import BaseEngine
__all__ = ["Settle", "Delay", "Tick", "Passive", "Active", "Simulator"]
class Command:
pass
class Settle(Command):
def __repr__(self):
return "(settle)"
class Delay(Command):
def __init__(self, interval=None):
self.interval = None if interval is None else float(interval)
def __repr__(self):
if self.interval is None:
return "(delay ε)"
else:
return "(delay {:.3}us)".format(self.interval * 1e6)
class Tick(Command):
def __init__(self, domain="sync"):
if not isinstance(domain, (str, ClockDomain)):
raise TypeError("Domain must be a string or a ClockDomain instance, not {!r}"
.format(domain))
assert domain != "comb"
self.domain = domain
def __repr__(self):
return "(tick {})".format(self.domain)
class Passive(Command):
def __repr__(self):
return "(passive)"
class Active(Command):
def __repr__(self):
return "(active)"
class Simulator:
def __init__(self, fragment, *, engine="pysim"):
if isinstance(engine, type) and issubclass(engine, BaseEngine):
pass
elif engine == "pysim":
from .pysim import PySimEngine
engine = PySimEngine
else:
raise TypeError("Value '{!r}' is not a simulation engine class or "
"a simulation engine name"
.format(engine))
self._fragment = Fragment.get(fragment, platform=None).prepare()
self._engine = engine(self._fragment)
self._clocked = set()
def _check_process(self, process):
if not (inspect.isgeneratorfunction(process) or inspect.iscoroutinefunction(process)):
raise TypeError("Cannot add a process {!r} because it is not a generator function"
.format(process))
return process
def add_process(self, process):
process = self._check_process(process)
def wrapper():
# Only start a bench process after comb settling, so that the reset values are correct.
yield Settle()
yield from process()
self._engine.add_coroutine_process(wrapper, default_cmd=None)
def add_sync_process(self, process, *, domain="sync"):
process = self._check_process(process)
def wrapper():
# Only start a sync process after the first clock edge (or reset edge, if the domain
# uses an asynchronous reset). This matches the behavior of synchronous FFs.
yield Tick(domain)
yield from process()
self._engine.add_coroutine_process(wrapper, default_cmd=Tick(domain))
def add_clock(self, period, *, phase=None, domain="sync", if_exists=False):
"""Add a clock process.
Adds a process that drives the clock signal of ``domain`` at a 50% duty cycle.
Arguments
---------
period : float
Clock period. The process will toggle the ``domain`` clock signal every ``period / 2``
seconds.
phase : None or float
Clock phase. The process will wait ``phase`` seconds before the first clock transition.
If not specified, defaults to ``period / 2``.
domain : str or ClockDomain
Driven clock domain. If specified as a string, the domain with that name is looked up
in the root fragment of the simulation.
if_exists : bool
If ``False`` (the default), raise an error if the driven domain is specified as
a string and the root fragment does not have such a domain. If ``True``, do nothing
in this case.
"""
if isinstance(domain, ClockDomain):
pass
elif domain in self._fragment.domains:
domain = self._fragment.domains[domain]
elif if_exists:
return
else:
raise ValueError("Domain {!r} is not present in simulation"
.format(domain))
if domain in self._clocked:
raise ValueError("Domain {!r} already has a clock driving it"
.format(domain.name))
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 = period / 2
self._engine.add_clock_process(domain.clk, phase=phase, period=period)
self._clocked.add(domain)
def reset(self):
"""Reset the simulation.
Assign the reset value to every signal in the simulation, and restart every user process.
"""
self._engine.reset()
# TODO(nmigen-0.4): replace with _real_step
@deprecated("instead of `sim.step()`, use `sim.advance()`")
def step(self):
return self.advance()
def advance(self):
"""Advance the simulation.
Run every process and commit changes until a fixed point is reached, then advance time
to the closest deadline (if any). If there is an unstable combinatorial loop,
this function will never return.
Returns ``True`` if there are any active processes, ``False`` otherwise.
"""
return self._engine.advance()
def run(self):
"""Run the simulation while any processes are active.
Processes added with :meth:`add_process` and :meth:`add_sync_process` are initially active,
and may change their status using the ``yield Passive()`` and ``yield Active()`` commands.
Processes compiled from HDL and added with :meth:`add_clock` are always passive.
"""
while self.advance():
pass
def run_until(self, deadline, *, run_passive=False):
"""Run the simulation until it advances to ``deadline``.
If ``run_passive`` is ``False``, the simulation also stops when there are no active
processes, similar to :meth:`run`. Otherwise, the simulation will stop only after it
advances to or past ``deadline``.
If the simulation stops advancing, this function will never return.
"""
assert self._engine.now <= deadline
while (self.advance() or run_passive) and self._engine.now < deadline:
pass
def write_vcd(self, vcd_file, gtkw_file=None, *, traces=()):
"""Write waveforms to a Value Change Dump file, optionally populating a GTKWave save file.
This method returns a context manager. It can be used as: ::
sim = Simulator(frag)
sim.add_clock(1e-6)
with sim.write_vcd("dump.vcd", "dump.gtkw"):
sim.run_until(1e-3)
Arguments
---------
vcd_file : str or file-like object
Verilog Value Change Dump file or filename.
gtkw_file : str or file-like object
GTKWave save file or filename.
traces : iterable of Signal
Signals to display traces for.
"""
if self._engine.now != 0.0:
for file in (vcd_file, gtkw_file):
if hasattr(file, "close"):
file.close()
raise ValueError("Cannot start writing waveforms after advancing simulation time")
return self._engine.write_vcd(vcd_file=vcd_file, gtkw_file=gtkw_file, traces=traces)

View file

@ -1,20 +1,17 @@
from contextlib import contextmanager
import itertools
import inspect
from vcd import VCDWriter
from vcd.gtkw import GTKWSave
from .._utils import deprecated
from ..hdl import *
from ..hdl.ast import SignalDict
from ._cmds import *
from ._core import *
from ._base import *
from ._pyrtl import _FragmentCompiler
from ._pycoro import PyCoroProcess
from ._pyclock import PyClockProcess
__all__ = ["Settle", "Delay", "Tick", "Passive", "Active", "Simulator"]
__all__ = ["PySimEngine"]
class _NameExtractor:
@ -49,15 +46,7 @@ class _NameExtractor:
return self.names
class _WaveformWriter:
def update(self, timestamp, signal, value):
raise NotImplementedError # :nocov:
def close(self, timestamp):
raise NotImplementedError # :nocov:
class _VCDWaveformWriter(_WaveformWriter):
class _VCDWriter:
@staticmethod
def timestamp_to_vcd(timestamp):
return timestamp * (10 ** 10) # 1/(100 ps)
@ -162,7 +151,55 @@ class _VCDWaveformWriter(_WaveformWriter):
self.gtkw_file.close()
class _SignalState:
class _Timeline:
def __init__(self):
self.now = 0.0
self.deadlines = dict()
def reset(self):
self.now = 0.0
self.deadlines.clear()
def at(self, run_at, process):
assert process not in self.deadlines
self.deadlines[process] = run_at
def delay(self, delay_by, process):
if delay_by is None:
run_at = self.now
else:
run_at = self.now + delay_by
self.at(run_at, process)
def advance(self):
nearest_processes = set()
nearest_deadline = None
for process, deadline in self.deadlines.items():
if deadline is None:
if nearest_deadline is not None:
nearest_processes.clear()
nearest_processes.add(process)
nearest_deadline = self.now
break
elif nearest_deadline is None or deadline <= nearest_deadline:
assert deadline >= self.now
if nearest_deadline is not None and deadline < nearest_deadline:
nearest_processes.clear()
nearest_processes.add(process)
nearest_deadline = deadline
if not nearest_processes:
return False
for process in nearest_processes:
process.runnable = True
del self.deadlines[process]
self.now = nearest_deadline
return True
class _PySignalState(BaseSignalState):
__slots__ = ("signal", "curr", "next", "waiters", "pending")
def __init__(self, signal, pending):
@ -189,9 +226,9 @@ class _SignalState:
return awoken_any
class _SimulatorState:
class _PySimulation(BaseSimulation):
def __init__(self):
self.timeline = Timeline()
self.timeline = _Timeline()
self.signals = SignalDict()
self.slots = []
self.pending = set()
@ -207,7 +244,7 @@ class _SimulatorState:
return self.signals[signal]
except KeyError:
index = len(self.slots)
self.slots.append(_SignalState(signal, self.pending))
self.slots.append(_PySignalState(signal, self.pending))
self.signals[signal] = index
return index
@ -222,6 +259,9 @@ class _SimulatorState:
assert process in self.slots[index].waiters
del self.slots[index].waiters[process]
def wait_interval(self, process, interval):
self.timeline.delay(interval, process)
def commit(self):
converged = True
for signal_state in self.pending:
@ -231,98 +271,29 @@ class _SimulatorState:
return converged
class Simulator:
class PySimEngine(BaseEngine):
def __init__(self, fragment):
self._state = _SimulatorState()
self._fragment = Fragment.get(fragment, platform=None).prepare()
self._state = _PySimulation()
self._timeline = self._state.timeline
self._fragment = fragment
self._processes = _FragmentCompiler(self._state)(self._fragment)
self._clocked = set()
self._waveform_writers = []
self._vcd_writers = []
def _check_process(self, process):
if not (inspect.isgeneratorfunction(process) or inspect.iscoroutinefunction(process)):
raise TypeError("Cannot add a process {!r} because it is not a generator function"
.format(process))
return process
def _add_coroutine_process(self, process, *, default_cmd):
def add_coroutine_process(self, process, *, default_cmd):
self._processes.add(PyCoroProcess(self._state, self._fragment.domains, process,
default_cmd=default_cmd))
def add_process(self, process):
process = self._check_process(process)
def wrapper():
# Only start a bench process after comb settling, so that the reset values are correct.
yield Settle()
yield from process()
self._add_coroutine_process(wrapper, default_cmd=None)
def add_sync_process(self, process, *, domain="sync"):
process = self._check_process(process)
def wrapper():
# Only start a sync process after the first clock edge (or reset edge, if the domain
# uses an asynchronous reset). This matches the behavior of synchronous FFs.
yield Tick(domain)
yield from process()
return self._add_coroutine_process(wrapper, default_cmd=Tick(domain))
def add_clock(self, period, *, phase=None, domain="sync", if_exists=False):
"""Add a clock process.
Adds a process that drives the clock signal of ``domain`` at a 50% duty cycle.
Arguments
---------
period : float
Clock period. The process will toggle the ``domain`` clock signal every ``period / 2``
seconds.
phase : None or float
Clock phase. The process will wait ``phase`` seconds before the first clock transition.
If not specified, defaults to ``period / 2``.
domain : str or ClockDomain
Driven clock domain. If specified as a string, the domain with that name is looked up
in the root fragment of the simulation.
if_exists : bool
If ``False`` (the default), raise an error if the driven domain is specified as
a string and the root fragment does not have such a domain. If ``True``, do nothing
in this case.
"""
if isinstance(domain, ClockDomain):
pass
elif domain in self._fragment.domains:
domain = self._fragment.domains[domain]
elif if_exists:
return
else:
raise ValueError("Domain {!r} is not present in simulation"
.format(domain))
if domain in self._clocked:
raise ValueError("Domain {!r} already has a clock driving it"
.format(domain.name))
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 = period / 2
self._processes.add(PyClockProcess(self._state, domain.clk, phase=phase, period=period))
self._clocked.add(domain)
def add_clock_process(self, clock, *, phase, period):
self._processes.add(PyClockProcess(self._state, clock,
phase=phase, period=period))
def reset(self):
"""Reset the simulation.
Assign the reset value to every signal in the simulation, and restart every user process.
"""
self._state.reset()
for process in self._processes:
process.reset()
def _real_step(self):
"""Step the simulation.
Run every process and commit changes until a fixed point is reached. If there is
an unstable combinatorial loop, this function will never return.
"""
def _step(self):
# Performs the two phases of a delta cycle in a loop:
converged = False
while not converged:
@ -332,86 +303,30 @@ class Simulator:
process.runnable = False
process.run()
for waveform_writer in self._waveform_writers:
for vcd_writer in self._vcd_writers:
for signal_state in self._state.pending:
waveform_writer.update(self._state.timeline.now,
vcd_writer.update(self._timeline.now,
signal_state.signal, signal_state.next)
# 2. commit: apply every queued signal change, waking up any waiting processes
converged = self._state.commit()
# TODO(nmigen-0.4): replace with _real_step
@deprecated("instead of `sim.step()`, use `sim.advance()`")
def step(self):
return self.advance()
def advance(self):
"""Advance the simulation.
Run every process and commit changes until a fixed point is reached, then advance time
to the closest deadline (if any). If there is an unstable combinatorial loop,
this function will never return.
Returns ``True`` if there are any active processes, ``False`` otherwise.
"""
self._real_step()
self._state.timeline.advance()
self._step()
self._timeline.advance()
return any(not process.passive for process in self._processes)
def run(self):
"""Run the simulation while any processes are active.
Processes added with :meth:`add_process` and :meth:`add_sync_process` are initially active,
and may change their status using the ``yield Passive()`` and ``yield Active()`` commands.
Processes compiled from HDL and added with :meth:`add_clock` are always passive.
"""
while self.advance():
pass
def run_until(self, deadline, *, run_passive=False):
"""Run the simulation until it advances to ``deadline``.
If ``run_passive`` is ``False``, the simulation also stops when there are no active
processes, similar to :meth:`run`. Otherwise, the simulation will stop only after it
advances to or past ``deadline``.
If the simulation stops advancing, this function will never return.
"""
assert self._state.timeline.now <= deadline
while (self.advance() or run_passive) and self._state.timeline.now < deadline:
pass
@property
def now(self):
return self._timeline.now
@contextmanager
def write_vcd(self, vcd_file, gtkw_file=None, *, traces=()):
"""Write waveforms to a Value Change Dump file, optionally populating a GTKWave save file.
This method returns a context manager. It can be used as: ::
sim = Simulator(frag)
sim.add_clock(1e-6)
with sim.write_vcd("dump.vcd", "dump.gtkw"):
sim.run_until(1e-3)
Arguments
---------
vcd_file : str or file-like object
Verilog Value Change Dump file or filename.
gtkw_file : str or file-like object
GTKWave save file or filename.
traces : iterable of Signal
Signals to display traces for.
"""
if self._state.timeline.now != 0.0:
for file in (vcd_file, gtkw_file):
if hasattr(file, "close"):
file.close()
raise ValueError("Cannot start writing waveforms after advancing simulation time")
waveform_writer = _VCDWaveformWriter(self._fragment,
def write_vcd(self, *, vcd_file, gtkw_file, traces):
vcd_writer = _VCDWriter(self._fragment,
vcd_file=vcd_file, gtkw_file=gtkw_file, traces=traces)
try:
self._waveform_writers.append(waveform_writer)
self._vcd_writers.append(vcd_writer)
yield
finally:
waveform_writer.close(self._state.timeline.now)
self._waveform_writers.remove(waveform_writer)
vcd_writer.close(self._timeline.now)
self._vcd_writers.remove(vcd_writer)

View file

@ -7,5 +7,5 @@ __all__ = ["LatticeMachXO2Platform"]
# TODO(nmigen-0.4): remove
warnings.warn("instead of vendor.lattice_machxo2, use vendor.lattice_machxo_2_3l",
warnings.warn("instead of nmigen.vendor.lattice_machxo2, use nmigen.vendor.lattice_machxo_2_3l",
DeprecationWarning, stacklevel=2)

View file

@ -1,7 +1,7 @@
# nmigen: UnusedElaboratable=no
from nmigen.hdl import *
from nmigen.back.pysim import *
from nmigen.sim import *
from nmigen.lib.cdc import *
from .utils import *

View file

@ -1,6 +1,6 @@
from nmigen.hdl import *
from nmigen.asserts import *
from nmigen.back.pysim import *
from nmigen.sim import *
from nmigen.lib.coding import *
from .utils import *

View file

@ -2,7 +2,7 @@
from nmigen.hdl import *
from nmigen.asserts import *
from nmigen.back.pysim import *
from nmigen.sim import *
from nmigen.lib.fifo import *
from .utils import *

View file

@ -1,6 +1,6 @@
from nmigen.hdl import *
from nmigen.hdl.rec import *
from nmigen.back.pysim import *
from nmigen.sim import *
from nmigen.lib.io import *
from .utils import *

View file

@ -4,7 +4,7 @@ import unittest
from nmigen.hdl import *
from nmigen.asserts import *
from nmigen.sim.pysim import *
from nmigen.sim import *
from nmigen.lib.scheduler import *
from .utils import *

View file

@ -8,7 +8,7 @@ from nmigen.hdl.mem import *
from nmigen.hdl.rec import *
from nmigen.hdl.dsl import *
from nmigen.hdl.ir import *
from nmigen.back.pysim import *
from nmigen.sim import *
from .utils import *