|
|
@ -1,10 +1,10 @@ |
|
|
|
"""High-Throughput combinational logic timing simulators. |
|
|
|
"""High-throughput combinational logic timing simulators. |
|
|
|
|
|
|
|
|
|
|
|
These simulators work similarly to :py:class:`~kyupy.logic_sim.LogicSim`. |
|
|
|
These simulators work similarly to :py:class:`~kyupy.logic_sim.LogicSim`. |
|
|
|
They propagate values through the combinational circuit from (pseudo) primary inputs to (pseudo) primary outputs. |
|
|
|
They propagate values through the combinational circuit from (pseudo) primary inputs to (pseudo) primary outputs. |
|
|
|
Instead of propagating logic values, these simulators propagate signal histories (waveforms). |
|
|
|
Instead of propagating logic values, these simulators propagate signal histories (waveforms). |
|
|
|
They are designed to run many simulations in parallel and while their latencies are quite high, they achieve |
|
|
|
They are designed to run many simulations in parallel and while their latencies are quite high, they can achieve |
|
|
|
high throughput performance. |
|
|
|
high throughput. |
|
|
|
|
|
|
|
|
|
|
|
The simulators are not event-based and are not capable of simulating sequential circuits directly. |
|
|
|
The simulators are not event-based and are not capable of simulating sequential circuits directly. |
|
|
|
|
|
|
|
|
|
|
@ -20,9 +20,13 @@ import numpy as np |
|
|
|
from . import numba, cuda, hr_bytes |
|
|
|
from . import numba, cuda, hr_bytes |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TMAX = np.float32(2 ** 127) # almost np.PINF for 32-bit floating point values |
|
|
|
TMAX = np.float32(2 ** 127) |
|
|
|
TMAX_OVL = np.float32(1.1 * 2 ** 127) # almost np.PINF with overflow mark |
|
|
|
"""A large 32-bit floating point value used to mark the end of a waveform.""" |
|
|
|
TMIN = np.float32(-2 ** 127) # almost np.NINF for 32-bit floating point values |
|
|
|
TMAX_OVL = np.float32(1.1 * 2 ** 127) |
|
|
|
|
|
|
|
"""A large 32-bit floating point value used to mark the end of a waveform that |
|
|
|
|
|
|
|
may be incomplete due to an overflow.""" |
|
|
|
|
|
|
|
TMIN = np.float32(-2 ** 127) |
|
|
|
|
|
|
|
"""A large negative 32-bit floating point value used at the beginning of waveforms that start with logic-1.""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Heap: |
|
|
|
class Heap: |
|
|
@ -92,7 +96,23 @@ class Heap: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class WaveSim: |
|
|
|
class WaveSim: |
|
|
|
"""A waveform-based combinational logic timing simulator.""" |
|
|
|
"""A waveform-based combinational logic timing simulator running on CPU. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:param circuit: The circuit to simulate. |
|
|
|
|
|
|
|
:param timing: The timing annotation of the circuit (see :py:func:`kyupy.sdf.DelayFile.annotation` for details) |
|
|
|
|
|
|
|
:param sims: The number of parallel simulations. |
|
|
|
|
|
|
|
:param wavecaps: The number of floats available in each waveform. Waveforms are encoding the signal switching |
|
|
|
|
|
|
|
history by storing transition times. The waveform capacity roughly corresponds to the number of transitions |
|
|
|
|
|
|
|
that can be stored. A capacity of ``n`` can store at least ``n-2`` transitions. If more transitions are |
|
|
|
|
|
|
|
generated during simulation, the latest glitch is removed (freeing up two transition times) and an overflow |
|
|
|
|
|
|
|
flag is set. If an integer is given, all waveforms are set to that same capacity. With an array of length |
|
|
|
|
|
|
|
``len(circuit.lines)`` the capacity can be controlled for each intermediate waveform individually. |
|
|
|
|
|
|
|
:param strip_forks: If enabled, the simulator will not evaluate fork nodes explicitly. This saves simulation time |
|
|
|
|
|
|
|
by reducing the number of nodes to simulate, but (interconnect) delay annotations of lines read by fork nodes |
|
|
|
|
|
|
|
are ignored. |
|
|
|
|
|
|
|
:param keep_waveforms: If disabled, memory of intermediate signal waveforms will be re-used. This greatly reduces |
|
|
|
|
|
|
|
memory footprint, but intermediate signal waveforms become unaccessible after a propagation. |
|
|
|
|
|
|
|
""" |
|
|
|
def __init__(self, circuit, timing, sims=8, wavecaps=16, strip_forks=False, keep_waveforms=True): |
|
|
|
def __init__(self, circuit, timing, sims=8, wavecaps=16, strip_forks=False, keep_waveforms=True): |
|
|
|
self.circuit = circuit |
|
|
|
self.circuit = circuit |
|
|
|
self.sims = sims |
|
|
|
self.sims = sims |
|
|
@ -258,12 +278,24 @@ class WaveSim: |
|
|
|
f'levels={len(self.level_starts)} mem={hr_bytes(total_mem)}>' |
|
|
|
f'levels={len(self.level_starts)} mem={hr_bytes(total_mem)}>' |
|
|
|
|
|
|
|
|
|
|
|
def get_line_delay(self, line, polarity): |
|
|
|
def get_line_delay(self, line, polarity): |
|
|
|
|
|
|
|
"""Returns the current delay of the given ``line`` and ``polarity`` in the simulation model.""" |
|
|
|
return self.timing[line, 0, polarity] |
|
|
|
return self.timing[line, 0, polarity] |
|
|
|
|
|
|
|
|
|
|
|
def set_line_delay(self, line, polarity, delay): |
|
|
|
def set_line_delay(self, line, polarity, delay): |
|
|
|
|
|
|
|
"""Sets a new ``delay`` for the given ``line`` and ``polarity`` in the simulation model.""" |
|
|
|
self.timing[line, 0, polarity] = delay |
|
|
|
self.timing[line, 0, polarity] = delay |
|
|
|
|
|
|
|
|
|
|
|
def assign(self, vectors, time=0.0, offset=0): |
|
|
|
def assign(self, vectors, time=0.0, offset=0): |
|
|
|
|
|
|
|
"""Assigns new values to the primary inputs and state-elements. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:param vectors: The values to assign preferably in 8-valued logic. The values are converted to |
|
|
|
|
|
|
|
appropriate waveforms with or one transition (``RISE``, ``FALL``) no transitions |
|
|
|
|
|
|
|
(``ZERO``, ``ONE``, and others). |
|
|
|
|
|
|
|
:type vectors: :py:class:`~kyupy.logic.BPArray` |
|
|
|
|
|
|
|
:param time: The transition time of the generated waveforms. |
|
|
|
|
|
|
|
:param offset: The offset into the vector set. The vector assigned to the first simulator is |
|
|
|
|
|
|
|
``vectors[offset]``. |
|
|
|
|
|
|
|
""" |
|
|
|
nvectors = min(len(vectors) - offset, self.sims) |
|
|
|
nvectors = min(len(vectors) - offset, self.sims) |
|
|
|
for i in range(len(self.interface)): |
|
|
|
for i in range(len(self.interface)): |
|
|
|
ppi_loc = self.sat[self.ppi_offset + i, 0] |
|
|
|
ppi_loc = self.sat[self.ppi_offset + i, 0] |
|
|
@ -287,6 +319,12 @@ class WaveSim: |
|
|
|
self.state[ppi_loc + toggle, p] = TMAX |
|
|
|
self.state[ppi_loc + toggle, p] = TMAX |
|
|
|
|
|
|
|
|
|
|
|
def propagate(self, sims=None, sd=0.0, seed=1): |
|
|
|
def propagate(self, sims=None, sd=0.0, seed=1): |
|
|
|
|
|
|
|
"""Propagates all waveforms from the (pseudo) primary inputs to the (pseudo) primary outputs. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:param sims: Number of parallel simulations to execute. If None, all available simulations are performed. |
|
|
|
|
|
|
|
:param sd: Standard deviation for injection of random delay variation. Active, if value is positive. |
|
|
|
|
|
|
|
:param seed: Random seed for delay variations. |
|
|
|
|
|
|
|
""" |
|
|
|
sims = min(sims or self.sims, self.sims) |
|
|
|
sims = min(sims or self.sims, self.sims) |
|
|
|
for op_start, op_stop in zip(self.level_starts, self.level_stops): |
|
|
|
for op_start, op_stop in zip(self.level_starts, self.level_stops): |
|
|
|
self.overflows += level_eval(self.ops, op_start, op_stop, self.state, self.sat, 0, sims, |
|
|
|
self.overflows += level_eval(self.ops, op_start, op_stop, self.state, self.sat, 0, sims, |
|
|
@ -294,6 +332,8 @@ class WaveSim: |
|
|
|
self.lst_eat_valid = False |
|
|
|
self.lst_eat_valid = False |
|
|
|
|
|
|
|
|
|
|
|
def wave(self, line, vector): |
|
|
|
def wave(self, line, vector): |
|
|
|
|
|
|
|
"""Returns the desired waveform from the simulation state. Only valid, if simulator was |
|
|
|
|
|
|
|
instanciated with ``keep_waveforms=True``.""" |
|
|
|
if line < 0: |
|
|
|
if line < 0: |
|
|
|
return [TMAX] |
|
|
|
return [TMAX] |
|
|
|
mem, wcap, _ = self.sat[line] |
|
|
|
mem, wcap, _ = self.sat[line] |
|
|
@ -307,7 +347,34 @@ class WaveSim: |
|
|
|
def wave_ppo(self, o, vector): |
|
|
|
def wave_ppo(self, o, vector): |
|
|
|
return self.wave(self.ppo_offset + o, vector) |
|
|
|
return self.wave(self.ppo_offset + o, vector) |
|
|
|
|
|
|
|
|
|
|
|
def capture(self, time=TMAX, sd=0, seed=1, cdata=None, offset=0): |
|
|
|
def capture(self, time=TMAX, sd=0.0, seed=1, cdata=None, offset=0): |
|
|
|
|
|
|
|
"""Simulates a capture operation at all state-elements and primary outputs. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The capture analyzes the propagated waveforms at and around the given capture time and returns |
|
|
|
|
|
|
|
various results for each capture operation. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:param time: The desired capture time. By default, a capture of the settled value is performed. |
|
|
|
|
|
|
|
:param sd: A standard deviation for uncertainty in the actual capture time. |
|
|
|
|
|
|
|
:param seed: The random seed for a capture with uncertainty. |
|
|
|
|
|
|
|
:param cdata: An array to copy capture data into (optional). See the return value for details. |
|
|
|
|
|
|
|
:param offset: An offset into the supplied capture data array. |
|
|
|
|
|
|
|
:return: The capture data as numpy array. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The 3-dimensional capture data array contains for each interface node (axis 0), |
|
|
|
|
|
|
|
and each test (axis 1), seven values: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
0. Probability of capturing a 1 at the given capture time (same as next value, if no |
|
|
|
|
|
|
|
standard deviation given). |
|
|
|
|
|
|
|
1. A capture value decided by random sampling according to above probability and given seed. |
|
|
|
|
|
|
|
2. The final value (assume a very late capture time). |
|
|
|
|
|
|
|
3. True, if there was a premature capture (capture error), i.e. final value is different |
|
|
|
|
|
|
|
from captured value. |
|
|
|
|
|
|
|
4. Earliest arrival time. The time at which the output transitioned from its initial value. |
|
|
|
|
|
|
|
5. Latest stabilization time. The time at which the output transitioned to its final value. |
|
|
|
|
|
|
|
6. Overflow indicator. If non-zero, some signals in the input cone of this output had more |
|
|
|
|
|
|
|
transitions than specified in ``wavecaps``. Some transitions have been discarded, the |
|
|
|
|
|
|
|
final values in the waveforms are still valid. |
|
|
|
|
|
|
|
""" |
|
|
|
for i, node in enumerate(self.interface): |
|
|
|
for i, node in enumerate(self.interface): |
|
|
|
if len(node.ins) == 0: continue |
|
|
|
if len(node.ins) == 0: continue |
|
|
|
for p in range(self.sims): |
|
|
|
for p in range(self.sims): |
|
|
@ -320,6 +387,14 @@ class WaveSim: |
|
|
|
return self.cdata |
|
|
|
return self.cdata |
|
|
|
|
|
|
|
|
|
|
|
def reassign(self, time=0.0): |
|
|
|
def reassign(self, time=0.0): |
|
|
|
|
|
|
|
"""Re-assigns the last capture to the appropriate pseudo-primary inputs. Generates a new set of |
|
|
|
|
|
|
|
waveforms at the PPIs that start with the previous final value of that PPI, and transitions at the |
|
|
|
|
|
|
|
given time to the value captured in a previous simulation. :py:func:`~WaveSim.capture` must be called |
|
|
|
|
|
|
|
prior to this function. The final value of each PPI is taken from the randomly sampled concrete logic |
|
|
|
|
|
|
|
values in the capture data. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:param time: The transition time at the inputs (usually 0.0). |
|
|
|
|
|
|
|
""" |
|
|
|
for i in range(len(self.interface)): |
|
|
|
for i in range(len(self.interface)): |
|
|
|
ppi_loc = self.sat[self.ppi_offset + i, 0] |
|
|
|
ppi_loc = self.sat[self.ppi_offset + i, 0] |
|
|
|
ppo_loc = self.sat[self.ppo_offset + i, 0] |
|
|
|
ppo_loc = self.sat[self.ppo_offset + i, 0] |
|
|
@ -544,7 +619,12 @@ def wave_eval(op, state, sat, st_idx, line_times, sd=0.0, seed=0): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class WaveSimCuda(WaveSim): |
|
|
|
class WaveSimCuda(WaveSim): |
|
|
|
"""A GPU-accelerated waveform-based combinational logic timing simulator.""" |
|
|
|
"""A GPU-accelerated waveform-based combinational logic timing simulator. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The API is the same as for :py:class:`WaveSim`. |
|
|
|
|
|
|
|
All internal memories are mirrored into GPU memory upon construction. |
|
|
|
|
|
|
|
Some operations like access to single waveforms can involve large communication overheads. |
|
|
|
|
|
|
|
""" |
|
|
|
def __init__(self, circuit, timing, sims=8, wavecaps=16, strip_forks=False, keep_waveforms=True): |
|
|
|
def __init__(self, circuit, timing, sims=8, wavecaps=16, strip_forks=False, keep_waveforms=True): |
|
|
|
super().__init__(circuit, timing, sims, wavecaps, strip_forks, keep_waveforms) |
|
|
|
super().__init__(circuit, timing, sims, wavecaps, strip_forks, keep_waveforms) |
|
|
|
|
|
|
|
|
|
|
@ -602,10 +682,10 @@ class WaveSimCuda(WaveSim): |
|
|
|
|
|
|
|
|
|
|
|
def wave(self, line, vector): |
|
|
|
def wave(self, line, vector): |
|
|
|
if line < 0: |
|
|
|
if line < 0: |
|
|
|
return None |
|
|
|
return [TMAX] |
|
|
|
mem, wcap, _ = self.sat[line] |
|
|
|
mem, wcap, _ = self.sat[line] |
|
|
|
if mem < 0: |
|
|
|
if mem < 0: |
|
|
|
return None |
|
|
|
return [TMAX] |
|
|
|
return self.d_state[mem:mem + wcap, vector] |
|
|
|
return self.d_state[mem:mem + wcap, vector] |
|
|
|
|
|
|
|
|
|
|
|
def capture(self, time=TMAX, sd=0, seed=1, cdata=None, offset=0): |
|
|
|
def capture(self, time=TMAX, sd=0, seed=1, cdata=None, offset=0): |
|
|
|