sim: split into base, core, and engines.
[nmigen.git] / nmigen / sim / _pycoro.py
1 import inspect
2
3 from ..hdl import *
4 from ..hdl.ast import Statement, SignalSet
5 from .core import Tick, Settle, Delay, Passive, Active
6 from ._base import BaseProcess
7 from ._pyrtl import _ValueCompiler, _RHSValueCompiler, _StatementCompiler
8
9
10 __all__ = ["PyCoroProcess"]
11
12
13 class PyCoroProcess(BaseProcess):
14 def __init__(self, state, domains, constructor, *, default_cmd=None):
15 self.state = state
16 self.domains = domains
17 self.constructor = constructor
18 self.default_cmd = default_cmd
19
20 self.reset()
21
22 def reset(self):
23 self.runnable = True
24 self.passive = False
25
26 self.coroutine = self.constructor()
27 self.exec_locals = {
28 "slots": self.state.slots,
29 "result": None,
30 **_ValueCompiler.helpers
31 }
32 self.waits_on = SignalSet()
33
34 def src_loc(self):
35 coroutine = self.coroutine
36 if coroutine is None:
37 return None
38 while coroutine.gi_yieldfrom is not None and inspect.isgenerator(coroutine.gi_yieldfrom):
39 coroutine = coroutine.gi_yieldfrom
40 if inspect.isgenerator(coroutine):
41 frame = coroutine.gi_frame
42 if inspect.iscoroutine(coroutine):
43 frame = coroutine.cr_frame
44 return "{}:{}".format(inspect.getfile(frame), inspect.getlineno(frame))
45
46 def add_trigger(self, signal, trigger=None):
47 self.state.add_trigger(self, signal, trigger=trigger)
48 self.waits_on.add(signal)
49
50 def clear_triggers(self):
51 for signal in self.waits_on:
52 self.state.remove_trigger(self, signal)
53 self.waits_on.clear()
54
55 def run(self):
56 if self.coroutine is None:
57 return
58
59 self.clear_triggers()
60
61 response = None
62 while True:
63 try:
64 command = self.coroutine.send(response)
65 if command is None:
66 command = self.default_cmd
67 response = None
68
69 if isinstance(command, Value):
70 exec(_RHSValueCompiler.compile(self.state, command, mode="curr"),
71 self.exec_locals)
72 response = Const.normalize(self.exec_locals["result"], command.shape())
73
74 elif isinstance(command, Statement):
75 exec(_StatementCompiler.compile(self.state, command),
76 self.exec_locals)
77
78 elif type(command) is Tick:
79 domain = command.domain
80 if isinstance(domain, ClockDomain):
81 pass
82 elif domain in self.domains:
83 domain = self.domains[domain]
84 else:
85 raise NameError("Received command {!r} that refers to a nonexistent "
86 "domain {!r} from process {!r}"
87 .format(command, command.domain, self.src_loc()))
88 self.add_trigger(domain.clk, trigger=1 if domain.clk_edge == "pos" else 0)
89 if domain.rst is not None and domain.async_reset:
90 self.add_trigger(domain.rst, trigger=1)
91 return
92
93 elif type(command) is Settle:
94 self.state.wait_interval(self, None)
95 return
96
97 elif type(command) is Delay:
98 self.state.wait_interval(self, command.interval)
99 return
100
101 elif type(command) is Passive:
102 self.passive = True
103
104 elif type(command) is Active:
105 self.passive = False
106
107 elif command is None: # only possible if self.default_cmd is None
108 raise TypeError("Received default command from process {!r} that was added "
109 "with add_process(); did you mean to add this process with "
110 "add_sync_process() instead?"
111 .format(self.src_loc()))
112
113 else:
114 raise TypeError("Received unsupported command {!r} from process {!r}"
115 .format(command, self.src_loc()))
116
117 except StopIteration:
118 self.passive = True
119 self.coroutine = None
120 return
121
122 except Exception as exn:
123 self.coroutine.throw(exn)