@ -1,4 +1,9 @@
@@ -1,4 +1,9 @@
""" This module contains definitions and utilities for 2-, 4-, and 8-valued logic operations.
""" This module contains definitions and tools for 2-, 4-, and 8-valued logic operations.
Logic values are stored in numpy arrays with data type ` ` np . uint8 ` ` .
There are no explicit data structures in KyuPy for holding patterns , pattern sets or vectors .
However , there are conventions on logic value encoding and on the order of axes .
Utility functions defined here follow these conventions .
8 logic values are defined as integer constants .
@ -21,10 +26,22 @@ Except when bit0 differs from bit1, but bit2 (activity) is 0:
@@ -21,10 +26,22 @@ Except when bit0 differs from bit1, but bit2 (activity) is 0:
4 - valued logic only considers bit0 and bit1 .
8 - valued logic considers all 3 bits .
Logic values are stored in numpy arrays of data type ` ` np . uint8 ` ` .
The axis convention is as follows :
* The * * last * * axis goes along patterns / vectors . I . e . ` ` values [ . . . , 0 ] ` ` is pattern 0 , ` ` values [ . . . , 1 ] ` ` is pattern 1 , etc .
* The * * second - to - last * * axis goes along the I / O and flip - flops of circuits . For a circuit ` ` c ` ` , this axis is usually
` ` len ( c . s_nodes ) ` ` long . The values of all inputs , outputs and flip - flops are stored within the same array and the location
along the second - to - last axis is determined by the order in ` ` c . s_nodes ` ` .
Two storage formats are used in KyuPy :
* ` ` mv . . . ` ` ( for " multi-valued " ) : Each logic value is stored in the least significant 3 bits of ` ` np . uint8 ` ` .
* ` ` bp . . . ` ` ( for " bit-parallel " ) : Groups of 8 logic values are stored as three ` ` np . uint8 ` ` . This format is used
for bit - parallel logic simulations . It is also more memory - efficient .
The functions in this module use the ` ` mv . . . ` ` and ` ` bp . . . ` ` prefixes to signify the storage format they operate on .
"""
import math
from collections . abc import Iterable
import numpy as np
@ -80,41 +97,12 @@ def interpret(value):
@@ -80,41 +97,12 @@ def interpret(value):
return UNKNOWN
_bit_in_lut = np . array ( [ 2 * * x for x in range ( 7 , - 1 , - 1 ) ] , dtype = ' uint8 ' )
@numba . njit
def bit_in ( a , pos ) :
return a [ pos >> 3 ] & _bit_in_lut [ pos & 7 ]
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.
def mvarray ( * a ) :
""" Converts (lists of) Boolean values or strings into a multi-valued array.
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 ` .
The given values are interpreted and the axes are arranged as per KyuPy ' s convention.
Use this function to convert strings into multi - valued arrays .
"""
dtype = np . dtype ( dtype )
bits = 8 * dtype . itemsize
a = a [ . . . , : bits ]
if a . shape [ - 1 ] < bits :
p = [ ( 0 , 0 ) ] * ( len ( a . shape ) - 1 ) + [ ( 0 , bits - a . shape [ - 1 ] ) ]
a = np . pad ( a , p , ' edge ' ) if dtype . name [ 0 ] == ' i ' else np . pad ( a , p , ' constant ' , constant_values = 0 )
return np . packbits ( a , bitorder = ' little ' ) . view ( dtype ) . reshape ( a . shape [ : - 1 ] )
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 )
@ -122,24 +110,13 @@ def mvarray(*a):
@@ -122,24 +110,13 @@ def mvarray(*a):
def mv_str ( mva , delim = ' \n ' ) :
""" Renders a given multi-valued array into a string.
"""
sa = np . choose ( mva , np . array ( [ * ' 0X-1PRFN ' ] , dtype = np . unicode_ ) )
if mva . ndim == 1 : return ' ' . join ( sa )
return delim . join ( [ ' ' . join ( c ) for c in sa . swapaxes ( - 1 , - 2 ) ] )
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 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
np . putmask ( out , ( inp == UNKNOWN ) , UNKNOWN ) # restore UNKNOWN
@ -148,13 +125,10 @@ def _mv_not(out, inp):
@@ -148,13 +125,10 @@ def _mv_not(out, inp):
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 .
: param out : Optionally an : py : class : ` MVArray ` as storage destination . If None , a new : py : class : ` MVArray `
is returned .
: return : An : py : class : ` MVArray ` with the result .
: param x1 : A multi - valued array .
: param out : An optional storage destination . If None , a new multi - valued array is returned .
: return : A multi - valued array with the result .
"""
#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
@ -176,14 +150,11 @@ def _mv_or(out, *ins):
@@ -176,14 +150,11 @@ def _mv_or(out, *ins):
def mv_or ( x1 , x2 , out = None ) :
""" A multi-valued OR operator.
: param x1 : An : py : class : ` MVArray ` or data the : py : class : ` MVArray ` constructor accepts .
: param x2 : An : py : class : ` MVArray ` or data the : py : class : ` MVArray ` constructor accepts .
: param out : Optionally an : py : class : ` MVArray ` as storage destination . If None , a new : py : class : ` MVArray `
is returned .
: return : An : py : class : ` MVArray ` with the result .
: param x1 : A multi - valued array .
: param x2 : A multi - valued array .
: param out : An optional storage destination . If None , a new multi - valued array is returned .
: return : A multi - valued array with the result .
"""
#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
@ -206,14 +177,11 @@ def _mv_and(out, *ins):
@@ -206,14 +177,11 @@ def _mv_and(out, *ins):
def mv_and ( x1 , x2 , out = None ) :
""" A multi-valued AND operator.
: param x1 : An : py : class : ` MVArray ` or data the : py : class : ` MVArray ` constructor accepts .
: param x2 : An : py : class : ` MVArray ` or data the : py : class : ` MVArray ` constructor accepts .
: param out : Optionally an : py : class : ` MVArray ` as storage destination . If None , a new : py : class : ` MVArray `
is returned .
: return : An : py : class : ` MVArray ` with the result .
: param x1 : A multi - valued array .
: param x2 : A multi - valued array .
: param out : An optional storage destination . If None , a new multi - valued array is returned .
: return : A multi - valued array with the result .
"""
#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
@ -233,24 +201,28 @@ def _mv_xor(out, *ins):
@@ -233,24 +201,28 @@ def _mv_xor(out, *ins):
def mv_xor ( x1 , x2 , out = None ) :
""" A multi-valued XOR operator.
: param x1 : An : py : class : ` MVArray ` or data the : py : class : ` MVArray ` constructor accepts .
: param x2 : An : py : class : ` MVArray ` or data the : py : class : ` MVArray ` constructor accepts .
: param out : Optionally an : py : class : ` MVArray ` as storage destination . If None , a new : py : class : ` MVArray `
is returned .
: return : An : py : class : ` MVArray ` with the result .
: param x1 : A multi - valued array .
: param x2 : A multi - valued array .
: param out : An optional storage destination . If None , a new multi - valued array is returned .
: return : A multi - valued array with the result .
"""
#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.
""" A multi-valued latch operator.
A latch outputs ` ` d ` ` when transparent ( ` ` t ` ` is high ) .
It outputs ` ` q_prev ` ` when in latched state ( ` ` t ` ` is low ) .
: param d : A multi - valued array for the data input .
: param t : A multi - valued array for the control input .
: param q_prev : A multi - valued array with the output value of this latch from the previous clock cycle .
: param out : An optional storage destination . If None , a new multi - valued array is returned .
: return : A multi - valued array for the latch output ` ` q ` ` .
"""
#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 )
@ -268,16 +240,11 @@ def mv_transition(init, final, out=None):
@@ -268,16 +240,11 @@ def mv_transition(init, final, out=None):
Pulses in the input data are ignored . If any of the inputs are ` ` UNKNOWN ` ` , the result is ` ` UNKNOWN ` ` .
If both inputs are ` ` UNASSIGNED ` ` , the result is ` ` UNASSIGNED ` ` .
: param init : An : py : class : ` MVArray ` or data the : py : class : ` MVArray ` constructor accepts .
: param final : An : py : class : ` MVArray ` or data the : py : class : ` MVArray ` constructor accepts .
: param out : Optionally an : py : class : ` MVArray ` as storage destination . If None , a new : py : class : ` MVArray `
is returned .
: return : An : py : class : ` MVArray ` with the result .
: param init : A multi - valued array .
: param final : A multi - valued array .
: param out : An optional storage destination . If None , a new multi - valued array is returned .
: return : A multi - valued array 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 np . empty ( np . broadcast ( init , final ) . shape , dtype = np . uint8 )
out [ . . . ] = ( init & 0b010 ) | ( final & 0b001 )
out [ . . . ] | = ( ( out << 1 ) ^ ( out << 2 ) ) & 0b100
@ -288,6 +255,28 @@ def mv_transition(init, final, out=None):
@@ -288,6 +255,28 @@ def mv_transition(init, final, out=None):
return out
def mv_to_bp ( mva ) :
""" Converts a multi-valued array into a bit-parallel array.
"""
if mva . ndim == 1 : mva = mva [ . . . , np . newaxis ]
return np . packbits ( unpackbits ( mva ) [ . . . , : 3 ] , axis = - 2 , bitorder = ' little ' ) . swapaxes ( - 1 , - 2 )
def bparray ( * a ) :
""" Converts (lists of) Boolean values or strings into a bit-parallel array.
The given values are interpreted and the axes are arranged as per KyuPy ' s convention.
Use this function to convert strings into bit - parallel arrays .
"""
return mv_to_bp ( mvarray ( * a ) )
def bp_to_mv ( bpa ) :
""" Converts a bit-parallel array into a multi-valued array.
"""
return packbits ( np . unpackbits ( bpa , axis = - 1 , bitorder = ' little ' ) . swapaxes ( - 1 , - 2 ) )
def bp_buf ( out , inp ) :
unknown = ( inp [ . . . , 0 , : ] ^ inp [ . . . , 1 , : ] ) & ~ inp [ . . . , 2 , : ]
out [ . . . , 0 , : ] = inp [ . . . , 0 , : ] | unknown
@ -356,3 +345,37 @@ def bp_latch(out, d, t, q_prev):
@@ -356,3 +345,37 @@ def bp_latch(out, d, t, q_prev):
out [ . . . , 1 , : ] & = ~ any_unknown
out [ . . . , 2 , : ] & = ~ any_unknown
return out
_bit_in_lut = np . array ( [ 2 * * x for x in range ( 7 , - 1 , - 1 ) ] , dtype = ' uint8 ' )
@numba . njit
def bit_in ( a , pos ) :
return a [ pos >> 3 ] & _bit_in_lut [ pos & 7 ]
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 of each item .
"""
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.
Similar 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 )
bits = 8 * dtype . itemsize
a = a [ . . . , : bits ]
if a . shape [ - 1 ] < bits :
p = [ ( 0 , 0 ) ] * ( len ( a . shape ) - 1 ) + [ ( 0 , bits - a . shape [ - 1 ] ) ]
a = np . pad ( a , p , ' edge ' ) if dtype . name [ 0 ] == ' i ' else np . pad ( a , p , ' constant ' , constant_values = 0 )
return np . packbits ( a , bitorder = ' little ' ) . view ( dtype ) . reshape ( a . shape [ : - 1 ] )