1 from contextlib
import contextmanager
3 from vcd
import VCDWriter
4 from vcd
.gtkw
import GTKWSave
6 from nmigen
.hdl
import ClockSignal
, ResetSignal
7 from nmigen
.hdl
.ast
import SignalDict
8 from nmigen
.sim
._base
import BaseSignalState
, BaseSimulation
, BaseEngine
9 from openpower
.decoder
.test
._pyrtl
import _FragmentCompiler
10 from nmigen
.sim
._pycoro
import PyCoroProcess
11 from nmigen
.sim
._pyclock
import PyClockProcess
18 from os
.path
import dirname
, join
19 from collections
import namedtuple
22 __all__
= ["PySimEngine"]
27 self
.names
= SignalDict()
29 def __call__(self
, fragment
, *, hierarchy
=("top",)):
30 def add_signal_name(signal
):
31 hierarchical_signal_name
= (*hierarchy
, signal
.name
)
32 if signal
not in self
.names
:
33 self
.names
[signal
] = {hierarchical_signal_name}
35 self
.names
[signal
].add(hierarchical_signal_name
)
37 for domain_name
, domain_signals
in fragment
.drivers
.items():
38 if domain_name
is not None:
39 domain
= fragment
.domains
[domain_name
]
40 add_signal_name(domain
.clk
)
41 if domain
.rst
is not None:
42 add_signal_name(domain
.rst
)
44 for statement
in fragment
.statements
:
45 for signal
in statement
._lhs
_signals
() | statement
._rhs
_signals
():
46 if not isinstance(signal
, (ClockSignal
, ResetSignal
)):
47 add_signal_name(signal
)
49 for subfragment_index
, (subfragment
, subfragment_name
) in \
50 enumerate(fragment
.subfragments
):
51 if subfragment_name
is None:
52 subfragment_name
= "U${}".format(subfragment_index
)
53 self(subfragment
, hierarchy
=(*hierarchy
, subfragment_name
))
60 def timestamp_to_vcd(timestamp
):
61 return timestamp
* (10 ** 10) # 1/(100 ps)
64 def decode_to_vcd(signal
, value
):
65 return signal
.decoder(value
).expandtabs().replace(" ", "_")
67 def __init__(self
, fragment
, *, vcd_file
, gtkw_file
=None, traces
=()):
68 if isinstance(vcd_file
, str):
69 vcd_file
= open(vcd_file
, "wt")
70 if isinstance(gtkw_file
, str):
71 gtkw_file
= open(gtkw_file
, "wt")
73 self
.vcd_vars
= SignalDict()
74 self
.vcd_file
= vcd_file
75 self
.vcd_writer
= vcd_file
and VCDWriter(self
.vcd_file
,
76 timescale
="100 ps", comment
="Generated by nMigen")
78 self
.gtkw_names
= SignalDict()
79 self
.gtkw_file
= gtkw_file
80 self
.gtkw_save
= gtkw_file
and GTKWSave(self
.gtkw_file
)
84 signal_names
= _NameExtractor()(fragment
)
86 trace_names
= SignalDict()
88 if trace
not in signal_names
:
89 trace_names
[trace
] = {("top", trace
.name
)}
90 self
.traces
.append(trace
)
92 if self
.vcd_writer
is None:
95 for signal
, names
in (itertools
.chain(signal_names
.items(),
96 trace_names
.items())):
100 var_init
= self
.decode_to_vcd(signal
, signal
.reset
)
103 var_size
= signal
.width
104 var_init
= signal
.reset
106 for (*var_scope
, var_name
) in names
:
111 var_name_suffix
= var_name
113 var_name_suffix
= "{}${}".format(var_name
, suffix
)
114 if signal
not in self
.vcd_vars
:
115 vcd_var
= self
.vcd_writer
.register_var(
116 scope
=var_scope
, name
=var_name_suffix
,
117 var_type
=var_type
, size
=var_size
, init
=var_init
)
118 self
.vcd_vars
[signal
] = vcd_var
120 self
.vcd_writer
.register_alias(
121 scope
=var_scope
, name
=var_name_suffix
,
122 var
=self
.vcd_vars
[signal
])
125 suffix
= (suffix
or 0) + 1
127 if signal
not in self
.gtkw_names
:
128 self
.gtkw_names
[signal
] = (*var_scope
, var_name_suffix
)
130 def update(self
, timestamp
, signal
, value
):
131 vcd_var
= self
.vcd_vars
.get(signal
)
135 vcd_timestamp
= self
.timestamp_to_vcd(timestamp
)
137 var_value
= self
.decode_to_vcd(signal
, value
)
140 self
.vcd_writer
.change(vcd_var
, vcd_timestamp
, var_value
)
142 def close(self
, timestamp
):
143 if self
.vcd_writer
is not None:
144 self
.vcd_writer
.close(self
.timestamp_to_vcd(timestamp
))
146 if self
.gtkw_save
is not None:
147 self
.gtkw_save
.dumpfile(self
.vcd_file
.name
)
148 self
.gtkw_save
.dumpfile_size(self
.vcd_file
.tell())
150 self
.gtkw_save
.treeopen("top")
151 for signal
in self
.traces
:
152 if len(signal
) > 1 and not signal
.decoder
:
153 suffix
= "[{}:0]".format(len(signal
) - 1)
156 self
.gtkw_save
.trace(".".join(self
.gtkw_names
[signal
]) + suffix
)
158 if self
.vcd_file
is not None:
159 self
.vcd_file
.close()
160 if self
.gtkw_file
is not None:
161 self
.gtkw_file
.close()
167 self
.deadlines
= dict()
171 self
.deadlines
.clear()
173 def at(self
, run_at
, process
):
174 assert process
not in self
.deadlines
175 self
.deadlines
[process
] = run_at
177 def delay(self
, delay_by
, process
):
181 run_at
= self
.now
+ delay_by
182 self
.at(run_at
, process
)
185 nearest_processes
= set()
186 nearest_deadline
= None
187 for process
, deadline
in self
.deadlines
.items():
189 if nearest_deadline
is not None:
190 nearest_processes
.clear()
191 nearest_processes
.add(process
)
192 nearest_deadline
= self
.now
194 elif nearest_deadline
is None or deadline
<= nearest_deadline
:
195 assert deadline
>= self
.now
196 if nearest_deadline
is not None and deadline
< nearest_deadline
:
197 nearest_processes
.clear()
198 nearest_processes
.add(process
)
199 nearest_deadline
= deadline
201 if not nearest_processes
:
204 for process
in nearest_processes
:
205 process
.runnable
= True
206 del self
.deadlines
[process
]
207 self
.now
= nearest_deadline
212 class _PySignalState
:
213 __slots__
= ("signal", "waiters", "sim_state", "index")
215 def __init__(self
, signal
, index
, sim_state
):
217 self
.waiters
= dict()
219 self
.sim_state
= sim_state
# Ugly. need it to have a reference to crtl.
221 def set(self
, value
):
222 self
.sim_state
.crtl
.set(self
.index
, value
)
225 if self
.sim_state
.crtl
.capture(self
.index
) == 0:
228 # Waiters are not implemented in C yet.
230 for process
, trigger
in self
.waiters
.items():
231 if trigger
is None or trigger
== self
.curr
:
232 process
.runnable
= awoken_any
= True
238 return self
.sim_state
.crtl
.get_curr(self
.index
)
242 return self
.sim_state
.crtl
.get_next(self
.index
)
247 self
.timeline
= _Timeline()
248 self
.signals
= SignalDict()
251 self
.crtl
= None # Initialized later.
254 self
.timeline
.reset()
255 for signal
, index
in self
.signals
.items():
256 self
.slots
[index
].curr
= self
.slots
[index
].next
= signal
.reset
259 def get_signal(self
, signal
):
261 return self
.signals
[signal
]
263 index
= len(self
.slots
)
264 self
.slots
.append(_PySignalState(signal
, index
, self
))
265 self
.signals
[signal
] = index
268 def add_trigger(self
, process
, signal
, *, trigger
=None):
269 index
= self
.get_signal(signal
)
270 assert (process
not in self
.slots
[index
].waiters
or
271 self
.slots
[index
].waiters
[process
] == trigger
)
272 self
.slots
[index
].waiters
[process
] = trigger
274 def remove_trigger(self
, process
, signal
):
275 index
= self
.get_signal(signal
)
276 assert process
in self
.slots
[index
].waiters
277 del self
.slots
[index
].waiters
[process
]
279 def wait_interval(self
, process
, interval
):
280 self
.timeline
.delay(interval
, process
)
282 def commit(self
, changed
=None):
285 for pending_index
in range(self
.crtl
.pending_count
):
286 index
= self
.crtl
.pending
[pending_index
]
287 signal_state
= self
.slots
[index
]
289 if signal_state
.commit():
292 if changed
is not None:
293 changed
.add(signal_state
)
295 self
.crtl
.clear_pending()
299 class PySimEngine(BaseEngine
):
302 def __init__(self
, fragment
):
303 self
._state
= _PySimulation()
304 self
._timeline
= self
._state
.timeline
306 self
._fragment
= fragment
308 # blow away and recreate crtl subdirectory. (hope like hell
309 # nobody is using this in their current working directory)
310 if PySimEngine
._crtl
_counter
== 1:
311 shutil
.rmtree("crtl", True)
314 except FileExistsError
:
317 # "Processes" are the compiled modules. Each module ends up
318 # with its own run() function
319 self
._processes
= _FragmentCompiler(self
._state
)(self
._fragment
)
321 # get absolute path of this directory as the base
322 filedir
= os
.path
.dirname(os
.path
.abspath(__file__
))
324 # read header file template
325 chdr
= os
.path
.join(filedir
, "crtl_template.h")
326 with
open(chdr
) as cdef_file
:
327 template
= cdef_file
.read()
329 # fill in the template
330 cdef
= template
% (len(self
._state
.slots
), len(self
._state
.slots
))
331 for process
in self
._processes
:
332 cdef
+= f
"void run_{process.name}(void);\n"
334 # write out the header file
335 with
open("crtl/common.h", "w") as cdef_file
:
336 cdef_file
.write(cdef
)
338 # same with c template: read template first
339 srcf
= os
.path
.join(filedir
, "crtl_template.c")
340 with
open(srcf
) as src_file
:
341 template
= src_file
.read()
344 src
= template
% (len(self
._state
.slots
), len(self
._state
.slots
))
347 with
open("crtl/common.c", "w") as src_file
:
350 # build module named crtlNNN in crtl subdirectory
351 modulename
= "crtl.crtl%d" % PySimEngine
._crtl
_counter
352 sources
= ["crtl/common.c"]
353 sources
+= [f
"crtl/{process.name}.c" for process
in self
._processes
]
355 ffibuilder
.cdef(cdef
)
356 ffibuilder
.set_source(modulename
, cdef
, sources
=sources
)
357 ffibuilder
.compile(verbose
=True)
359 # append search path of crtl directory before attempting import
360 sys
.path
.append(os
.path
.join(os
.getcwd()))
361 self
._state
.crtl
= importlib
.import_module(modulename
).lib
363 # Use a counter to generate unique names for modules, because Python
364 # won't reload C extension modules.
365 PySimEngine
._crtl
_counter
+= 1
367 # for each process (fragment/module) get its run() function
368 for process
in self
._processes
:
369 process
.crtl
= self
._state
.crtl
370 process
.run
= getattr(process
.crtl
, f
"run_{process.name}")
372 self
._vcd
_writers
= []
374 def add_coroutine_process(self
, process
, *, default_cmd
):
375 self
._processes
.add(PyCoroProcess(self
._state
, self
._fragment
.domains
,
377 default_cmd
=default_cmd
))
379 def add_clock_process(self
, clock
, *, phase
, period
):
380 self
._processes
.add(PyClockProcess(self
._state
, clock
,
381 phase
=phase
, period
=period
))
385 for process
in self
._processes
:
389 changed
= set() if self
._vcd
_writers
else None
391 # Performs the two phases of a delta cycle in a loop:
394 # 1. eval: run and suspend every non-waiting process once,
395 # queueing signal changes
396 for process
in self
._processes
:
398 process
.runnable
= False
401 # 2. commit: apply every queued signal change,
402 # waking up any waiting processes
403 converged
= self
._state
.commit(changed
)
405 for vcd_writer
in self
._vcd
_writers
:
406 for signal_state
in changed
:
407 vcd_writer
.update(self
._timeline
.now
,
408 signal_state
.signal
, signal_state
.curr
)
412 self
._timeline
.advance()
413 return any(not process
.passive
for process
in self
._processes
)
417 return self
._timeline
.now
420 def write_vcd(self
, *, vcd_file
, gtkw_file
, traces
):
421 vcd_writer
= _VCDWriter(self
._fragment
,
422 vcd_file
=vcd_file
, gtkw_file
=gtkw_file
, traces
=traces
)
424 self
._vcd
_writers
.append(vcd_writer
)
427 vcd_writer
.close(self
._timeline
.now
)
428 self
._vcd
_writers
.remove(vcd_writer
)