1 from contextlib
import contextmanager
3 from vcd
import VCDWriter
4 from vcd
.gtkw
import GTKWSave
7 from ..hdl
.ast
import SignalDict
9 from ._pyrtl
import _FragmentCompiler
10 from ._pycoro
import PyCoroProcess
11 from ._pyclock
import PyClockProcess
14 __all__
= ["PySimEngine"]
19 self
.names
= SignalDict()
21 def __call__(self
, fragment
, *, hierarchy
=("top",)):
22 def add_signal_name(signal
):
23 hierarchical_signal_name
= (*hierarchy
, signal
.name
)
24 if signal
not in self
.names
:
25 self
.names
[signal
] = {hierarchical_signal_name}
27 self
.names
[signal
].add(hierarchical_signal_name
)
29 for domain_name
, domain_signals
in fragment
.drivers
.items():
30 if domain_name
is not None:
31 domain
= fragment
.domains
[domain_name
]
32 add_signal_name(domain
.clk
)
33 if domain
.rst
is not None:
34 add_signal_name(domain
.rst
)
36 for statement
in fragment
.statements
:
37 for signal
in statement
._lhs
_signals
() | statement
._rhs
_signals
():
38 if not isinstance(signal
, (ClockSignal
, ResetSignal
)):
39 add_signal_name(signal
)
41 for subfragment_index
, (subfragment
, subfragment_name
) in enumerate(fragment
.subfragments
):
42 if subfragment_name
is None:
43 subfragment_name
= "U${}".format(subfragment_index
)
44 self(subfragment
, hierarchy
=(*hierarchy
, subfragment_name
))
51 def timestamp_to_vcd(timestamp
):
52 return timestamp
* (10 ** 10) # 1/(100 ps)
55 def decode_to_vcd(signal
, value
):
56 return signal
.decoder(value
).expandtabs().replace(" ", "_")
58 def __init__(self
, fragment
, *, vcd_file
, gtkw_file
=None, traces
=()):
59 if isinstance(vcd_file
, str):
60 vcd_file
= open(vcd_file
, "wt")
61 if isinstance(gtkw_file
, str):
62 gtkw_file
= open(gtkw_file
, "wt")
64 self
.vcd_vars
= SignalDict()
65 self
.vcd_file
= vcd_file
66 self
.vcd_writer
= vcd_file
and VCDWriter(self
.vcd_file
,
67 timescale
="100 ps", comment
="Generated by nMigen")
69 self
.gtkw_names
= SignalDict()
70 self
.gtkw_file
= gtkw_file
71 self
.gtkw_save
= gtkw_file
and GTKWSave(self
.gtkw_file
)
75 signal_names
= _NameExtractor()(fragment
)
77 trace_names
= SignalDict()
79 if trace
not in signal_names
:
80 trace_names
[trace
] = {("top", trace
.name
)}
81 self
.traces
.append(trace
)
83 if self
.vcd_writer
is None:
86 for signal
, names
in itertools
.chain(signal_names
.items(), trace_names
.items()):
90 var_init
= self
.decode_to_vcd(signal
, signal
.reset
)
93 var_size
= signal
.width
94 var_init
= signal
.reset
96 for (*var_scope
, var_name
) in names
:
101 var_name_suffix
= var_name
103 var_name_suffix
= "{}${}".format(var_name
, suffix
)
104 if signal
not in self
.vcd_vars
:
105 vcd_var
= self
.vcd_writer
.register_var(
106 scope
=var_scope
, name
=var_name_suffix
,
107 var_type
=var_type
, size
=var_size
, init
=var_init
)
108 self
.vcd_vars
[signal
] = vcd_var
110 self
.vcd_writer
.register_alias(
111 scope
=var_scope
, name
=var_name_suffix
,
112 var
=self
.vcd_vars
[signal
])
115 suffix
= (suffix
or 0) + 1
117 if signal
not in self
.gtkw_names
:
118 self
.gtkw_names
[signal
] = (*var_scope
, var_name_suffix
)
120 def update(self
, timestamp
, signal
, value
):
121 vcd_var
= self
.vcd_vars
.get(signal
)
125 vcd_timestamp
= self
.timestamp_to_vcd(timestamp
)
127 var_value
= self
.decode_to_vcd(signal
, value
)
130 self
.vcd_writer
.change(vcd_var
, vcd_timestamp
, var_value
)
132 def close(self
, timestamp
):
133 if self
.vcd_writer
is not None:
134 self
.vcd_writer
.close(self
.timestamp_to_vcd(timestamp
))
136 if self
.gtkw_save
is not None:
137 self
.gtkw_save
.dumpfile(self
.vcd_file
.name
)
138 self
.gtkw_save
.dumpfile_size(self
.vcd_file
.tell())
140 self
.gtkw_save
.treeopen("top")
141 for signal
in self
.traces
:
142 if len(signal
) > 1 and not signal
.decoder
:
143 suffix
= "[{}:0]".format(len(signal
) - 1)
146 self
.gtkw_save
.trace(".".join(self
.gtkw_names
[signal
]) + suffix
)
148 if self
.vcd_file
is not None:
149 self
.vcd_file
.close()
150 if self
.gtkw_file
is not None:
151 self
.gtkw_file
.close()
157 self
.deadlines
= dict()
161 self
.deadlines
.clear()
163 def at(self
, run_at
, process
):
164 assert process
not in self
.deadlines
165 self
.deadlines
[process
] = run_at
167 def delay(self
, delay_by
, process
):
171 run_at
= self
.now
+ delay_by
172 self
.at(run_at
, process
)
175 nearest_processes
= set()
176 nearest_deadline
= None
177 for process
, deadline
in self
.deadlines
.items():
179 if nearest_deadline
is not None:
180 nearest_processes
.clear()
181 nearest_processes
.add(process
)
182 nearest_deadline
= self
.now
184 elif nearest_deadline
is None or deadline
<= nearest_deadline
:
185 assert deadline
>= self
.now
186 if nearest_deadline
is not None and deadline
< nearest_deadline
:
187 nearest_processes
.clear()
188 nearest_processes
.add(process
)
189 nearest_deadline
= deadline
191 if not nearest_processes
:
194 for process
in nearest_processes
:
195 process
.runnable
= True
196 del self
.deadlines
[process
]
197 self
.now
= nearest_deadline
202 class _PySignalState(BaseSignalState
):
203 __slots__
= ("signal", "curr", "next", "waiters", "pending")
205 def __init__(self
, signal
, pending
):
207 self
.pending
= pending
208 self
.waiters
= dict()
209 self
.curr
= self
.next
= signal
.reset
211 def set(self
, value
):
212 if self
.next
== value
:
215 self
.pending
.add(self
)
218 if self
.curr
== self
.next
:
220 self
.curr
= self
.next
223 for process
, trigger
in self
.waiters
.items():
224 if trigger
is None or trigger
== self
.curr
:
225 process
.runnable
= awoken_any
= True
229 class _PySimulation(BaseSimulation
):
231 self
.timeline
= _Timeline()
232 self
.signals
= SignalDict()
237 self
.timeline
.reset()
238 for signal
, index
in self
.signals
.items():
239 self
.slots
[index
].curr
= self
.slots
[index
].next
= signal
.reset
242 def get_signal(self
, signal
):
244 return self
.signals
[signal
]
246 index
= len(self
.slots
)
247 self
.slots
.append(_PySignalState(signal
, self
.pending
))
248 self
.signals
[signal
] = index
251 def add_trigger(self
, process
, signal
, *, trigger
=None):
252 index
= self
.get_signal(signal
)
253 assert (process
not in self
.slots
[index
].waiters
or
254 self
.slots
[index
].waiters
[process
] == trigger
)
255 self
.slots
[index
].waiters
[process
] = trigger
257 def remove_trigger(self
, process
, signal
):
258 index
= self
.get_signal(signal
)
259 assert process
in self
.slots
[index
].waiters
260 del self
.slots
[index
].waiters
[process
]
262 def wait_interval(self
, process
, interval
):
263 self
.timeline
.delay(interval
, process
)
267 for signal_state
in self
.pending
:
268 if signal_state
.commit():
274 class PySimEngine(BaseEngine
):
275 def __init__(self
, fragment
):
276 self
._state
= _PySimulation()
277 self
._timeline
= self
._state
.timeline
279 self
._fragment
= fragment
280 self
._processes
= _FragmentCompiler(self
._state
)(self
._fragment
)
281 self
._vcd
_writers
= []
283 def add_coroutine_process(self
, process
, *, default_cmd
):
284 self
._processes
.add(PyCoroProcess(self
._state
, self
._fragment
.domains
, process
,
285 default_cmd
=default_cmd
))
287 def add_clock_process(self
, clock
, *, phase
, period
):
288 self
._processes
.add(PyClockProcess(self
._state
, clock
,
289 phase
=phase
, period
=period
))
293 for process
in self
._processes
:
297 # Performs the two phases of a delta cycle in a loop:
300 # 1. eval: run and suspend every non-waiting process once, queueing signal changes
301 for process
in self
._processes
:
303 process
.runnable
= False
306 for vcd_writer
in self
._vcd
_writers
:
307 for signal_state
in self
._state
.pending
:
308 vcd_writer
.update(self
._timeline
.now
,
309 signal_state
.signal
, signal_state
.next
)
311 # 2. commit: apply every queued signal change, waking up any waiting processes
312 converged
= self
._state
.commit()
316 self
._timeline
.advance()
317 return any(not process
.passive
for process
in self
._processes
)
321 return self
._timeline
.now
324 def write_vcd(self
, *, vcd_file
, gtkw_file
, traces
):
325 vcd_writer
= _VCDWriter(self
._fragment
,
326 vcd_file
=vcd_file
, gtkw_file
=gtkw_file
, traces
=traces
)
328 self
._vcd
_writers
.append(vcd_writer
)
331 vcd_writer
.close(self
._timeline
.now
)
332 self
._vcd
_writers
.remove(vcd_writer
)