From 18c17b5f76f64d8bdb0120e80598a0a6e038d39d Mon Sep 17 00:00:00 2001 From: Stefan Holst Date: Wed, 6 Jan 2021 11:32:43 +0900 Subject: [PATCH] more docs and reprs --- docs/conf.py | 2 +- docs/simulators.rst | 2 ++ src/kyupy/__init__.py | 19 +++++++++++++++ src/kyupy/logic_sim.py | 55 +++++++++++++++++++++++++++++++++++++----- src/kyupy/wave_sim.py | 28 ++++++++++++--------- 5 files changed, 88 insertions(+), 18 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index cb2e436..a71e8e4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -24,7 +24,7 @@ copyright = '2020, Stefan Holst' author = 'Stefan Holst' # The full version, including alpha/beta/rc tags -release = '0.0.2' +release = '0.0.3' # -- General configuration --------------------------------------------------- diff --git a/docs/simulators.rst b/docs/simulators.rst index 8d5f6b6..1cb48bb 100644 --- a/docs/simulators.rst +++ b/docs/simulators.rst @@ -4,6 +4,8 @@ Simulators Logic Simulation - :mod:`kyupy.logic_sim` ----------------------------------------- +.. automodule:: kyupy.logic_sim + .. autoclass:: kyupy.logic_sim.LogicSim :members: diff --git a/src/kyupy/__init__.py b/src/kyupy/__init__.py index d1bb8db..389858e 100644 --- a/src/kyupy/__init__.py +++ b/src/kyupy/__init__.py @@ -10,6 +10,25 @@ import gzip 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: def __init__(self): self.start = time.perf_counter() diff --git a/src/kyupy/logic_sim.py b/src/kyupy/logic_sim.py index 70b3d18..fee054f 100644 --- a/src/kyupy/logic_sim.py +++ b/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 numpy as np -from . import logic +from . import logic, hr_bytes class LogicSim: """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] self.m = m mdim = math.ceil(math.log2(m)) @@ -40,8 +55,15 @@ class LogicSim: raise ValueError(f'Unknown node kind {n.kind}') self.node_fct.append(fcts[0]) + def __repr__(self): + return f'' + 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'): stimuli = stimuli.data for stim, node in zip(stimuli, self.interface): @@ -61,16 +83,37 @@ class LogicSim: self.state_epoch[line.reader.index] = self.epoch 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'): responses = responses.data for resp, node in zip(responses, self.interface): if len(node.ins) == 0: continue resp[...] = self.state[node.ins[0].index] - # print(responses) 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(): if self.state_epoch[node.index] != self.epoch: continue inputs = [self.state[line.index] if line else self.zero for line in node.ins] diff --git a/src/kyupy/wave_sim.py b/src/kyupy/wave_sim.py index 2766997..a5b604c 100644 --- a/src/kyupy/wave_sim.py +++ b/src/kyupy/wave_sim.py @@ -1,6 +1,6 @@ """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. 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 @@ -17,8 +17,7 @@ from bisect import bisect, insort_left import numpy as np -from . import numba -from . import cuda +from . import numba, cuda, hr_bytes TMAX = np.float32(2 ** 127) # almost np.PINF for 32-bit floating point values @@ -253,6 +252,12 @@ class WaveSim: m0 = ~m1 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'' + def get_line_delay(self, line, polarity): return self.timing[line, 0, polarity] @@ -283,10 +288,7 @@ class WaveSim: self.state[ppi_loc + toggle, p] = TMAX def propagate(self, sims=None, sd=0.0, seed=1): - if sims is None: - sims = self.sims - else: - sims = min(sims, self.sims) + sims = min(sims or self.sims, self.sims) 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.timing, sd, seed) @@ -559,6 +561,13 @@ class WaveSimCuda(WaveSim): 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'' + def get_line_delay(self, line, polarity): return self.d_timing[line, 0, polarity] @@ -586,10 +595,7 @@ class WaveSimCuda(WaveSim): return gx, gy def propagate(self, sims=None, sd=0.0, seed=1): - if sims is None: - sims = self.sims - else: - sims = min(sims, self.sims) + sims = min(sims or self.sims, self.sims) for op_start, op_stop in zip(self.level_starts, self.level_stops): 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),