Browse Source

documentation improvements

devel
Stefan Holst 4 years ago
parent
commit
8b5a71f498
  1. 7
      Demo.ipynb
  2. 1
      docs/simulators.rst
  3. 5
      src/kyupy/circuit.py
  4. 242
      src/kyupy/logic.py
  5. 4
      src/kyupy/logic_sim.py
  6. 4
      src/kyupy/wave_sim.py

7
Demo.ipynb

@ -489,11 +489,11 @@ @@ -489,11 +489,11 @@
"\n",
"for cell in b14.topological_order():\n",
" if 'DFF' in cell.kind or 'input' == cell.kind:\n",
" levels[cell.index] = 0\n",
" levels[cell] = 0\n",
" elif '__fork__' == cell.kind:\n",
" levels[cell.index] = levels[cell.ins[0].driver.index] # forks only have exactly one driver\n",
" levels[cell] = levels[cell.ins[0].driver] # forks only have exactly one driver\n",
" else:\n",
" levels[cell.index] = max([levels[line.driver.index] for line in cell.ins]) + 1\n",
" levels[cell] = max([levels[line.driver] for line in cell.ins]) + 1\n",
" \n",
"print(f'Maximum logic depth: {np.max(levels)}')"
]
@ -1118,6 +1118,7 @@ @@ -1118,6 +1118,7 @@
"metadata": {},
"source": [
"The capture data contains for each PI, PO, and scan flip-flop (axis 0), and each test (axis 1) seven values:\n",
"\n",
"0. Probability of capturing a 1 at the given capture time (same as next value, if no standard deviation given).\n",
"1. A capture value decided by random sampling according to above probability.\n",
"2. The final value (assume a very late capture time).\n",

1
docs/simulators.rst

@ -14,6 +14,7 @@ Timing Simulation - :mod:`kyupy.wave_sim` @@ -14,6 +14,7 @@ Timing Simulation - :mod:`kyupy.wave_sim`
-----------------------------------------
.. automodule:: kyupy.wave_sim
:members: TMAX, TMAX_OVL, TMIN
.. autoclass:: kyupy.wave_sim.WaveSim
:members:

5
src/kyupy/circuit.py

@ -245,8 +245,9 @@ class Circuit: @@ -245,8 +245,9 @@ class Circuit:
return header + '\n'.join([str(n) for n in self.nodes])
def __repr__(self):
name = f" '{self.name}'" if self.name else ''
return f'<Circuit{name} with {len(self.nodes)} nodes, {len(self.lines)} lines, {len(self.interface)} ports>'
name = f' {self.name}' if self.name else ''
return f'<Circuit{name} cells={len(self.cells)} forks={len(self.forks)} ' + \
f'lines={len(self.lines)} ports={len(self.interface)}>'
def topological_order(self):
"""Generator function to iterate over all nodes in topological order.

242
src/kyupy/logic.py

@ -58,6 +58,12 @@ on a signal. ``'N'``, ``'n'``, and ``'v'`` are interpreted as ``NPULSE``. @@ -58,6 +58,12 @@ on a signal. ``'N'``, ``'n'``, and ``'v'`` are interpreted as ``NPULSE``.
def interpret(value):
"""Converts characters, strings, and lists of them to lists of logic constants defined above.
:param value: A character (string of length 1), Boolean, Integer, None, or Iterable.
Iterables (such as strings) are traversed and their individual characters are interpreted.
:return: A logic constant or a (possibly multi-dimensional) list of logic constants.
"""
if isinstance(value, Iterable) and not (isinstance(value, str) and len(value) == 1):
return list(map(interpret, value))
if value in [0, '0', False, 'L', 'l']:
@ -85,6 +91,79 @@ def bit_in(a, pos): @@ -85,6 +91,79 @@ def bit_in(a, pos):
return a[pos >> 3] & _bit_in_lut[pos & 7]
class MVArray:
"""An n-dimensional array of m-valued logic values.
This class wraps a numpy.ndarray of type uint8 and adds support for encoding and
interpreting 2-valued, 4-valued, and 8-valued logic values.
Each logic value is stored as an uint8, manipulations of individual values are cheaper than in
:py:class:`BPArray`.
:param a: If a tuple is given, it is interpreted as desired shape. To make an array of ``n`` vectors
compatible with a simulator ``sim``, use ``(len(sim.interface), n)``. If a :py:class:`BPArray` or
:py:class:`MVArray` is given, a deep copy is made. If a string, a list of strings, a list of characters,
or a list of lists of characters are given, the data is interpreted best-effort and the array is
initialized accordingly.
:param m: The arity of the logic. Can be set to 2, 4, or 8. If None is given, the arity of a given
:py:class:`BPArray` or :py:class:`MVArray` is used, or, if the array is initialized differently, 8 is used.
"""
def __init__(self, a, m=None):
self.m = m or 8
assert self.m in [2, 4, 8]
# Try our best to interpret given a.
if isinstance(a, MVArray):
self.data = a.data.copy()
"""The wrapped 2-dimensional ndarray of logic values.
* Axis 0 is PI/PO/FF position, the length of this axis is called "width".
* Axis 1 is vector/pattern, the length of this axis is called "length".
"""
self.m = m or a.m
elif hasattr(a, 'data'): # assume it is a BPArray. Can't use isinstance() because BPArray isn't declared yet.
self.data = np.zeros((a.width, a.length), dtype=np.uint8)
self.m = m or a.m
for i in range(a.data.shape[-2]):
self.data[...] <<= 1
self.data[...] |= np.unpackbits(a.data[..., -i-1, :], axis=1)[:, :a.length]
if a.data.shape[-2] == 1:
self.data *= 3
elif isinstance(a, int):
self.data = np.full((a, 1), UNASSIGNED, dtype=np.uint8)
elif isinstance(a, tuple):
self.data = np.full(a, UNASSIGNED, dtype=np.uint8)
else:
if isinstance(a, str): a = [a]
self.data = np.asarray(interpret(a), dtype=np.uint8)
self.data = self.data[:, np.newaxis] if self.data.ndim == 1 else np.moveaxis(self.data, -2, -1)
# Cast data to m-valued logic.
if self.m == 2:
self.data[...] = ((self.data & 0b001) & ((self.data >> 1) & 0b001) | (self.data == RISE)) * ONE
elif self.m == 4:
self.data[...] = (self.data & 0b011) & ((self.data != FALL) * ONE) | ((self.data == RISE) * ONE)
elif self.m == 8:
self.data[...] = self.data & 0b111
self.length = self.data.shape[-1]
self.width = self.data.shape[-2]
def __repr__(self):
return f'<MVArray length={self.length} width={self.width} m={self.m} mem={hr_bytes(self.data.nbytes)}>'
def __str__(self):
return str([self[idx] for idx in range(self.length)])
def __getitem__(self, vector_idx):
"""Returns a string representing the desired vector."""
chars = ["0", "X", "-", "1", "P", "R", "F", "N"]
return ''.join(chars[v] for v in self.data[:, vector_idx])
def __len__(self):
return self.length
def mv_cast(*args, m=8):
return [a if isinstance(a, MVArray) else MVArray(a, m=m) for a in args]
@ -100,6 +179,13 @@ def _mv_not(m, out, inp): @@ -100,6 +179,13 @@ def _mv_not(m, out, inp):
def mv_not(x1, out=None):
"""A multi-valued NOT operator.
:param x1: An :py:class:`MVArray` or data the :py:class:`MVArray` constructor accepts.
:param out: Optionally an :py:class:`MVArray` as storage destination. If None, a new :py:class:`MVArray`
is returned.
:return: An :py:class:`MVArray` with the result.
"""
m = mv_getm(x1)
x1 = mv_cast(x1, m=m)[0]
out = out or MVArray(x1.data.shape, m=m)
@ -125,6 +211,14 @@ def _mv_or(m, out, *ins): @@ -125,6 +211,14 @@ def _mv_or(m, out, *ins):
def mv_or(x1, x2, out=None):
"""A multi-valued OR operator.
:param x1: An :py:class:`MVArray` or data the :py:class:`MVArray` constructor accepts.
:param x2: An :py:class:`MVArray` or data the :py:class:`MVArray` constructor accepts.
:param out: Optionally an :py:class:`MVArray` as storage destination. If None, a new :py:class:`MVArray`
is returned.
:return: An :py:class:`MVArray` with the result.
"""
m = mv_getm(x1, x2)
x1, x2 = mv_cast(x1, x2, m=m)
out = out or MVArray(np.broadcast(x1.data, x2.data).shape, m=m)
@ -151,6 +245,14 @@ def _mv_and(m, out, *ins): @@ -151,6 +245,14 @@ def _mv_and(m, out, *ins):
def mv_and(x1, x2, out=None):
"""A multi-valued AND operator.
:param x1: An :py:class:`MVArray` or data the :py:class:`MVArray` constructor accepts.
:param x2: An :py:class:`MVArray` or data the :py:class:`MVArray` constructor accepts.
:param out: Optionally an :py:class:`MVArray` as storage destination. If None, a new :py:class:`MVArray`
is returned.
:return: An :py:class:`MVArray` with the result.
"""
m = mv_getm(x1, x2)
x1, x2 = mv_cast(x1, x2, m=m)
out = out or MVArray(np.broadcast(x1.data, x2.data).shape, m=m)
@ -174,6 +276,14 @@ def _mv_xor(m, out, *ins): @@ -174,6 +276,14 @@ def _mv_xor(m, out, *ins):
def mv_xor(x1, x2, out=None):
"""A multi-valued XOR operator.
:param x1: An :py:class:`MVArray` or data the :py:class:`MVArray` constructor accepts.
:param x2: An :py:class:`MVArray` or data the :py:class:`MVArray` constructor accepts.
:param out: Optionally an :py:class:`MVArray` as storage destination. If None, a new :py:class:`MVArray`
is returned.
:return: An :py:class:`MVArray` with the result.
"""
m = mv_getm(x1, x2)
x1, x2 = mv_cast(x1, x2, m=m)
out = out or MVArray(np.broadcast(x1.data, x2.data).shape, m=m)
@ -182,6 +292,16 @@ def mv_xor(x1, x2, out=None): @@ -182,6 +292,16 @@ def mv_xor(x1, x2, out=None):
def mv_transition(init, final, out=None):
"""Computes the logic transitions from the initial values of ``init`` to the final values of ``final``.
Pulses in the input data are ignored. If any of the inputs are ``UNKNOWN``, the result is ``UNKNOWN``.
If both inputs are ``UNASSIGNED``, the result is ``UNASSIGNED``.
:param init: An :py:class:`MVArray` or data the :py:class:`MVArray` constructor accepts.
:param final: An :py:class:`MVArray` or data the :py:class:`MVArray` constructor accepts.
:param out: Optionally an :py:class:`MVArray` as storage destination. If None, a new :py:class:`MVArray`
is returned.
:return: An :py:class:`MVArray` with the result.
"""
m = mv_getm(init, final)
init, final = mv_cast(init, final, m=m)
init = init.data
@ -196,65 +316,46 @@ def mv_transition(init, final, out=None): @@ -196,65 +316,46 @@ def mv_transition(init, final, out=None):
return out
class MVArray:
"""An n-dimensional array of m-valued logic values.
This class wraps a numpy.ndarray of type uint8 and adds support for encoding and
interpreting 2-valued, 4-valued, and 8-valued logic values.
Each logic value is stored as an uint8, value manipulations are cheaper than in BPArray.
An MVArray always has 2 axes:
class BPArray:
"""An n-dimensional array of m-valued logic values that uses bit-parallel storage.
* Axis 0 is PI/PO/FF position, the length of this axis is called "width".
* Axis 1 is vector/pattern, the length of this axis is called "length".
The primary use of this format is in aiding efficient bit-parallel logic simulation.
The secondary benefit over :py:class:`MVArray` is its memory efficiency.
Accessing individual values is more expensive than with :py:class:`MVArray`.
Therefore it may be more efficient to unpack the data into an :py:class:`MVArray` and pack it again into a
:py:class:`BPArray` for simulation.
See :py:class:`MVArray` for constructor parameters.
"""
def __init__(self, a, m=None):
self.m = m or 8
assert self.m in [2, 4, 8]
# Try our best to interpret given a.
if not isinstance(a, MVArray) and not isinstance(a, BPArray):
a = MVArray(a, m)
self.m = a.m
if isinstance(a, MVArray):
self.data = a.data.copy()
self.m = m or a.m
elif hasattr(a, 'data'): # assume it is a BPArray. Can't use isinstance() because BPArray isn't declared yet.
self.data = np.zeros((a.width, a.length), dtype=np.uint8)
self.m = m or a.m
for i in range(a.data.shape[-2]):
self.data[...] <<= 1
self.data[...] |= np.unpackbits(a.data[..., -i-1, :], axis=1)[:, :a.length]
if a.data.shape[-2] == 1:
self.data *= 3
elif isinstance(a, int):
self.data = np.full((a, 1), UNASSIGNED, dtype=np.uint8)
elif isinstance(a, tuple):
self.data = np.full(a, UNASSIGNED, dtype=np.uint8)
else:
if isinstance(a, str): a = [a]
self.data = np.asarray(interpret(a), dtype=np.uint8)
self.data = self.data[:, np.newaxis] if self.data.ndim == 1 else np.moveaxis(self.data, -2, -1)
# Cast data to m-valued logic.
if self.m == 2:
self.data[...] = ((self.data & 0b001) & ((self.data >> 1) & 0b001) | (self.data == RISE)) * ONE
elif self.m == 4:
self.data[...] = (self.data & 0b011) & ((self.data != FALL) * ONE) | ((self.data == RISE) * ONE)
elif self.m == 8:
self.data[...] = self.data & 0b111
if m is not None and m != a.m:
a = MVArray(a, m) # cast data
self.m = a.m
assert self.m in [2, 4, 8]
nwords = math.ceil(math.log2(self.m))
nbytes = (a.data.shape[-1] - 1) // 8 + 1
self.data = np.zeros(a.data.shape[:-1] + (nwords, nbytes), dtype=np.uint8)
"""The wrapped 3-dimensional ndarray.
self.length = self.data.shape[-1]
self.width = self.data.shape[-2]
* Axis 0 is PI/PO/FF position, the length of this axis is called "width".
* Axis 1 has length ``ceil(log2(m))`` for storing all bits.
* Axis 2 are the vectors/patterns packed into uint8 words.
"""
for i in range(self.data.shape[-2]):
self.data[..., i, :] = np.packbits((a.data >> i) & 1, axis=-1)
else: # we have a BPArray
self.data = a.data.copy() # TODO: support conversion to different m
self.m = a.m
self.length = a.length
self.width = a.width
def __repr__(self):
return f'<MVArray length={self.length} width={self.width} m={self.m} mem={hr_bytes(self.data.nbytes)}>'
def __str__(self):
return str([self[idx] for idx in range(self.length)])
def __getitem__(self, vector_idx):
chars = ["0", "X", "-", "1", "P", "R", "F", "N"]
return ''.join(chars[v] for v in self.data[:, vector_idx])
return f'<BPArray length={self.length} width={self.width} m={self.m} mem={hr_bytes(self.data.nbytes)}>'
def __len__(self):
return self.length
@ -359,44 +460,3 @@ def bp_xor(out, *ins): @@ -359,44 +460,3 @@ def bp_xor(out, *ins):
out[..., 0, :] |= any_unknown
out[..., 1, :] &= ~any_unknown
out[..., 2, :] &= ~any_unknown
class BPArray:
"""An n-dimensional array of m-valued logic values that uses bit-parallel storage.
The primary use of this format is in aiding efficient bit-parallel logic simulation.
The secondary benefit over MVArray is its memory efficiency.
Accessing individual values is more expensive than with :py:class:`MVArray`.
It is advised to first construct a MVArray, pack it into a :py:class:`BPArray` for simulation and unpack the results
back into a :py:class:`MVArray` for value access.
The values along the last axis (vectors/patterns) are packed into uint8 words.
The second-last axis has length ceil(log2(m)) for storing all bits.
All other axes stay the same as in MVArray.
"""
def __init__(self, a, m=None):
if not isinstance(a, MVArray) and not isinstance(a, BPArray):
a = MVArray(a, m)
self.m = a.m
if isinstance(a, MVArray):
if m is not None and m != a.m:
a = MVArray(a, m) # cast data
self.m = a.m
assert self.m in [2, 4, 8]
nwords = math.ceil(math.log2(self.m))
nbytes = (a.data.shape[-1] - 1) // 8 + 1
self.data = np.zeros(a.data.shape[:-1] + (nwords, nbytes), dtype=np.uint8)
for i in range(self.data.shape[-2]):
self.data[..., i, :] = np.packbits((a.data >> i) & 1, axis=-1)
else: # we have a BPArray
self.data = a.data.copy() # TODO: support conversion to different m
self.m = a.m
self.length = a.length
self.width = a.width
def __repr__(self):
return f'<BPArray length={self.length} width={self.width} m={self.m} mem={hr_bytes(self.data.nbytes)}>'
def __len__(self):
return self.length

4
src/kyupy/logic_sim.py

@ -126,10 +126,10 @@ class LogicSim: @@ -126,10 +126,10 @@ class LogicSim:
def cycle(self, state, inject_cb=None):
"""Assigns the given state, propagates it and captures the new state.
:param responses: A bit-parallel array in a compatible shape holding the current circuit state.
:param state: A bit-parallel array in a compatible shape holding the current circuit state.
The contained data is assigned to the PI and PPI and overwritten by data at the PO and PPO after
propagation.
:type responses: :py:class:`~kyupy.logic.BPArray`
:type state: :py:class:`~kyupy.logic.BPArray`
:param inject_cb: A callback function for manipulating intermediate signal values. See :py:func:`propagate`.
:returns: The given state object.
"""

4
src/kyupy/wave_sim.py

@ -332,8 +332,8 @@ class WaveSim: @@ -332,8 +332,8 @@ class WaveSim:
self.lst_eat_valid = False
def wave(self, line, vector):
"""Returns the desired waveform from the simulation state. Only valid, if simulator was
instanciated with ``keep_waveforms=True``."""
# """Returns the desired waveform from the simulation state. Only valid, if simulator was
# instantiated with ``keep_waveforms=True``."""
if line < 0:
return [TMAX]
mem, wcap, _ = self.sat[line]

Loading…
Cancel
Save