Browse Source

more docs and reprs

devel
Stefan Holst 4 years ago
parent
commit
18c17b5f76
  1. 2
      docs/conf.py
  2. 2
      docs/simulators.rst
  3. 19
      src/kyupy/__init__.py
  4. 55
      src/kyupy/logic_sim.py
  5. 28
      src/kyupy/wave_sim.py

2
docs/conf.py

@ -24,7 +24,7 @@ copyright = '2020, Stefan Holst'
author = 'Stefan Holst' author = 'Stefan Holst'
# The full version, including alpha/beta/rc tags # The full version, including alpha/beta/rc tags
release = '0.0.2' release = '0.0.3'
# -- General configuration --------------------------------------------------- # -- General configuration ---------------------------------------------------

2
docs/simulators.rst

@ -4,6 +4,8 @@ Simulators
Logic Simulation - :mod:`kyupy.logic_sim` Logic Simulation - :mod:`kyupy.logic_sim`
----------------------------------------- -----------------------------------------
.. automodule:: kyupy.logic_sim
.. autoclass:: kyupy.logic_sim.LogicSim .. autoclass:: kyupy.logic_sim.LogicSim
:members: :members:

19
src/kyupy/__init__.py

@ -10,6 +10,25 @@ import gzip
import numpy as np import numpy as np
def hr_sci(value):
multiplier = 0
while abs(value) >= 1000:
value /= 1000
multiplier += 1
while abs(value) < 1:
value *= 1000
multiplier -= 1
return f'{value:.3f}{" kMGTafpnµm"[multiplier]}'
def hr_bytes(nbytes):
multiplier = 0
while abs(nbytes) >= 1000:
nbytes /= 1024
multiplier += 1
return f'{nbytes:.1f}{["", "ki", "Mi", "Gi", "Ti", "Pi"][multiplier]}B'
class Log: class Log:
def __init__(self): def __init__(self):
self.start = time.perf_counter() self.start = time.perf_counter()

55
src/kyupy/logic_sim.py

@ -1,14 +1,29 @@
"""A High-Throughput combinational logic simulator.
The class :py:class:`~kyupy.logic_sim.LogicSim` performs parallel simulations of the combinational part of a circuit.
The logic operations are performed bit-parallel on packed numpy arrays.
Simple sequential circuits can be simulated by repeated assignments and propagations.
However, this simulator ignores the clock network and simply assumes that all state-elements are clocked all the time.
"""
import math import math
import numpy as np import numpy as np
from . import logic from . import logic, hr_bytes
class LogicSim: class LogicSim:
"""A bit-parallel naïve combinational simulator for 2-, 4-, or 8-valued logic. """A bit-parallel naïve combinational simulator for 2-, 4-, or 8-valued logic.
:param circuit: The circuit to simulate.
:type circuit: :py:class:`~kyupy.circuit.Circuit`
:param sims: The number of parallel logic simulations to perform.
:type sims: int
:param m: The arity of the logic, must be 2, 4, or 8.
:type m: int
""" """
def __init__(self, circuit, sims=1, m=8): def __init__(self, circuit, sims=8, m=8):
assert m in [2, 4, 8] assert m in [2, 4, 8]
self.m = m self.m = m
mdim = math.ceil(math.log2(m)) mdim = math.ceil(math.log2(m))
@ -40,8 +55,15 @@ class LogicSim:
raise ValueError(f'Unknown node kind {n.kind}') raise ValueError(f'Unknown node kind {n.kind}')
self.node_fct.append(fcts[0]) self.node_fct.append(fcts[0])
def __repr__(self):
return f'<LogicSim {self.circuit.name} sims={self.sims} m={self.m} state_mem={hr_bytes(self.state.nbytes)}>'
def assign(self, stimuli): def assign(self, stimuli):
"""Assign stimuli to the primary inputs and state-elements (flip-flops).""" """Assign stimuli to the primary inputs and state-elements (flip-flops).
:param stimuli: The input data to assign. Must be in bit-parallel storage format and in a compatible shape.
:type stimuli: :py:class:`~kyupy.logic.BPArray`
"""
if hasattr(stimuli, 'data'): if hasattr(stimuli, 'data'):
stimuli = stimuli.data stimuli = stimuli.data
for stim, node in zip(stimuli, self.interface): for stim, node in zip(stimuli, self.interface):
@ -61,16 +83,37 @@ class LogicSim:
self.state_epoch[line.reader.index] = self.epoch self.state_epoch[line.reader.index] = self.epoch
def capture(self, responses): def capture(self, responses):
"""Capture the current values at the primary outputs and in the state-elements (flip-flops).""" """Capture the current values at the primary outputs and in the state-elements (flip-flops).
:param responses: A bit-parallel storage target for the responses in a compatible shape.
:type responses: :py:class:`~kyupy.logic.BPArray`
"""
if hasattr(responses, 'data'): if hasattr(responses, 'data'):
responses = responses.data responses = responses.data
for resp, node in zip(responses, self.interface): for resp, node in zip(responses, self.interface):
if len(node.ins) == 0: continue if len(node.ins) == 0: continue
resp[...] = self.state[node.ins[0].index] resp[...] = self.state[node.ins[0].index]
# print(responses)
def propagate(self, inject_cb=None): def propagate(self, inject_cb=None):
"""Propagate the input values towards the outputs (Perform all logic operations in topological order).""" """Propagate the input values towards the outputs (Perform all logic operations in topological order).
If the circuit is sequential (it contains flip-flops), one call simulates one clock cycle.
Multiple clock cycles are simulated by a assign-propagate-capture loop:
.. code-block:: python
# initial state in state_bp
for cycle in range(10): # simulate 10 clock cycles
sim.assign(state_bp)
sim.propagate()
sim.capture(state_bp)
:param inject_cb: A callback function for manipulating intermediate signal values.
This function is called with a line index and its new logic values (in bit-parallel format) after
evaluation of a node. The callback may manipulate the given values in-place, the simulation
resumes with the manipulated values after the callback returns.
:type inject_cb: ``f(int, ndarray)``
"""
for node in self.circuit.topological_order(): for node in self.circuit.topological_order():
if self.state_epoch[node.index] != self.epoch: continue if self.state_epoch[node.index] != self.epoch: continue
inputs = [self.state[line.index] if line else self.zero for line in node.ins] inputs = [self.state[line.index] if line else self.zero for line in node.ins]

28
src/kyupy/wave_sim.py

@ -1,6 +1,6 @@
"""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 achieve
@ -17,8 +17,7 @@ from bisect import bisect, insort_left
import numpy as np import numpy as np
from . import numba from . import numba, cuda, hr_bytes
from . import cuda
TMAX = np.float32(2 ** 127) # almost np.PINF for 32-bit floating point values TMAX = np.float32(2 ** 127) # almost np.PINF for 32-bit floating point values
@ -253,6 +252,12 @@ class WaveSim:
m0 = ~m1 m0 = ~m1
self.mask = np.rollaxis(np.vstack((m0, m1)), 1) self.mask = np.rollaxis(np.vstack((m0, m1)), 1)
def __repr__(self):
total_mem = self.state.nbytes + self.sat.nbytes + self.ops.nbytes + self.cdata.nbytes
return f'<WaveSim {self.circuit.name} sims={self.sims} ops={len(self.ops)} ' + \
f'levels={len(self.level_starts)} state_mem={hr_bytes(self.state.nbytes)} ' + \
f'total_mem={hr_bytes(total_mem)}>'
def get_line_delay(self, line, polarity): def get_line_delay(self, line, polarity):
return self.timing[line, 0, polarity] return self.timing[line, 0, polarity]
@ -283,10 +288,7 @@ 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):
if sims is None: sims = min(sims or self.sims, self.sims)
sims = self.sims
else:
sims = min(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,
self.timing, sd, seed) self.timing, sd, seed)
@ -559,6 +561,13 @@ class WaveSimCuda(WaveSim):
self._block_dim = (32, 16) self._block_dim = (32, 16)
def __repr__(self):
total_mem = self.state.nbytes + self.sat.nbytes + self.ops.nbytes + self.timing.nbytes + \
self.tdata.nbytes + self.cdata.nbytes
return f'<WaveSimCuda {self.circuit.name} sims={self.sims} ops={len(self.ops)} ' + \
f'levels={len(self.level_starts)} state_mem={hr_bytes(self.state.nbytes)} ' + \
f'total_mem={hr_bytes(total_mem)}>'
def get_line_delay(self, line, polarity): def get_line_delay(self, line, polarity):
return self.d_timing[line, 0, polarity] return self.d_timing[line, 0, polarity]
@ -586,10 +595,7 @@ class WaveSimCuda(WaveSim):
return gx, gy return gx, gy
def propagate(self, sims=None, sd=0.0, seed=1): def propagate(self, sims=None, sd=0.0, seed=1):
if sims is None: sims = min(sims or self.sims, self.sims)
sims = self.sims
else:
sims = min(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):
grid_dim = self._grid_dim(sims, op_stop - op_start) grid_dim = self._grid_dim(sims, op_stop - op_start)
wave_kernel[grid_dim, self._block_dim](self.d_ops, op_start, op_stop, self.d_state, self.sat, int(0), wave_kernel[grid_dim, self._block_dim](self.d_ops, op_start, op_stop, self.d_state, self.sat, int(0),

Loading…
Cancel
Save