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
20 from os
.path
import dirname
, join
21 from collections
import namedtuple
23 from openpower
.decoder
.test
.crtl_path
import get_crtl_path
25 __all__
= ["PySimEngine"]
30 self
.names
= SignalDict()
32 def __call__(self
, fragment
, *, hierarchy
=("top",)):
33 def add_signal_name(signal
):
34 hierarchical_signal_name
= (*hierarchy
, signal
.name
)
35 if signal
not in self
.names
:
36 self
.names
[signal
] = {hierarchical_signal_name}
38 self
.names
[signal
].add(hierarchical_signal_name
)
40 for domain_name
, domain_signals
in fragment
.drivers
.items():
41 if domain_name
is not None:
42 domain
= fragment
.domains
[domain_name
]
43 add_signal_name(domain
.clk
)
44 if domain
.rst
is not None:
45 add_signal_name(domain
.rst
)
47 for statement
in fragment
.statements
:
48 for signal
in statement
._lhs
_signals
() | statement
._rhs
_signals
():
49 if not isinstance(signal
, (ClockSignal
, ResetSignal
)):
50 add_signal_name(signal
)
52 for subfragment_index
, (subfragment
, subfragment_name
) in \
53 enumerate(fragment
.subfragments
):
54 if subfragment_name
is None:
55 subfragment_name
= "U${}".format(subfragment_index
)
56 self(subfragment
, hierarchy
=(*hierarchy
, subfragment_name
))
63 def timestamp_to_vcd(timestamp
):
64 return timestamp
* (10 ** 10) # 1/(100 ps)
67 def decode_to_vcd(signal
, value
):
68 return signal
.decoder(value
).expandtabs().replace(" ", "_")
70 def __init__(self
, fragment
, *, vcd_file
, gtkw_file
=None, traces
=()):
71 if isinstance(vcd_file
, str):
72 vcd_file
= open(vcd_file
, "wt")
73 if isinstance(gtkw_file
, str):
74 gtkw_file
= open(gtkw_file
, "wt")
76 self
.vcd_vars
= SignalDict()
77 self
.vcd_file
= vcd_file
78 self
.vcd_writer
= vcd_file
and VCDWriter(self
.vcd_file
,
79 timescale
="100 ps", comment
="Generated by nMigen")
81 self
.gtkw_names
= SignalDict()
82 self
.gtkw_file
= gtkw_file
83 self
.gtkw_save
= gtkw_file
and GTKWSave(self
.gtkw_file
)
87 signal_names
= _NameExtractor()(fragment
)
89 trace_names
= SignalDict()
91 if trace
not in signal_names
:
92 trace_names
[trace
] = {("top", trace
.name
)}
93 self
.traces
.append(trace
)
95 if self
.vcd_writer
is None:
98 for signal
, names
in (itertools
.chain(signal_names
.items(),
99 trace_names
.items())):
103 var_init
= self
.decode_to_vcd(signal
, signal
.reset
)
106 var_size
= signal
.width
107 var_init
= signal
.reset
109 for (*var_scope
, var_name
) in names
:
114 var_name_suffix
= var_name
116 var_name_suffix
= "{}${}".format(var_name
, suffix
)
117 if signal
not in self
.vcd_vars
:
118 vcd_var
= self
.vcd_writer
.register_var(
119 scope
=var_scope
, name
=var_name_suffix
,
120 var_type
=var_type
, size
=var_size
, init
=var_init
)
121 self
.vcd_vars
[signal
] = vcd_var
123 self
.vcd_writer
.register_alias(
124 scope
=var_scope
, name
=var_name_suffix
,
125 var
=self
.vcd_vars
[signal
])
128 suffix
= (suffix
or 0) + 1
130 if signal
not in self
.gtkw_names
:
131 self
.gtkw_names
[signal
] = (*var_scope
, var_name_suffix
)
133 def update(self
, timestamp
, signal
, value
):
134 vcd_var
= self
.vcd_vars
.get(signal
)
138 vcd_timestamp
= self
.timestamp_to_vcd(timestamp
)
140 var_value
= self
.decode_to_vcd(signal
, value
)
143 self
.vcd_writer
.change(vcd_var
, vcd_timestamp
, var_value
)
145 def close(self
, timestamp
):
146 if self
.vcd_writer
is not None:
147 self
.vcd_writer
.close(self
.timestamp_to_vcd(timestamp
))
149 if self
.gtkw_save
is not None:
150 self
.gtkw_save
.dumpfile(self
.vcd_file
.name
)
151 self
.gtkw_save
.dumpfile_size(self
.vcd_file
.tell())
153 self
.gtkw_save
.treeopen("top")
154 for signal
in self
.traces
:
155 if len(signal
) > 1 and not signal
.decoder
:
156 suffix
= "[{}:0]".format(len(signal
) - 1)
159 self
.gtkw_save
.trace(".".join(self
.gtkw_names
[signal
]) + suffix
)
161 if self
.vcd_file
is not None:
162 self
.vcd_file
.close()
163 if self
.gtkw_file
is not None:
164 self
.gtkw_file
.close()
170 self
.deadlines
= dict()
174 self
.deadlines
.clear()
176 def at(self
, run_at
, process
):
177 assert process
not in self
.deadlines
178 self
.deadlines
[process
] = run_at
180 def delay(self
, delay_by
, process
):
184 run_at
= self
.now
+ delay_by
185 self
.at(run_at
, process
)
188 nearest_processes
= set()
189 nearest_deadline
= None
190 for process
, deadline
in self
.deadlines
.items():
192 if nearest_deadline
is not None:
193 nearest_processes
.clear()
194 nearest_processes
.add(process
)
195 nearest_deadline
= self
.now
197 elif nearest_deadline
is None or deadline
<= nearest_deadline
:
198 assert deadline
>= self
.now
199 if nearest_deadline
is not None and deadline
< nearest_deadline
:
200 nearest_processes
.clear()
201 nearest_processes
.add(process
)
202 nearest_deadline
= deadline
204 if not nearest_processes
:
207 for process
in nearest_processes
:
208 process
.runnable
= True
209 del self
.deadlines
[process
]
210 self
.now
= nearest_deadline
215 class _PySignalState
:
216 __slots__
= ("signal", "waiters", "sim_state", "index")
218 def __init__(self
, signal
, index
, sim_state
):
220 self
.waiters
= dict()
222 self
.sim_state
= sim_state
# Ugly. need it to have a reference to crtl.
224 def set(self
, value
):
225 self
.sim_state
.crtl
.set(self
.index
, value
)
228 if self
.sim_state
.crtl
.capture(self
.index
) == 0:
231 # Waiters are not implemented in C yet.
233 for process
, trigger
in self
.waiters
.items():
234 if trigger
is None or trigger
== self
.curr
:
235 process
.runnable
= awoken_any
= True
241 return self
.sim_state
.crtl
.get_curr(self
.index
)
245 return self
.sim_state
.crtl
.get_next(self
.index
)
250 self
.timeline
= _Timeline()
251 self
.signals
= SignalDict()
254 self
.crtl
= None # Initialized later.
257 self
.timeline
.reset()
258 for signal
, index
in self
.signals
.items():
259 self
.slots
[index
].curr
= self
.slots
[index
].next
= signal
.reset
262 def get_signal(self
, signal
):
264 return self
.signals
[signal
]
266 index
= len(self
.slots
)
267 self
.slots
.append(_PySignalState(signal
, index
, self
))
268 self
.signals
[signal
] = index
271 def get_signal_macro(self
, signal
):
272 index
= self
.get_signal(signal
)
274 # Replace dots with double underscores, and change everything else that
275 # cannot be put in a C macro identifier to a single underscore.
276 sanitized_name
= re
.sub(r
"\W|^(?=\d)", "_", signal
.name
)
278 return f
"{sanitized_name}_{index}"
280 def add_trigger(self
, process
, signal
, *, trigger
=None):
281 index
= self
.get_signal(signal
)
282 assert (process
not in self
.slots
[index
].waiters
or
283 self
.slots
[index
].waiters
[process
] == trigger
)
284 self
.slots
[index
].waiters
[process
] = trigger
286 def remove_trigger(self
, process
, signal
):
287 index
= self
.get_signal(signal
)
288 assert process
in self
.slots
[index
].waiters
289 del self
.slots
[index
].waiters
[process
]
291 def wait_interval(self
, process
, interval
):
292 self
.timeline
.delay(interval
, process
)
294 def commit(self
, changed
=None):
297 for pending_index
in range(self
.crtl
.pending_count
):
298 index
= self
.crtl
.pending
[pending_index
]
299 signal_state
= self
.slots
[index
]
301 if signal_state
.commit():
304 if changed
is not None:
305 changed
.add(signal_state
)
307 self
.crtl
.clear_pending()
311 class PySimEngine(BaseEngine
):
314 def __init__(self
, fragment
):
315 self
._state
= _PySimulation()
316 self
._timeline
= self
._state
.timeline
318 self
._fragment
= fragment
320 # create crtl directory
321 crtl
= get_crtl_path()
323 # "Processes" are the compiled modules. Each module ends up
324 # with its own run() function
325 self
._processes
= _FragmentCompiler(self
._state
)(self
._fragment
, "TOP")
327 # get absolute path of this directory as the base
328 filedir
= os
.path
.dirname(os
.path
.abspath(__file__
))
330 # read header file template
331 chdr
= os
.path
.join(filedir
, "crtl_template.h")
332 with
open(chdr
) as cdef_file
:
333 template
= cdef_file
.read()
335 # fill in the template
336 cdef
= template
% (len(self
._state
.slots
), len(self
._state
.slots
))
338 # macros to make signals readable
340 for signal
in self
._state
.signals
:
341 macro
= self
._state
.get_signal_macro(signal
)
342 index
= self
._state
.get_signal(signal
)
343 cdef
+= f
"#define {macro} {index}\n"
345 # run() functions of processes
347 for process
in self
._processes
:
348 cdef
+= f
"void run_{process.name}(void);\n"
350 # write out the header file
351 with
open(os
.path
.join(crtl
, "common.h"), "w") as cdef_file
:
352 cdef_file
.write(cdef
)
354 # same with c template: read template first
355 srcf
= os
.path
.join(filedir
, "crtl_template.c")
356 with
open(srcf
) as src_file
:
357 template
= src_file
.read()
360 src
= template
% (len(self
._state
.slots
), len(self
._state
.slots
))
363 with
open(os
.path
.join(crtl
, "common.c"), "w") as src_file
:
366 # build module named crtlNNN in crtl subdirectory
367 modulename
= f
"{crtl}.crtl{PySimEngine._crtl_counter}"
368 sources
= [os
.path
.join(crtl
, "common.c")]
369 for process
in self
._processes
:
370 sources
.append(os
.path
.join(crtl
, f
"{process.name}.c"))
372 ffibuilder
.cdef(cdef
)
373 ffibuilder
.set_source(modulename
, cdef
, sources
=sources
)
374 ffibuilder
.compile(verbose
=True)
376 # append search path of crtl directory before attempting import
377 cwd
= os
.path
.join(os
.getcwd())
378 if cwd
not in sys
.path
:
380 self
._state
.crtl
= importlib
.import_module(modulename
).lib
382 # Use a counter to generate unique names for modules, because Python
383 # won't reload C extension modules.
384 PySimEngine
._crtl
_counter
+= 1
386 # for each process (fragment/module) get its run() function
387 for process
in self
._processes
:
388 process
.crtl
= self
._state
.crtl
389 process
.run
= getattr(process
.crtl
, f
"run_{process.name}")
391 self
._vcd
_writers
= []
393 def add_coroutine_process(self
, process
, *, default_cmd
):
394 self
._processes
.add(PyCoroProcess(self
._state
, self
._fragment
.domains
,
396 default_cmd
=default_cmd
))
398 def add_clock_process(self
, clock
, *, phase
, period
):
399 self
._processes
.add(PyClockProcess(self
._state
, clock
,
400 phase
=phase
, period
=period
))
404 for process
in self
._processes
:
408 changed
= set() if self
._vcd
_writers
else None
410 # Performs the two phases of a delta cycle in a loop:
413 # 1. eval: run and suspend every non-waiting process once,
414 # queueing signal changes
415 for process
in self
._processes
:
417 process
.runnable
= False
420 # 2. commit: apply every queued signal change,
421 # waking up any waiting processes
422 converged
= self
._state
.commit(changed
)
424 for vcd_writer
in self
._vcd
_writers
:
425 for signal_state
in changed
:
426 vcd_writer
.update(self
._timeline
.now
,
427 signal_state
.signal
, signal_state
.curr
)
431 self
._timeline
.advance()
432 return any(not process
.passive
for process
in self
._processes
)
436 return self
._timeline
.now
439 def write_vcd(self
, *, vcd_file
, gtkw_file
, traces
):
440 vcd_writer
= _VCDWriter(self
._fragment
,
441 vcd_file
=vcd_file
, gtkw_file
=gtkw_file
, traces
=traces
)
443 self
._vcd
_writers
.append(vcd_writer
)
446 vcd_writer
.close(self
._timeline
.now
)
447 self
._vcd
_writers
.remove(vcd_writer
)