|
|
@ -58,6 +58,12 @@ on a signal. ``'N'``, ``'n'``, and ``'v'`` are interpreted as ``NPULSE``. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def interpret(value): |
|
|
|
def interpret(value): |
|
|
|
|
|
|
|
"""Converts characters, strings, and lists of them to lists of logic constants defined above. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:param value: A character (string of length 1), Boolean, Integer, None, or Iterable. |
|
|
|
|
|
|
|
Iterables (such as strings) are traversed and their individual characters are interpreted. |
|
|
|
|
|
|
|
:return: A logic constant or a (possibly multi-dimensional) list of logic constants. |
|
|
|
|
|
|
|
""" |
|
|
|
if isinstance(value, Iterable) and not (isinstance(value, str) and len(value) == 1): |
|
|
|
if isinstance(value, Iterable) and not (isinstance(value, str) and len(value) == 1): |
|
|
|
return list(map(interpret, value)) |
|
|
|
return list(map(interpret, value)) |
|
|
|
if value in [0, '0', False, 'L', 'l']: |
|
|
|
if value in [0, '0', False, 'L', 'l']: |
|
|
@ -85,6 +91,79 @@ def bit_in(a, pos): |
|
|
|
return a[pos >> 3] & _bit_in_lut[pos & 7] |
|
|
|
return a[pos >> 3] & _bit_in_lut[pos & 7] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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, 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 __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'<MVArray length={self.length} width={self.width} m={self.m} mem={hr_bytes(self.data.nbytes)}>' |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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): |
|
|
|
def mv_cast(*args, m=8): |
|
|
|
return [a if isinstance(a, MVArray) else MVArray(a, m=m) for a in args] |
|
|
|
return [a if isinstance(a, MVArray) else MVArray(a, m=m) for a in args] |
|
|
|
|
|
|
|
|
|
|
@ -100,6 +179,13 @@ def _mv_not(m, out, inp): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def mv_not(x1, out=None): |
|
|
|
def mv_not(x1, 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. |
|
|
|
|
|
|
|
""" |
|
|
|
m = mv_getm(x1) |
|
|
|
m = mv_getm(x1) |
|
|
|
x1 = mv_cast(x1, m=m)[0] |
|
|
|
x1 = mv_cast(x1, m=m)[0] |
|
|
|
out = out or MVArray(x1.data.shape, m=m) |
|
|
|
out = out or MVArray(x1.data.shape, m=m) |
|
|
@ -125,6 +211,14 @@ def _mv_or(m, out, *ins): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def mv_or(x1, x2, out=None): |
|
|
|
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. |
|
|
|
|
|
|
|
""" |
|
|
|
m = mv_getm(x1, x2) |
|
|
|
m = mv_getm(x1, x2) |
|
|
|
x1, x2 = mv_cast(x1, x2, m=m) |
|
|
|
x1, x2 = mv_cast(x1, x2, m=m) |
|
|
|
out = out or MVArray(np.broadcast(x1.data, x2.data).shape, m=m) |
|
|
|
out = out or MVArray(np.broadcast(x1.data, x2.data).shape, m=m) |
|
|
@ -151,6 +245,14 @@ def _mv_and(m, out, *ins): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def mv_and(x1, x2, out=None): |
|
|
|
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. |
|
|
|
|
|
|
|
""" |
|
|
|
m = mv_getm(x1, x2) |
|
|
|
m = mv_getm(x1, x2) |
|
|
|
x1, x2 = mv_cast(x1, x2, m=m) |
|
|
|
x1, x2 = mv_cast(x1, x2, m=m) |
|
|
|
out = out or MVArray(np.broadcast(x1.data, x2.data).shape, m=m) |
|
|
|
out = out or MVArray(np.broadcast(x1.data, x2.data).shape, m=m) |
|
|
@ -174,6 +276,14 @@ def _mv_xor(m, out, *ins): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def mv_xor(x1, x2, out=None): |
|
|
|
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. |
|
|
|
|
|
|
|
""" |
|
|
|
m = mv_getm(x1, x2) |
|
|
|
m = mv_getm(x1, x2) |
|
|
|
x1, x2 = mv_cast(x1, x2, m=m) |
|
|
|
x1, x2 = mv_cast(x1, x2, m=m) |
|
|
|
out = out or MVArray(np.broadcast(x1.data, x2.data).shape, m=m) |
|
|
|
out = out or MVArray(np.broadcast(x1.data, x2.data).shape, m=m) |
|
|
@ -182,6 +292,16 @@ def mv_xor(x1, x2, out=None): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def mv_transition(init, final, out=None): |
|
|
|
def mv_transition(init, final, out=None): |
|
|
|
|
|
|
|
"""Computes the logic transitions from the initial values of ``init`` to the final values of ``final``. |
|
|
|
|
|
|
|
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. |
|
|
|
|
|
|
|
""" |
|
|
|
m = mv_getm(init, final) |
|
|
|
m = mv_getm(init, final) |
|
|
|
init, final = mv_cast(init, final, m=m) |
|
|
|
init, final = mv_cast(init, final, m=m) |
|
|
|
init = init.data |
|
|
|
init = init.data |
|
|
@ -196,65 +316,46 @@ def mv_transition(init, final, out=None): |
|
|
|
return out |
|
|
|
return out |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MVArray: |
|
|
|
class BPArray: |
|
|
|
"""An n-dimensional array of m-valued logic values. |
|
|
|
"""An n-dimensional array of m-valued logic values that uses bit-parallel storage. |
|
|
|
|
|
|
|
|
|
|
|
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. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
An MVArray always has 2 axes: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* Axis 0 is PI/PO/FF position, the length of this axis is called "width". |
|
|
|
The primary use of this format is in aiding efficient bit-parallel logic simulation. |
|
|
|
* Axis 1 is vector/pattern, the length of this axis is called "length". |
|
|
|
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): |
|
|
|
def __init__(self, a, m=None): |
|
|
|
self.m = m or 8 |
|
|
|
if not isinstance(a, MVArray) and not isinstance(a, BPArray): |
|
|
|
assert self.m in [2, 4, 8] |
|
|
|
a = MVArray(a, m) |
|
|
|
|
|
|
|
self.m = a.m |
|
|
|
# Try our best to interpret given a. |
|
|
|
|
|
|
|
if isinstance(a, MVArray): |
|
|
|
if isinstance(a, MVArray): |
|
|
|
self.data = a.data.copy() |
|
|
|
if m is not None and m != a.m: |
|
|
|
self.m = m or a.m |
|
|
|
a = MVArray(a, m) # cast data |
|
|
|
elif hasattr(a, 'data'): # assume it is a BPArray. Can't use isinstance() because BPArray isn't declared yet. |
|
|
|
self.m = a.m |
|
|
|
self.data = np.zeros((a.width, a.length), dtype=np.uint8) |
|
|
|
assert self.m in [2, 4, 8] |
|
|
|
self.m = m or a.m |
|
|
|
nwords = math.ceil(math.log2(self.m)) |
|
|
|
for i in range(a.data.shape[-2]): |
|
|
|
nbytes = (a.data.shape[-1] - 1) // 8 + 1 |
|
|
|
self.data[...] <<= 1 |
|
|
|
self.data = np.zeros(a.data.shape[:-1] + (nwords, nbytes), dtype=np.uint8) |
|
|
|
self.data[...] |= np.unpackbits(a.data[..., -i-1, :], axis=1)[:, :a.length] |
|
|
|
"""The wrapped 3-dimensional ndarray. |
|
|
|
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] |
|
|
|
* Axis 0 is PI/PO/FF position, the length of this axis is called "width". |
|
|
|
self.width = self.data.shape[-2] |
|
|
|
* 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): |
|
|
|
def __repr__(self): |
|
|
|
return f'<MVArray length={self.length} width={self.width} m={self.m} mem={hr_bytes(self.data.nbytes)}>' |
|
|
|
return f'<BPArray length={self.length} width={self.width} m={self.m} mem={hr_bytes(self.data.nbytes)}>' |
|
|
|
|
|
|
|
|
|
|
|
def __str__(self): |
|
|
|
|
|
|
|
return str([self[idx] for idx in range(self.length)]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __getitem__(self, vector_idx): |
|
|
|
|
|
|
|
chars = ["0", "X", "-", "1", "P", "R", "F", "N"] |
|
|
|
|
|
|
|
return ''.join(chars[v] for v in self.data[:, vector_idx]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __len__(self): |
|
|
|
def __len__(self): |
|
|
|
return self.length |
|
|
|
return self.length |
|
|
@ -359,44 +460,3 @@ def bp_xor(out, *ins): |
|
|
|
out[..., 0, :] |= any_unknown |
|
|
|
out[..., 0, :] |= any_unknown |
|
|
|
out[..., 1, :] &= ~any_unknown |
|
|
|
out[..., 1, :] &= ~any_unknown |
|
|
|
out[..., 2, :] &= ~any_unknown |
|
|
|
out[..., 2, :] &= ~any_unknown |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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. |
|
|
|
|
|
|
|
Accessing individual values is more expensive than with :py:class:`MVArray`. |
|
|
|
|
|
|
|
It is advised to first construct a MVArray, pack it into a :py:class:`BPArray` for simulation and unpack the results |
|
|
|
|
|
|
|
back into a :py:class:`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) |
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
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'<BPArray length={self.length} width={self.width} m={self.m} mem={hr_bytes(self.data.nbytes)}>' |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __len__(self): |
|
|
|
|
|
|
|
return self.length |
|
|
|
|
|
|
|