
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.
124 lines
4.4 KiB
Python
124 lines
4.4 KiB
Python
import inspect
|
|
|
|
from ..hdl import *
|
|
from ..hdl.ast import Statement, SignalSet
|
|
from .core import Tick, Settle, Delay, Passive, Active
|
|
from ._base import BaseProcess
|
|
from ._pyrtl import _ValueCompiler, _RHSValueCompiler, _StatementCompiler
|
|
|
|
|
|
__all__ = ["PyCoroProcess"]
|
|
|
|
|
|
class PyCoroProcess(BaseProcess):
|
|
def __init__(self, state, domains, constructor, *, default_cmd=None):
|
|
self.state = state
|
|
self.domains = domains
|
|
self.constructor = constructor
|
|
self.default_cmd = default_cmd
|
|
|
|
self.reset()
|
|
|
|
def reset(self):
|
|
self.runnable = True
|
|
self.passive = False
|
|
|
|
self.coroutine = self.constructor()
|
|
self.exec_locals = {
|
|
"slots": self.state.slots,
|
|
"result": None,
|
|
**_ValueCompiler.helpers
|
|
}
|
|
self.waits_on = SignalSet()
|
|
|
|
def src_loc(self):
|
|
coroutine = self.coroutine
|
|
if coroutine is None:
|
|
return None
|
|
while coroutine.gi_yieldfrom is not None and inspect.isgenerator(coroutine.gi_yieldfrom):
|
|
coroutine = coroutine.gi_yieldfrom
|
|
if inspect.isgenerator(coroutine):
|
|
frame = coroutine.gi_frame
|
|
if inspect.iscoroutine(coroutine):
|
|
frame = coroutine.cr_frame
|
|
return "{}:{}".format(inspect.getfile(frame), inspect.getlineno(frame))
|
|
|
|
def add_trigger(self, signal, trigger=None):
|
|
self.state.add_trigger(self, signal, trigger=trigger)
|
|
self.waits_on.add(signal)
|
|
|
|
def clear_triggers(self):
|
|
for signal in self.waits_on:
|
|
self.state.remove_trigger(self, signal)
|
|
self.waits_on.clear()
|
|
|
|
def run(self):
|
|
if self.coroutine is None:
|
|
return
|
|
|
|
self.clear_triggers()
|
|
|
|
response = None
|
|
while True:
|
|
try:
|
|
command = self.coroutine.send(response)
|
|
if command is None:
|
|
command = self.default_cmd
|
|
response = None
|
|
|
|
if isinstance(command, Value):
|
|
exec(_RHSValueCompiler.compile(self.state, command, mode="curr"),
|
|
self.exec_locals)
|
|
response = Const.normalize(self.exec_locals["result"], command.shape())
|
|
|
|
elif isinstance(command, Statement):
|
|
exec(_StatementCompiler.compile(self.state, command),
|
|
self.exec_locals)
|
|
|
|
elif type(command) is Tick:
|
|
domain = command.domain
|
|
if isinstance(domain, ClockDomain):
|
|
pass
|
|
elif domain in self.domains:
|
|
domain = self.domains[domain]
|
|
else:
|
|
raise NameError("Received command {!r} that refers to a nonexistent "
|
|
"domain {!r} from process {!r}"
|
|
.format(command, command.domain, self.src_loc()))
|
|
self.add_trigger(domain.clk, trigger=1 if domain.clk_edge == "pos" else 0)
|
|
if domain.rst is not None and domain.async_reset:
|
|
self.add_trigger(domain.rst, trigger=1)
|
|
return
|
|
|
|
elif type(command) is Settle:
|
|
self.state.wait_interval(self, None)
|
|
return
|
|
|
|
elif type(command) is Delay:
|
|
self.state.wait_interval(self, command.interval)
|
|
return
|
|
|
|
elif type(command) is Passive:
|
|
self.passive = True
|
|
|
|
elif type(command) is Active:
|
|
self.passive = False
|
|
|
|
elif command is None: # only possible if self.default_cmd is None
|
|
raise TypeError("Received default command from process {!r} that was added "
|
|
"with add_process(); did you mean to add this process with "
|
|
"add_sync_process() instead?"
|
|
.format(self.src_loc()))
|
|
|
|
else:
|
|
raise TypeError("Received unsupported command {!r} from process {!r}"
|
|
.format(command, self.src_loc()))
|
|
|
|
except StopIteration:
|
|
self.passive = True
|
|
self.coroutine = None
|
|
return
|
|
|
|
except Exception as exn:
|
|
self.coroutine.throw(exn)
|