3 from .._utils
import deprecated
6 from ._base
import BaseEngine
9 __all__
= ["Settle", "Delay", "Tick", "Passive", "Active", "Simulator"]
16 class Settle(Command
):
22 def __init__(self
, interval
=None):
23 self
.interval
= None if interval
is None else float(interval
)
26 if self
.interval
is None:
29 return "(delay {:.3}us)".format(self
.interval
* 1e6
)
33 def __init__(self
, domain
="sync"):
34 if not isinstance(domain
, (str, ClockDomain
)):
35 raise TypeError("Domain must be a string or a ClockDomain instance, not {!r}"
37 assert domain
!= "comb"
41 return "(tick {})".format(self
.domain
)
44 class Passive(Command
):
49 class Active(Command
):
55 def __init__(self
, fragment
, *, engine
="pysim"):
56 if isinstance(engine
, type) and issubclass(engine
, BaseEngine
):
58 elif engine
== "pysim":
59 from .pysim
import PySimEngine
62 raise TypeError("Value '{!r}' is not a simulation engine class or "
63 "a simulation engine name"
66 self
._fragment
= Fragment
.get(fragment
, platform
=None).prepare()
67 self
._engine
= engine(self
._fragment
)
70 def _check_process(self
, process
):
71 if not (inspect
.isgeneratorfunction(process
) or inspect
.iscoroutinefunction(process
)):
72 raise TypeError("Cannot add a process {!r} because it is not a generator function"
76 def add_process(self
, process
):
77 process
= self
._check
_process
(process
)
79 # Only start a bench process after comb settling, so that the reset values are correct.
82 self
._engine
.add_coroutine_process(wrapper
, default_cmd
=None)
84 def add_sync_process(self
, process
, *, domain
="sync"):
85 process
= self
._check
_process
(process
)
87 # Only start a sync process after the first clock edge (or reset edge, if the domain
88 # uses an asynchronous reset). This matches the behavior of synchronous FFs.
91 self
._engine
.add_coroutine_process(wrapper
, default_cmd
=Tick(domain
))
93 def add_clock(self
, period
, *, phase
=None, domain
="sync", if_exists
=False):
94 """Add a clock process.
96 Adds a process that drives the clock signal of ``domain`` at a 50% duty cycle.
101 Clock period. The process will toggle the ``domain`` clock signal every ``period / 2``
103 phase : None or float
104 Clock phase. The process will wait ``phase`` seconds before the first clock transition.
105 If not specified, defaults to ``period / 2``.
106 domain : str or ClockDomain
107 Driven clock domain. If specified as a string, the domain with that name is looked up
108 in the root fragment of the simulation.
110 If ``False`` (the default), raise an error if the driven domain is specified as
111 a string and the root fragment does not have such a domain. If ``True``, do nothing
114 if isinstance(domain
, ClockDomain
):
116 elif domain
in self
._fragment
.domains
:
117 domain
= self
._fragment
.domains
[domain
]
121 raise ValueError("Domain {!r} is not present in simulation"
123 if domain
in self
._clocked
:
124 raise ValueError("Domain {!r} already has a clock driving it"
125 .format(domain
.name
))
128 # By default, delay the first edge by half period. This causes any synchronous activity
129 # to happen at a non-zero time, distinguishing it from the reset values in the waveform
132 self
._engine
.add_clock_process(domain
.clk
, phase
=phase
, period
=period
)
133 self
._clocked
.add(domain
)
136 """Reset the simulation.
138 Assign the reset value to every signal in the simulation, and restart every user process.
142 # TODO(nmigen-0.4): replace with _real_step
143 @deprecated("instead of `sim.step()`, use `sim.advance()`")
145 return self
.advance()
148 """Advance the simulation.
150 Run every process and commit changes until a fixed point is reached, then advance time
151 to the closest deadline (if any). If there is an unstable combinatorial loop,
152 this function will never return.
154 Returns ``True`` if there are any active processes, ``False`` otherwise.
156 return self
._engine
.advance()
159 """Run the simulation while any processes are active.
161 Processes added with :meth:`add_process` and :meth:`add_sync_process` are initially active,
162 and may change their status using the ``yield Passive()`` and ``yield Active()`` commands.
163 Processes compiled from HDL and added with :meth:`add_clock` are always passive.
165 while self
.advance():
168 def run_until(self
, deadline
, *, run_passive
=False):
169 """Run the simulation until it advances to ``deadline``.
171 If ``run_passive`` is ``False``, the simulation also stops when there are no active
172 processes, similar to :meth:`run`. Otherwise, the simulation will stop only after it
173 advances to or past ``deadline``.
175 If the simulation stops advancing, this function will never return.
177 assert self
._engine
.now
<= deadline
178 while (self
.advance() or run_passive
) and self
._engine
.now
< deadline
:
181 def write_vcd(self
, vcd_file
, gtkw_file
=None, *, traces
=()):
182 """Write waveforms to a Value Change Dump file, optionally populating a GTKWave save file.
184 This method returns a context manager. It can be used as: ::
186 sim = Simulator(frag)
188 with sim.write_vcd("dump.vcd", "dump.gtkw"):
193 vcd_file : str or file-like object
194 Verilog Value Change Dump file or filename.
195 gtkw_file : str or file-like object
196 GTKWave save file or filename.
197 traces : iterable of Signal
198 Signals to display traces for.
200 if self
._engine
.now
!= 0.0:
201 for file in (vcd_file
, gtkw_file
):
202 if hasattr(file, "close"):
204 raise ValueError("Cannot start writing waveforms after advancing simulation time")
206 return self
._engine
.write_vcd(vcd_file
=vcd_file
, gtkw_file
=gtkw_file
, traces
=traces
)