From a77ac4a397f30e3b5fe2e661424657c045b76353 Mon Sep 17 00:00:00 2001 From: Stefan Holst Date: Mon, 14 Dec 2020 14:06:18 +0900 Subject: [PATCH] start designing new data structures for m-valued logic --- src/kyupy/logic.py | 146 ++++++++++++++++++++++++++++++++++++++++++++ tests/test_logic.py | 105 +++++++++++++++++++++++++++++++ 2 files changed, 251 insertions(+) create mode 100644 src/kyupy/logic.py create mode 100644 tests/test_logic.py diff --git a/src/kyupy/logic.py b/src/kyupy/logic.py new file mode 100644 index 0000000..6927788 --- /dev/null +++ b/src/kyupy/logic.py @@ -0,0 +1,146 @@ +"""Data structures for 2-valued, 4-valued, and 8-valued logic computation. + +Integer constants: ZERO, ONE, UNASSIGNED, UNKNOWN, RISING, FALLING, PPULSE, NPULSE. + +* The bits in the constants have the following meaning: + + * bit 0: Final/settled binary value of a signal + * bit 1: Initial binary value of a signal + * bit 2: 1, if activity or transitions are present on a signal + +Special meaning is given to values where bits 0 and 1 differ, but activity is 0. +These values are interpreted as 'unknown' or 'unassigned' in 4-valued and 8-valued logic. + +* 4-valued logic: 2 bits for storage, the third bit is implicitly 0 + + * 0 (0b00) : '0', 0, False, logic-0 (kyupy.logic.ZERO) + * 1 (0b01) : '-', None, unassigned (kyupy.logic.UNASSIGNED) + * 2 (0b10) : 'X', unknown (kyupy.logic.UNKNOWN) + * 3 (0b11) : '1', 1, True, logic-1 (kyupy.logic.ONE) + +* 8-valued logic: 3 bits for storage, adds the following 4 interpretations + + * 4 (0b100) : 'P', positive pulse 0 -> 1 -> 0 (kyupy.logic.PPULSE) + * 5 (0b101) : 'R', rising transition (kyupy.logic.RISING) + * 6 (0b110) : 'F', falling transition (kyupy.logic.FALLING) + * 7 (0b111) : 'N', negative pulse 1 -> 0 -> 1 (kyupy.logic.NPULSE) + +""" + +import math +from collections.abc import Iterable + +import numpy as np + +ZERO = 0b000 +UNASSIGNED = 0b001 +UNKNOWN = 0b010 +ONE = 0b011 +PPULSE = 0b100 +RISING = 0b101 +FALLING = 0b110 +NPULSE = 0b111 + + +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 RISING + if value in ['F', 'f', '\\']: + return FALLING + if value in ['P', 'p', '^']: + return PPULSE + if value in ['N', 'n', 'v']: + return NPULSE + return UNKNOWN + + +class MVArray: + """An n-dimensional array of m-valued logic values. + + This class wraps a numpy.ndarray of type uint8 and adds support for encoding and + interpreting 2-valued, 4-valued, and 8-valued logic values. + Each logic value is stored as an uint8, value manipulations are cheaper than in BPArray. + + Axis convention (1 axis, a single vector/pattern): + + * Axis is PI/PO/FF position, the length of this axis is called "width". + + Axis convention for 2 and more axes is consistent with BPArray: + + * Second-last axis is PI/PO/FF position, the length of this axis is called "width". + * Last axis is vector/pattern, the length of this axis is called "length". + + """ + + def __init__(self, a, m=None): + self.m = m or 4 + assert self.m in range(2, 256) + + # Try our best to interpret given a. + if isinstance(a, MVArray): + self.data = a.data.copy() + self.m = m or a.m + elif isinstance(a, int) or isinstance(a, tuple): + self.data = np.full(a, UNASSIGNED, dtype=np.uint8) + else: + self.data = np.asarray(interpret(a), dtype=np.uint8) + if self.data.ndim > 1: + self.data = 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 == RISING)) * ONE + elif self.m == 4: + self.data[...] = (self.data & 0b011) & ((self.data != FALLING) * ONE) | ((self.data == RISING) * ONE) + elif self.m == 8: + self.data[...] = self.data & 0b111 + + self.length = 1 if self.data.ndim == 1 else self.data.shape[-1] + self.width = len(self.data) if self.data.ndim == 1 else self.data.shape[-2] + + def __repr__(self): + return f'' + + +class BPArray: + """An n-dimensional array of m-valued logic values that uses bit-parallel storage. + + The primary use of this format is in aiding efficient bit-parallel logic simulation. + The secondary benefit over MVArray is its memory efficiency. + Direct value manipulations are more expensive than with MVArray. + It is advised to first construct a MVArray, pack it into a BPArray for simulation and unpack the results + back into a MVArray for value access. + + The values along the last axis (vectors/patterns) are packed into uint8 words. + The second-last axis has length ceil(log2(m)) for storing all bits. + All other axes stay the same as in MVArray. + """ + + def __init__(self, a, m=None): + if not isinstance(a, MVArray) and not isinstance(a, BPArray): + a = MVArray(a, m) + if isinstance(a, MVArray): + if m is not None and m != a.m: + a = MVArray(a, m) # cast data + self.m = a.m + assert self.m in [2, 4, 8] + nwords = math.ceil(math.log2(self.m)) + nbytes = (a.data.shape[-1] - 1) // 8 + 1 + self.data = np.zeros(a.data.shape[:-1] + (nwords, nbytes), dtype=np.uint8) + for i in range(self.data.shape[-2]): + self.data[..., i, :] = np.packbits((a.data >> i) & 1, axis=-1) + else: # we have a BPArray + self.data = a.data.copy() # TODO: support conversion to different m + self.length = a.length + self.width = a.width + + def __repr__(self): + return f'' diff --git a/tests/test_logic.py b/tests/test_logic.py new file mode 100644 index 0000000..073a254 --- /dev/null +++ b/tests/test_logic.py @@ -0,0 +1,105 @@ +import kyupy.logic as lg + + +def test_mvarray(): + + # instantiation with shape + + ary = lg.MVArray(4) + assert ary.length == 1 + assert ary.width == 4 + + ary = lg.MVArray((3, 2)) + assert ary.length == 2 + assert ary.width == 3 + + # instantiation with single vector + + ary = lg.MVArray([1, 0, 1]) + assert ary.length == 1 + assert ary.width == 3 + + ary = lg.MVArray("10X-") + assert ary.length == 1 + assert ary.width == 4 + + # 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 + + # 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']) + 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") + 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.FALLING + + 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.RISING + assert ary.data[6] == lg.FALLING + 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