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