From 1eb8d87884b81530ad7de0449822ff650ef7d811 Mon Sep 17 00:00:00 2001 From: stefan Date: Sun, 12 Mar 2023 23:59:10 +0900 Subject: [PATCH] faster logic sim, removing MVArray, BPArray --- src/kyupy/circuit.py | 7 +- src/kyupy/logic.py | 479 ++++++++++++------------------------- src/kyupy/logic_sim.py | 312 +++++++++++++----------- src/kyupy/sim.py | 18 +- src/kyupy/stil.py | 56 ++--- tests/test_logic.py | 313 ++++++------------------ tests/test_logic_sim.py | 185 +++++++------- tests/test_stil.py | 4 +- tests/test_wave_sim.py | 57 ++--- tests/test_wave_sim_old.py | 138 ----------- 10 files changed, 553 insertions(+), 1016 deletions(-) delete mode 100644 tests/test_wave_sim_old.py diff --git a/src/kyupy/circuit.py b/src/kyupy/circuit.py index 8310e4f..23e30bf 100644 --- a/src/kyupy/circuit.py +++ b/src/kyupy/circuit.py @@ -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 diff --git a/src/kyupy/logic.py b/src/kyupy/logic.py index 4e5fe3e..c6dc199 100644 --- a/src/kyupy/logic.py +++ b/src/kyupy/logic.py @@ -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): 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): 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'' - - 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): 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): 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): 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): 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): 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'' - - 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 diff --git a/src/kyupy/logic_sim.py b/src/kyupy/logic_sim.py index e004463..6f44c15 100644 --- a/src/kyupy/logic_sim.py +++ b/src/kyupy/logic_sim.py @@ -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: :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'' + return f'' - 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: (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: 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: :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]) \ No newline at end of file + # 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]) \ No newline at end of file diff --git a/src/kyupy/sim.py b/src/kyupy/sim.py index 4d4aa3f..76efe98 100644 --- a/src/kyupy/sim.py +++ b/src/kyupy/sim.py @@ -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: 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: 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: 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: '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: 'oai22': (OAI22, OAI22, OAI22), 'oa21': (OA21, OA21, OA21), 'oai21': (OAI21, OAI21, OAI21), - + 'mux21': (MUX21, MUX21, MUX21), } @@ -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) diff --git a/src/kyupy/stil.py b/src/kyupy/stil.py index 3856f32..1a92c95 100644 --- a/src/kyupy/stil.py +++ b/src/kyupy/stil.py @@ -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: 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: 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: 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 diff --git a/tests/test_logic.py b/tests/test_logic.py index 27b61ae..9e74ec8 100644 --- a/tests/test_logic.py +++ b/tests/test_logic.py @@ -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')) diff --git a/tests/test_logic_sim.py b/tests/test_logic_sim.py index b581cb6..22b5866 100644 --- a/tests/test_logic_sim.py +++ b/tests/test_logic_sim.py @@ -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]) diff --git a/tests/test_stil.py b/tests/test_stil.py index 3bb0182..0b26259 100644 --- a/tests/test_stil.py +++ b/tests/test_stil.py @@ -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): 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) diff --git a/tests/test_wave_sim.py b/tests/test_wave_sim.py index d7944e1..b8fc18c 100644 --- a/tests/test_wave_sim.py +++ b/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.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(): 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(): 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(): 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(): 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(): 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): diff --git a/tests/test_wave_sim_old.py b/tests/test_wave_sim_old.py deleted file mode 100644 index b23d69d..0000000 --- a/tests/test_wave_sim_old.py +++ /dev/null @@ -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))