Skip to content
Snippets Groups Projects
Commit 40a7fc79 authored by auphelia's avatar auphelia
Browse files

Merge branch 'dev' into feature/exec_hlslib_nodes

parents 8255062d b18a9c31
No related branches found
No related tags found
No related merge requests found
import sys
import numpy as np
from finn.core.datatype import DataType
from finn.core.utils import pack_innermost_dim_as_hex_string
def numpy_to_hls_code(ndarray, dtype, hls_var_name, pack_innermost_dim=True):
"""Return C++ code representation of a numpy ndarray with FINN DataType
dtype, using hls_var_name as the resulting C++ variable name. If
pack_innermost_dim is specified, the innermost dimension of the ndarray
will be packed into a hex string using array2hexstring.
"""
hls_dtype = dtype.get_hls_datatype_str()
if type(ndarray) != np.ndarray or ndarray.dtype != np.float32:
# try to convert to a float numpy array (container dtype is float)
ndarray = np.asarray(ndarray, dtype=np.float32)
if pack_innermost_dim:
idimlen = ndarray.shape[-1]
idimbits = idimlen * dtype.bitwidth()
ndarray = pack_innermost_dim_as_hex_string(ndarray, dtype, idimbits)
hls_dtype = "ap_uint<%d>" % idimbits
ndims = ndarray.ndim
# add type string and variable name
# e.g. "const ap_uint<64>" "weightMem0"
ret = "%s %s" % (hls_dtype, hls_var_name)
# add dimensions
for d in range(ndims):
ret += "[%d]" % ndarray.shape[d]
orig_printops = np.get_printoptions()
np.set_printoptions(threshold=sys.maxsize)
# define a function to convert a single element into a C++ init string
# a single element can be a hex string if we are using packing
def elem2str(x):
if type(x) == str or type(x) == np.str_ or type(x) == np.str:
return '%s("%s", 16)' % (hls_dtype, x)
elif type(x) == np.float32:
if dtype == DataType.FLOAT32:
return str(x)
else:
return str(int(x))
else:
raise Exception("Unsupported type for numpy_to_hls_code")
strarr = np.array2string(ndarray, separator=", ", formatter={"all": elem2str})
np.set_printoptions(**orig_printops)
strarr = strarr.replace("[", "{").replace("]", "}")
ret = ret + " = \n" + strarr + ";"
return ret
......@@ -130,3 +130,13 @@ class DataType(Enum):
"""Return whether this DataType represents integer values only."""
# only FLOAT32 is noninteger for now
return self != DataType.FLOAT32
def get_hls_datatype_str(self):
"""Return the corresponding Vivado HLS datatype name."""
if self.is_integer():
if self.signed():
return "ap_int<%d>" % self.bitwidth()
else:
return "ap_uint<%d>" % self.bitwidth()
else:
return "float"
......@@ -3,6 +3,9 @@ import string
import numpy as np
import onnx
from bitstring import BitArray
from finn.core.datatype import DataType
def valueinfo_to_tensor(vi):
......@@ -35,3 +38,72 @@ def random_string(stringLength=6):
"""Randomly generate a string of letters and digits."""
lettersAndDigits = string.ascii_letters + string.digits
return "".join(random.choice(lettersAndDigits) for i in range(stringLength))
def array2hexstring(array, dtype, pad_to_nbits):
"""
Pack given one-dimensional NumPy array with FINN DataType dtype into a hex
string.
Any BIPOLAR values will be converted to a single bit with a 0 representing
-1.
pad_to_nbits is used to prepend leading zeros to ensure packed strings of
fixed width. The minimum value for pad_to_nbits is 4, since a single hex
digit is four bits.
Examples:
array2hexstring([1, 1, 1, 0], DataType.BINARY, 4) = "e"
array2hexstring([1, 1, 1, 0], DataType.BINARY, 8) = "0e"
"""
if pad_to_nbits < 4:
pad_to_nbits = 4
# ensure input is a numpy array with float values
if type(array) != np.ndarray or array.dtype != np.float32:
# try to convert to a float numpy array (container dtype is float)
array = np.asarray(array, dtype=np.float32)
# ensure one-dimensional array to pack
assert array.ndim == 1
if dtype == DataType.BIPOLAR:
# convert bipolar values to binary
array = (array + 1) / 2
dtype = DataType.BINARY
lineval = BitArray(length=0)
bw = dtype.bitwidth()
for val in array:
# ensure that this value is permitted by chosen dtype
assert dtype.allowed(val)
if dtype.is_integer():
if dtype.signed():
lineval.append(BitArray(int=int(val), length=bw))
else:
lineval.append(BitArray(uint=int(val), length=bw))
else:
lineval.append(BitArray(float=val, length=bw))
if pad_to_nbits >= lineval.len:
# extend to the desired output width (a minimum of 4 bits)
lineval.prepend(BitArray(length=pad_to_nbits - lineval.len))
else:
raise Exception("Number of bits is greater than pad_to_nbits")
# represent as hex
return lineval.hex
def pack_innermost_dim_as_hex_string(ndarray, dtype, pad_to_nbits):
"""Pack the innermost dimension of the given numpy ndarray into hex
strings using array2hexstring. Examples:
A = [[1, 1, 1, 0], [0, 1, 1, 0]]
eA = ["0e", "06"]
pack_innermost_dim_as_hex_string(A, DataType.BINARY, 8) == eA
B = [[[3, 3], [3, 3]], [[1, 3], [3, 1]]]
eB = [[ "0f", "0f"], ["07", "0d"]]
pack_innermost_dim_as_hex_string(B, DataType.UINT2, 8) == eB
"""
if type(ndarray) != np.ndarray or ndarray.dtype != np.float32:
# try to convert to a float numpy array (container dtype is float)
ndarray = np.asarray(ndarray, dtype=np.float32)
def fun(x):
return array2hexstring(x, dtype, pad_to_nbits)
return np.apply_along_axis(fun, ndarray.ndim - 1, ndarray)
import numpy as np
from finn.backend.fpgadataflow.utils import numpy_to_hls_code
from finn.core.datatype import DataType
from finn.core.utils import array2hexstring, pack_innermost_dim_as_hex_string
def test_array2hexstring():
assert array2hexstring([1, 1, 1, 0], DataType.BINARY, 4) == "e"
assert array2hexstring([1, 1, 1, 0], DataType.BINARY, 8) == "0e"
assert array2hexstring([1, 1, 1, -1], DataType.BIPOLAR, 8) == "0e"
assert array2hexstring([3, 3, 3, 3], DataType.UINT2, 8) == "ff"
assert array2hexstring([1, 3, 3, 1], DataType.UINT2, 8) == "7d"
assert array2hexstring([1, -1, 1, -1], DataType.INT2, 8) == "77"
assert array2hexstring([1, 1, 1, -1], DataType.INT4, 16) == "111f"
assert array2hexstring([-1], DataType.FLOAT32, 32) == "bf800000"
assert array2hexstring([17.125], DataType.FLOAT32, 32) == "41890000"
def test_pack_innermost_dim_as_hex_string():
A = [[1, 1, 1, 0], [0, 1, 1, 0]]
eA = np.asarray(["0e", "06"])
assert (pack_innermost_dim_as_hex_string(A, DataType.BINARY, 8) == eA).all()
B = [[[3, 3], [3, 3]], [[1, 3], [3, 1]]]
eB = np.asarray([["0f", "0f"], ["07", "0d"]])
assert (pack_innermost_dim_as_hex_string(B, DataType.UINT2, 8) == eB).all()
def test_numpy_to_hls_code():
def remove_all_whitespace(s):
return "".join(s.split())
A = [[1, 1, 1, 0], [0, 1, 1, 0]]
ret = numpy_to_hls_code(A, DataType.BINARY, "test", True)
eA = """ap_uint<4> test[2] =
{ap_uint<4>("e", 16), ap_uint<4>("6", 16)};"""
assert remove_all_whitespace(ret) == remove_all_whitespace(eA)
B = [[[3, 3], [3, 3]], [[1, 3], [3, 1]]]
ret = numpy_to_hls_code(B, DataType.UINT2, "test", True)
eB = """ap_uint<4> test[2][2] =
{{ap_uint<4>("f", 16), ap_uint<4>("f", 16)},
{ap_uint<4>("7", 16), ap_uint<4>("d", 16)}};"""
assert remove_all_whitespace(ret) == remove_all_whitespace(eB)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment