back.pysim: support async reset.
[nmigen.git] / nmigen / back / pysim.py
1 import math
2 import inspect
3 import warnings
4 from contextlib import contextmanager
5 from bitarray import bitarray
6 from vcd import VCDWriter
7 from vcd.gtkw import GTKWSave
8
9 from ..tools import flatten
10 from ..hdl.ast import *
11 from ..hdl.ir import *
12 from ..hdl.xfrm import ValueVisitor, StatementVisitor
13
14
15 __all__ = ["Simulator", "Delay", "Tick", "Passive", "DeadlineError"]
16
17
18 class DeadlineError(Exception):
19 pass
20
21
22 class _State:
23 __slots__ = ("curr", "curr_dirty", "next", "next_dirty")
24
25 def __init__(self):
26 self.curr = []
27 self.next = []
28 self.curr_dirty = bitarray()
29 self.next_dirty = bitarray()
30
31 def add(self, value):
32 slot = len(self.curr)
33 self.curr.append(value)
34 self.next.append(value)
35 self.curr_dirty.append(True)
36 self.next_dirty.append(False)
37 return slot
38
39 def set(self, slot, value):
40 if self.next[slot] != value:
41 self.next_dirty[slot] = True
42 self.next[slot] = value
43
44 def commit(self, slot):
45 old_value = self.curr[slot]
46 new_value = self.next[slot]
47 if old_value != new_value:
48 self.next_dirty[slot] = False
49 self.curr_dirty[slot] = True
50 self.curr[slot] = new_value
51 return old_value, new_value
52
53 def flush_curr_dirty(self):
54 while True:
55 try:
56 slot = self.curr_dirty.index(True)
57 except ValueError:
58 break
59 self.curr_dirty[slot] = False
60 yield slot
61
62 def iter_next_dirty(self):
63 start = 0
64 while True:
65 try:
66 slot = self.next_dirty.index(True, start)
67 start = slot + 1
68 except ValueError:
69 break
70 yield slot
71
72
73 normalize = Const.normalize
74
75
76 class _ValueCompiler(ValueVisitor):
77 def on_AnyConst(self, value):
78 raise NotImplementedError # :nocov:
79
80 def on_AnySeq(self, value):
81 raise NotImplementedError # :nocov:
82
83 def on_Sample(self, value):
84 raise NotImplementedError # :nocov:
85
86 def on_Record(self, value):
87 return self(Cat(value.fields.values()))
88
89
90 class _RHSValueCompiler(_ValueCompiler):
91 def __init__(self, signal_slots, sensitivity=None, mode="rhs"):
92 self.signal_slots = signal_slots
93 self.sensitivity = sensitivity
94 self.signal_mode = mode
95
96 def on_Const(self, value):
97 return lambda state: value.value
98
99 def on_Signal(self, value):
100 if self.sensitivity is not None:
101 self.sensitivity.add(value)
102 if value not in self.signal_slots:
103 # A signal that is neither driven nor a port always remains at its reset state.
104 return lambda state: value.reset
105 value_slot = self.signal_slots[value]
106 if self.signal_mode == "rhs":
107 return lambda state: state.curr[value_slot]
108 elif self.signal_mode == "lhs":
109 return lambda state: state.next[value_slot]
110 else:
111 raise ValueError # :nocov:
112
113 def on_ClockSignal(self, value):
114 raise NotImplementedError # :nocov:
115
116 def on_ResetSignal(self, value):
117 raise NotImplementedError # :nocov:
118
119 def on_Operator(self, value):
120 shape = value.shape()
121 if len(value.operands) == 1:
122 arg, = map(self, value.operands)
123 if value.op == "~":
124 return lambda state: normalize(~arg(state), shape)
125 if value.op == "-":
126 return lambda state: normalize(-arg(state), shape)
127 if value.op == "b":
128 return lambda state: normalize(bool(arg(state)), shape)
129 elif len(value.operands) == 2:
130 lhs, rhs = map(self, value.operands)
131 if value.op == "+":
132 return lambda state: normalize(lhs(state) + rhs(state), shape)
133 if value.op == "-":
134 return lambda state: normalize(lhs(state) - rhs(state), shape)
135 if value.op == "*":
136 return lambda state: normalize(lhs(state) * rhs(state), shape)
137 if value.op == "&":
138 return lambda state: normalize(lhs(state) & rhs(state), shape)
139 if value.op == "|":
140 return lambda state: normalize(lhs(state) | rhs(state), shape)
141 if value.op == "^":
142 return lambda state: normalize(lhs(state) ^ rhs(state), shape)
143 if value.op == "<<":
144 def sshl(lhs, rhs):
145 return lhs << rhs if rhs >= 0 else lhs >> -rhs
146 return lambda state: normalize(sshl(lhs(state), rhs(state)), shape)
147 if value.op == ">>":
148 def sshr(lhs, rhs):
149 return lhs >> rhs if rhs >= 0 else lhs << -rhs
150 return lambda state: normalize(sshr(lhs(state), rhs(state)), shape)
151 if value.op == "==":
152 return lambda state: normalize(lhs(state) == rhs(state), shape)
153 if value.op == "!=":
154 return lambda state: normalize(lhs(state) != rhs(state), shape)
155 if value.op == "<":
156 return lambda state: normalize(lhs(state) < rhs(state), shape)
157 if value.op == "<=":
158 return lambda state: normalize(lhs(state) <= rhs(state), shape)
159 if value.op == ">":
160 return lambda state: normalize(lhs(state) > rhs(state), shape)
161 if value.op == ">=":
162 return lambda state: normalize(lhs(state) >= rhs(state), shape)
163 elif len(value.operands) == 3:
164 if value.op == "m":
165 sel, val1, val0 = map(self, value.operands)
166 return lambda state: val1(state) if sel(state) else val0(state)
167 raise NotImplementedError("Operator '{}' not implemented".format(value.op)) # :nocov:
168
169 def on_Slice(self, value):
170 shape = value.shape()
171 arg = self(value.value)
172 shift = value.start
173 mask = (1 << (value.end - value.start)) - 1
174 return lambda state: normalize((arg(state) >> shift) & mask, shape)
175
176 def on_Part(self, value):
177 shape = value.shape()
178 arg = self(value.value)
179 shift = self(value.offset)
180 mask = (1 << value.width) - 1
181 return lambda state: normalize((arg(state) >> shift(state)) & mask, shape)
182
183 def on_Cat(self, value):
184 shape = value.shape()
185 parts = []
186 offset = 0
187 for opnd in value.parts:
188 parts.append((offset, (1 << len(opnd)) - 1, self(opnd)))
189 offset += len(opnd)
190 def eval(state):
191 result = 0
192 for offset, mask, opnd in parts:
193 result |= (opnd(state) & mask) << offset
194 return normalize(result, shape)
195 return eval
196
197 def on_Repl(self, value):
198 shape = value.shape()
199 offset = len(value.value)
200 mask = (1 << len(value.value)) - 1
201 count = value.count
202 opnd = self(value.value)
203 def eval(state):
204 result = 0
205 for _ in range(count):
206 result <<= offset
207 result |= opnd(state)
208 return normalize(result, shape)
209 return eval
210
211 def on_ArrayProxy(self, value):
212 shape = value.shape()
213 elems = list(map(self, value.elems))
214 index = self(value.index)
215 def eval(state):
216 index_value = index(state)
217 if index_value >= len(elems):
218 index_value = len(elems) - 1
219 return normalize(elems[index_value](state), shape)
220 return eval
221
222
223 class _LHSValueCompiler(_ValueCompiler):
224 def __init__(self, signal_slots, rhs_compiler):
225 self.signal_slots = signal_slots
226 self.rhs_compiler = rhs_compiler
227
228 def on_Const(self, value):
229 raise TypeError # :nocov:
230
231 def on_Signal(self, value):
232 shape = value.shape()
233 value_slot = self.signal_slots[value]
234 def eval(state, rhs):
235 state.set(value_slot, normalize(rhs, shape))
236 return eval
237
238 def on_ClockSignal(self, value):
239 raise NotImplementedError # :nocov:
240
241 def on_ResetSignal(self, value):
242 raise NotImplementedError # :nocov:
243
244 def on_Operator(self, value):
245 raise TypeError # :nocov:
246
247 def on_Slice(self, value):
248 lhs_r = self.rhs_compiler(value.value)
249 lhs_l = self(value.value)
250 shift = value.start
251 mask = (1 << (value.end - value.start)) - 1
252 def eval(state, rhs):
253 lhs_value = lhs_r(state)
254 lhs_value &= ~(mask << shift)
255 lhs_value |= (rhs & mask) << shift
256 lhs_l(state, lhs_value)
257 return eval
258
259 def on_Part(self, value):
260 lhs_r = self.rhs_compiler(value.value)
261 lhs_l = self(value.value)
262 shift = self.rhs_compiler(value.offset)
263 mask = (1 << value.width) - 1
264 def eval(state, rhs):
265 lhs_value = lhs_r(state)
266 shift_value = shift(state)
267 lhs_value &= ~(mask << shift_value)
268 lhs_value |= (rhs & mask) << shift_value
269 lhs_l(state, lhs_value)
270 return eval
271
272 def on_Cat(self, value):
273 parts = []
274 offset = 0
275 for opnd in value.parts:
276 parts.append((offset, (1 << len(opnd)) - 1, self(opnd)))
277 offset += len(opnd)
278 def eval(state, rhs):
279 for offset, mask, opnd in parts:
280 opnd(state, (rhs >> offset) & mask)
281 return eval
282
283 def on_Repl(self, value):
284 raise TypeError # :nocov:
285
286 def on_ArrayProxy(self, value):
287 elems = list(map(self, value.elems))
288 index = self.rhs_compiler(value.index)
289 def eval(state, rhs):
290 index_value = index(state)
291 if index_value >= len(elems):
292 index_value = len(elems) - 1
293 elems[index_value](state, rhs)
294 return eval
295
296
297 class _StatementCompiler(StatementVisitor):
298 def __init__(self, signal_slots):
299 self.sensitivity = SignalSet()
300 self.rrhs_compiler = _RHSValueCompiler(signal_slots, self.sensitivity, mode="rhs")
301 self.lrhs_compiler = _RHSValueCompiler(signal_slots, self.sensitivity, mode="lhs")
302 self.lhs_compiler = _LHSValueCompiler(signal_slots, self.lrhs_compiler)
303
304 def on_Assign(self, stmt):
305 shape = stmt.lhs.shape()
306 lhs = self.lhs_compiler(stmt.lhs)
307 rhs = self.rrhs_compiler(stmt.rhs)
308 def run(state):
309 lhs(state, normalize(rhs(state), shape))
310 return run
311
312 def on_Assert(self, stmt):
313 raise NotImplementedError("Asserts not yet implemented for Simulator backend.") # :nocov:
314
315 def on_Assume(self, stmt):
316 pass # :nocov:
317
318 def on_Switch(self, stmt):
319 test = self.rrhs_compiler(stmt.test)
320 cases = []
321 for value, stmts in stmt.cases.items():
322 if "-" in value:
323 mask = "".join("0" if b == "-" else "1" for b in value)
324 value = "".join("0" if b == "-" else b for b in value)
325 else:
326 mask = "1" * len(value)
327 mask = int(mask, 2)
328 value = int(value, 2)
329 def make_test(mask, value):
330 return lambda test: test & mask == value
331 cases.append((make_test(mask, value), self.on_statements(stmts)))
332 def run(state):
333 test_value = test(state)
334 for check, body in cases:
335 if check(test_value):
336 body(state)
337 return
338 return run
339
340 def on_statements(self, stmts):
341 stmts = [self.on_statement(stmt) for stmt in stmts]
342 def run(state):
343 for stmt in stmts:
344 stmt(state)
345 return run
346
347
348 class Simulator:
349 def __init__(self, fragment, vcd_file=None, gtkw_file=None, traces=()):
350 self._fragment = Fragment.get(fragment, platform=None)
351
352 self._signal_slots = SignalDict() # Signal -> int/slot
353 self._slot_signals = list() # int/slot -> Signal
354
355 self._domains = dict() # str/domain -> ClockDomain
356 self._domain_triggers = list() # int/slot -> str/domain
357
358 self._signals = SignalSet() # {Signal}
359 self._comb_signals = bitarray() # {Signal}
360 self._sync_signals = bitarray() # {Signal}
361 self._user_signals = bitarray() # {Signal}
362 self._domain_signals = dict() # str/domain -> {Signal}
363
364 self._started = False
365 self._timestamp = 0.
366 self._delta = 0.
367 self._epsilon = 1e-10
368 self._fastest_clock = self._epsilon
369 self._state = _State()
370
371 self._processes = set() # {process}
372 self._process_loc = dict() # process -> str/loc
373 self._passive = set() # {process}
374 self._suspended = set() # {process}
375 self._wait_deadline = dict() # process -> float/timestamp
376 self._wait_tick = dict() # process -> str/domain
377
378 self._funclets = list() # int/slot -> set(lambda)
379
380 self._vcd_file = vcd_file
381 self._vcd_writer = None
382 self._vcd_signals = list() # int/slot -> set(vcd_signal)
383 self._vcd_names = list() # int/slot -> str/name
384 self._gtkw_file = gtkw_file
385 self._traces = traces
386
387 self._run_called = False
388
389 @staticmethod
390 def _check_process(process):
391 if inspect.isgeneratorfunction(process):
392 process = process()
393 if not inspect.isgenerator(process):
394 raise TypeError("Cannot add a process '{!r}' because it is not a generator or "
395 "a generator function"
396 .format(process))
397 return process
398
399 def _name_process(self, process):
400 if process in self._process_loc:
401 return self._process_loc[process]
402 else:
403 frame = process.gi_frame
404 return "{}:{}".format(inspect.getfile(frame), inspect.getlineno(frame))
405
406 def add_process(self, process):
407 process = self._check_process(process)
408 self._processes.add(process)
409
410 def add_sync_process(self, process, domain="sync"):
411 process = self._check_process(process)
412 def sync_process():
413 try:
414 cmd = None
415 while True:
416 if cmd is None:
417 cmd = Tick(domain)
418 result = yield cmd
419 self._process_loc[sync_process] = self._name_process(process)
420 cmd = process.send(result)
421 except StopIteration:
422 pass
423 sync_process = sync_process()
424 self.add_process(sync_process)
425
426 def add_clock(self, period, phase=None, domain="sync"):
427 if self._fastest_clock == self._epsilon or period < self._fastest_clock:
428 self._fastest_clock = period
429
430 half_period = period / 2
431 if phase is None:
432 phase = half_period
433 clk = self._domains[domain].clk
434 def clk_process():
435 yield Passive()
436 yield Delay(phase)
437 while True:
438 yield clk.eq(1)
439 yield Delay(half_period)
440 yield clk.eq(0)
441 yield Delay(half_period)
442 self.add_process(clk_process)
443
444 def __enter__(self):
445 if self._vcd_file:
446 self._vcd_writer = VCDWriter(self._vcd_file, timescale="100 ps",
447 comment="Generated by nMigen")
448
449 root_fragment = self._fragment.prepare()
450 self._domains = root_fragment.domains
451
452 hierarchy = {}
453 def add_fragment(fragment, scope=()):
454 hierarchy[fragment] = scope
455 for index, (subfragment, name) in enumerate(fragment.subfragments):
456 if name is None:
457 add_fragment(subfragment, (*scope, "U{}".format(index)))
458 else:
459 add_fragment(subfragment, (*scope, name))
460 add_fragment(root_fragment, scope=("top",))
461
462 def add_signal(signal):
463 if signal not in self._signals:
464 self._signals.add(signal)
465
466 signal_slot = self._state.add(normalize(signal.reset, signal.shape()))
467 self._signal_slots[signal] = signal_slot
468 self._slot_signals.append(signal)
469
470 self._comb_signals.append(False)
471 self._sync_signals.append(False)
472 self._user_signals.append(False)
473 for domain in self._domains:
474 if domain not in self._domain_signals:
475 self._domain_signals[domain] = bitarray()
476 self._domain_signals[domain].append(False)
477
478 self._funclets.append(set())
479
480 self._domain_triggers.append(None)
481 if self._vcd_writer:
482 self._vcd_signals.append(set())
483 self._vcd_names.append(None)
484
485 return self._signal_slots[signal]
486
487 def add_domain_signal(signal, domain):
488 signal_slot = add_signal(signal)
489 self._domain_triggers[signal_slot] = domain
490
491 for fragment, fragment_scope in hierarchy.items():
492 for signal in fragment.iter_signals():
493 add_signal(signal)
494
495 for domain, cd in fragment.domains.items():
496 add_domain_signal(cd.clk, domain)
497 if cd.rst is not None:
498 add_domain_signal(cd.rst, domain)
499
500 for fragment, fragment_scope in hierarchy.items():
501 for signal in fragment.iter_signals():
502 if not self._vcd_writer:
503 continue
504
505 signal_slot = self._signal_slots[signal]
506
507 for i, (subfragment, name) in enumerate(fragment.subfragments):
508 if signal in subfragment.ports:
509 var_name = "{}_{}".format(name or "U{}".format(i), signal.name)
510 break
511 else:
512 var_name = signal.name
513
514 if signal.decoder:
515 var_type = "string"
516 var_size = 1
517 var_init = signal.decoder(signal.reset).replace(" ", "_")
518 else:
519 var_type = "wire"
520 var_size = signal.nbits
521 var_init = signal.reset
522
523 suffix = None
524 while True:
525 try:
526 if suffix is None:
527 var_name_suffix = var_name
528 else:
529 var_name_suffix = "{}${}".format(var_name, suffix)
530 self._vcd_signals[signal_slot].add(self._vcd_writer.register_var(
531 scope=".".join(fragment_scope), name=var_name_suffix,
532 var_type=var_type, size=var_size, init=var_init))
533 if self._vcd_names[signal_slot] is None:
534 self._vcd_names[signal_slot] = \
535 ".".join(fragment_scope + (var_name_suffix,))
536 break
537 except KeyError:
538 suffix = (suffix or 0) + 1
539
540 for domain, signals in fragment.drivers.items():
541 signals_bits = bitarray(len(self._signals))
542 signals_bits.setall(False)
543 for signal in signals:
544 signals_bits[self._signal_slots[signal]] = True
545
546 if domain is None:
547 self._comb_signals |= signals_bits
548 else:
549 self._sync_signals |= signals_bits
550 self._domain_signals[domain] |= signals_bits
551
552 statements = []
553 for domain, signals in fragment.drivers.items():
554 reset_stmts = []
555 hold_stmts = []
556 for signal in signals:
557 reset_stmts.append(signal.eq(signal.reset))
558 hold_stmts .append(signal.eq(signal))
559
560 if domain is None:
561 statements += reset_stmts
562 else:
563 if self._domains[domain].async_reset:
564 statements.append(Switch(self._domains[domain].rst,
565 {0: hold_stmts, 1: reset_stmts}))
566 else:
567 statements += hold_stmts
568 statements += fragment.statements
569
570 compiler = _StatementCompiler(self._signal_slots)
571 funclet = compiler(statements)
572
573 def add_funclet(signal, funclet):
574 if signal in self._signal_slots:
575 self._funclets[self._signal_slots[signal]].add(funclet)
576
577 for signal in compiler.sensitivity:
578 add_funclet(signal, funclet)
579 for domain, cd in fragment.domains.items():
580 add_funclet(cd.clk, funclet)
581 if cd.rst is not None:
582 add_funclet(cd.rst, funclet)
583
584 self._user_signals = bitarray(len(self._signals))
585 self._user_signals.setall(True)
586 self._user_signals &= ~self._comb_signals
587 self._user_signals &= ~self._sync_signals
588
589 return self
590
591 def _update_dirty_signals(self):
592 """Perform the statement part of IR processes (aka RTLIL case)."""
593 # First, for all dirty signals, use sensitivity lists to determine the set of fragments
594 # that need their statements to be reevaluated because the signals changed at the previous
595 # delta cycle.
596 funclets = set()
597 for signal_slot in self._state.flush_curr_dirty():
598 funclets.update(self._funclets[signal_slot])
599
600 # Second, compute the values of all signals at the start of the next delta cycle, by
601 # running precompiled statements.
602 for funclet in funclets:
603 funclet(self._state)
604
605 def _commit_signal(self, signal_slot, domains):
606 """Perform the driver part of IR processes (aka RTLIL sync), for individual signals."""
607 # Take the computed value (at the start of this delta cycle) of a signal (that could have
608 # come from an IR process that ran earlier, or modified by a simulator process) and update
609 # the value for this delta cycle.
610 old, new = self._state.commit(signal_slot)
611 if old == new:
612 return
613
614 # If the signal is a clock that triggers synchronous logic, record that fact.
615 if new == 1 and self._domain_triggers[signal_slot] is not None:
616 domains.add(self._domain_triggers[signal_slot])
617
618 if self._vcd_writer:
619 # Finally, dump the new value to the VCD file.
620 for vcd_signal in self._vcd_signals[signal_slot]:
621 signal = self._slot_signals[signal_slot]
622 if signal.decoder:
623 var_value = signal.decoder(new).replace(" ", "_")
624 else:
625 var_value = new
626 vcd_timestamp = (self._timestamp + self._delta) / self._epsilon
627 self._vcd_writer.change(vcd_signal, vcd_timestamp, var_value)
628
629 def _commit_comb_signals(self, domains):
630 """Perform the comb part of IR processes (aka RTLIL always)."""
631 # Take the computed value (at the start of this delta cycle) of every comb signal and
632 # update the value for this delta cycle.
633 for signal_slot in self._state.iter_next_dirty():
634 if self._comb_signals[signal_slot]:
635 self._commit_signal(signal_slot, domains)
636
637 def _commit_sync_signals(self, domains):
638 """Perform the sync part of IR processes (aka RTLIL posedge)."""
639 # At entry, `domains` contains a list of every simultaneously triggered sync update.
640 while domains:
641 # Advance the timeline a bit (purely for observational purposes) and commit all of them
642 # at the same timestamp.
643 self._delta += self._epsilon
644 curr_domains, domains = domains, set()
645
646 while curr_domains:
647 domain = curr_domains.pop()
648
649 # Wake up any simulator processes that wait for a domain tick.
650 for process, wait_domain in list(self._wait_tick.items()):
651 if domain == wait_domain:
652 del self._wait_tick[process]
653 self._suspended.remove(process)
654
655 # Immediately run the process. It is important that this happens here,
656 # and not on the next step, when all the processes will run anyway,
657 # because Tick() simulates an edge triggered process. Like DFFs that latch
658 # a value from the previous clock cycle, simulator processes observe signal
659 # values from the previous clock cycle on a tick, too.
660 self._run_process(process)
661
662 # Take the computed value (at the start of this delta cycle) of every sync signal
663 # in this domain and update the value for this delta cycle. This can trigger more
664 # synchronous logic, so record that.
665 for signal_slot in self._state.iter_next_dirty():
666 if self._domain_signals[domain][signal_slot]:
667 self._commit_signal(signal_slot, domains)
668
669 # Unless handling synchronous logic above has triggered more synchronous logic (which
670 # can happen e.g. if a domain is clocked off a clock divisor in fabric), we're done.
671 # Otherwise, do one more round of updates.
672
673 def _run_process(self, process):
674 try:
675 cmd = process.send(None)
676 while True:
677 if type(cmd) is Delay:
678 if cmd.interval is None:
679 interval = self._epsilon
680 else:
681 interval = cmd.interval
682 self._wait_deadline[process] = self._timestamp + interval
683 self._suspended.add(process)
684 break
685
686 elif type(cmd) is Tick:
687 self._wait_tick[process] = cmd.domain
688 self._suspended.add(process)
689 break
690
691 elif type(cmd) is Passive:
692 self._passive.add(process)
693
694 elif type(cmd) is Assign:
695 lhs_signals = cmd.lhs._lhs_signals()
696 for signal in lhs_signals:
697 if not signal in self._signals:
698 raise ValueError("Process '{}' sent a request to set signal '{!r}', "
699 "which is not a part of simulation"
700 .format(self._name_process(process), signal))
701 signal_slot = self._signal_slots[signal]
702 if self._comb_signals[signal_slot]:
703 raise ValueError("Process '{}' sent a request to set signal '{!r}', "
704 "which is a part of combinatorial assignment in "
705 "simulation"
706 .format(self._name_process(process), signal))
707
708 if type(cmd.lhs) is Signal and type(cmd.rhs) is Const:
709 # Fast path.
710 self._state.set(self._signal_slots[cmd.lhs],
711 normalize(cmd.rhs.value, cmd.lhs.shape()))
712 else:
713 compiler = _StatementCompiler(self._signal_slots)
714 funclet = compiler(cmd)
715 funclet(self._state)
716
717 domains = set()
718 for signal in lhs_signals:
719 self._commit_signal(self._signal_slots[signal], domains)
720 self._commit_sync_signals(domains)
721
722 elif type(cmd) is Signal:
723 # Fast path.
724 cmd = process.send(self._state.curr[self._signal_slots[cmd]])
725 continue
726
727 elif isinstance(cmd, Value):
728 compiler = _RHSValueCompiler(self._signal_slots)
729 funclet = compiler(cmd)
730 cmd = process.send(funclet(self._state))
731 continue
732
733 else:
734 raise TypeError("Received unsupported command '{!r}' from process '{}'"
735 .format(cmd, self._name_process(process)))
736
737 cmd = process.send(None)
738
739 except StopIteration:
740 self._processes.remove(process)
741 self._passive.discard(process)
742
743 except Exception as e:
744 process.throw(e)
745
746 def step(self, run_passive=False):
747 # Are there any delta cycles we should run?
748 if self._state.curr_dirty.any():
749 # We might run some delta cycles, and we have simulator processes waiting on
750 # a deadline. Take care to not exceed the closest deadline.
751 if self._wait_deadline and \
752 (self._timestamp + self._delta) >= min(self._wait_deadline.values()):
753 # Oops, we blew the deadline. We *could* run the processes now, but this is
754 # virtually certainly a logic loop and a design bug, so bail out instead.d
755 raise DeadlineError("Delta cycles exceeded process deadline; combinatorial loop?")
756
757 domains = set()
758 while self._state.curr_dirty.any():
759 self._update_dirty_signals()
760 self._commit_comb_signals(domains)
761 self._commit_sync_signals(domains)
762 return True
763
764 # Are there any processes that haven't had a chance to run yet?
765 if len(self._processes) > len(self._suspended):
766 # Schedule an arbitrary one.
767 process = (self._processes - set(self._suspended)).pop()
768 self._run_process(process)
769 return True
770
771 # All processes are suspended. Are any of them active?
772 if len(self._processes) > len(self._passive) or run_passive:
773 # Are any of them suspended before a deadline?
774 if self._wait_deadline:
775 # Schedule the one with the lowest deadline.
776 process, deadline = min(self._wait_deadline.items(), key=lambda x: x[1])
777 del self._wait_deadline[process]
778 self._suspended.remove(process)
779 self._timestamp = deadline
780 self._delta = 0.
781 self._run_process(process)
782 return True
783
784 # No processes, or all processes are passive. Nothing to do!
785 return False
786
787 def run(self):
788 self._run_called = True
789
790 while self.step():
791 pass
792
793 def run_until(self, deadline, run_passive=False):
794 self._run_called = True
795
796 while self._timestamp < deadline:
797 if not self.step(run_passive):
798 return False
799
800 return True
801
802 def __exit__(self, *args):
803 if not self._run_called:
804 warnings.warn("Simulation created, but not run", UserWarning)
805
806 if self._vcd_writer:
807 vcd_timestamp = (self._timestamp + self._delta) / self._epsilon
808 self._vcd_writer.close(vcd_timestamp)
809
810 if self._vcd_file and self._gtkw_file:
811 gtkw_save = GTKWSave(self._gtkw_file)
812 if hasattr(self._vcd_file, "name"):
813 gtkw_save.dumpfile(self._vcd_file.name)
814 if hasattr(self._vcd_file, "tell"):
815 gtkw_save.dumpfile_size(self._vcd_file.tell())
816
817 gtkw_save.treeopen("top")
818 gtkw_save.zoom_markers(math.log(self._epsilon / self._fastest_clock) - 14)
819
820 def add_trace(signal, **kwargs):
821 signal_slot = self._signal_slots[signal]
822 if self._vcd_names[signal_slot] is not None:
823 if len(signal) > 1:
824 suffix = "[{}:0]".format(len(signal) - 1)
825 else:
826 suffix = ""
827 gtkw_save.trace(self._vcd_names[signal_slot] + suffix, **kwargs)
828
829 for domain, cd in self._domains.items():
830 with gtkw_save.group("d.{}".format(domain)):
831 if cd.rst is not None:
832 add_trace(cd.rst)
833 add_trace(cd.clk)
834
835 for signal in self._traces:
836 add_trace(signal)
837
838 if self._vcd_file:
839 self._vcd_file.close()
840 if self._gtkw_file:
841 self._gtkw_file.close()