start designing new data structures for m-valued logic
This commit is contained in:
1 parent
08a22cf251
commit
a77ac4a397
2 files changed
+251
No files matched your search
@@ -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 @@
|
||||
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
|
||||
Reference in new issue
Block a user