Browse Source

faster logic sim, removing MVArray, BPArray

devel
stefan 2 years ago
parent
commit
1eb8d87884
  1. 7
      src/kyupy/circuit.py
  2. 479
      src/kyupy/logic.py
  3. 312
      src/kyupy/logic_sim.py
  4. 18
      src/kyupy/sim.py
  5. 56
      src/kyupy/stil.py
  6. 313
      tests/test_logic.py
  7. 185
      tests/test_logic_sim.py
  8. 4
      tests/test_stil.py
  9. 57
      tests/test_wave_sim.py
  10. 138
      tests/test_wave_sim_old.py

7
src/kyupy/circuit.py

@ -391,10 +391,7 @@ class Circuit: @@ -391,10 +391,7 @@ class Circuit:
d = d[j]
d[path[-1]] = i
def sorted_values(d):
return [sorted_values(v) for k, v in sorted(d.items())] if isinstance(d, dict) else d
def sorted_values(d): return [sorted_values(v) for k, v in sorted(d.items())] if isinstance(d, dict) else d
l = sorted_values(d_top)
while isinstance(l, list) and len(l) == 1: l = l[0]
return l
return None if isinstance(l, list) and len(l) == 0 else l

479
src/kyupy/logic.py

@ -66,20 +66,13 @@ def interpret(value): @@ -66,20 +66,13 @@ def interpret(value):
"""
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']:
return ZERO
if value in [1, '1', True, 'H', 'h']:
return ONE
if value in [None, '-', 'Z', 'z']:
return UNASSIGNED
if value in ['R', 'r', '/']:
return RISE
if value in ['F', 'f', '\\']:
return FALL
if value in ['P', 'p', '^']:
return PPULSE
if value in ['N', 'n', 'v']:
return NPULSE
if value in [0, '0', False, 'L', 'l']: return ZERO
if value in [1, '1', True, 'H', 'h']: return ONE
if value in [None, '-', 'Z', 'z']: return UNASSIGNED
if value in ['R', 'r', '/']: return RISE
if value in ['F', 'f', '\\']: return FALL
if value in ['P', 'p', '^']: return PPULSE
if value in ['N', 'n', 'v']: return NPULSE
return UNKNOWN
@ -91,18 +84,21 @@ def bit_in(a, pos): @@ -91,18 +84,21 @@ def bit_in(a, pos):
return a[pos >> 3] & _bit_in_lut[pos & 7]
def unpackbits(a):
"""Returns an uint8-array containing the unpacked bits of an array `a`.
Preserves the shape of `a` and adds a new last axis with the bits of each item.
Bits are returned in 'little'-order, i.e., a[...,0] is the least significant bit.
def unpackbits(a : np.ndarray):
"""Unpacks the bits of given ndarray `a`.
Similar to `np.unpackbits`, but accepts any dtype, preserves the shape of `a` and
adds a new last axis with the bits of each item. Bits are in 'little'-order, i.e.,
a[...,0] is the least significant bit.
"""
return np.unpackbits(a.view(np.uint8), bitorder='little').reshape(*a.shape, 8*a.itemsize)
def packbits(a, dtype=np.uint8):
"""Packs the values of a boolean-valued array `a` along its last axis into bits.
Returns the bits as an array of given type and the shape of `a` with the last axis removed.
The last axis of `a` is truncated or padded according to the bit-width of the given data type.
Similary to `np.packbits`, but returns an array of given dtype and the shape of `a` with the last axis removed.
The last axis of `a` is truncated or padded according to the bit-width of the given dtype.
Signed integer datatypes are padded with the most significant bit, all others are padded with `0`.
"""
dtype = np.dtype(dtype)
@ -114,94 +110,32 @@ def packbits(a, dtype=np.uint8): @@ -114,94 +110,32 @@ def packbits(a, dtype=np.uint8):
return np.packbits(a, bitorder='little').view(dtype).reshape(a.shape[:-1])
class MVArray:
"""An n-dimensional array of m-valued logic values.
def mvarray(*a):
mva = np.array(interpret(a), dtype=np.uint8)
if mva.ndim < 2: return mva
if mva.shape[-2] > 1: return mva.swapaxes(-1, -2)
return mva[..., 0, :]
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 mv_to_bp(mva):
if mva.ndim == 1: mva = mva[..., np.newaxis]
return np.packbits(unpackbits(mva)[...,:3], axis=-2, bitorder='little').swapaxes(-1,-2)
def bparray(*a):
return mv_to_bp(mvarray(*a))
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]
def mv_getm(*args):
return max([a.m for a in args if isinstance(a, MVArray)] + [0]) or 8
def _mv_not(m, out, inp):
def bp_to_mv(bpa):
return packbits(np.unpackbits(bpa, axis=-1, bitorder='little').swapaxes(-1,-2))
def _mv_not(out, inp):
np.bitwise_xor(inp, 0b11, out=out) # this also exchanges UNASSIGNED <-> UNKNOWN
if m > 2:
np.putmask(out, (inp == UNKNOWN), UNKNOWN) # restore UNKNOWN
np.putmask(out, (inp == UNKNOWN), UNKNOWN) # restore UNKNOWN
def mv_not(x1, out=None):
def mv_not(x1 : np.ndarray, out=None):
"""A multi-valued NOT operator.
:param x1: An :py:class:`MVArray` or data the :py:class:`MVArray` constructor accepts.
@ -209,28 +143,24 @@ def mv_not(x1, out=None): @@ -209,28 +143,24 @@ def mv_not(x1, out=None):
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)
_mv_not(m, out.data, x1.data)
#m = mv_getm(x1)
#x1 = mv_cast(x1, m=m)[0]
out = out or np.empty(x1.shape, dtype=np.uint8)
_mv_not(out, x1)
return out
def _mv_or(m, out, *ins):
if m > 2:
any_unknown = (ins[0] == UNKNOWN) | (ins[0] == UNASSIGNED)
for inp in ins[1:]: any_unknown |= (inp == UNKNOWN) | (inp == UNASSIGNED)
any_one = (ins[0] == ONE)
for inp in ins[1:]: any_one |= (inp == ONE)
def _mv_or(out, *ins):
any_unknown = (ins[0] == UNKNOWN) | (ins[0] == UNASSIGNED)
for inp in ins[1:]: any_unknown |= (inp == UNKNOWN) | (inp == UNASSIGNED)
any_one = (ins[0] == ONE)
for inp in ins[1:]: any_one |= (inp == ONE)
out[...] = ZERO
np.putmask(out, any_one, ONE)
for inp in ins:
np.bitwise_or(out, inp, out=out, where=~any_one)
np.putmask(out, (any_unknown & ~any_one), UNKNOWN)
else:
out[...] = ZERO
for inp in ins: np.bitwise_or(out, inp, out=out)
out[...] = ZERO
np.putmask(out, any_one, ONE)
for inp in ins:
np.bitwise_or(out, inp, out=out, where=~any_one)
np.putmask(out, (any_unknown & ~any_one), UNKNOWN)
def mv_or(x1, x2, out=None):
@ -242,29 +172,25 @@ def mv_or(x1, x2, out=None): @@ -242,29 +172,25 @@ def mv_or(x1, x2, out=None):
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)
_mv_or(m, out.data, x1.data, x2.data)
#m = mv_getm(x1, x2)
#x1, x2 = mv_cast(x1, x2, m=m)
out = out or np.empty(np.broadcast(x1, x2).shape, dtype=np.uint8)
_mv_or(out, x1, x2)
return out
def _mv_and(m, out, *ins):
if m > 2:
any_unknown = (ins[0] == UNKNOWN) | (ins[0] == UNASSIGNED)
for inp in ins[1:]: any_unknown |= (inp == UNKNOWN) | (inp == UNASSIGNED)
any_zero = (ins[0] == ZERO)
for inp in ins[1:]: any_zero |= (inp == ZERO)
def _mv_and(out, *ins):
any_unknown = (ins[0] == UNKNOWN) | (ins[0] == UNASSIGNED)
for inp in ins[1:]: any_unknown |= (inp == UNKNOWN) | (inp == UNASSIGNED)
any_zero = (ins[0] == ZERO)
for inp in ins[1:]: any_zero |= (inp == ZERO)
out[...] = ONE
np.putmask(out, any_zero, ZERO)
for inp in ins:
np.bitwise_and(out, inp | 0b100, out=out, where=~any_zero)
if m > 4: np.bitwise_or(out, inp & 0b100, out=out, where=~any_zero)
np.putmask(out, (any_unknown & ~any_zero), UNKNOWN)
else:
out[...] = ONE
for inp in ins: np.bitwise_and(out, inp, out=out)
out[...] = ONE
np.putmask(out, any_zero, ZERO)
for inp in ins:
np.bitwise_and(out, inp | 0b100, out=out, where=~any_zero)
np.bitwise_or(out, inp & 0b100, out=out, where=~any_zero)
np.putmask(out, (any_unknown & ~any_zero), UNKNOWN)
def mv_and(x1, x2, out=None):
@ -276,26 +202,22 @@ def mv_and(x1, x2, out=None): @@ -276,26 +202,22 @@ def mv_and(x1, x2, out=None):
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)
_mv_and(m, out.data, x1.data, x2.data)
#m = mv_getm(x1, x2)
#x1, x2 = mv_cast(x1, x2, m=m)
out = out or np.empty(np.broadcast(x1, x2).shape, dtype=np.uint8)
_mv_and(out, x1, x2)
return out
def _mv_xor(m, out, *ins):
if m > 2:
any_unknown = (ins[0] == UNKNOWN) | (ins[0] == UNASSIGNED)
for inp in ins[1:]: any_unknown |= (inp == UNKNOWN) | (inp == UNASSIGNED)
def _mv_xor(out, *ins):
any_unknown = (ins[0] == UNKNOWN) | (ins[0] == UNASSIGNED)
for inp in ins[1:]: any_unknown |= (inp == UNKNOWN) | (inp == UNASSIGNED)
out[...] = ZERO
for inp in ins:
np.bitwise_xor(out, inp & 0b011, out=out)
if m > 4: np.bitwise_or(out, inp & 0b100, out=out)
np.putmask(out, any_unknown, UNKNOWN)
else:
out[...] = ZERO
for inp in ins: np.bitwise_xor(out, inp, out=out)
out[...] = ZERO
for inp in ins:
np.bitwise_xor(out, inp & 0b011, out=out)
np.bitwise_or(out, inp & 0b100, out=out)
np.putmask(out, any_unknown, UNKNOWN)
def mv_xor(x1, x2, out=None):
@ -307,27 +229,27 @@ def mv_xor(x1, x2, out=None): @@ -307,27 +229,27 @@ def mv_xor(x1, x2, out=None):
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)
_mv_xor(m, out.data, x1.data, x2.data)
#m = mv_getm(x1, x2)
#x1, x2 = mv_cast(x1, x2, m=m)
out = out or np.empty(np.broadcast(x1, x2).shape, dtype=np.uint8)
_mv_xor(out, x1, x2)
return out
def mv_latch(d, t, q_prev, out=None):
"""A latch that is transparent if `t` is high. `q_prev` has to be the output value from the previous clock cycle.
"""
m = mv_getm(d, t, q_prev)
d, t, q_prev = mv_cast(d, t, q_prev, m=m)
out = out or MVArray(np.broadcast(d.data, t.data, q_prev).shape, m=m)
out.data[...] = t.data & d.data & 0b011
out.data[...] |= ~t.data & 0b010 & (q_prev.data << 1)
out.data[...] |= ~t.data & 0b001 & (out.data >> 1)
out.data[...] |= ((out.data << 1) ^ (out.data << 2)) & 0b100
unknown = (t.data == UNKNOWN) \
| (t.data == UNASSIGNED) \
| (((d.data == UNKNOWN) | (d.data == UNASSIGNED)) & (t.data != ZERO))
np.putmask(out.data, unknown, UNKNOWN)
#m = mv_getm(d, t, q_prev)
#d, t, q_prev = mv_cast(d, t, q_prev, m=m)
out = out or np.empty(np.broadcast(d, t, q_prev).shape, dtype=np.uint8)
out[...] = t & d & 0b011
out[...] |= ~t & 0b010 & (q_prev << 1)
out[...] |= ~t & 0b001 & (out >> 1)
out[...] |= ((out << 1) ^ (out << 2)) & 0b100
unknown = (t == UNKNOWN) \
| (t == UNASSIGNED) \
| (((d == UNKNOWN) | (d == UNASSIGNED)) & (t != ZERO))
np.putmask(out, unknown, UNKNOWN)
return out
@ -342,185 +264,86 @@ def mv_transition(init, final, out=None): @@ -342,185 +264,86 @@ def mv_transition(init, final, out=None):
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
final = final.data
out = out or MVArray(np.broadcast(init, final).shape, m=8)
out.data[...] = (init & 0b010) | (final & 0b001)
out.data[...] |= ((out.data << 1) ^ (out.data << 2)) & 0b100
#m = mv_getm(init, final)
#init, final = mv_cast(init, final, m=m)
#init = init.data
#final = final.data
out = out or np.empty(np.broadcast(init, final).shape, dtype=np.uint8)
out[...] = (init & 0b010) | (final & 0b001)
out[...] |= ((out << 1) ^ (out << 2)) & 0b100
unknown = (init == UNKNOWN) | (init == UNASSIGNED) | (final == UNKNOWN) | (final == UNASSIGNED)
unassigned = (init == UNASSIGNED) & (final == UNASSIGNED)
np.putmask(out.data, unknown, UNKNOWN)
np.putmask(out.data, unassigned, UNASSIGNED)
np.putmask(out, unknown, UNKNOWN)
np.putmask(out, unassigned, UNASSIGNED)
return out
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 :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):
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)
"""The wrapped 3-dimensional ndarray.
* 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'<BPArray length={self.length} width={self.width} m={self.m} mem={hr_bytes(self.data.nbytes)}>'
def __len__(self):
return self.length
def bp_buf(out, inp):
md = out.shape[-2]
assert md == inp.shape[-2]
if md > 1:
unknown = inp[..., 0, :] ^ inp[..., 1, :]
if md > 2: unknown &= ~inp[..., 2, :]
out[..., 0, :] = inp[..., 0, :] | unknown
out[..., 1, :] = inp[..., 1, :] & ~unknown
if md > 2: out[..., 2, :] = inp[..., 2, :] & ~unknown
else:
out[..., 0, :] = inp[..., 0, :]
unknown = inp[..., 0, :] ^ inp[..., 1, :]
unknown &= ~inp[..., 2, :]
out[..., 0, :] = inp[..., 0, :] | unknown
out[..., 1, :] = inp[..., 1, :] & ~unknown
out[..., 2, :] = inp[..., 2, :] & ~unknown
return out
def bp_not(out, inp):
md = out.shape[-2]
assert md == inp.shape[-2]
if md > 1:
unknown = inp[..., 0, :] ^ inp[..., 1, :]
if md > 2: unknown &= ~inp[..., 2, :]
out[..., 0, :] = ~inp[..., 0, :] | unknown
out[..., 1, :] = ~inp[..., 1, :] & ~unknown
if md > 2: out[..., 2, :] = inp[..., 2, :] & ~unknown
else:
out[..., 0, :] = ~inp[..., 0, :]
unknown = inp[..., 0, :] ^ inp[..., 1, :]
unknown &= ~inp[..., 2, :]
out[..., 0, :] = ~inp[..., 0, :] | unknown
out[..., 1, :] = ~inp[..., 1, :] & ~unknown
out[..., 2, :] = inp[..., 2, :] & ~unknown
return out
def bp_or(out, *ins):
md = out.shape[-2]
for inp in ins: assert md == inp.shape[-2]
out[...] = 0
if md == 1:
for inp in ins: out[..., 0, :] |= inp[..., 0, :]
elif md == 2:
any_unknown = ins[0][..., 0, :] ^ ins[0][..., 1, :]
for inp in ins[1:]: any_unknown |= inp[..., 0, :] ^ inp[..., 1, :]
any_one = ins[0][..., 0, :] & ins[0][..., 1, :]
for inp in ins[1:]: any_one |= inp[..., 0, :] & inp[..., 1, :]
for inp in ins:
out[..., 0, :] |= inp[..., 0, :] | any_unknown
out[..., 1, :] |= inp[..., 1, :] & (~any_unknown | any_one)
else:
any_unknown = (ins[0][..., 0, :] ^ ins[0][..., 1, :]) & ~ins[0][..., 2, :]
for inp in ins[1:]: any_unknown |= (inp[..., 0, :] ^ inp[..., 1, :]) & ~inp[..., 2, :]
any_one = ins[0][..., 0, :] & ins[0][..., 1, :] & ~ins[0][..., 2, :]
for inp in ins[1:]: any_one |= inp[..., 0, :] & inp[..., 1, :] & ~inp[..., 2, :]
for inp in ins:
out[..., 0, :] |= inp[..., 0, :] | any_unknown
out[..., 1, :] |= inp[..., 1, :] & (~any_unknown | any_one)
out[..., 2, :] |= inp[..., 2, :] & (~any_unknown | any_one) & ~any_one
any_unknown = (ins[0][..., 0, :] ^ ins[0][..., 1, :]) & ~ins[0][..., 2, :]
for inp in ins[1:]: any_unknown |= (inp[..., 0, :] ^ inp[..., 1, :]) & ~inp[..., 2, :]
any_one = ins[0][..., 0, :] & ins[0][..., 1, :] & ~ins[0][..., 2, :]
for inp in ins[1:]: any_one |= inp[..., 0, :] & inp[..., 1, :] & ~inp[..., 2, :]
for inp in ins:
out[..., 0, :] |= inp[..., 0, :] | any_unknown
out[..., 1, :] |= inp[..., 1, :] & (~any_unknown | any_one)
out[..., 2, :] |= inp[..., 2, :] & (~any_unknown | any_one) & ~any_one
return out
def bp_and(out, *ins):
md = out.shape[-2]
for inp in ins: assert md == inp.shape[-2]
out[...] = 0xff
if md == 1:
for inp in ins: out[..., 0, :] &= inp[..., 0, :]
elif md == 2:
any_unknown = ins[0][..., 0, :] ^ ins[0][..., 1, :]
for inp in ins[1:]: any_unknown |= inp[..., 0, :] ^ inp[..., 1, :]
any_zero = ~ins[0][..., 0, :] & ~ins[0][..., 1, :]
for inp in ins[1:]: any_zero |= ~inp[..., 0, :] & ~inp[..., 1, :]
for inp in ins:
out[..., 0, :] &= inp[..., 0, :] | (any_unknown & ~any_zero)
out[..., 1, :] &= inp[..., 1, :] & ~any_unknown
else:
any_unknown = (ins[0][..., 0, :] ^ ins[0][..., 1, :]) & ~ins[0][..., 2, :]
for inp in ins[1:]: any_unknown |= (inp[..., 0, :] ^ inp[..., 1, :]) & ~inp[..., 2, :]
any_zero = ~ins[0][..., 0, :] & ~ins[0][..., 1, :] & ~ins[0][..., 2, :]
for inp in ins[1:]: any_zero |= ~inp[..., 0, :] & ~inp[..., 1, :] & ~inp[..., 2, :]
out[..., 2, :] = 0
for inp in ins:
out[..., 0, :] &= inp[..., 0, :] | (any_unknown & ~any_zero)
out[..., 1, :] &= inp[..., 1, :] & ~any_unknown
out[..., 2, :] |= inp[..., 2, :] & (~any_unknown | any_zero) & ~any_zero
any_unknown = (ins[0][..., 0, :] ^ ins[0][..., 1, :]) & ~ins[0][..., 2, :]
for inp in ins[1:]: any_unknown |= (inp[..., 0, :] ^ inp[..., 1, :]) & ~inp[..., 2, :]
any_zero = ~ins[0][..., 0, :] & ~ins[0][..., 1, :] & ~ins[0][..., 2, :]
for inp in ins[1:]: any_zero |= ~inp[..., 0, :] & ~inp[..., 1, :] & ~inp[..., 2, :]
out[..., 2, :] = 0
for inp in ins:
out[..., 0, :] &= inp[..., 0, :] | (any_unknown & ~any_zero)
out[..., 1, :] &= inp[..., 1, :] & ~any_unknown
out[..., 2, :] |= inp[..., 2, :] & (~any_unknown | any_zero) & ~any_zero
return out
def bp_xor(out, *ins):
md = out.shape[-2]
for inp in ins: assert md == inp.shape[-2]
out[...] = 0
if md == 1:
for inp in ins: out[..., 0, :] ^= inp[..., 0, :]
elif md == 2:
any_unknown = ins[0][..., 0, :] ^ ins[0][..., 1, :]
for inp in ins[1:]: any_unknown |= inp[..., 0, :] ^ inp[..., 1, :]
for inp in ins: out[...] ^= inp
out[..., 0, :] |= any_unknown
out[..., 1, :] &= ~any_unknown
else:
any_unknown = (ins[0][..., 0, :] ^ ins[0][..., 1, :]) & ~ins[0][..., 2, :]
for inp in ins[1:]: any_unknown |= (inp[..., 0, :] ^ inp[..., 1, :]) & ~inp[..., 2, :]
for inp in ins:
out[..., 0, :] ^= inp[..., 0, :]
out[..., 1, :] ^= inp[..., 1, :]
out[..., 2, :] |= inp[..., 2, :]
out[..., 0, :] |= any_unknown
out[..., 1, :] &= ~any_unknown
out[..., 2, :] &= ~any_unknown
any_unknown = (ins[0][..., 0, :] ^ ins[0][..., 1, :]) & ~ins[0][..., 2, :]
for inp in ins[1:]: any_unknown |= (inp[..., 0, :] ^ inp[..., 1, :]) & ~inp[..., 2, :]
for inp in ins:
out[..., 0, :] ^= inp[..., 0, :]
out[..., 1, :] ^= inp[..., 1, :]
out[..., 2, :] |= inp[..., 2, :]
out[..., 0, :] |= any_unknown
out[..., 1, :] &= ~any_unknown
out[..., 2, :] &= ~any_unknown
return out
def bp_latch(out, d, t, q_prev):
md = out.shape[-2]
assert md == d.shape[-2]
assert md == t.shape[-2]
assert md == q_prev.shape[-2]
if md == 1:
out[...] = (d & t) | (q_prev & ~t)
elif md == 2:
any_unknown = t[..., 0, :] ^ t[..., 1, :]
any_unknown |= (d[..., 0, :] ^ d[..., 1, :]) & (t[..., 0, :] | t[..., 1, :])
out[...] = (d & t) | (q_prev & ~t)
out[..., 0, :] |= any_unknown
out[..., 1, :] &= ~any_unknown
else:
any_unknown = (t[..., 0, :] ^ t[..., 1, :]) & ~t[..., 2, :]
any_unknown |= ((d[..., 0, :] ^ d[..., 1, :]) & ~d[..., 2, :]) & (t[..., 0, :] | t[..., 1, :] | t[..., 2, :])
out[..., 1, :] = (d[..., 1, :] & t[..., 1, :]) | (q_prev[..., 0, :] & ~t[..., 1, :])
out[..., 0, :] = (d[..., 0, :] & t[..., 0, :]) | (out[..., 1, :] & ~t[..., 0, :])
out[..., 2, :] = out[..., 1, :] ^ out[..., 0, :]
out[..., 0, :] |= any_unknown
out[..., 1, :] &= ~any_unknown
out[..., 2, :] &= ~any_unknown
any_unknown = (t[..., 0, :] ^ t[..., 1, :]) & ~t[..., 2, :]
any_unknown |= ((d[..., 0, :] ^ d[..., 1, :]) & ~d[..., 2, :]) & (t[..., 0, :] | t[..., 1, :] | t[..., 2, :])
out[..., 1, :] = (d[..., 1, :] & t[..., 1, :]) | (q_prev[..., 0, :] & ~t[..., 1, :])
out[..., 0, :] = (d[..., 0, :] & t[..., 0, :]) | (out[..., 1, :] & ~t[..., 0, :])
out[..., 2, :] = out[..., 1, :] ^ out[..., 0, :]
out[..., 0, :] |= any_unknown
out[..., 1, :] &= ~any_unknown
out[..., 2, :] &= ~any_unknown
return out

312
src/kyupy/logic_sim.py

@ -11,9 +11,10 @@ import math @@ -11,9 +11,10 @@ import math
import numpy as np
from . import logic, hr_bytes
from .sim import SimOps, SimPrim
class LogicSim:
class LogicSim(SimOps):
"""A bit-parallel naïve combinational simulator for 2-, 4-, or 8-valued logic.
:param circuit: The circuit to simulate.
@ -23,75 +24,96 @@ class LogicSim: @@ -23,75 +24,96 @@ class LogicSim:
:param m: The arity of the logic, must be 2, 4, or 8.
:type m: int
"""
def __init__(self, circuit, sims=8, m=8):
def __init__(self, circuit, sims=8, m=8, c_reuse=False, strip_forks=False):
assert m in [2, 4, 8]
super().__init__(circuit, c_reuse=c_reuse, strip_forks=strip_forks)
self.m = m
mdim = math.ceil(math.log2(m))
self.circuit = circuit
self.mdim = math.ceil(math.log2(m))
self.sims = sims
nbytes = (sims - 1) // 8 + 1
dffs = [n for n in circuit.nodes if 'dff' in n.kind.lower()]
latches = [n for n in circuit.nodes if 'latch' in n.kind.lower()]
self.interface = list(circuit.io_nodes) + dffs + latches
self.width = len(self.interface)
"""The number of bits in the circuit state (number of ports + number of state-elements)."""
self.state = np.zeros((len(circuit.lines), mdim, nbytes), dtype='uint8')
self.state_epoch = np.zeros(len(circuit.nodes), dtype='int8') - 1
self.tmp = np.zeros((5, mdim, nbytes), dtype='uint8')
self.zero = np.zeros((mdim, nbytes), dtype='uint8')
self.epoch = 0
self.latch_dict = dict((n.index, i) for i, n in enumerate(latches))
self.latch_state = np.zeros((len(latches), mdim, nbytes), dtype='uint8')
known_fct = [(f[:-4], getattr(self, f)) for f in dir(self) if f.endswith('_fct')]
self.node_fct = []
for n in circuit.nodes:
t = n.kind.lower().replace('__fork__', 'fork')
t = t.replace('nbuff', 'fork')
t = t.replace('input', 'fork')
t = t.replace('output', 'fork')
t = t.replace('__const0__', 'const0')
t = t.replace('__const1__', 'const1')
t = t.replace('tieh', 'const1')
t = t.replace('ibuff', 'not')
t = t.replace('inv', 'not')
fcts = [f for n, f in known_fct if t.startswith(n)]
if len(fcts) < 1:
raise ValueError(f'Unknown node kind {n.kind}')
self.node_fct.append(fcts[0])
self.c = np.zeros((self.c_len, self.mdim, nbytes), dtype=np.uint8)
self.s = np.zeros((2, self.s_len, 3, nbytes), dtype=np.uint8)
self.s[:,:,1,:] = 255 # unassigned
self.pi_s_locs = np.flatnonzero(self.vat[self.ppi_offset+np.arange(len(self.circuit.io_nodes)), 0] >= 0)
self.po_s_locs = np.flatnonzero(self.vat[self.ppo_offset+np.arange(len(self.circuit.io_nodes)), 0] >= 0)
self.ppio_s_locs = np.arange(len(self.circuit.io_nodes), len(self.s_nodes))
self.pippi_s_locs = np.concatenate([self.pi_s_locs, self.ppio_s_locs])
self.poppo_s_locs = np.concatenate([self.po_s_locs, self.ppio_s_locs])
self.pi_c_locs = self.vat[self.ppi_offset+self.pi_s_locs, 0]
self.po_c_locs = self.vat[self.ppo_offset+self.po_s_locs, 0]
self.ppi_c_locs = self.vat[self.ppi_offset+self.ppio_s_locs, 0]
self.ppo_c_locs = self.vat[self.ppo_offset+self.ppio_s_locs, 0]
self.pippi_c_locs = np.concatenate([self.pi_c_locs, self.ppi_c_locs])
self.poppo_c_locs = np.concatenate([self.po_c_locs, self.ppo_c_locs])
#dffs = [n for n in circuit.nodes if 'dff' in n.kind.lower()]
#latches = [n for n in circuit.nodes if 'latch' in n.kind.lower()]
#self.interface = list(circuit.io_nodes) + dffs + latches
#self.width = len(self.interface)
#"""The number of bits in the circuit state (number of ports + number of state-elements)."""
#self.state = np.zeros((len(circuit.lines), mdim, nbytes), dtype='uint8')
#self.state_epoch = np.zeros(len(circuit.nodes), dtype='int8') - 1
#self.tmp = np.zeros((5, mdim, nbytes), dtype='uint8')
#self.zero = np.zeros((mdim, nbytes), dtype='uint8')
#self.epoch = 0
#self.latch_dict = dict((n.index, i) for i, n in enumerate(latches))
#self.latch_state = np.zeros((len(latches), mdim, nbytes), dtype='uint8')
# known_fct = [(f[:-4], getattr(self, f)) for f in dir(self) if f.endswith('_fct')]
# self.node_fct = []
# for n in circuit.nodes:
# t = n.kind.lower().replace('__fork__', 'fork')
# t = t.replace('nbuff', 'fork')
# t = t.replace('input', 'fork')
# t = t.replace('output', 'fork')
# t = t.replace('__const0__', 'const0')
# t = t.replace('__const1__', 'const1')
# t = t.replace('tieh', 'const1')
# t = t.replace('ibuff', 'not')
# t = t.replace('inv', 'not')
# fcts = [f for n, f in known_fct if t.startswith(n)]
# if len(fcts) < 1:
# raise ValueError(f'Unknown node kind {n.kind}')
# 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)}>'
return f'<LogicSim {self.circuit.name} sims={self.sims} m={self.m} state_mem={hr_bytes(self.c.nbytes)}>'
def assign(self, stimuli):
def s_to_c(self):
"""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`
:returns: The given stimuli object.
"""
for node, stim in zip(self.interface, stimuli.data if hasattr(stimuli, 'data') else stimuli):
if len(node.outs) == 0: continue
if node.index in self.latch_dict:
self.latch_state[self.latch_dict[node.index]] = stim
else:
outputs = [self.state[line] if line else self.tmp[3] for line in node.outs]
self.node_fct[node]([stim], outputs)
for line in node.outs:
if line is not None: self.state_epoch[line.reader] = self.epoch
for n in self.circuit.nodes:
if n.kind in ('__const1__', '__const0__'):
outputs = [self.state[line] if line else self.tmp[3] for line in n.outs]
self.node_fct[n]([], outputs)
for line in n.outs:
if line is not None: self.state_epoch[line.reader] = self.epoch
return stimuli
def capture(self, responses, ff_transitions=False):
self.c[self.pippi_c_locs] = self.s[0, self.pippi_s_locs]
# for node, stim in zip(self.interface, stimuli.data if hasattr(stimuli, 'data') else stimuli):
# if len(node.outs) == 0: continue
# if node.index in self.latch_dict:
# self.latch_state[self.latch_dict[node.index]] = stim
# else:
# outputs = [self.state[line] if line else self.tmp[3] for line in node.outs]
# self.node_fct[node]([stim], outputs)
# for line in node.outs:
# if line is not None: self.state_epoch[line.reader] = self.epoch
# for n in self.circuit.nodes:
# if n.kind in ('__const1__', '__const0__'):
# outputs = [self.state[line] if line else self.tmp[3] for line in n.outs]
# self.node_fct[n]([], outputs)
# for line in n.outs:
# if line is not None: self.state_epoch[line.reader] = self.epoch
# return stimuli
def c_to_s(self): #, responses, ff_transitions=False):
"""Capture the current values at the primary outputs and in the state-elements (flip-flops).
For primary outputs, the logic value is stored unmodified in the given target array.
For flip-flops, the logic value is either stored unmodified (`ff_transitions=False`)
@ -103,26 +125,27 @@ class LogicSim: @@ -103,26 +125,27 @@ class LogicSim:
(the currently assigned pattern) and the new state.
:returns: The given responses object.
"""
for node, resp in zip(self.interface, responses.data if hasattr(responses, 'data') else responses):
if len(node.ins) == 0: continue
if node.index in self.latch_dict:
resp[...] = self.state[node.outs[0]]
else:
resp[...] = self.state[node.ins[0]]
if not ff_transitions: continue
# outs of DFFs contain the previously assigned value (previous state)
if self.m > 2 and 'dff' in node.kind.lower() and len(node.outs) > 0:
if node.outs[0] is None:
resp[1, :] = ~self.state[node.outs[1], 0, :] # assume QN is connected, take inverse of that.
else:
resp[1, :] = self.state[node.outs[0], 0, :]
if self.m > 4:
resp[..., 2, :] = resp[..., 0, :] ^ resp[..., 1, :]
# FIXME: We don't handle X or - correctly.
return responses
def propagate(self, inject_cb=None):
self.s[1, self.poppo_s_locs] = self.c[self.poppo_c_locs]
# for node, resp in zip(self.interface, responses.data if hasattr(responses, 'data') else responses):
# if len(node.ins) == 0: continue
# if node.index in self.latch_dict:
# resp[...] = self.state[node.outs[0]]
# else:
# resp[...] = self.state[node.ins[0]]
# if not ff_transitions: continue
# # outs of DFFs contain the previously assigned value (previous state)
# if self.m > 2 and 'dff' in node.kind.lower() and len(node.outs) > 0:
# if node.outs[0] is None:
# resp[1, :] = ~self.state[node.outs[1], 0, :] # assume QN is connected, take inverse of that.
# else:
# resp[1, :] = self.state[node.outs[0], 0, :]
# if self.m > 4:
# resp[..., 2, :] = resp[..., 0, :] ^ resp[..., 1, :]
# # FIXME: We don't handle X or - correctly.
# return responses
def c_prop(self, inject_cb=None):
"""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.
@ -142,19 +165,39 @@ class LogicSim: @@ -142,19 +165,39 @@ class LogicSim:
resumes with the manipulated values after the callback returns.
:type inject_cb: ``f(Line, ndarray)``
"""
for node in self.circuit.topological_order():
if self.state_epoch[node] != self.epoch: continue
inputs = [self.state[line] if line else self.zero for line in node.ins]
outputs = [self.state[line] if line else self.tmp[3] for line in node.outs]
if node.index in self.latch_dict:
inputs.append(self.latch_state[self.latch_dict[node.index]])
self.node_fct[node](inputs, outputs)
for line in node.outs:
if inject_cb is not None: inject_cb(line, self.state[line])
self.state_epoch[line.reader] = self.epoch
self.epoch = (self.epoch + 1) % 128
def cycle(self, state, inject_cb=None):
for op, o0, i0, i1, i2, i3 in self.ops:
o0, i0, i1, i2, i3 = [self.vat[x,0] for x in (o0, i0, i1, i2, i3)]
if op == SimPrim.BUF1: self.c[o0]=self.c[i0]
elif op == SimPrim.INV1: logic.bp_not(self.c[o0], self.c[i0])
elif op == SimPrim.AND2: logic.bp_and(self.c[o0], self.c[i0], self.c[i1])
elif op == SimPrim.NAND2: logic.bp_and(self.c[o0], self.c[i0], self.c[i1]); logic.bp_not(self.c[o0], self.c[o0])
elif op == SimPrim.OR2: logic.bp_or(self.c[o0], self.c[i0], self.c[i1])
elif op == SimPrim.NOR2: logic.bp_or(self.c[o0], self.c[i0], self.c[i1]); logic.bp_not(self.c[o0], self.c[o0])
elif op == SimPrim.XOR2: logic.bp_xor(self.c[o0], self.c[i0], self.c[i1])
elif op == SimPrim.XNOR2: logic.bp_xor(self.c[o0], self.c[i0], self.c[i1]); logic.bp_not(self.c[o0], self.c[o0])
else: print(f'unknown SimPrim {op}')
if inject_cb is not None: inject_cb(o0, self.s[o0])
# for node in self.circuit.topological_order():
# if self.state_epoch[node] != self.epoch: continue
# inputs = [self.state[line] if line else self.zero for line in node.ins]
# outputs = [self.state[line] if line else self.tmp[3] for line in node.outs]
# if node.index in self.latch_dict:
# inputs.append(self.latch_state[self.latch_dict[node.index]])
# self.node_fct[node](inputs, outputs)
# for line in node.outs:
# if inject_cb is not None: inject_cb(line, self.state[line])
# self.state_epoch[line.reader] = self.epoch
# self.epoch = (self.epoch + 1) % 128
def s_ppo_to_ppi(self):
if self.m == 2:
self.s[0, self.ppio_s_locs, 0] = self.s[1, self.ppio_s_locs, 0]
else:
self.s[0, self.ppio_s_locs, 1] = self.s[0, self.ppio_s_locs, 0] # initial value is previously assigned final value
self.s[0, self.ppio_s_locs, 0] = self.s[1, self.ppio_s_locs, 0] # final value is newly captured final value
self.s[0, self.ppio_s_locs, 2] = self.s[0, self.ppio_s_locs, 0] ^ self.s[0, self.ppio_s_locs, 1] # TODO: not correct for X, -
def cycle(self, inject_cb=None):
"""Assigns the given state, propagates it and captures the new state.
:param state: A bit-parallel array in a compatible shape holding the current circuit state.
@ -164,61 +207,62 @@ class LogicSim: @@ -164,61 +207,62 @@ class LogicSim:
:param inject_cb: A callback function for manipulating intermediate signal values. See :py:func:`propagate`.
:returns: The given state object.
"""
self.assign(state)
self.propagate(inject_cb)
return self.capture(state)
self.s_to_c()
self.c_prop(inject_cb)
self.c_to_s()
self.s_ppo_to_ppi()
def fork_fct(self, inputs, outputs):
for o in outputs: o[...] = inputs[0]
# def fork_fct(self, inputs, outputs):
# for o in outputs: o[...] = inputs[0]
def const0_fct(self, _, outputs):
for o in outputs: o[...] = 0
# def const0_fct(self, _, outputs):
# for o in outputs: o[...] = 0
def const1_fct(self, _, outputs):
for o in outputs:
o[...] = 0
logic.bp_not(o, o)
# def const1_fct(self, _, outputs):
# for o in outputs:
# o[...] = 0
# logic.bp_not(o, o)
def not_fct(self, inputs, outputs):
logic.bp_not(outputs[0], inputs[0])
# def not_fct(self, inputs, outputs):
# logic.bp_not(outputs[0], inputs[0])
def and_fct(self, inputs, outputs):
logic.bp_and(outputs[0], *inputs)
# def and_fct(self, inputs, outputs):
# logic.bp_and(outputs[0], *inputs)
def or_fct(self, inputs, outputs):
logic.bp_or(outputs[0], *inputs)
# def or_fct(self, inputs, outputs):
# logic.bp_or(outputs[0], *inputs)
def xor_fct(self, inputs, outputs):
logic.bp_xor(outputs[0], *inputs)
# def xor_fct(self, inputs, outputs):
# logic.bp_xor(outputs[0], *inputs)
def sdff_fct(self, inputs, outputs):
logic.bp_buf(outputs[0], inputs[0])
if len(outputs) > 1:
logic.bp_not(outputs[1], inputs[0])
# def sdff_fct(self, inputs, outputs):
# logic.bp_buf(outputs[0], inputs[0])
# if len(outputs) > 1:
# logic.bp_not(outputs[1], inputs[0])
def dff_fct(self, inputs, outputs):
logic.bp_buf(outputs[0], inputs[0])
if len(outputs) > 1:
logic.bp_not(outputs[1], inputs[0])
# def dff_fct(self, inputs, outputs):
# logic.bp_buf(outputs[0], inputs[0])
# if len(outputs) > 1:
# logic.bp_not(outputs[1], inputs[0])
def latch_fct(self, inputs, outputs):
logic.bp_latch(outputs[0], inputs[0], inputs[1], inputs[2])
if len(outputs) > 1:
logic.bp_not(outputs[1], inputs[0])
# def latch_fct(self, inputs, outputs):
# logic.bp_latch(outputs[0], inputs[0], inputs[1], inputs[2])
# if len(outputs) > 1:
# logic.bp_not(outputs[1], inputs[0])
def nand_fct(self, inputs, outputs):
logic.bp_and(outputs[0], *inputs)
logic.bp_not(outputs[0], outputs[0])
# def nand_fct(self, inputs, outputs):
# logic.bp_and(outputs[0], *inputs)
# logic.bp_not(outputs[0], outputs[0])
def nor_fct(self, inputs, outputs):
logic.bp_or(outputs[0], *inputs)
logic.bp_not(outputs[0], outputs[0])
# def nor_fct(self, inputs, outputs):
# logic.bp_or(outputs[0], *inputs)
# logic.bp_not(outputs[0], outputs[0])
def xnor_fct(self, inputs, outputs):
logic.bp_xor(outputs[0], *inputs)
logic.bp_not(outputs[0], outputs[0])
# def xnor_fct(self, inputs, outputs):
# logic.bp_xor(outputs[0], *inputs)
# logic.bp_not(outputs[0], outputs[0])
def aoi21_fct(self, inputs, outputs):
logic.bp_and(self.tmp[0], inputs[0], inputs[1])
logic.bp_or(outputs[0], self.tmp[0], inputs[2])
logic.bp_not(outputs[0], outputs[0])
# def aoi21_fct(self, inputs, outputs):
# logic.bp_and(self.tmp[0], inputs[0], inputs[1])
# logic.bp_or(outputs[0], self.tmp[0], inputs[2])
# logic.bp_not(outputs[0], outputs[0])

18
src/kyupy/sim.py

@ -7,7 +7,7 @@ import numpy as np @@ -7,7 +7,7 @@ import numpy as np
class SimPrim:
BUF1 = 0b1010_1010_1010_1010
INV1 = 0b0101_0101_0101_0101
NAND4 = 0b0111_1111_1111_1111
NAND3 = 0b0111_1111_0111_1111
NAND2 = 0b0111_0111_0111_0111
@ -19,7 +19,7 @@ class SimPrim: @@ -19,7 +19,7 @@ class SimPrim:
AND4 = 0b1000_0000_0000_0000
AND3 = 0b1000_0000_1000_0000
AND2 = 0b1000_1000_1000_1000
OR4 = 0b1111_1111_1111_1110
OR3 = 0b1111_1110_1111_1110
OR2 = 0b1110_1110_1110_1110
@ -27,7 +27,7 @@ class SimPrim: @@ -27,7 +27,7 @@ class SimPrim:
XOR4 = 0b0110_1001_1001_0110
XOR3 = 0b1001_0110_1001_0110
XOR2 = 0b0110_0110_0110_0110
XNOR4 = 0b1001_0110_0110_1001
XNOR3 = 0b0110_1001_0110_1001
XNOR2 = 0b1001_1001_1001_1001
@ -41,7 +41,7 @@ class SimPrim: @@ -41,7 +41,7 @@ class SimPrim:
OA21 = 0b1010_1000_1010_1000
OAI21 = 0b0101_0111_0101_0111
MUX21 = 0b1110_0100_1110_0100
kind_prefixes = {
'nand': (NAND4, NAND3, NAND2),
'nor': (NOR4, NOR3, NOR2),
@ -49,7 +49,7 @@ class SimPrim: @@ -49,7 +49,7 @@ class SimPrim:
'or': (OR4, OR3, OR2),
'xor': (XOR4, XOR3, XOR2),
'xnor': (XNOR4, XNOR3, XNOR2),
'not': (INV1, INV1, INV1),
'inv': (INV1, INV1, INV1),
'ibuf': (INV1, INV1, INV1),
@ -71,7 +71,7 @@ class SimPrim: @@ -71,7 +71,7 @@ class SimPrim:
'oai22': (OAI22, OAI22, OAI22),
'oa21': (OA21, OA21, OA21),
'oai21': (OAI21, OAI21, OAI21),
'mux21': (MUX21, MUX21, MUX21),
}
@ -157,10 +157,12 @@ class SimOps: @@ -157,10 +157,12 @@ class SimOps:
"""
def __init__(self, circuit, c_caps=1, c_reuse=False, strip_forks=False):
self.circuit = circuit
self.s_nodes = list(circuit.io_nodes) + [n for n in circuit.nodes if 'dff' in n.kind.lower()]
dffs = [n for n in circuit.nodes if 'dff' in n.kind.lower()]
latches = [n for n in circuit.nodes if 'latch' in n.kind.lower()]
self.s_nodes = list(circuit.io_nodes) + dffs + latches
self.s_len = len(self.s_nodes)
keep_signals = not c_reuse
if isinstance(c_caps, int):
c_caps = [c_caps] * len(circuit.lines)

56
src/kyupy/stil.py

@ -11,6 +11,7 @@ obtain the appropriate vector sets. @@ -11,6 +11,7 @@ obtain the appropriate vector sets.
import re
from collections import namedtuple
import numpy as np
from lark import Lark, Transformer
from . import readtext, logic
@ -81,8 +82,8 @@ class StilFile: @@ -81,8 +82,8 @@ class StilFile:
scan_out_inversion.append(inversion)
scan_maps[chain[0]] = scan_map
scan_maps[chain[-1]] = scan_map
scan_inversions[chain[0]] = scan_in_inversion
scan_inversions[chain[-1]] = scan_out_inversion
scan_inversions[chain[0]] = logic.mvarray(scan_in_inversion)[0]
scan_inversions[chain[-1]] = logic.mvarray(scan_out_inversion)[0]
return interface, pi_map, po_map, scan_maps, scan_inversions
def tests(self, circuit):
@ -91,12 +92,12 @@ class StilFile: @@ -91,12 +92,12 @@ class StilFile:
This function assumes a static (stuck-at fault) test.
"""
interface, pi_map, _, scan_maps, scan_inversions = self._maps(circuit)
tests = logic.MVArray((len(interface), len(self.patterns)))
tests = np.full((len(interface), len(self.patterns)), logic.UNASSIGNED)
for i, p in enumerate(self.patterns):
for si_port in self.si_ports.keys():
pattern = logic.mv_xor(p.load[si_port], scan_inversions[si_port])
tests.data[scan_maps[si_port], i] = pattern.data[:, 0]
tests.data[pi_map, i] = logic.MVArray(p.capture['_pi']).data[:, 0]
pattern = logic.mv_xor(logic.mvarray(p.load[si_port]), scan_inversions[si_port])
tests[scan_maps[si_port], i] = pattern
tests[pi_map, i] = logic.mvarray(p.capture['_pi'])
return tests
def tests_loc(self, circuit):
@ -107,47 +108,40 @@ class StilFile: @@ -107,47 +108,40 @@ class StilFile:
test) and assembles the test pattern set from from pairs for initialization- and launch-patterns.
"""
interface, pi_map, po_map, scan_maps, scan_inversions = self._maps(circuit)
init = logic.MVArray((len(interface), len(self.patterns)), m=4)
# init = PackedVectors(len(self.patterns), len(interface), 2)
init = np.full((len(interface), len(self.patterns)), logic.UNASSIGNED)
for i, p in enumerate(self.patterns):
# init.set_values(i, '0' * len(interface))
for si_port in self.si_ports.keys():
pattern = logic.mv_xor(p.load[si_port], scan_inversions[si_port])
init.data[scan_maps[si_port], i] = pattern.data[:, 0]
init.data[pi_map, i] = logic.MVArray(p.launch['_pi'] if '_pi' in p.launch else p.capture['_pi']).data[:, 0]
launch_bp = logic.BPArray(init)
sim4v = LogicSim(circuit, len(init), m=4)
sim4v.assign(launch_bp)
sim4v.propagate()
sim4v.capture(launch_bp)
launch = logic.MVArray(launch_bp)
pattern = logic.mv_xor(logic.mvarray(p.load[si_port]), scan_inversions[si_port])
init[scan_maps[si_port], i] = pattern
init[pi_map, i] = logic.mvarray(p.launch['_pi'] if '_pi' in p.launch else p.capture['_pi'])
sim8v = LogicSim(circuit, init.shape[-1], m=8)
sim8v.s[0] = logic.mv_to_bp(init)
sim8v.s_to_c()
sim8v.c_prop()
sim8v.c_to_s()
launch = logic.bp_to_mv(sim8v.s[1])[..., :init.shape[-1]]
for i, p in enumerate(self.patterns):
# if there was no launch cycle or launch clock, then init = launch
if '_pi' not in p.launch or 'P' not in p.launch['_pi'] or 'P' not in p.capture['_pi']:
for si_port in self.si_ports.keys():
pattern = logic.mv_xor(p.load[si_port], scan_inversions[si_port])
launch.data[scan_maps[si_port], i] = pattern.data[:, 0]
pattern = logic.mv_xor(logic.mvarray(p.load[si_port]), scan_inversions[si_port])
launch[scan_maps[si_port], i] = pattern
if '_pi' in p.capture and 'P' in p.capture['_pi']:
launch.data[pi_map, i] = logic.MVArray(p.capture['_pi']).data[:, 0]
launch.data[po_map, i] = logic.UNASSIGNED
launch[pi_map, i] = logic.mvarray(p.capture['_pi'])
launch[po_map, i] = logic.UNASSIGNED
return logic.mv_transition(init, launch)
def responses(self, circuit):
"""Assembles and returns a scan test response pattern set for given circuit."""
interface, _, po_map, scan_maps, scan_inversions = self._maps(circuit)
resp = logic.MVArray((len(interface), len(self.patterns)))
# resp = PackedVectors(len(self.patterns), len(interface), 2)
resp = np.full((len(interface), len(self.patterns)), logic.UNASSIGNED)
for i, p in enumerate(self.patterns):
resp.data[po_map, i] = logic.MVArray(p.capture['_po'] if len(p.capture) > 0 else p.launch['_po']).data[:, 0]
# if len(p.capture) > 0:
# resp.set_values(i, p.capture['_po'], po_map)
# else:
# resp.set_values(i, p.launch['_po'], po_map)
resp[po_map, i] = logic.mvarray(p.capture['_po'] if len(p.capture) > 0 else p.launch['_po'])
for so_port in self.so_ports.keys():
pattern = logic.mv_xor(p.unload[so_port], scan_inversions[so_port])
resp.data[scan_maps[so_port], i] = pattern.data[:, 0]
# resp.set_values(i, p.unload[so_port], scan_maps[so_port], scan_inversions[so_port])
pattern = logic.mv_xor(logic.mvarray(p.unload[so_port]), scan_inversions[so_port])
resp[scan_maps[so_port], i] = pattern
return resp

313
tests/test_logic.py

@ -1,252 +1,75 @@ @@ -1,252 +1,75 @@
import numpy as np
import kyupy.logic as lg
from kyupy.logic import mvarray, bparray, bp_to_mv, mv_to_bp
def test_mvarray():
# instantiation with shape
ary = lg.MVArray(4)
assert ary.length == 1
assert len(ary) == 1
assert ary.width == 4
ary = lg.MVArray((3, 2))
assert ary.length == 2
assert len(ary) == 2
assert ary.width == 3
# instantiation with single vector
ary = lg.MVArray([1, 0, 1])
assert ary.length == 1
assert ary.width == 3
assert str(ary) == "['101']"
assert ary[0] == '101'
ary = lg.MVArray("10X-")
assert ary.length == 1
assert ary.width == 4
assert str(ary) == "['10X-']"
assert ary[0] == '10X-'
ary = lg.MVArray("1")
assert ary.length == 1
assert ary.width == 1
ary = lg.MVArray(["1"])
assert ary.length == 1
assert ary.width == 1
# instantiation with multiple vectors
ary = lg.MVArray([[0, 0], [0, 1], [1, 0], [1, 1]])
assert ary.length == 4
assert ary.width == 2
ary = lg.MVArray(["000", "001", "110", "---"])
assert ary.length == 4
assert ary.width == 3
assert str(ary) == "['000', '001', '110', '---']"
assert ary[2] == '110'
# casting to 2-valued logic
ary = lg.MVArray([0, 1, 2, None], m=2)
assert ary.data[0] == lg.ZERO
assert ary.data[1] == lg.ONE
assert ary.data[2] == lg.ZERO
assert ary.data[3] == lg.ZERO
ary = lg.MVArray("0-X1PRFN", m=2)
assert ary.data[0] == lg.ZERO
assert ary.data[1] == lg.ZERO
assert ary.data[2] == lg.ZERO
assert ary.data[3] == lg.ONE
assert ary.data[4] == lg.ZERO
assert ary.data[5] == lg.ONE
assert ary.data[6] == lg.ZERO
assert ary.data[7] == lg.ONE
# casting to 4-valued logic
ary = lg.MVArray([0, 1, 2, None, 'F'], m=4)
assert ary.data[0] == lg.ZERO
assert ary.data[1] == lg.ONE
assert ary.data[2] == lg.UNKNOWN
assert ary.data[3] == lg.UNASSIGNED
assert ary.data[4] == lg.ZERO
ary = lg.MVArray("0-X1PRFN", m=4)
assert ary.data[0] == lg.ZERO
assert ary.data[1] == lg.UNASSIGNED
assert ary.data[2] == lg.UNKNOWN
assert ary.data[3] == lg.ONE
assert ary.data[4] == lg.ZERO
assert ary.data[5] == lg.ONE
assert ary.data[6] == lg.ZERO
assert ary.data[7] == lg.ONE
# casting to 8-valued logic
ary = lg.MVArray([0, 1, 2, None, 'F'], m=8)
assert ary.data[0] == lg.ZERO
assert ary.data[1] == lg.ONE
assert ary.data[2] == lg.UNKNOWN
assert ary.data[3] == lg.UNASSIGNED
assert ary.data[4] == lg.FALL
ary = lg.MVArray("0-X1PRFN", m=8)
assert ary.data[0] == lg.ZERO
assert ary.data[1] == lg.UNASSIGNED
assert ary.data[2] == lg.UNKNOWN
assert ary.data[3] == lg.ONE
assert ary.data[4] == lg.PPULSE
assert ary.data[5] == lg.RISE
assert ary.data[6] == lg.FALL
assert ary.data[7] == lg.NPULSE
# copy constructor and casting
ary8 = lg.MVArray(ary, m=8)
assert ary8.length == 1
assert ary8.width == 8
assert ary8.data[7] == lg.NPULSE
ary4 = lg.MVArray(ary, m=4)
assert ary4.data[1] == lg.UNASSIGNED
assert ary4.data[7] == lg.ONE
ary2 = lg.MVArray(ary, m=2)
assert ary2.data[1] == lg.ZERO
assert ary2.data[7] == lg.ONE
def test_mv_operations():
x1_2v = lg.MVArray("0011", m=2)
x2_2v = lg.MVArray("0101", m=2)
x1_4v = lg.MVArray("0000XXXX----1111", m=4)
x2_4v = lg.MVArray("0X-10X-10X-10X-1", m=4)
x1_8v = lg.MVArray("00000000XXXXXXXX--------11111111PPPPPPPPRRRRRRRRFFFFFFFFNNNNNNNN", m=8)
x2_8v = lg.MVArray("0X-1PRFN0X-1PRFN0X-1PRFN0X-1PRFN0X-1PRFN0X-1PRFN0X-1PRFN0X-1PRFN", m=8)
assert lg.mv_not(x1_2v)[0] == '1100'
assert lg.mv_not(x1_4v)[0] == '1111XXXXXXXX0000'
assert lg.mv_not(x1_8v)[0] == '11111111XXXXXXXXXXXXXXXX00000000NNNNNNNNFFFFFFFFRRRRRRRRPPPPPPPP'
assert lg.mv_or(x1_2v, x2_2v)[0] == '0111'
assert lg.mv_or(x1_4v, x2_4v)[0] == '0XX1XXX1XXX11111'
assert lg.mv_or(x1_8v, x2_8v)[0] == '0XX1PRFNXXX1XXXXXXX1XXXX11111111PXX1PRFNRXX1RRNNFXX1FNFNNXX1NNNN'
assert lg.mv_and(x1_2v, x2_2v)[0] == '0001'
assert lg.mv_and(x1_4v, x2_4v)[0] == '00000XXX0XXX0XX1'
assert lg.mv_and(x1_8v, x2_8v)[0] == '000000000XXXXXXX0XXXXXXX0XX1PRFN0XXPPPPP0XXRPRPR0XXFPPFF0XXNPRFN'
assert lg.mv_xor(x1_2v, x2_2v)[0] == '0110'
assert lg.mv_xor(x1_4v, x2_4v)[0] == '0XX1XXXXXXXX1XX0'
assert lg.mv_xor(x1_8v, x2_8v)[0] == '0XX1PRFNXXXXXXXXXXXXXXXX1XX0NFRPPXXNPRFNRXXFRPNFFXXRFNPRNXXPNFRP'
x30_2v = lg.MVArray("0000", m=2)
x31_2v = lg.MVArray("1111", m=2)
x30_4v = lg.MVArray("0000000000000000", m=4)
x31_4v = lg.MVArray("1111111111111111", m=4)
x30_8v = lg.MVArray("0000000000000000000000000000000000000000000000000000000000000000", m=8)
x31_8v = lg.MVArray("1111111111111111111111111111111111111111111111111111111111111111", m=8)
assert lg.mv_latch(x1_2v, x2_2v, x30_2v)[0] == '0001'
assert lg.mv_latch(x1_2v, x2_2v, x31_2v)[0] == '1011'
assert lg.mv_latch(x1_4v, x2_4v, x30_4v)[0] == '0XX00XXX0XXX0XX1'
assert lg.mv_latch(x1_4v, x2_4v, x31_4v)[0] == '1XX01XXX1XXX1XX1'
assert lg.mv_latch(x1_8v, x2_8v, x30_8v)[0] == '0XX000000XXXXXXX0XXXXXXX0XX10R110XX000000XXR0R0R0XXF001F0XX10R11'
assert lg.mv_latch(x1_8v, x2_8v, x31_8v)[0] == '1XX01F001XXXXXXX1XXXXXXX1XX111111XX01F001XXR110R1XXF1F1F1XX11111'
def assert_equal_shape_and_contents(actual, desired):
desired = np.array(desired, dtype=np.uint8)
assert actual.shape == desired.shape
np.testing.assert_allclose(actual, desired)
def test_mvarray_single_vector():
assert_equal_shape_and_contents(mvarray(1, 0, 1), [lg.ONE, lg.ZERO, lg.ONE])
assert_equal_shape_and_contents(mvarray([1, 0, 1]), [lg.ONE, lg.ZERO, lg.ONE])
assert_equal_shape_and_contents(mvarray('10X-RFPN'), [lg.ONE, lg.ZERO, lg.UNKNOWN, lg.UNASSIGNED, lg.RISE, lg.FALL, lg.PPULSE, lg.NPULSE])
assert_equal_shape_and_contents(mvarray(['1']), [lg.ONE])
assert_equal_shape_and_contents(mvarray('1'), [lg.ONE])
def test_mvarray_multi_vector():
assert_equal_shape_and_contents(mvarray([0, 0], [0, 1], [1, 0], [1, 1]), [[lg.ZERO, lg.ZERO, lg.ONE, lg.ONE], [lg.ZERO, lg.ONE, lg.ZERO, lg.ONE]])
assert_equal_shape_and_contents(mvarray('10X', '--1'), [[lg.ONE, lg.UNASSIGNED], [lg.ZERO, lg.UNASSIGNED], [lg.UNKNOWN, lg.ONE]])
def test_mv_ops():
x1_8v = mvarray('00000000XXXXXXXX--------11111111PPPPPPPPRRRRRRRRFFFFFFFFNNNNNNNN')
x2_8v = mvarray('0X-1PRFN'*8)
assert_equal_shape_and_contents(lg.mv_not(x1_8v), mvarray('11111111XXXXXXXXXXXXXXXX00000000NNNNNNNNFFFFFFFFRRRRRRRRPPPPPPPP'))
assert_equal_shape_and_contents(lg.mv_or(x1_8v, x2_8v), mvarray('0XX1PRFNXXX1XXXXXXX1XXXX11111111PXX1PRFNRXX1RRNNFXX1FNFNNXX1NNNN'))
assert_equal_shape_and_contents(lg.mv_and(x1_8v, x2_8v), mvarray('000000000XXXXXXX0XXXXXXX0XX1PRFN0XXPPPPP0XXRPRPR0XXFPPFF0XXNPRFN'))
assert_equal_shape_and_contents(lg.mv_xor(x1_8v, x2_8v), mvarray('0XX1PRFNXXXXXXXXXXXXXXXX1XX0NFRPPXXNPRFNRXXFRPNFFXXRFNPRNXXPNFRP'))
# TODO
#assert_equal_shape_and_contents(lg.mv_transition(x1_8v, x2_8v), mvarray('0XXR PRFNXXXXXXXXXXXXXXXX1XX0NFRPPXXNPRFNRXXFRPNFFXXRFNPRNXXPNFRP'))
x30_8v = mvarray('0000000000000000000000000000000000000000000000000000000000000000')
x31_8v = mvarray('1111111111111111111111111111111111111111111111111111111111111111')
assert_equal_shape_and_contents(lg.mv_latch(x1_8v, x2_8v, x30_8v), mvarray('0XX000000XXXXXXX0XXXXXXX0XX10R110XX000000XXR0R0R0XXF001F0XX10R11'))
assert_equal_shape_and_contents(lg.mv_latch(x1_8v, x2_8v, x31_8v), mvarray('1XX01F001XXXXXXX1XXXXXXX1XX111111XX01F001XXR110R1XXF1F1F1XX11111'))
def test_bparray():
ary = lg.BPArray(4)
assert ary.length == 1
assert len(ary) == 1
assert ary.width == 4
ary = lg.BPArray((3, 2))
assert ary.length == 2
assert len(ary) == 2
assert ary.width == 3
assert lg.MVArray(lg.BPArray("01", m=2))[0] == '01'
assert lg.MVArray(lg.BPArray("0X-1", m=4))[0] == '0X-1'
assert lg.MVArray(lg.BPArray("0X-1PRFN", m=8))[0] == '0X-1PRFN'
x1_2v = lg.BPArray("0011", m=2)
x2_2v = lg.BPArray("0101", m=2)
x1_4v = lg.BPArray("0000XXXX----1111", m=4)
x2_4v = lg.BPArray("0X-10X-10X-10X-1", m=4)
x1_8v = lg.BPArray("00000000XXXXXXXX--------11111111PPPPPPPPRRRRRRRRFFFFFFFFNNNNNNNN", m=8)
x2_8v = lg.BPArray("0X-1PRFN0X-1PRFN0X-1PRFN0X-1PRFN0X-1PRFN0X-1PRFN0X-1PRFN0X-1PRFN", m=8)
out_2v = lg.BPArray((4, 1), m=2)
out_4v = lg.BPArray((16, 1), m=4)
out_8v = lg.BPArray((64, 1), m=8)
lg.bp_buf(out_2v.data, x1_2v.data)
lg.bp_buf(out_4v.data, x1_4v.data)
lg.bp_buf(out_8v.data, x1_8v.data)
assert lg.MVArray(out_2v)[0] == '0011'
assert lg.MVArray(out_4v)[0] == '0000XXXXXXXX1111'
assert lg.MVArray(out_8v)[0] == '00000000XXXXXXXXXXXXXXXX11111111PPPPPPPPRRRRRRRRFFFFFFFFNNNNNNNN'
lg.bp_not(out_2v.data, x1_2v.data)
lg.bp_not(out_4v.data, x1_4v.data)
lg.bp_not(out_8v.data, x1_8v.data)
assert lg.MVArray(out_2v)[0] == '1100'
assert lg.MVArray(out_4v)[0] == '1111XXXXXXXX0000'
assert lg.MVArray(out_8v)[0] == '11111111XXXXXXXXXXXXXXXX00000000NNNNNNNNFFFFFFFFRRRRRRRRPPPPPPPP'
lg.bp_or(out_2v.data, x1_2v.data, x2_2v.data)
lg.bp_or(out_4v.data, x1_4v.data, x2_4v.data)
lg.bp_or(out_8v.data, x1_8v.data, x2_8v.data)
assert lg.MVArray(out_2v)[0] == '0111'
assert lg.MVArray(out_4v)[0] == '0XX1XXX1XXX11111'
assert lg.MVArray(out_8v)[0] == '0XX1PRFNXXX1XXXXXXX1XXXX11111111PXX1PRFNRXX1RRNNFXX1FNFNNXX1NNNN'
lg.bp_and(out_2v.data, x1_2v.data, x2_2v.data)
lg.bp_and(out_4v.data, x1_4v.data, x2_4v.data)
lg.bp_and(out_8v.data, x1_8v.data, x2_8v.data)
assert lg.MVArray(out_2v)[0] == '0001'
assert lg.MVArray(out_4v)[0] == '00000XXX0XXX0XX1'
assert lg.MVArray(out_8v)[0] == '000000000XXXXXXX0XXXXXXX0XX1PRFN0XXPPPPP0XXRPRPR0XXFPPFF0XXNPRFN'
lg.bp_xor(out_2v.data, x1_2v.data, x2_2v.data)
lg.bp_xor(out_4v.data, x1_4v.data, x2_4v.data)
lg.bp_xor(out_8v.data, x1_8v.data, x2_8v.data)
assert lg.MVArray(out_2v)[0] == '0110'
assert lg.MVArray(out_4v)[0] == '0XX1XXXXXXXX1XX0'
assert lg.MVArray(out_8v)[0] == '0XX1PRFNXXXXXXXXXXXXXXXX1XX0NFRPPXXNPRFNRXXFRPNFFXXRFNPRNXXPNFRP'
x30_2v = lg.BPArray("0000", m=2)
x30_4v = lg.BPArray("0000000000000000", m=4)
x30_8v = lg.BPArray("0000000000000000000000000000000000000000000000000000000000000000", m=8)
lg.bp_latch(out_2v.data, x1_2v.data, x2_2v.data, x30_2v.data)
lg.bp_latch(out_4v.data, x1_4v.data, x2_4v.data, x30_4v.data)
lg.bp_latch(out_8v.data, x1_8v.data, x2_8v.data, x30_8v.data)
assert lg.MVArray(out_2v)[0] == '0001'
assert lg.MVArray(out_4v)[0] == '0XX00XXX0XXX0XX1'
assert lg.MVArray(out_8v)[0] == '0XX000000XXXXXXX0XXXXXXX0XX10R110XX000000XXR0R0R0XXF001F0XX10R11'
x31_2v = lg.BPArray("1111", m=2)
x31_4v = lg.BPArray("1111111111111111", m=4)
x31_8v = lg.BPArray("1111111111111111111111111111111111111111111111111111111111111111", m=8)
lg.bp_latch(out_2v.data, x1_2v.data, x2_2v.data, x31_2v.data)
lg.bp_latch(out_4v.data, x1_4v.data, x2_4v.data, x31_4v.data)
lg.bp_latch(out_8v.data, x1_8v.data, x2_8v.data, x31_8v.data)
assert lg.MVArray(out_2v)[0] == '1011'
assert lg.MVArray(out_4v)[0] == '1XX01XXX1XXX1XX1'
assert lg.MVArray(out_8v)[0] == '1XX01F001XXXXXXX1XXXXXXX1XX111111XX01F001XXR110R1XXF1F1F1XX11111'
bpa = bparray('0X-1PRFN')
assert bpa.shape == (8, 3, 1)
bpa = bparray('0X-1PRFN-')
assert bpa.shape == (9, 3, 1)
bpa = bparray('000', '001', '010', '011', '100', '101', '110', '111')
assert bpa.shape == (3, 3, 1)
bpa = bparray('000', '001', '010', '011', '100', '101', '110', '111', 'RFX')
assert bpa.shape == (3, 3, 2)
assert_equal_shape_and_contents(bp_to_mv(bparray('0X-1PRFN'))[:,0], mvarray('0X-1PRFN'))
assert_equal_shape_and_contents(bparray('0X-1PRFN'), mv_to_bp(mvarray('0X-1PRFN')))
x1_8v = bparray('00000000XXXXXXXX--------11111111PPPPPPPPRRRRRRRRFFFFFFFFNNNNNNNN')
x2_8v = bparray('0X-1PRFN'*8)
out_8v = np.empty((64, 3, 1), dtype=np.uint8)
assert_equal_shape_and_contents(bp_to_mv(lg.bp_buf(out_8v, x1_8v))[:,0], mvarray('00000000XXXXXXXXXXXXXXXX11111111PPPPPPPPRRRRRRRRFFFFFFFFNNNNNNNN'))
assert_equal_shape_and_contents(bp_to_mv(lg.bp_or(out_8v, x1_8v, x2_8v))[:,0], mvarray('0XX1PRFNXXX1XXXXXXX1XXXX11111111PXX1PRFNRXX1RRNNFXX1FNFNNXX1NNNN'))
assert_equal_shape_and_contents(bp_to_mv(lg.bp_and(out_8v, x1_8v, x2_8v))[:,0], mvarray('000000000XXXXXXX0XXXXXXX0XX1PRFN0XXPPPPP0XXRPRPR0XXFPPFF0XXNPRFN'))
assert_equal_shape_and_contents(bp_to_mv(lg.bp_xor(out_8v, x1_8v, x2_8v))[:,0], mvarray('0XX1PRFNXXXXXXXXXXXXXXXX1XX0NFRPPXXNPRFNRXXFRPNFFXXRFNPRNXXPNFRP'))
x30_8v = bparray('0000000000000000000000000000000000000000000000000000000000000000')
x31_8v = bparray('1111111111111111111111111111111111111111111111111111111111111111')
assert_equal_shape_and_contents(bp_to_mv(lg.bp_latch(out_8v, x1_8v, x2_8v, x30_8v))[:,0], mvarray('0XX000000XXXXXXX0XXXXXXX0XX10R110XX000000XXR0R0R0XXF001F0XX10R11'))
assert_equal_shape_and_contents(bp_to_mv(lg.bp_latch(out_8v, x1_8v, x2_8v, x31_8v))[:,0], mvarray('1XX01F001XXXXXXX1XXXXXXX1XX111111XX01F001XXR110R1XXF1F1F1XX11111'))

185
tests/test_logic_sim.py

@ -1,135 +1,126 @@ @@ -1,135 +1,126 @@
import numpy as np
from kyupy.logic_sim import LogicSim
from kyupy import bench
from kyupy.logic import MVArray, BPArray
from kyupy.logic import mvarray, bparray, bp_to_mv, mv_to_bp
from kyupy import logic
def assert_equal_shape_and_contents(actual, desired):
desired = np.array(desired, dtype=np.uint8)
assert actual.shape == desired.shape
np.testing.assert_allclose(actual, desired)
def test_2v():
c = bench.parse('input(x, y) output(a, o, n) a=and(x,y) o=or(x,y) n=not(x)')
s = LogicSim(c, 4, m=2)
assert len(s.interface) == 5
mva = MVArray(['00000', '01000', '10000', '11000'], m=2)
bpa = BPArray(mva)
s.assign(bpa)
s.propagate()
s.capture(bpa)
mva = MVArray(bpa)
assert mva[0] == '00001'
assert mva[1] == '01011'
assert mva[2] == '10010'
assert mva[3] == '11110'
s = LogicSim(c, 8, m=8) # FIXME: do m=2
assert s.s_len == 5
bpa = bparray('00---', '01---', '10---', '11---')
s.s[0] = bpa
s.s_to_c()
s.c_prop()
s.c_to_s()
mva = bp_to_mv(s.s[1])
assert_equal_shape_and_contents(mva[...,:4], mvarray('--001', '--011', '--010', '--110'))
def test_4v():
c = bench.parse('input(x, y) output(a, o, n) a=and(x,y) o=or(x,y) n=not(x)')
s = LogicSim(c, 16, m=4)
assert len(s.interface) == 5
mva = MVArray(['00000', '01000', '0-000', '0X000',
'10000', '11000', '1-000', '1X000',
'-0000', '-1000', '--000', '-X000',
'X0000', 'X1000', 'X-000', 'XX000'], m=4)
bpa = BPArray(mva)
s.assign(bpa)
s.propagate()
s.capture(bpa)
mva = MVArray(bpa)
assert mva[0] == '00001'
assert mva[1] == '01011'
assert mva[2] == '0-0X1'
assert mva[3] == '0X0X1'
assert mva[4] == '10010'
assert mva[5] == '11110'
assert mva[6] == '1-X10'
assert mva[7] == '1XX10'
assert mva[8] == '-00XX'
assert mva[9] == '-1X1X'
assert mva[10] == '--XXX'
assert mva[11] == '-XXXX'
assert mva[12] == 'X00XX'
assert mva[13] == 'X1X1X'
assert mva[14] == 'X-XXX'
assert mva[15] == 'XXXXX'
s = LogicSim(c, 16, m=8) # FIXME: m=4
assert s.s_len == 5
bpa = bparray(
'00---', '01---', '0----', '0X---',
'10---', '11---', '1----', '1X---',
'-0---', '-1---', '-----', '-X---',
'X0---', 'X1---', 'X----', 'XX---')
s.s[0] = bpa
s.s_to_c()
s.c_prop()
s.c_to_s()
mva = bp_to_mv(s.s[1])
assert_equal_shape_and_contents(mva, mvarray(
'--001', '--011', '--0X1', '--0X1',
'--010', '--110', '--X10', '--X10',
'--0XX', '--X1X', '--XXX', '--XXX',
'--0XX', '--X1X', '--XXX', '--XXX'))
def test_8v():
c = bench.parse('input(x, y) output(a, o, n, xo) a=and(x,y) o=or(x,y) n=not(x) xo=xor(x,y)')
s = LogicSim(c, 64, m=8)
assert len(s.interface) == 6
mva = MVArray(['000010', '010111', '0-0X1X', '0X0X1X', '0R0R1R', '0F0F1F', '0P0P1P', '0N0N1N',
'100101', '111100', '1-X10X', '1XX10X', '1RR10F', '1FF10R', '1PP10N', '1NN10P',
'-00XXX', '-1X1XX', '--XXXX', '-XXXXX', '-RXXXX', '-FXXXX', '-PXXXX', '-NXXXX',
'X00XXX', 'X1X1XX', 'X-XXXX', 'XXXXXX', 'XRXXXX', 'XFXXXX', 'XPXXXX', 'XNXXXX',
'R00RFR', 'R1R1FF', 'R-XXFX', 'RXXXFX', 'RRRRFP', 'RFPNFN', 'RPPRFR', 'RNRNFF',
'F00FRF', 'F1F1RR', 'F-XXRX', 'FXXXRX', 'FRPNRN', 'FFFFRP', 'FPPFRF', 'FNFNRR',
'P00PNP', 'P1P1NN', 'P-XXNX', 'PXXXNX', 'PRPRNR', 'PFPFNF', 'PPPPNP', 'PNPNNN',
'N00NPN', 'N1N1PP', 'N-XXPX', 'NXXXPX', 'NRRNPF', 'NFFNPR', 'NPPNPN', 'NNNNPP'], m=8)
bpa = BPArray(mva)
s.assign(bpa)
s.propagate()
resp_bp = BPArray(bpa)
s.capture(resp_bp)
resp = MVArray(resp_bp)
for i in range(64):
assert resp[i] == mva[i]
assert s.s_len == 6
mva = mvarray(
'000010', '010111', '0-0X1X', '0X0X1X', '0R0R1R', '0F0F1F', '0P0P1P', '0N0N1N',
'100101', '111100', '1-X10X', '1XX10X', '1RR10F', '1FF10R', '1PP10N', '1NN10P',
'-00XXX', '-1X1XX', '--XXXX', '-XXXXX', '-RXXXX', '-FXXXX', '-PXXXX', '-NXXXX',
'X00XXX', 'X1X1XX', 'X-XXXX', 'XXXXXX', 'XRXXXX', 'XFXXXX', 'XPXXXX', 'XNXXXX',
'R00RFR', 'R1R1FF', 'R-XXFX', 'RXXXFX', 'RRRRFP', 'RFPNFN', 'RPPRFR', 'RNRNFF',
'F00FRF', 'F1F1RR', 'F-XXRX', 'FXXXRX', 'FRPNRN', 'FFFFRP', 'FPPFRF', 'FNFNRR',
'P00PNP', 'P1P1NN', 'P-XXNX', 'PXXXNX', 'PRPRNR', 'PFPFNF', 'PPPPNP', 'PNPNNN',
'N00NPN', 'N1N1PP', 'N-XXPX', 'NXXXPX', 'NRRNPF', 'NFFNPR', 'NPPNPN', 'NNNNPP')
tests = np.copy(mva)
tests[2:] = logic.UNASSIGNED
bpa = mv_to_bp(tests)
s.s[0] = bpa
s.s_to_c()
s.c_prop()
s.c_to_s()
resp = bp_to_mv(s.s[1])
exp_resp = np.copy(mva)
exp_resp[:2] = logic.UNASSIGNED
np.testing.assert_allclose(resp, exp_resp)
def test_loop():
c = bench.parse('q=dff(d) d=not(q)')
s = LogicSim(c, 4, m=8)
assert len(s.interface) == 1
mva = MVArray([['0'], ['1'], ['R'], ['F']], m=8)
assert s.s_len == 1
mva = mvarray([['0'], ['1'], ['R'], ['F']])
s.assign(BPArray(mva))
s.propagate()
resp_bp = BPArray((len(s.interface), s.sims))
s.capture(resp_bp)
resp = MVArray(resp_bp)
# TODO
# s.assign(BPArray(mva))
# s.propagate()
# resp_bp = BPArray((len(s.interface), s.sims))
# s.capture(resp_bp)
# resp = MVArray(resp_bp)
assert resp[0] == '1'
assert resp[1] == '0'
assert resp[2] == 'F'
assert resp[3] == 'R'
# assert resp[0] == '1'
# assert resp[1] == '0'
# assert resp[2] == 'F'
# assert resp[3] == 'R'
resp_bp = s.cycle(resp_bp)
resp = MVArray(resp_bp)
# resp_bp = s.cycle(resp_bp)
# resp = MVArray(resp_bp)
assert resp[0] == '0'
assert resp[1] == '1'
assert resp[2] == 'R'
assert resp[3] == 'F'
# assert resp[0] == '0'
# assert resp[1] == '1'
# assert resp[2] == 'R'
# assert resp[3] == 'F'
def test_latch():
c = bench.parse('input(d, t) output(q) q=latch(d, t)')
s = LogicSim(c, 8, m=8)
assert len(s.interface) == 4
mva = MVArray(['00-0', '00-1', '01-0', '01-1', '10-0', '10-1', '11-0', '11-1'], m=8)
exp = MVArray(['0000', '0011', '0100', '0100', '1000', '1011', '1111', '1111'], m=8)
assert s.s_len == 4
mva = mvarray('00-0', '00-1', '01-0', '01-1', '10-0', '10-1', '11-0', '11-1')
exp = mvarray('0000', '0011', '0100', '0100', '1000', '1011', '1111', '1111')
resp = MVArray(s.cycle(BPArray(mva)))
# TODO
# resp = MVArray(s.cycle(BPArray(mva)))
for i in range(len(mva)):
assert resp[i] == exp[i]
# for i in range(len(mva)):
# assert resp[i] == exp[i]
def test_b01(mydir):
c = bench.load(mydir / 'b01.bench')
# 2-valued
s = LogicSim(c, 8, m=2)
assert len(s.interface) == 9
mva = MVArray((len(s.interface), 8), m=2)
# mva.randomize()
bpa = BPArray(mva)
s.assign(bpa)
s.propagate()
s.capture(bpa)
# 8-valued
s = LogicSim(c, 8, m=8)
mva = MVArray((len(s.interface), 8), m=8)
# mva.randomize()
bpa = BPArray(mva)
s.assign(bpa)
s.propagate()
s.capture(bpa)
mva = np.zeros((s.s_len, 8), dtype=np.uint8)
s.s[0] = mv_to_bp(mva)
s.s_to_c()
s.c_prop()
s.c_to_s()
bp_to_mv(s.s[1])

4
tests/test_stil.py

@ -3,7 +3,7 @@ from kyupy import stil, verilog @@ -3,7 +3,7 @@ from kyupy import stil, verilog
def test_b14(mydir):
b14 = verilog.load(mydir / 'b14.v.gz')
s = stil.load(mydir / 'b14.stuck.stil.gz')
assert len(s.signal_groups) == 10
assert len(s.scan_chains) == 1
@ -12,7 +12,7 @@ def test_b14(mydir): @@ -12,7 +12,7 @@ def test_b14(mydir):
resp = s.responses(b14)
assert len(tests) > 0
assert len(resp) > 0
s2 = stil.load(mydir / 'b14.transition.stil.gz')
tests = s2.tests_loc(b14)
resp = s2.responses(b14)

57
tests/test_wave_sim.py

@ -3,7 +3,7 @@ import numpy as np @@ -3,7 +3,7 @@ import numpy as np
from kyupy.wave_sim import WaveSim, WaveSimCuda, wave_eval_cpu, TMIN, TMAX
from kyupy.logic_sim import LogicSim
from kyupy import verilog, sdf, logic, bench
from kyupy.logic import MVArray, BPArray
from kyupy.logic import mvarray
from kyupy.sim import SimPrim
@ -27,7 +27,7 @@ def test_nand_delays(): @@ -27,7 +27,7 @@ def test_nand_delays():
line_times[2, :, 1] = 0.6
line_times[3, :, 0] = 0.7 # as above for D -> Z
line_times[3, :, 1] = 0.8
sdata = np.asarray([1, -1, 0, 0], dtype='float32')
def wave_assert(inputs, output):
@ -54,7 +54,7 @@ def test_tiny_circuit(): @@ -54,7 +54,7 @@ def test_tiny_circuit():
lt[:,0,:] = 1.0 # unit delay for all lines
wsim = WaveSim(c, lt)
assert len(wsim.s) == 5
# values for x
wsim.s[0,0,:3] = 0, 0.1, 0
wsim.s[0,1,:3] = 0, 0.2, 1
@ -66,7 +66,7 @@ def test_tiny_circuit(): @@ -66,7 +66,7 @@ def test_tiny_circuit():
wsim.s[1,1,:3] = 1, 0.6, 0
wsim.s[1,2,:3] = 1, 0.7, 0
wsim.s[1,3,:3] = 0, 0.8, 1
wsim.s_to_c()
x_c_loc = wsim.vat[wsim.ppi_offset+0, 0] # check x waveforms
@ -94,7 +94,7 @@ def test_tiny_circuit(): @@ -94,7 +94,7 @@ def test_tiny_circuit():
np.testing.assert_allclose(wsim.c[o_c_loc:o_c_loc+3, 1], [TMIN, TMAX, TMAX])
np.testing.assert_allclose(wsim.c[o_c_loc:o_c_loc+3, 2], [TMIN, 1.7, TMAX])
np.testing.assert_allclose(wsim.c[o_c_loc:o_c_loc+3, 3], [TMIN, TMAX, TMAX])
n_c_loc = wsim.vat[wsim.ppo_offset+4, 0] # check n waveforms
np.testing.assert_allclose(wsim.c[n_c_loc:n_c_loc+3, 0], [TMIN, TMAX, TMAX])
np.testing.assert_allclose(wsim.c[n_c_loc:n_c_loc+3, 1], [TMIN, 1.2, TMAX])
@ -123,37 +123,38 @@ def test_tiny_circuit(): @@ -123,37 +123,38 @@ def test_tiny_circuit():
def compare_to_logic_sim(wsim: WaveSim):
tests = MVArray((len(wsim.s_nodes), wsim.sims))
choices = np.asarray([logic.ZERO, logic.ONE, logic.RISE, logic.FALL], dtype=np.uint8)
rng = np.random.default_rng(10)
tests.data[...] = rng.choice(choices, tests.data.shape)
tests = rng.choice(choices, (wsim.s_len, wsim.sims))
wsim.s[:, :, 0] = (tests.data & 2) >> 1
wsim.s[:, :, 3] = (tests.data & 2) >> 1
wsim.s[:, :, 0] = (tests & 2) >> 1
wsim.s[:, :, 3] = (tests & 2) >> 1
wsim.s[:, :, 1] = 0.0
wsim.s[:, :, 2] = tests.data & 1
wsim.s[:, :, 6] = tests.data & 1
wsim.s[:, :, 2] = tests & 1
wsim.s[:, :, 6] = tests & 1
wsim.s_to_c()
wsim.c_prop()
wsim.c_to_s()
resp = MVArray(tests)
resp.data[...] = np.array(wsim.s[:, :, 6], dtype=np.uint8) | (np.array(wsim.s[:, :, 3], dtype=np.uint8)<<1)
resp.data |= ((resp.data ^ (resp.data >> 1)) & 1) << 2 # transitions
tests_bp = BPArray(tests)
lsim = LogicSim(wsim.circuit, len(tests_bp))
lsim.assign(tests_bp)
lsim.propagate()
exp_bp = BPArray(tests_bp)
lsim.capture(exp_bp)
exp = MVArray(exp_bp)
for i in range(8):
exp_str = exp[i].replace('P', '0').replace('N', '1')
res_str = resp[i].replace('P', '0').replace('N', '1')
assert res_str == exp_str
resp = np.array(wsim.s[:, :, 6], dtype=np.uint8) | (np.array(wsim.s[:, :, 3], dtype=np.uint8)<<1)
resp |= ((resp ^ (resp >> 1)) & 1) << 2 # transitions
resp[wsim.pi_s_locs] = logic.UNASSIGNED
lsim = LogicSim(wsim.circuit, tests.shape[-1])
lsim.s[0] = logic.mv_to_bp(tests)
lsim.s_to_c()
lsim.c_prop()
lsim.c_to_s()
exp = logic.bp_to_mv(lsim.s[1])
resp[resp == logic.PPULSE] = logic.ZERO
resp[resp == logic.NPULSE] = logic.ONE
exp[exp == logic.PPULSE] = logic.ZERO
exp[exp == logic.NPULSE] = logic.ONE
np.testing.assert_allclose(resp, exp)
def test_b14(b14_circuit, b14_timing):

138
tests/test_wave_sim_old.py

@ -1,138 +0,0 @@ @@ -1,138 +0,0 @@
import numpy as np
from kyupy.wave_sim_old import WaveSim, WaveSimCuda, wave_eval, TMIN, TMAX
from kyupy.logic_sim import LogicSim
from kyupy import verilog, sdf, logic
from kyupy.logic import MVArray, BPArray
def test_wave_eval():
# SDF specifies IOPATH delays with respect to output polarity
# SDF pulse rejection value is determined by IOPATH causing last transition and polarity of last transition
line_times = np.zeros((3, 2, 2))
line_times[0, 0, 0] = 0.1 # A -> Z rise delay
line_times[0, 0, 1] = 0.2 # A -> Z fall delay
line_times[0, 1, 0] = 0.1 # A -> Z negative pulse limit (terminate in rising Z)
line_times[0, 1, 1] = 0.2 # A -> Z positive pulse limit
line_times[1, 0, 0] = 0.3 # as above for B -> Z
line_times[1, 0, 1] = 0.4
line_times[1, 1, 0] = 0.3
line_times[1, 1, 1] = 0.4
state = np.zeros((3*16, 1)) + TMAX # 3 waveforms of capacity 16
state[::16, 0] = 16 # first entry is capacity
a = state[0:16, 0]
b = state[16:32, 0]
z = state[32:, 0]
sat = np.zeros((3, 3), dtype='int')
sat[0] = 0, 16, 0
sat[1] = 16, 16, 0
sat[2] = 32, 16, 0
sdata = np.asarray([1, -1, 0, 0], dtype='float32')
wave_eval((0b0111, 2, 0, 1), state, sat, 0, line_times, sdata)
assert z[0] == TMIN
a[0] = TMIN
wave_eval((0b0111, 2, 0, 1), state, sat, 0, line_times, sdata)
assert z[0] == TMIN
b[0] = TMIN
wave_eval((0b0111, 2, 0, 1), state, sat, 0, line_times, sdata)
assert z[0] == TMAX
a[0] = 1 # A _/^^^
b[0] = 2 # B __/^^
wave_eval((0b0111, 2, 0, 1), state, sat, 0, line_times, sdata)
assert z[0] == TMIN # ^^^\___ B -> Z fall delay
assert z[1] == 2.4
assert z[2] == TMAX
a[0] = TMIN # A ^^^^^^
b[0] = TMIN # B ^^^\__
b[1] = 2
wave_eval((0b0111, 2, 0, 1), state, sat, 0, line_times, sdata)
assert z[0] == 2.3 # ___/^^^ B -> Z rise delay
assert z[1] == TMAX
# pos pulse of 0.35 at B -> 0.45 after delays
a[0] = TMIN # A ^^^^^^^^
b[0] = TMIN
b[1] = 2 # B ^^\__/^^
b[2] = 2.35
wave_eval((0b0111, 2, 0, 1), state, sat, 0, line_times, sdata)
assert z[0] == 2.3 # __/^^\__
assert z[1] == 2.75
assert z[2] == TMAX
# neg pulse of 0.45 at B -> 0.35 after delays
a[0] = TMIN # A ^^^^^^^^
b[0] = 2 # B __/^^\__
b[1] = 2.45
b[2] = TMAX
wave_eval((0b0111, 2, 0, 1), state, sat, 0, line_times, sdata)
assert z[0] == TMIN # ^^\__/^^
assert z[1] == 2.4
assert z[2] == 2.75
assert z[3] == TMAX
# neg pulse of 0.35 at B -> 0.25 after delays (filtered)
a[0] = TMIN # A ^^^^^^^^
b[0] = 2 # B __/^^\__
b[1] = 2.35
b[2] = TMAX
wave_eval((0b0111, 2, 0, 1), state, sat, 0, line_times, sdata)
assert z[0] == TMIN # ^^^^^^
assert z[1] == TMAX
# pos pulse of 0.25 at B -> 0.35 after delays (filtered)
a[0] = TMIN # A ^^^^^^^^
b[0] = TMIN
b[1] = 2 # B ^^\__/^^
b[2] = 2.25
wave_eval((0b0111, 2, 0, 1), state, sat, 0, line_times, sdata)
assert z[0] == TMAX # ______
def compare_to_logic_sim(wsim):
tests = MVArray((len(wsim.interface), wsim.sims))
choices = np.asarray([logic.ZERO, logic.ONE, logic.RISE, logic.FALL], dtype=np.uint8)
rng = np.random.default_rng(10)
tests.data[...] = rng.choice(choices, tests.data.shape)
tests_bp = BPArray(tests)
wsim.assign(tests_bp)
wsim.propagate()
cdata = wsim.capture()
resp = MVArray(tests)
for iidx, inode in enumerate(wsim.interface):
if len(inode.ins) > 0:
for vidx in range(wsim.sims):
resp.data[iidx, vidx] = logic.ZERO if cdata[iidx, vidx, 0] < 0.5 else logic.ONE
# resp.set_value(vidx, iidx, 0 if cdata[iidx, vidx, 0] < 0.5 else 1)
lsim = LogicSim(wsim.circuit, len(tests_bp))
lsim.assign(tests_bp)
lsim.propagate()
exp_bp = BPArray(tests_bp)
lsim.capture(exp_bp)
exp = MVArray(exp_bp)
for i in range(8):
exp_str = exp[i].replace('R', '1').replace('F', '0').replace('P', '0').replace('N', '1')
res_str = resp[i].replace('R', '1').replace('F', '0').replace('P', '0').replace('N', '1')
assert res_str == exp_str
def test_b14(b14_circuit, b14_timing):
compare_to_logic_sim(WaveSim(b14_circuit, b14_timing, 8))
def test_b14_strip_forks(b14_circuit, b14_timing):
compare_to_logic_sim(WaveSim(b14_circuit, b14_timing, 8, strip_forks=True))
def test_b14_cuda(b14_circuit, b14_timing):
compare_to_logic_sim(WaveSimCuda(b14_circuit, b14_timing, 8, strip_forks=True))
Loading…
Cancel
Save