Browse Source

faster logic sim, removing MVArray, BPArray

devel
stefan 2 years ago
parent
commit
1eb8d87884
  1. 7
      src/kyupy/circuit.py
  2. 345
      src/kyupy/logic.py
  3. 312
      src/kyupy/logic_sim.py
  4. 4
      src/kyupy/sim.py
  5. 56
      src/kyupy/stil.py
  6. 313
      tests/test_logic.py
  7. 171
      tests/test_logic_sim.py
  8. 47
      tests/test_wave_sim.py
  9. 138
      tests/test_wave_sim_old.py

7
src/kyupy/circuit.py

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

345
src/kyupy/logic.py

@ -66,20 +66,13 @@ def interpret(value):
""" """
if isinstance(value, Iterable) and not (isinstance(value, str) and len(value) == 1): if isinstance(value, Iterable) and not (isinstance(value, str) and len(value) == 1):
return list(map(interpret, value)) return list(map(interpret, value))
if value in [0, '0', False, 'L', 'l']: if value in [0, '0', False, 'L', 'l']: return ZERO
return ZERO if value in [1, '1', True, 'H', 'h']: return ONE
if value in [1, '1', True, 'H', 'h']: if value in [None, '-', 'Z', 'z']: return UNASSIGNED
return ONE if value in ['R', 'r', '/']: return RISE
if value in [None, '-', 'Z', 'z']: if value in ['F', 'f', '\\']: return FALL
return UNASSIGNED if value in ['P', 'p', '^']: return PPULSE
if value in ['R', 'r', '/']: if value in ['N', 'n', 'v']: return NPULSE
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 return UNKNOWN
@ -91,18 +84,21 @@ def bit_in(a, pos):
return a[pos >> 3] & _bit_in_lut[pos & 7] return a[pos >> 3] & _bit_in_lut[pos & 7]
def unpackbits(a): def unpackbits(a : np.ndarray):
"""Returns an uint8-array containing the unpacked bits of an array `a`. """Unpacks the bits of given ndarray `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. 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) return np.unpackbits(a.view(np.uint8), bitorder='little').reshape(*a.shape, 8*a.itemsize)
def packbits(a, dtype=np.uint8): def packbits(a, dtype=np.uint8):
"""Packs the values of a boolean-valued array `a` along its last axis into bits. """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`. Signed integer datatypes are padded with the most significant bit, all others are padded with `0`.
""" """
dtype = np.dtype(dtype) dtype = np.dtype(dtype)
@ -114,94 +110,32 @@ def packbits(a, dtype=np.uint8):
return np.packbits(a, bitorder='little').view(dtype).reshape(a.shape[:-1]) return np.packbits(a, bitorder='little').view(dtype).reshape(a.shape[:-1])
class MVArray: def mvarray(*a):
"""An n-dimensional array of m-valued logic values. 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 def mv_to_bp(mva):
compatible with a simulator ``sim``, use ``(len(sim.interface), n)``. If a :py:class:`BPArray` or if mva.ndim == 1: mva = mva[..., np.newaxis]
:py:class:`MVArray` is given, a deep copy is made. If a string, a list of strings, a list of characters, return np.packbits(unpackbits(mva)[...,:3], axis=-2, bitorder='little').swapaxes(-1,-2)
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. def bparray(*a):
if isinstance(a, MVArray): return mv_to_bp(mvarray(*a))
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". def bp_to_mv(bpa):
""" return packbits(np.unpackbits(bpa, axis=-1, bitorder='little').swapaxes(-1,-2))
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) def _mv_not(out, inp):
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):
np.bitwise_xor(inp, 0b11, out=out) # this also exchanges UNASSIGNED <-> UNKNOWN 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. """A multi-valued NOT operator.
:param x1: An :py:class:`MVArray` or data the :py:class:`MVArray` constructor accepts. :param x1: An :py:class:`MVArray` or data the :py:class:`MVArray` constructor accepts.
@ -209,15 +143,14 @@ def mv_not(x1, out=None):
is returned. is returned.
:return: An :py:class:`MVArray` with the result. :return: An :py:class:`MVArray` with the result.
""" """
m = mv_getm(x1) #m = mv_getm(x1)
x1 = mv_cast(x1, m=m)[0] #x1 = mv_cast(x1, m=m)[0]
out = out or MVArray(x1.data.shape, m=m) out = out or np.empty(x1.shape, dtype=np.uint8)
_mv_not(m, out.data, x1.data) _mv_not(out, x1)
return out return out
def _mv_or(m, out, *ins): def _mv_or(out, *ins):
if m > 2:
any_unknown = (ins[0] == UNKNOWN) | (ins[0] == UNASSIGNED) any_unknown = (ins[0] == UNKNOWN) | (ins[0] == UNASSIGNED)
for inp in ins[1:]: any_unknown |= (inp == UNKNOWN) | (inp == UNASSIGNED) for inp in ins[1:]: any_unknown |= (inp == UNKNOWN) | (inp == UNASSIGNED)
any_one = (ins[0] == ONE) any_one = (ins[0] == ONE)
@ -228,9 +161,6 @@ def _mv_or(m, out, *ins):
for inp in ins: for inp in ins:
np.bitwise_or(out, inp, out=out, where=~any_one) np.bitwise_or(out, inp, out=out, where=~any_one)
np.putmask(out, (any_unknown & ~any_one), UNKNOWN) np.putmask(out, (any_unknown & ~any_one), UNKNOWN)
else:
out[...] = ZERO
for inp in ins: np.bitwise_or(out, inp, out=out)
def mv_or(x1, x2, out=None): def mv_or(x1, x2, out=None):
@ -242,15 +172,14 @@ def mv_or(x1, x2, out=None):
is returned. is returned.
:return: An :py:class:`MVArray` with the result. :return: An :py:class:`MVArray` with the result.
""" """
m = mv_getm(x1, x2) #m = mv_getm(x1, x2)
x1, x2 = mv_cast(x1, x2, m=m) #x1, x2 = mv_cast(x1, x2, m=m)
out = out or MVArray(np.broadcast(x1.data, x2.data).shape, m=m) out = out or np.empty(np.broadcast(x1, x2).shape, dtype=np.uint8)
_mv_or(m, out.data, x1.data, x2.data) _mv_or(out, x1, x2)
return out return out
def _mv_and(m, out, *ins): def _mv_and(out, *ins):
if m > 2:
any_unknown = (ins[0] == UNKNOWN) | (ins[0] == UNASSIGNED) any_unknown = (ins[0] == UNKNOWN) | (ins[0] == UNASSIGNED)
for inp in ins[1:]: any_unknown |= (inp == UNKNOWN) | (inp == UNASSIGNED) for inp in ins[1:]: any_unknown |= (inp == UNKNOWN) | (inp == UNASSIGNED)
any_zero = (ins[0] == ZERO) any_zero = (ins[0] == ZERO)
@ -260,11 +189,8 @@ def _mv_and(m, out, *ins):
np.putmask(out, any_zero, ZERO) np.putmask(out, any_zero, ZERO)
for inp in ins: for inp in ins:
np.bitwise_and(out, inp | 0b100, out=out, where=~any_zero) 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.bitwise_or(out, inp & 0b100, out=out, where=~any_zero)
np.putmask(out, (any_unknown & ~any_zero), UNKNOWN) np.putmask(out, (any_unknown & ~any_zero), UNKNOWN)
else:
out[...] = ONE
for inp in ins: np.bitwise_and(out, inp, out=out)
def mv_and(x1, x2, out=None): def mv_and(x1, x2, out=None):
@ -276,26 +202,22 @@ def mv_and(x1, x2, out=None):
is returned. is returned.
:return: An :py:class:`MVArray` with the result. :return: An :py:class:`MVArray` with the result.
""" """
m = mv_getm(x1, x2) #m = mv_getm(x1, x2)
x1, x2 = mv_cast(x1, x2, m=m) #x1, x2 = mv_cast(x1, x2, m=m)
out = out or MVArray(np.broadcast(x1.data, x2.data).shape, m=m) out = out or np.empty(np.broadcast(x1, x2).shape, dtype=np.uint8)
_mv_and(m, out.data, x1.data, x2.data) _mv_and(out, x1, x2)
return out return out
def _mv_xor(m, out, *ins): def _mv_xor(out, *ins):
if m > 2:
any_unknown = (ins[0] == UNKNOWN) | (ins[0] == UNASSIGNED) any_unknown = (ins[0] == UNKNOWN) | (ins[0] == UNASSIGNED)
for inp in ins[1:]: any_unknown |= (inp == UNKNOWN) | (inp == UNASSIGNED) for inp in ins[1:]: any_unknown |= (inp == UNKNOWN) | (inp == UNASSIGNED)
out[...] = ZERO out[...] = ZERO
for inp in ins: for inp in ins:
np.bitwise_xor(out, inp & 0b011, out=out) np.bitwise_xor(out, inp & 0b011, out=out)
if m > 4: np.bitwise_or(out, inp & 0b100, out=out) np.bitwise_or(out, inp & 0b100, out=out)
np.putmask(out, any_unknown, UNKNOWN) np.putmask(out, any_unknown, UNKNOWN)
else:
out[...] = ZERO
for inp in ins: np.bitwise_xor(out, inp, out=out)
def mv_xor(x1, x2, out=None): def mv_xor(x1, x2, out=None):
@ -307,27 +229,27 @@ def mv_xor(x1, x2, out=None):
is returned. is returned.
:return: An :py:class:`MVArray` with the result. :return: An :py:class:`MVArray` with the result.
""" """
m = mv_getm(x1, x2) #m = mv_getm(x1, x2)
x1, x2 = mv_cast(x1, x2, m=m) #x1, x2 = mv_cast(x1, x2, m=m)
out = out or MVArray(np.broadcast(x1.data, x2.data).shape, m=m) out = out or np.empty(np.broadcast(x1, x2).shape, dtype=np.uint8)
_mv_xor(m, out.data, x1.data, x2.data) _mv_xor(out, x1, x2)
return out return out
def mv_latch(d, t, q_prev, out=None): 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. """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) #m = mv_getm(d, t, q_prev)
d, t, q_prev = mv_cast(d, t, q_prev, m=m) #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 = out or np.empty(np.broadcast(d, t, q_prev).shape, dtype=np.uint8)
out.data[...] = t.data & d.data & 0b011 out[...] = t & d & 0b011
out.data[...] |= ~t.data & 0b010 & (q_prev.data << 1) out[...] |= ~t & 0b010 & (q_prev << 1)
out.data[...] |= ~t.data & 0b001 & (out.data >> 1) out[...] |= ~t & 0b001 & (out >> 1)
out.data[...] |= ((out.data << 1) ^ (out.data << 2)) & 0b100 out[...] |= ((out << 1) ^ (out << 2)) & 0b100
unknown = (t.data == UNKNOWN) \ unknown = (t == UNKNOWN) \
| (t.data == UNASSIGNED) \ | (t == UNASSIGNED) \
| (((d.data == UNKNOWN) | (d.data == UNASSIGNED)) & (t.data != ZERO)) | (((d == UNKNOWN) | (d == UNASSIGNED)) & (t != ZERO))
np.putmask(out.data, unknown, UNKNOWN) np.putmask(out, unknown, UNKNOWN)
return out return out
@ -342,106 +264,40 @@ def mv_transition(init, final, out=None):
is returned. is returned.
:return: An :py:class:`MVArray` with the result. :return: An :py:class:`MVArray` with the result.
""" """
m = mv_getm(init, final) #m = mv_getm(init, final)
init, final = mv_cast(init, final, m=m) #init, final = mv_cast(init, final, m=m)
init = init.data #init = init.data
final = final.data #final = final.data
out = out or MVArray(np.broadcast(init, final).shape, m=8) out = out or np.empty(np.broadcast(init, final).shape, dtype=np.uint8)
out.data[...] = (init & 0b010) | (final & 0b001) out[...] = (init & 0b010) | (final & 0b001)
out.data[...] |= ((out.data << 1) ^ (out.data << 2)) & 0b100 out[...] |= ((out << 1) ^ (out << 2)) & 0b100
unknown = (init == UNKNOWN) | (init == UNASSIGNED) | (final == UNKNOWN) | (final == UNASSIGNED) unknown = (init == UNKNOWN) | (init == UNASSIGNED) | (final == UNKNOWN) | (final == UNASSIGNED)
unassigned = (init == UNASSIGNED) & (final == UNASSIGNED) unassigned = (init == UNASSIGNED) & (final == UNASSIGNED)
np.putmask(out.data, unknown, UNKNOWN) np.putmask(out, unknown, UNKNOWN)
np.putmask(out.data, unassigned, UNASSIGNED) np.putmask(out, unassigned, UNASSIGNED)
return out 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): def bp_buf(out, inp):
md = out.shape[-2]
assert md == inp.shape[-2]
if md > 1:
unknown = inp[..., 0, :] ^ inp[..., 1, :] unknown = inp[..., 0, :] ^ inp[..., 1, :]
if md > 2: unknown &= ~inp[..., 2, :] unknown &= ~inp[..., 2, :]
out[..., 0, :] = inp[..., 0, :] | unknown out[..., 0, :] = inp[..., 0, :] | unknown
out[..., 1, :] = inp[..., 1, :] & ~unknown out[..., 1, :] = inp[..., 1, :] & ~unknown
if md > 2: out[..., 2, :] = inp[..., 2, :] & ~unknown out[..., 2, :] = inp[..., 2, :] & ~unknown
else: return out
out[..., 0, :] = inp[..., 0, :]
def bp_not(out, inp): def bp_not(out, inp):
md = out.shape[-2]
assert md == inp.shape[-2]
if md > 1:
unknown = inp[..., 0, :] ^ inp[..., 1, :] unknown = inp[..., 0, :] ^ inp[..., 1, :]
if md > 2: unknown &= ~inp[..., 2, :] unknown &= ~inp[..., 2, :]
out[..., 0, :] = ~inp[..., 0, :] | unknown out[..., 0, :] = ~inp[..., 0, :] | unknown
out[..., 1, :] = ~inp[..., 1, :] & ~unknown out[..., 1, :] = ~inp[..., 1, :] & ~unknown
if md > 2: out[..., 2, :] = inp[..., 2, :] & ~unknown out[..., 2, :] = inp[..., 2, :] & ~unknown
else: return out
out[..., 0, :] = ~inp[..., 0, :]
def bp_or(out, *ins): def bp_or(out, *ins):
md = out.shape[-2]
for inp in ins: assert md == inp.shape[-2]
out[...] = 0 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, :] 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[1:]: any_unknown |= (inp[..., 0, :] ^ inp[..., 1, :]) & ~inp[..., 2, :]
any_one = ins[0][..., 0, :] & ins[0][..., 1, :] & ~ins[0][..., 2, :] any_one = ins[0][..., 0, :] & ins[0][..., 1, :] & ~ins[0][..., 2, :]
@ -450,23 +306,11 @@ def bp_or(out, *ins):
out[..., 0, :] |= inp[..., 0, :] | any_unknown out[..., 0, :] |= inp[..., 0, :] | any_unknown
out[..., 1, :] |= inp[..., 1, :] & (~any_unknown | any_one) out[..., 1, :] |= inp[..., 1, :] & (~any_unknown | any_one)
out[..., 2, :] |= inp[..., 2, :] & (~any_unknown | any_one) & ~any_one out[..., 2, :] |= inp[..., 2, :] & (~any_unknown | any_one) & ~any_one
return out
def bp_and(out, *ins): def bp_and(out, *ins):
md = out.shape[-2]
for inp in ins: assert md == inp.shape[-2]
out[...] = 0xff 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, :] 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[1:]: any_unknown |= (inp[..., 0, :] ^ inp[..., 1, :]) & ~inp[..., 2, :]
any_zero = ~ins[0][..., 0, :] & ~ins[0][..., 1, :] & ~ins[0][..., 2, :] any_zero = ~ins[0][..., 0, :] & ~ins[0][..., 1, :] & ~ins[0][..., 2, :]
@ -476,21 +320,11 @@ def bp_and(out, *ins):
out[..., 0, :] &= inp[..., 0, :] | (any_unknown & ~any_zero) out[..., 0, :] &= inp[..., 0, :] | (any_unknown & ~any_zero)
out[..., 1, :] &= inp[..., 1, :] & ~any_unknown out[..., 1, :] &= inp[..., 1, :] & ~any_unknown
out[..., 2, :] |= inp[..., 2, :] & (~any_unknown | any_zero) & ~any_zero out[..., 2, :] |= inp[..., 2, :] & (~any_unknown | any_zero) & ~any_zero
return out
def bp_xor(out, *ins): def bp_xor(out, *ins):
md = out.shape[-2]
for inp in ins: assert md == inp.shape[-2]
out[...] = 0 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, :] 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[1:]: any_unknown |= (inp[..., 0, :] ^ inp[..., 1, :]) & ~inp[..., 2, :]
for inp in ins: for inp in ins:
@ -500,22 +334,10 @@ def bp_xor(out, *ins):
out[..., 0, :] |= any_unknown out[..., 0, :] |= any_unknown
out[..., 1, :] &= ~any_unknown out[..., 1, :] &= ~any_unknown
out[..., 2, :] &= ~any_unknown out[..., 2, :] &= ~any_unknown
return out
def bp_latch(out, d, t, q_prev): 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 = (t[..., 0, :] ^ t[..., 1, :]) & ~t[..., 2, :]
any_unknown |= ((d[..., 0, :] ^ d[..., 1, :]) & ~d[..., 2, :]) & (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[..., 1, :] = (d[..., 1, :] & t[..., 1, :]) | (q_prev[..., 0, :] & ~t[..., 1, :])
@ -524,3 +346,4 @@ def bp_latch(out, d, t, q_prev):
out[..., 0, :] |= any_unknown out[..., 0, :] |= any_unknown
out[..., 1, :] &= ~any_unknown out[..., 1, :] &= ~any_unknown
out[..., 2, :] &= ~any_unknown out[..., 2, :] &= ~any_unknown
return out

312
src/kyupy/logic_sim.py

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

4
src/kyupy/sim.py

@ -157,7 +157,9 @@ class SimOps:
""" """
def __init__(self, circuit, c_caps=1, c_reuse=False, strip_forks=False): def __init__(self, circuit, c_caps=1, c_reuse=False, strip_forks=False):
self.circuit = circuit 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) self.s_len = len(self.s_nodes)
keep_signals = not c_reuse keep_signals = not c_reuse

56
src/kyupy/stil.py

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

313
tests/test_logic.py

@ -1,252 +1,75 @@
import numpy as np
import kyupy.logic as lg import kyupy.logic as lg
from kyupy.logic import mvarray, bparray, bp_to_mv, mv_to_bp
def test_mvarray(): def assert_equal_shape_and_contents(actual, desired):
desired = np.array(desired, dtype=np.uint8)
# instantiation with shape assert actual.shape == desired.shape
np.testing.assert_allclose(actual, desired)
ary = lg.MVArray(4)
assert ary.length == 1
assert len(ary) == 1 def test_mvarray_single_vector():
assert ary.width == 4 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])
ary = lg.MVArray((3, 2)) 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 ary.length == 2 assert_equal_shape_and_contents(mvarray(['1']), [lg.ONE])
assert len(ary) == 2 assert_equal_shape_and_contents(mvarray('1'), [lg.ONE])
assert ary.width == 3
# instantiation with single vector 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]])
ary = lg.MVArray([1, 0, 1]) assert_equal_shape_and_contents(mvarray('10X', '--1'), [[lg.ONE, lg.UNASSIGNED], [lg.ZERO, lg.UNASSIGNED], [lg.UNKNOWN, lg.ONE]])
assert ary.length == 1
assert ary.width == 3
assert str(ary) == "['101']" def test_mv_ops():
assert ary[0] == '101' x1_8v = mvarray('00000000XXXXXXXX--------11111111PPPPPPPPRRRRRRRRFFFFFFFFNNNNNNNN')
x2_8v = mvarray('0X-1PRFN'*8)
ary = lg.MVArray("10X-")
assert ary.length == 1 assert_equal_shape_and_contents(lg.mv_not(x1_8v), mvarray('11111111XXXXXXXXXXXXXXXX00000000NNNNNNNNFFFFFFFFRRRRRRRRPPPPPPPP'))
assert ary.width == 4 assert_equal_shape_and_contents(lg.mv_or(x1_8v, x2_8v), mvarray('0XX1PRFNXXX1XXXXXXX1XXXX11111111PXX1PRFNRXX1RRNNFXX1FNFNNXX1NNNN'))
assert str(ary) == "['10X-']" assert_equal_shape_and_contents(lg.mv_and(x1_8v, x2_8v), mvarray('000000000XXXXXXX0XXXXXXX0XX1PRFN0XXPPPPP0XXRPRPR0XXFPPFF0XXNPRFN'))
assert ary[0] == '10X-' assert_equal_shape_and_contents(lg.mv_xor(x1_8v, x2_8v), mvarray('0XX1PRFNXXXXXXXXXXXXXXXX1XX0NFRPPXXNPRFNRXXFRPNFFXXRFNPRNXXPNFRP'))
ary = lg.MVArray("1") # TODO
assert ary.length == 1 #assert_equal_shape_and_contents(lg.mv_transition(x1_8v, x2_8v), mvarray('0XXR PRFNXXXXXXXXXXXXXXXX1XX0NFRPPXXNPRFNRXXFRPNFFXXRFNPRNXXPNFRP'))
assert ary.width == 1
x30_8v = mvarray('0000000000000000000000000000000000000000000000000000000000000000')
ary = lg.MVArray(["1"]) x31_8v = mvarray('1111111111111111111111111111111111111111111111111111111111111111')
assert ary.length == 1
assert ary.width == 1 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'))
# 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 test_bparray(): def test_bparray():
ary = lg.BPArray(4) bpa = bparray('0X-1PRFN')
assert ary.length == 1 assert bpa.shape == (8, 3, 1)
assert len(ary) == 1
assert ary.width == 4 bpa = bparray('0X-1PRFN-')
assert bpa.shape == (9, 3, 1)
ary = lg.BPArray((3, 2))
assert ary.length == 2 bpa = bparray('000', '001', '010', '011', '100', '101', '110', '111')
assert len(ary) == 2 assert bpa.shape == (3, 3, 1)
assert ary.width == 3
bpa = bparray('000', '001', '010', '011', '100', '101', '110', '111', 'RFX')
assert lg.MVArray(lg.BPArray("01", m=2))[0] == '01' assert bpa.shape == (3, 3, 2)
assert lg.MVArray(lg.BPArray("0X-1", m=4))[0] == '0X-1'
assert lg.MVArray(lg.BPArray("0X-1PRFN", m=8))[0] == '0X-1PRFN' 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_2v = lg.BPArray("0011", m=2)
x2_2v = lg.BPArray("0101", m=2) x1_8v = bparray('00000000XXXXXXXX--------11111111PPPPPPPPRRRRRRRRFFFFFFFFNNNNNNNN')
x1_4v = lg.BPArray("0000XXXX----1111", m=4) x2_8v = bparray('0X-1PRFN'*8)
x2_4v = lg.BPArray("0X-10X-10X-10X-1", m=4)
x1_8v = lg.BPArray("00000000XXXXXXXX--------11111111PPPPPPPPRRRRRRRRFFFFFFFFNNNNNNNN", m=8) out_8v = np.empty((64, 3, 1), dtype=np.uint8)
x2_8v = lg.BPArray("0X-1PRFN0X-1PRFN0X-1PRFN0X-1PRFN0X-1PRFN0X-1PRFN0X-1PRFN0X-1PRFN", m=8)
assert_equal_shape_and_contents(bp_to_mv(lg.bp_buf(out_8v, x1_8v))[:,0], mvarray('00000000XXXXXXXXXXXXXXXX11111111PPPPPPPPRRRRRRRRFFFFFFFFNNNNNNNN'))
out_2v = lg.BPArray((4, 1), m=2) assert_equal_shape_and_contents(bp_to_mv(lg.bp_or(out_8v, x1_8v, x2_8v))[:,0], mvarray('0XX1PRFNXXX1XXXXXXX1XXXX11111111PXX1PRFNRXX1RRNNFXX1FNFNNXX1NNNN'))
out_4v = lg.BPArray((16, 1), m=4) assert_equal_shape_and_contents(bp_to_mv(lg.bp_and(out_8v, x1_8v, x2_8v))[:,0], mvarray('000000000XXXXXXX0XXXXXXX0XX1PRFN0XXPPPPP0XXRPRPR0XXFPPFF0XXNPRFN'))
out_8v = lg.BPArray((64, 1), m=8) assert_equal_shape_and_contents(bp_to_mv(lg.bp_xor(out_8v, x1_8v, x2_8v))[:,0], mvarray('0XX1PRFNXXXXXXXXXXXXXXXX1XX0NFRPPXXNPRFNRXXFRPNFFXXRFNPRNXXPNFRP'))
lg.bp_buf(out_2v.data, x1_2v.data) x30_8v = bparray('0000000000000000000000000000000000000000000000000000000000000000')
lg.bp_buf(out_4v.data, x1_4v.data) x31_8v = bparray('1111111111111111111111111111111111111111111111111111111111111111')
lg.bp_buf(out_8v.data, x1_8v.data)
assert_equal_shape_and_contents(bp_to_mv(lg.bp_latch(out_8v, x1_8v, x2_8v, x30_8v))[:,0], mvarray('0XX000000XXXXXXX0XXXXXXX0XX10R110XX000000XXR0R0R0XXF001F0XX10R11'))
assert lg.MVArray(out_2v)[0] == '0011' assert_equal_shape_and_contents(bp_to_mv(lg.bp_latch(out_8v, x1_8v, x2_8v, x31_8v))[:,0], mvarray('1XX01F001XXXXXXX1XXXXXXX1XX111111XX01F001XXR110R1XXF1F1F1XX11111'))
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'

171
tests/test_logic_sim.py

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

47
tests/test_wave_sim.py

@ -3,7 +3,7 @@ import numpy as np
from kyupy.wave_sim import WaveSim, WaveSimCuda, wave_eval_cpu, TMIN, TMAX from kyupy.wave_sim import WaveSim, WaveSimCuda, wave_eval_cpu, TMIN, TMAX
from kyupy.logic_sim import LogicSim from kyupy.logic_sim import LogicSim
from kyupy import verilog, sdf, logic, bench from kyupy import verilog, sdf, logic, bench
from kyupy.logic import MVArray, BPArray from kyupy.logic import mvarray
from kyupy.sim import SimPrim from kyupy.sim import SimPrim
@ -123,37 +123,38 @@ def test_tiny_circuit():
def compare_to_logic_sim(wsim: WaveSim): 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) choices = np.asarray([logic.ZERO, logic.ONE, logic.RISE, logic.FALL], dtype=np.uint8)
rng = np.random.default_rng(10) 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[:, :, 0] = (tests & 2) >> 1
wsim.s[:, :, 3] = (tests.data & 2) >> 1 wsim.s[:, :, 3] = (tests & 2) >> 1
wsim.s[:, :, 1] = 0.0 wsim.s[:, :, 1] = 0.0
wsim.s[:, :, 2] = tests.data & 1 wsim.s[:, :, 2] = tests & 1
wsim.s[:, :, 6] = tests.data & 1 wsim.s[:, :, 6] = tests & 1
wsim.s_to_c() wsim.s_to_c()
wsim.c_prop() wsim.c_prop()
wsim.c_to_s() wsim.c_to_s()
resp = MVArray(tests) resp = np.array(wsim.s[:, :, 6], dtype=np.uint8) | (np.array(wsim.s[:, :, 3], dtype=np.uint8)<<1)
resp.data[...] = 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.data |= ((resp.data ^ (resp.data >> 1)) & 1) << 2 # transitions resp[wsim.pi_s_locs] = logic.UNASSIGNED
tests_bp = BPArray(tests) lsim = LogicSim(wsim.circuit, tests.shape[-1])
lsim = LogicSim(wsim.circuit, len(tests_bp)) lsim.s[0] = logic.mv_to_bp(tests)
lsim.assign(tests_bp) lsim.s_to_c()
lsim.propagate() lsim.c_prop()
exp_bp = BPArray(tests_bp) lsim.c_to_s()
lsim.capture(exp_bp) exp = logic.bp_to_mv(lsim.s[1])
exp = MVArray(exp_bp)
resp[resp == logic.PPULSE] = logic.ZERO
for i in range(8): resp[resp == logic.NPULSE] = logic.ONE
exp_str = exp[i].replace('P', '0').replace('N', '1')
res_str = resp[i].replace('P', '0').replace('N', '1') exp[exp == logic.PPULSE] = logic.ZERO
assert res_str == exp_str exp[exp == logic.NPULSE] = logic.ONE
np.testing.assert_allclose(resp, exp)
def test_b14(b14_circuit, b14_timing): def test_b14(b14_circuit, b14_timing):

138
tests/test_wave_sim_old.py

@ -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