2 changed files with 251 additions and 0 deletions
			
			
		| @ -0,0 +1,146 @@@@ -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'<MVArray length={self.length} width={self.width} m={self.m} bytes={self.data.nbytes}>' | ||||
| 
 | ||||
| 
 | ||||
| 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'<BPArray length={self.length} width={self.width} m={self.m} bytes={self.data.nbytes}>' | ||||
| @ -0,0 +1,105 @@@@ -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 | ||||
					Loading…
					
					
				
		Reference in new issue