2 from vcd
import VCDWriter
3 from vcd
.gtkw
import GTKWSave
5 from ..tools
import flatten
6 from ..fhdl
.ast
import *
7 from ..fhdl
.xfrm
import ValueTransformer
, StatementTransformer
10 __all__
= ["Simulator", "Delay", "Tick", "Passive", "DeadlineError"]
13 class DeadlineError(Exception):
18 __slots__
= ("curr", "curr_dirty", "next", "next_dirty")
21 self
.curr
= ValueDict()
22 self
.next
= ValueDict()
23 self
.curr_dirty
= ValueSet()
24 self
.next_dirty
= ValueSet()
26 def get(self
, signal
):
27 return self
.curr
[signal
]
29 def set_curr(self
, signal
, value
):
30 assert isinstance(value
, int)
31 if self
.curr
[signal
] != value
:
32 self
.curr_dirty
.add(signal
)
33 self
.curr
[signal
] = value
35 def set_next(self
, signal
, value
):
36 assert isinstance(value
, int)
37 if self
.next
[signal
] != value
:
38 self
.next_dirty
.add(signal
)
39 self
.next
[signal
] = value
41 def commit(self
, signal
):
42 old_value
= self
.curr
[signal
]
43 if self
.curr
[signal
] != self
.next
[signal
]:
44 self
.next_dirty
.remove(signal
)
45 self
.curr_dirty
.add(signal
)
46 self
.curr
[signal
] = self
.next
[signal
]
47 new_value
= self
.curr
[signal
]
48 return old_value
, new_value
51 dirty
, self
.dirty
= self
.dirty
, ValueSet()
53 yield signal
, self
.curr
[signal
], self
.next
[signal
]
56 normalize
= Const
.normalize
59 class _RHSValueCompiler(ValueTransformer
):
60 def __init__(self
, sensitivity
):
61 self
.sensitivity
= sensitivity
63 def on_Const(self
, value
):
64 return lambda state
: value
.value
66 def on_Signal(self
, value
):
67 self
.sensitivity
.add(value
)
68 return lambda state
: state
.get(value
)
70 def on_ClockSignal(self
, value
):
71 raise NotImplementedError # :nocov:
73 def on_ResetSignal(self
, value
):
74 raise NotImplementedError # :nocov:
76 def on_Operator(self
, value
):
78 if len(value
.operands
) == 1:
79 arg
, = map(self
, value
.operands
)
81 return lambda state
: normalize(~
arg(state
), shape
)
83 return lambda state
: normalize(-arg(state
), shape
)
84 elif len(value
.operands
) == 2:
85 lhs
, rhs
= map(self
, value
.operands
)
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
)
98 elif len(value
.operands
) == 3:
100 sel
, val1
, val0
= map(self
, value
.operands
)
101 return lambda state
: val1(state
) if sel(state
) else val0(state
)
102 raise NotImplementedError("Operator '{}' not implemented".format(value
.op
))
104 def on_Slice(self
, value
):
105 shape
= value
.shape()
106 arg
= self(value
.value
)
108 mask
= (1 << (value
.end
- value
.start
)) - 1
109 return lambda state
: normalize((arg(state
) >> shift
) & mask
, shape
)
111 def on_Part(self
, value
):
112 raise NotImplementedError
114 def on_Cat(self
, value
):
115 shape
= value
.shape()
118 for opnd
in value
.operands
:
119 parts
.append((offset
, (1 << len(opnd
)) - 1, self(opnd
)))
123 for offset
, mask
, opnd
in parts
:
124 result |
= (opnd(state
) & mask
) << offset
125 return normalize(result
, shape
)
128 def on_Repl(self
, value
):
129 shape
= value
.shape()
130 offset
= len(value
.value
)
131 mask
= (1 << len(value
.value
)) - 1
133 opnd
= self(value
.value
)
136 for _
in range(count
):
138 result |
= opnd(state
)
139 return normalize(result
, shape
)
143 class _StatementCompiler(StatementTransformer
):
145 self
.sensitivity
= ValueSet()
146 self
.rhs_compiler
= _RHSValueCompiler(self
.sensitivity
)
148 def lhs_compiler(self
, value
):
150 return lambda state
, arg
: state
.set_next(value
, arg
)
152 def on_Assign(self
, stmt
):
153 assert isinstance(stmt
.lhs
, Signal
)
154 shape
= stmt
.lhs
.shape()
155 lhs
= self
.lhs_compiler(stmt
.lhs
)
156 rhs
= self
.rhs_compiler(stmt
.rhs
)
158 lhs(state
, normalize(rhs(state
), shape
))
161 def on_Switch(self
, stmt
):
162 test
= self
.rhs_compiler(stmt
.test
)
164 for value
, stmts
in stmt
.cases
.items():
166 mask
= "".join("0" if b
== "-" else "1" for b
in value
)
167 value
= "".join("0" if b
== "-" else b
for b
in value
)
169 mask
= "1" * len(value
)
171 value
= int(value
, 2)
172 def make_test(mask
, value
):
173 return lambda test
: test
& mask
== value
174 cases
.append((make_test(mask
, value
), self
.on_statements(stmts
)))
176 test_value
= test(state
)
177 for check
, body
in cases
:
178 if check(test_value
):
183 def on_statements(self
, stmts
):
184 stmts
= [self
.on_statement(stmt
) for stmt
in stmts
]
192 def __init__(self
, fragment
, vcd_file
=None, gtkw_file
=None, gtkw_signals
=()):
193 self
._fragment
= fragment
195 self
._domains
= {} # str/domain -> ClockDomain
196 self
._domain
_triggers
= ValueDict() # Signal -> str/domain
197 self
._domain
_signals
= {} # str/domain -> {Signal}
199 self
._signals
= ValueSet() # {Signal}
200 self
._comb
_signals
= ValueSet() # {Signal}
201 self
._sync
_signals
= ValueSet() # {Signal}
202 self
._user
_signals
= ValueSet() # {Signal}
204 self
._started
= False
206 self
._epsilon
= 1e-10
207 self
._fastest
_clock
= self
._epsilon
208 self
._state
= _State()
210 self
._processes
= set() # {process}
211 self
._passive
= set() # {process}
212 self
._suspended
= set() # {process}
213 self
._wait
_deadline
= {} # process -> float/timestamp
214 self
._wait
_tick
= {} # process -> str/domain
216 self
._funclets
= ValueDict() # Signal -> set(lambda)
218 self
._vcd
_file
= vcd_file
219 self
._vcd
_writer
= None
220 self
._vcd
_signals
= ValueDict() # signal -> set(vcd_signal)
221 self
._vcd
_names
= ValueDict() # signal -> str/name
223 self
._gtkw
_file
= gtkw_file
224 self
._gtkw
_signals
= gtkw_signals
226 def add_process(self
, process
):
227 self
._processes
.add(process
)
229 def add_clock(self
, period
, domain
="sync"):
230 if self
._fastest
_clock
== self
._epsilon
or period
< self
._fastest
_clock
:
231 self
._fastest
_clock
= period
233 half_period
= period
/ 2
234 clk
= self
._domains
[domain
].clk
237 yield Delay(half_period
)
240 yield Delay(half_period
)
242 yield Delay(half_period
)
243 self
.add_process(clk_process())
245 def add_sync_process(self
, process
, domain
="sync"):
248 result
= process
.send(None)
250 result
= process
.send((yield (result
or Tick(domain
))))
251 except StopIteration:
253 self
.add_process(sync_process())
257 self
._vcd
_writer
= VCDWriter(self
._vcd
_file
, timescale
="100 ps",
258 comment
="Generated by nMigen")
260 root_fragment
= self
._fragment
.prepare()
262 self
._domains
= root_fragment
.domains
263 for domain
, cd
in self
._domains
.items():
264 self
._domain
_triggers
[cd
.clk
] = domain
265 if cd
.rst
is not None:
266 self
._domain
_triggers
[cd
.rst
] = domain
267 self
._domain
_signals
[domain
] = ValueSet()
270 def add_fragment(fragment
, hierarchy
=("top",)):
271 fragment_names
[fragment
] = hierarchy
272 for subfragment
, name
in fragment
.subfragments
:
273 add_fragment(subfragment
, (*hierarchy
, name
))
274 add_fragment(root_fragment
)
276 for fragment
, fragment_name
in fragment_names
.items():
277 for signal
in fragment
.iter_signals():
278 self
._signals
.add(signal
)
280 self
._state
.curr
[signal
] = self
._state
.next
[signal
] = \
281 normalize(signal
.reset
, signal
.shape())
282 self
._state
.curr_dirty
.add(signal
)
284 if signal
not in self
._vcd
_signals
:
285 self
._vcd
_signals
[signal
] = set()
287 for subfragment
, name
in fragment
.subfragments
:
288 if signal
in subfragment
.ports
:
289 var_name
= "{}_{}".format(name
, signal
.name
)
292 var_name
= signal
.name
297 var_init
= signal
.decoder(signal
.reset
).replace(" ", "_")
300 var_size
= signal
.nbits
301 var_init
= signal
.reset
307 var_name_suffix
= var_name
309 var_name_suffix
= "{}${}".format(var_name
, suffix
)
310 self
._vcd
_signals
[signal
].add(self
._vcd
_writer
.register_var(
311 scope
=".".join(fragment_name
), name
=var_name_suffix
,
312 var_type
=var_type
, size
=var_size
, init
=var_init
))
313 if signal
not in self
._vcd
_names
:
314 self
._vcd
_names
[signal
] = ".".join(fragment_name
+ (var_name_suffix
,))
317 suffix
= (suffix
or 0) + 1
319 for domain
, signals
in fragment
.drivers
.items():
321 self
._comb
_signals
.update(signals
)
323 self
._sync
_signals
.update(signals
)
324 self
._domain
_signals
[domain
].update(signals
)
327 for signal
in fragment
.iter_comb():
328 statements
.append(signal
.eq(signal
.reset
))
329 statements
+= fragment
.statements
331 def add_funclet(signal
, funclet
):
332 if signal
not in self
._funclets
:
333 self
._funclets
[signal
] = set()
334 self
._funclets
[signal
].add(funclet
)
336 compiler
= _StatementCompiler()
337 funclet
= compiler(statements
)
338 for signal
in compiler
.sensitivity
:
339 add_funclet(signal
, funclet
)
340 for domain
, cd
in fragment
.domains
.items():
341 add_funclet(cd
.clk
, funclet
)
342 if cd
.rst
is not None:
343 add_funclet(cd
.rst
, funclet
)
345 self
._user
_signals
= self
._signals
- self
._comb
_signals
- self
._sync
_signals
349 def _update_dirty_signals(self
):
350 """Perform the statement part of IR processes (aka RTLIL case)."""
351 # First, for all dirty signals, use sensitivity lists to determine the set of fragments
352 # that need their statements to be reevaluated because the signals changed at the previous
355 while self
._state
.curr_dirty
:
356 signal
= self
._state
.curr_dirty
.pop()
357 if signal
in self
._funclets
:
358 funclets
.update(self
._funclets
[signal
])
360 # Second, compute the values of all signals at the start of the next delta cycle, by
361 # running precompiled statements.
362 for funclet
in funclets
:
365 def _commit_signal(self
, signal
, domains
):
366 """Perform the driver part of IR processes (aka RTLIL sync), for individual signals."""
367 # Take the computed value (at the start of this delta cycle) of a signal (that could have
368 # come from an IR process that ran earlier, or modified by a simulator process) and update
369 # the value for this delta cycle.
370 old
, new
= self
._state
.commit(signal
)
372 # If the signal is a clock that triggers synchronous logic, record that fact.
373 if (old
, new
) == (0, 1) and signal
in self
._domain
_triggers
:
374 domains
.add(self
._domain
_triggers
[signal
])
376 if self
._vcd
_writer
and old
!= new
:
377 # Finally, dump the new value to the VCD file.
378 for vcd_signal
in self
._vcd
_signals
[signal
]:
380 var_value
= signal
.decoder(new
).replace(" ", "_")
383 self
._vcd
_writer
.change(vcd_signal
, self
._timestamp
/ self
._epsilon
, var_value
)
385 def _commit_comb_signals(self
, domains
):
386 """Perform the comb part of IR processes (aka RTLIL always)."""
387 # Take the computed value (at the start of this delta cycle) of every comb signal and
388 # update the value for this delta cycle.
389 for signal
in self
._state
.next_dirty
:
390 if signal
in self
._comb
_signals
or signal
in self
._user
_signals
:
391 self
._commit
_signal
(signal
, domains
)
393 def _commit_sync_signals(self
, domains
):
394 """Perform the sync part of IR processes (aka RTLIL posedge)."""
395 # At entry, `domains` contains a list of every simultaneously triggered sync update.
397 # Advance the timeline a bit (purely for observational purposes) and commit all of them
398 # at the same timestamp.
399 self
._timestamp
+= self
._epsilon
400 curr_domains
, domains
= domains
, set()
403 domain
= curr_domains
.pop()
405 # Take the computed value (at the start of this delta cycle) of every sync signal
406 # in this domain and update the value for this delta cycle. This can trigger more
407 # synchronous logic, so record that.
408 for signal
in self
._state
.next_dirty
:
409 if signal
in self
._domain
_signals
[domain
]:
410 self
._commit
_signal
(signal
, domains
)
412 # Wake up any simulator processes that wait for a domain tick.
413 for process
, wait_domain
in list(self
._wait
_tick
.items()):
414 if domain
== wait_domain
:
415 del self
._wait
_tick
[process
]
416 self
._suspended
.remove(process
)
418 # Unless handling synchronous logic above has triggered more synchronous logic (which
419 # can happen e.g. if a domain is clocked off a clock divisor in fabric), we're done.
420 # Otherwise, do one more round of updates.
422 def _run_process(self
, process
):
424 stmt
= process
.send(None)
425 except StopIteration:
426 self
._processes
.remove(process
)
427 self
._passive
.discard(process
)
430 if isinstance(stmt
, Delay
):
431 self
._wait
_deadline
[process
] = self
._timestamp
+ stmt
.interval
432 self
._suspended
.add(process
)
434 elif isinstance(stmt
, Tick
):
435 self
._wait
_tick
[process
] = stmt
.domain
436 self
._suspended
.add(process
)
438 elif isinstance(stmt
, Passive
):
439 self
._passive
.add(process
)
441 elif isinstance(stmt
, Assign
):
442 lhs_signals
= stmt
.lhs
._lhs
_signals
()
443 for signal
in lhs_signals
:
444 if not signal
in self
._signals
:
445 raise ValueError("Process {!r} sent a request to set signal '{!r}', "
446 "which is not a part of simulation"
447 .format(process
, signal
))
448 if signal
in self
._comb
_signals
:
449 raise ValueError("Process {!r} sent a request to set signal '{!r}', "
450 "which is a part of combinatorial assignment in simulation"
451 .format(process
, signal
))
453 funclet
= _StatementCompiler()(stmt
)
457 for signal
in lhs_signals
:
458 self
._commit
_signal
(signal
, domains
)
459 self
._commit
_sync
_signals
(domains
)
462 raise TypeError("Received unsupported statement '{!r}' from process {!r}"
463 .format(stmt
, process
))
465 def step(self
, run_passive
=False):
467 if self
._wait
_deadline
:
468 # We might run some delta cycles, and we have simulator processes waiting on
469 # a deadline. Take care to not exceed the closest deadline.
470 deadline
= min(self
._wait
_deadline
.values())
472 # Are there any delta cycles we should run?
473 while self
._state
.curr_dirty
:
474 self
._timestamp
+= self
._epsilon
475 if deadline
is not None and self
._timestamp
>= deadline
:
476 # Oops, we blew the deadline. We *could* run the processes now, but this is
477 # virtually certainly a logic loop and a design bug, so bail out instead.d
478 raise DeadlineError("Delta cycles exceeded process deadline; combinatorial loop?")
481 self
._update
_dirty
_signals
()
482 self
._commit
_comb
_signals
(domains
)
483 self
._commit
_sync
_signals
(domains
)
485 # Are there any processes that haven't had a chance to run yet?
486 if len(self
._processes
) > len(self
._suspended
):
487 # Schedule an arbitrary one.
488 process
= (self
._processes
- set(self
._suspended
)).pop()
489 self
._run
_process
(process
)
492 # All processes are suspended. Are any of them active?
493 if len(self
._processes
) > len(self
._passive
) or run_passive
:
494 # Are any of them suspended before a deadline?
495 if self
._wait
_deadline
:
496 # Schedule the one with the lowest deadline.
497 process
, deadline
= min(self
._wait
_deadline
.items(), key
=lambda x
: x
[1])
498 del self
._wait
_deadline
[process
]
499 self
._suspended
.remove(process
)
500 self
._timestamp
= deadline
501 self
._run
_process
(process
)
504 # No processes, or all processes are passive. Nothing to do!
511 def run_until(self
, deadline
, run_passive
=False):
512 while self
._timestamp
< deadline
:
513 if not self
.step(run_passive
):
518 def __exit__(self
, *args
):
520 self
._vcd
_writer
.close(self
._timestamp
/ self
._epsilon
)
522 if self
._vcd
_file
and self
._gtkw
_file
:
523 gtkw_save
= GTKWSave(self
._gtkw
_file
)
524 if hasattr(self
._vcd
_file
, "name"):
525 gtkw_save
.dumpfile(self
._vcd
_file
.name
)
526 if hasattr(self
._vcd
_file
, "tell"):
527 gtkw_save
.dumpfile_size(self
._vcd
_file
.tell())
529 gtkw_save
.treeopen("top")
530 gtkw_save
.zoom_markers(math
.log(self
._epsilon
/ self
._fastest
_clock
) - 14)
532 for domain
, cd
in self
._domains
.items():
533 with gtkw_save
.group("d.{}".format(domain
)):
534 if cd
.rst
is not None:
535 gtkw_save
.trace(self
._vcd
_names
[cd
.rst
])
536 gtkw_save
.trace(self
._vcd
_names
[cd
.clk
])
538 for signal
in self
._gtkw
_signals
:
539 if signal
in self
._vcd
_names
:
541 suffix
= "[{}:0]".format(len(signal
) - 1)
544 gtkw_save
.trace(self
._vcd
_names
[signal
] + suffix
)