3 from contextlib
import contextmanager
4 from vcd
import VCDWriter
5 from vcd
.gtkw
import GTKWSave
7 from ..tools
import flatten
8 from ..fhdl
.ast
import *
9 from ..fhdl
.xfrm
import ValueTransformer
, StatementTransformer
12 __all__
= ["Simulator", "Delay", "Tick", "Passive", "DeadlineError"]
15 class DeadlineError(Exception):
20 __slots__
= ("curr", "curr_dirty", "next", "next_dirty")
23 self
.curr
= ValueDict()
24 self
.next
= ValueDict()
25 self
.curr_dirty
= ValueSet()
26 self
.next_dirty
= ValueSet()
28 def set(self
, signal
, value
):
29 assert isinstance(value
, int)
30 if self
.next
[signal
] != value
:
31 self
.next_dirty
.add(signal
)
32 self
.next
[signal
] = value
34 def commit(self
, signal
):
35 old_value
= self
.curr
[signal
]
36 if self
.curr
[signal
] != self
.next
[signal
]:
37 self
.next_dirty
.remove(signal
)
38 self
.curr_dirty
.add(signal
)
39 self
.curr
[signal
] = self
.next
[signal
]
40 new_value
= self
.curr
[signal
]
41 return old_value
, new_value
44 normalize
= Const
.normalize
47 class _RHSValueCompiler(ValueTransformer
):
48 def __init__(self
, sensitivity
=None):
49 self
.sensitivity
= sensitivity
50 self
.signal_mode
= "next"
52 def on_Const(self
, value
):
53 return lambda state
: value
.value
55 def on_Signal(self
, value
):
57 self
.sensitivity
.add(value
)
58 if self
.signal_mode
== "curr":
59 return lambda state
: state
.curr
[value
]
60 if self
.signal_mode
== "next":
61 return lambda state
: state
.next
[value
]
62 raise NotImplementedError # :nocov:
64 def on_ClockSignal(self
, value
):
65 raise NotImplementedError # :nocov:
67 def on_ResetSignal(self
, value
):
68 raise NotImplementedError # :nocov:
70 def on_Operator(self
, value
):
72 if len(value
.operands
) == 1:
73 arg
, = map(self
, value
.operands
)
75 return lambda state
: normalize(~
arg(state
), shape
)
77 return lambda state
: normalize(-arg(state
), shape
)
79 return lambda state
: normalize(bool(arg(state
)), shape
)
80 elif len(value
.operands
) == 2:
81 lhs
, rhs
= map(self
, value
.operands
)
83 return lambda state
: normalize(lhs(state
) + rhs(state
), shape
)
85 return lambda state
: normalize(lhs(state
) - rhs(state
), shape
)
87 return lambda state
: normalize(lhs(state
) & rhs(state
), shape
)
89 return lambda state
: normalize(lhs(state
) |
rhs(state
), shape
)
91 return lambda state
: normalize(lhs(state
) ^
rhs(state
), shape
)
93 return lambda state
: normalize(lhs(state
) == rhs(state
), shape
)
95 return lambda state
: normalize(lhs(state
) != rhs(state
), shape
)
97 return lambda state
: normalize(lhs(state
) < rhs(state
), shape
)
99 return lambda state
: normalize(lhs(state
) <= rhs(state
), shape
)
101 return lambda state
: normalize(lhs(state
) > rhs(state
), shape
)
103 return lambda state
: normalize(lhs(state
) >= rhs(state
), shape
)
104 elif len(value
.operands
) == 3:
106 sel
, val1
, val0
= map(self
, value
.operands
)
107 return lambda state
: val1(state
) if sel(state
) else val0(state
)
108 raise NotImplementedError("Operator '{!r}' not implemented".format(value
.op
)) # :nocov:
110 def on_Slice(self
, value
):
111 shape
= value
.shape()
112 arg
= self(value
.value
)
114 mask
= (1 << (value
.end
- value
.start
)) - 1
115 return lambda state
: normalize((arg(state
) >> shift
) & mask
, shape
)
117 def on_Part(self
, value
):
118 raise NotImplementedError
120 def on_Cat(self
, value
):
121 shape
= value
.shape()
124 for opnd
in value
.operands
:
125 parts
.append((offset
, (1 << len(opnd
)) - 1, self(opnd
)))
129 for offset
, mask
, opnd
in parts
:
130 result |
= (opnd(state
) & mask
) << offset
131 return normalize(result
, shape
)
134 def on_Repl(self
, value
):
135 shape
= value
.shape()
136 offset
= len(value
.value
)
137 mask
= (1 << len(value
.value
)) - 1
139 opnd
= self(value
.value
)
142 for _
in range(count
):
144 result |
= opnd(state
)
145 return normalize(result
, shape
)
149 class _StatementCompiler(StatementTransformer
):
151 self
.sensitivity
= ValueSet()
152 self
.rhs_compiler
= _RHSValueCompiler(self
.sensitivity
)
157 self
.rhs_compiler
.signal_mode
= "curr"
160 self
.rhs_compiler
.signal_mode
= "next"
162 def lhs_compiler(self
, value
):
164 return lambda state
, arg
: state
.set(value
, arg
)
166 def on_Assign(self
, stmt
):
167 assert isinstance(stmt
.lhs
, Signal
)
168 shape
= stmt
.lhs
.shape()
169 lhs
= self
.lhs_compiler(stmt
.lhs
)
170 rhs
= self
.rhs_compiler(stmt
.rhs
)
172 lhs(state
, normalize(rhs(state
), shape
))
175 def on_Switch(self
, stmt
):
176 test
= self
.rhs_compiler(stmt
.test
)
178 for value
, stmts
in stmt
.cases
.items():
180 mask
= "".join("0" if b
== "-" else "1" for b
in value
)
181 value
= "".join("0" if b
== "-" else b
for b
in value
)
183 mask
= "1" * len(value
)
185 value
= int(value
, 2)
186 def make_test(mask
, value
):
187 return lambda test
: test
& mask
== value
188 cases
.append((make_test(mask
, value
), self
.on_statements(stmts
)))
190 test_value
= test(state
)
191 for check
, body
in cases
:
192 if check(test_value
):
197 def on_statements(self
, stmts
):
198 stmts
= [self
.on_statement(stmt
) for stmt
in stmts
]
206 def __init__(self
, fragment
, vcd_file
=None, gtkw_file
=None, traces
=()):
207 self
._fragment
= fragment
209 self
._domains
= {} # str/domain -> ClockDomain
210 self
._domain
_triggers
= ValueDict() # Signal -> str/domain
211 self
._domain
_signals
= {} # str/domain -> {Signal}
213 self
._signals
= ValueSet() # {Signal}
214 self
._comb
_signals
= ValueSet() # {Signal}
215 self
._sync
_signals
= ValueSet() # {Signal}
216 self
._user
_signals
= ValueSet() # {Signal}
218 self
._started
= False
220 self
._epsilon
= 1e-10
221 self
._fastest
_clock
= self
._epsilon
222 self
._state
= _State()
224 self
._processes
= set() # {process}
225 self
._passive
= set() # {process}
226 self
._suspended
= set() # {process}
227 self
._wait
_deadline
= {} # process -> float/timestamp
228 self
._wait
_tick
= {} # process -> str/domain
230 self
._funclets
= ValueDict() # Signal -> set(lambda)
232 self
._vcd
_file
= vcd_file
233 self
._vcd
_writer
= None
234 self
._vcd
_signals
= ValueDict() # signal -> set(vcd_signal)
235 self
._vcd
_names
= ValueDict() # signal -> str/name
236 self
._gtkw
_file
= gtkw_file
237 self
._traces
= traces
239 def _check_process(self
, process
):
240 if inspect
.isgeneratorfunction(process
):
242 if not inspect
.isgenerator(process
):
243 raise TypeError("Cannot add a process '{!r}' because it is not a generator or"
244 "a generator function"
248 def add_process(self
, process
):
249 process
= self
._check
_process
(process
)
250 self
._processes
.add(process
)
252 def add_sync_process(self
, process
, domain
="sync"):
253 process
= self
._check
_process
(process
)
259 result
= Tick(domain
)
260 result
= process
.send((yield result
))
261 except StopIteration:
263 self
.add_process(sync_process())
265 def add_clock(self
, period
, phase
=None, domain
="sync"):
266 if self
._fastest
_clock
== self
._epsilon
or period
< self
._fastest
_clock
:
267 self
._fastest
_clock
= period
269 half_period
= period
/ 2
272 clk
= self
._domains
[domain
].clk
278 yield Delay(half_period
)
280 yield Delay(half_period
)
281 self
.add_process(clk_process())
285 self
._vcd
_writer
= VCDWriter(self
._vcd
_file
, timescale
="100 ps",
286 comment
="Generated by nMigen")
288 root_fragment
= self
._fragment
.prepare()
290 self
._domains
= root_fragment
.domains
291 for domain
, cd
in self
._domains
.items():
292 self
._domain
_triggers
[cd
.clk
] = domain
293 if cd
.rst
is not None:
294 self
._domain
_triggers
[cd
.rst
] = domain
295 self
._domain
_signals
[domain
] = ValueSet()
298 def add_fragment(fragment
, scope
=()):
299 hierarchy
[fragment
] = scope
300 for subfragment
, name
in fragment
.subfragments
:
301 add_fragment(subfragment
, (*scope
, name
))
302 add_fragment(root_fragment
)
304 for fragment
, fragment_name
in hierarchy
.items():
305 for signal
in fragment
.iter_signals():
306 self
._signals
.add(signal
)
308 self
._state
.curr
[signal
] = self
._state
.next
[signal
] = \
309 normalize(signal
.reset
, signal
.shape())
310 self
._state
.curr_dirty
.add(signal
)
312 if not self
._vcd
_writer
:
315 if signal
not in self
._vcd
_signals
:
316 self
._vcd
_signals
[signal
] = set()
318 for subfragment
, name
in fragment
.subfragments
:
319 if signal
in subfragment
.ports
:
320 var_name
= "{}_{}".format(name
, signal
.name
)
323 var_name
= signal
.name
328 var_init
= signal
.decoder(signal
.reset
).replace(" ", "_")
331 var_size
= signal
.nbits
332 var_init
= signal
.reset
338 var_name_suffix
= var_name
340 var_name_suffix
= "{}${}".format(var_name
, suffix
)
341 self
._vcd
_signals
[signal
].add(self
._vcd
_writer
.register_var(
342 scope
=".".join(fragment_name
), name
=var_name_suffix
,
343 var_type
=var_type
, size
=var_size
, init
=var_init
))
344 if signal
not in self
._vcd
_names
:
345 self
._vcd
_names
[signal
] = ".".join(fragment_name
+ (var_name_suffix
,))
348 suffix
= (suffix
or 0) + 1
350 for domain
, signals
in fragment
.drivers
.items():
352 self
._comb
_signals
.update(signals
)
354 self
._sync
_signals
.update(signals
)
355 self
._domain
_signals
[domain
].update(signals
)
358 for signal
in fragment
.iter_comb():
359 initial_stmts
.append(signal
.eq(signal
.reset
))
360 for domain
, signal
in fragment
.iter_sync():
361 initial_stmts
.append(signal
.eq(signal
))
363 compiler
= _StatementCompiler()
365 with compiler
.initial():
366 funclet_init
= compiler(initial_stmts
)
367 funclet_frag
= compiler(fragment
.statements
)
372 funclet
= make_funclet()
374 def add_funclet(signal
, funclet
):
375 if signal
not in self
._funclets
:
376 self
._funclets
[signal
] = set()
377 self
._funclets
[signal
].add(funclet
)
379 for signal
in compiler
.sensitivity
:
380 add_funclet(signal
, funclet
)
381 for domain
, cd
in fragment
.domains
.items():
382 add_funclet(cd
.clk
, funclet
)
383 if cd
.rst
is not None:
384 add_funclet(cd
.rst
, funclet
)
386 self
._user
_signals
= self
._signals
- self
._comb
_signals
- self
._sync
_signals
390 def _update_dirty_signals(self
):
391 """Perform the statement part of IR processes (aka RTLIL case)."""
392 # First, for all dirty signals, use sensitivity lists to determine the set of fragments
393 # that need their statements to be reevaluated because the signals changed at the previous
396 while self
._state
.curr_dirty
:
397 signal
= self
._state
.curr_dirty
.pop()
398 if signal
in self
._funclets
:
399 funclets
.update(self
._funclets
[signal
])
401 # Second, compute the values of all signals at the start of the next delta cycle, by
402 # running precompiled statements.
403 for funclet
in funclets
:
406 def _commit_signal(self
, signal
, domains
):
407 """Perform the driver part of IR processes (aka RTLIL sync), for individual signals."""
408 # Take the computed value (at the start of this delta cycle) of a signal (that could have
409 # come from an IR process that ran earlier, or modified by a simulator process) and update
410 # the value for this delta cycle.
411 old
, new
= self
._state
.commit(signal
)
413 # If the signal is a clock that triggers synchronous logic, record that fact.
414 if (old
, new
) == (0, 1) and signal
in self
._domain
_triggers
:
415 domains
.add(self
._domain
_triggers
[signal
])
417 if self
._vcd
_writer
and old
!= new
:
418 # Finally, dump the new value to the VCD file.
419 for vcd_signal
in self
._vcd
_signals
[signal
]:
421 var_value
= signal
.decoder(new
).replace(" ", "_")
424 self
._vcd
_writer
.change(vcd_signal
, self
._timestamp
/ self
._epsilon
, var_value
)
426 def _commit_comb_signals(self
, domains
):
427 """Perform the comb part of IR processes (aka RTLIL always)."""
428 # Take the computed value (at the start of this delta cycle) of every comb signal and
429 # update the value for this delta cycle.
430 for signal
in self
._state
.next_dirty
:
431 if signal
in self
._comb
_signals
or signal
in self
._user
_signals
:
432 self
._commit
_signal
(signal
, domains
)
434 def _commit_sync_signals(self
, domains
):
435 """Perform the sync part of IR processes (aka RTLIL posedge)."""
436 # At entry, `domains` contains a list of every simultaneously triggered sync update.
438 # Advance the timeline a bit (purely for observational purposes) and commit all of them
439 # at the same timestamp.
440 self
._timestamp
+= self
._epsilon
441 curr_domains
, domains
= domains
, set()
444 domain
= curr_domains
.pop()
446 # Take the computed value (at the start of this delta cycle) of every sync signal
447 # in this domain and update the value for this delta cycle. This can trigger more
448 # synchronous logic, so record that.
449 for signal
in self
._state
.next_dirty
:
450 if signal
in self
._domain
_signals
[domain
]:
451 self
._commit
_signal
(signal
, domains
)
453 # Wake up any simulator processes that wait for a domain tick.
454 for process
, wait_domain
in list(self
._wait
_tick
.items()):
455 if domain
== wait_domain
:
456 del self
._wait
_tick
[process
]
457 self
._suspended
.remove(process
)
459 # Unless handling synchronous logic above has triggered more synchronous logic (which
460 # can happen e.g. if a domain is clocked off a clock divisor in fabric), we're done.
461 # Otherwise, do one more round of updates.
463 def _run_process(self
, process
):
464 def format_process(process
):
465 frame
= process
.gi_frame
466 return "{}:{}".format(inspect
.getfile(frame
), inspect
.getlineno(frame
))
469 cmd
= process
.send(None)
471 if isinstance(cmd
, Delay
):
472 if cmd
.interval
is None:
473 interval
= self
._epsilon
475 interval
= cmd
.interval
476 self
._wait
_deadline
[process
] = self
._timestamp
+ interval
477 self
._suspended
.add(process
)
479 elif isinstance(cmd
, Tick
):
480 self
._wait
_tick
[process
] = cmd
.domain
481 self
._suspended
.add(process
)
483 elif isinstance(cmd
, Passive
):
484 self
._passive
.add(process
)
486 elif isinstance(cmd
, Value
):
487 funclet
= _RHSValueCompiler()(cmd
)
488 cmd
= process
.send(funclet(self
._state
))
491 elif isinstance(cmd
, Assign
):
492 lhs_signals
= cmd
.lhs
._lhs
_signals
()
493 for signal
in lhs_signals
:
494 if not signal
in self
._signals
:
495 raise ValueError("Process '{}' sent a request to set signal '{!r}', "
496 "which is not a part of simulation"
497 .format(format_process(process
), signal
))
498 if signal
in self
._comb
_signals
:
499 raise ValueError("Process '{}' sent a request to set signal '{!r}', "
500 "which is a part of combinatorial assignment in "
502 .format(format_process(process
), signal
))
504 funclet
= _StatementCompiler()(cmd
)
508 for signal
in lhs_signals
:
509 self
._commit
_signal
(signal
, domains
)
510 self
._commit
_sync
_signals
(domains
)
513 raise TypeError("Received unsupported command '{!r}' from process '{}'"
514 .format(cmd
, format_process(process
)))
518 except StopIteration:
519 self
._processes
.remove(process
)
520 self
._passive
.discard(process
)
522 except Exception as e
:
525 def step(self
, run_passive
=False):
527 if self
._wait
_deadline
:
528 # We might run some delta cycles, and we have simulator processes waiting on
529 # a deadline. Take care to not exceed the closest deadline.
530 deadline
= min(self
._wait
_deadline
.values())
532 # Are there any delta cycles we should run?
533 while self
._state
.curr_dirty
:
534 self
._timestamp
+= self
._epsilon
535 if deadline
is not None and self
._timestamp
>= deadline
:
536 # Oops, we blew the deadline. We *could* run the processes now, but this is
537 # virtually certainly a logic loop and a design bug, so bail out instead.d
538 raise DeadlineError("Delta cycles exceeded process deadline; combinatorial loop?")
541 self
._update
_dirty
_signals
()
542 self
._commit
_comb
_signals
(domains
)
543 self
._commit
_sync
_signals
(domains
)
545 # Are there any processes that haven't had a chance to run yet?
546 if len(self
._processes
) > len(self
._suspended
):
547 # Schedule an arbitrary one.
548 process
= (self
._processes
- set(self
._suspended
)).pop()
549 self
._run
_process
(process
)
552 # All processes are suspended. Are any of them active?
553 if len(self
._processes
) > len(self
._passive
) or run_passive
:
554 # Are any of them suspended before a deadline?
555 if self
._wait
_deadline
:
556 # Schedule the one with the lowest deadline.
557 process
, deadline
= min(self
._wait
_deadline
.items(), key
=lambda x
: x
[1])
558 del self
._wait
_deadline
[process
]
559 self
._suspended
.remove(process
)
560 self
._timestamp
= deadline
561 self
._run
_process
(process
)
564 # No processes, or all processes are passive. Nothing to do!
571 def run_until(self
, deadline
, run_passive
=False):
572 while self
._timestamp
< deadline
:
573 if not self
.step(run_passive
):
578 def __exit__(self
, *args
):
580 self
._vcd
_writer
.close(self
._timestamp
/ self
._epsilon
)
582 if self
._vcd
_file
and self
._gtkw
_file
:
583 gtkw_save
= GTKWSave(self
._gtkw
_file
)
584 if hasattr(self
._vcd
_file
, "name"):
585 gtkw_save
.dumpfile(self
._vcd
_file
.name
)
586 if hasattr(self
._vcd
_file
, "tell"):
587 gtkw_save
.dumpfile_size(self
._vcd
_file
.tell())
589 gtkw_save
.treeopen("top")
590 gtkw_save
.zoom_markers(math
.log(self
._epsilon
/ self
._fastest
_clock
) - 14)
592 def add_trace(signal
, **kwargs
):
593 if signal
in self
._vcd
_names
:
595 suffix
= "[{}:0]".format(len(signal
) - 1)
598 gtkw_save
.trace(self
._vcd
_names
[signal
] + suffix
, **kwargs
)
600 for domain
, cd
in self
._domains
.items():
601 with gtkw_save
.group("d.{}".format(domain
)):
602 if cd
.rst
is not None:
606 for signal
in self
._traces
:
610 self
._vcd
_file
.close()
612 self
._gtkw
_file
.close()