diff --git a/src/finn/custom_op/fpgadataflow/thresholding_batch.py b/src/finn/custom_op/fpgadataflow/thresholding_batch.py index 7b8d09c4ada1c062b4db6d5829f0bc480e751cda..fa33c70218fab16f106da45e296f0d59ae4ea606 100644 --- a/src/finn/custom_op/fpgadataflow/thresholding_batch.py +++ b/src/finn/custom_op/fpgadataflow/thresholding_batch.py @@ -222,12 +222,7 @@ class Thresholding_Batch(HLSCustomOp): inp_hls_str = self.get_input_datatype().get_hls_datatype_str() out_hls_str = self.get_output_datatype().get_hls_datatype_str() # fill in TSrcI - # TODO checks/adjustments for bipolar inputs - inp_is_bipolar = self.get_input_datatype() == DataType.BIPOLAR - if inp_is_bipolar: - ret["TSrcI"] = "Recast<Binary>" - else: - ret["TSrcI"] = "Slice<%s>" % inp_hls_str + ret["TSrcI"] = "Slice<%s>" % inp_hls_str # fill in TDstI ret["TDstI"] = "Slice<%s>" % out_hls_str @@ -237,7 +232,7 @@ class Thresholding_Batch(HLSCustomOp): """Convert the original numpy weight matrix orig_weight_matrix into a form suitable for passing to the hlslib call: * ensure MH % PE == 0 - * for bipolar weights&inputs, ensure thresholds are positive + * for unsigned inputs, ensure thresholds are positive * interleave rows between PEs * reshape into (PE, TMEM, n_thres_steps) and return """ @@ -250,12 +245,11 @@ class Thresholding_Batch(HLSCustomOp): ), """Threshold matrix dimension is not as expected (2).""" n_thres_steps = orig_thres_matrix.shape[1] - inp_is_bipolar = self.get_input_datatype() == DataType.BIPOLAR - if inp_is_bipolar: + if not self.get_input_datatype().signed(): # ensure all thresholds are nonnegative assert (orig_thres_matrix >= 0).all() - # ensure all thresholds are integer - assert (orig_thres_matrix.astype(np.int32) == orig_thres_matrix).all() + # ensure all thresholds are integer + assert (orig_thres_matrix.astype(np.int32) == orig_thres_matrix).all() ret = orig_thres_matrix # ensure channels = mh , duplicating if necessary if ret.shape[0] == 1: @@ -286,10 +280,6 @@ class Thresholding_Batch(HLSCustomOp): threshold_tensor = self.get_hls_compatible_threshold_tensor(thresholds) tdt = DataType.INT32 - # use UINT32 threshold export for bipolar times bipolar - inp_is_bipolar = self.get_input_datatype() == DataType.BIPOLAR - if inp_is_bipolar: - tdt = DataType.UINT32 thresholds_hls_code = numpy_to_hls_code( threshold_tensor, tdt, "thresholds", False, True ) @@ -426,9 +416,6 @@ class Thresholding_Batch(HLSCustomOp): def read_npy_data(self): code_gen_dir = self.get_nodeattr("code_gen_dir_cppsim") dtype = self.get_input_datatype() - if dtype == DataType.BIPOLAR: - # use binary for bipolar storage - dtype = DataType.BINARY elem_bits = dtype.bitwidth() packed_bits = self.get_instream_width() packed_hls_type = "ap_uint<%d>" % packed_bits diff --git a/src/finn/transformation/fpgadataflow/convert_to_hls_layers.py b/src/finn/transformation/fpgadataflow/convert_to_hls_layers.py index d98f927bfeb7b23d945cd612d5125e6f2751891f..3ff86cab48d365c10e69bc2c764e8083c6a36880 100644 --- a/src/finn/transformation/fpgadataflow/convert_to_hls_layers.py +++ b/src/finn/transformation/fpgadataflow/convert_to_hls_layers.py @@ -33,6 +33,7 @@ from finn.transformation import Transformation from finn.custom_op.registry import getCustomOp from finn.transformation.infer_shapes import InferShapes from finn.transformation.infer_datatypes import InferDataTypes +import finn.core.data_layout as DataLayout class InferConvInpGen(Transformation): @@ -414,25 +415,24 @@ class InferThresholdingLayer(Transformation): thl_threshold = node.input[1] thl_output = node.output[0] thl_in_shape = model.get_tensor_shape(thl_input) - thl_out_shape = model.get_tensor_shape(thl_output) idt = model.get_tensor_datatype(thl_input) # skip conversion for layers with float input if not idt.is_integer(): continue - # extract weight shape, note that ONNX and finn-hlslib - # make different assumptions about dim order here - # ONNX assumes W has (in, out) shape - # finn-hlslib assumes W has (out, in) shape + # skip conversion if input is not NHWC or NC + thl_in_layout = model.get_tensor_layout(thl_input) + if thl_in_layout != DataLayout.NHWC and thl_in_layout != DataLayout.NC: + continue + + # now safe to assume number of channels is in last dimension ifc = int(thl_in_shape[-1]) # create node with no parallelization first pe = 1 assert ifc % pe == 0, "Requirement IFC divisable by PE is violated." odt = model.get_tensor_datatype(thl_output) - model.set_tensor_shape(thl_input, thl_in_shape) - model.set_tensor_shape(thl_output, thl_out_shape) # create and insert new StreamingFCLayer node new_node = helper.make_node( "Thresholding_Batch", diff --git a/tests/fpgadataflow/test_convert_to_hls_layers_cnv.py b/tests/fpgadataflow/test_convert_to_hls_layers_cnv.py index be32a365e6c05d92075877b95b7eff50ad1beed4..48803c9614f53a3a149c6eaac4289d10086513a5 100644 --- a/tests/fpgadataflow/test_convert_to_hls_layers_cnv.py +++ b/tests/fpgadataflow/test_convert_to_hls_layers_cnv.py @@ -39,6 +39,7 @@ from finn.core.modelwrapper import ModelWrapper from finn.transformation.fold_constants import FoldConstants from finn.transformation.general import GiveReadableTensorNames, GiveUniqueNodeNames from finn.transformation.infer_shapes import InferShapes +from finn.transformation.infer_data_layouts import InferDataLayouts from finn.transformation.streamline import Streamline from finn.util.test import get_test_model_trained from finn.transformation.double_to_single_float import DoubleToSingleFloat @@ -71,6 +72,7 @@ def test_convert_to_hls_layers_cnv_w1a1(fused_activation): model = model.transform(absorb.AbsorbTransposeIntoMultiThreshold()) model = model.transform(ConvertBipolarMatMulToXnorPopcount()) model = model.transform(Streamline()) + model = model.transform(InferDataLayouts()) # model.save("golden.onnx") # load one of the test vectors fn = pk.resource_filename("finn", "data/cifar10/cifar10-test-data-class3.npz") diff --git a/tests/fpgadataflow/test_fpgadataflow_thresholding.py b/tests/fpgadataflow/test_fpgadataflow_thresholding.py index 4975b06a4c4f051f4906e63d3bbad9951483ebbd..50b990f13494f22e985406791445b406e9946147 100644 --- a/tests/fpgadataflow/test_fpgadataflow_thresholding.py +++ b/tests/fpgadataflow/test_fpgadataflow_thresholding.py @@ -86,25 +86,16 @@ def make_single_thresholding_modelwrapper(T, pe, idt, odt): return model -def prepare_inputs(input_tensor, idt): - if idt == DataType.BIPOLAR: - # convert bipolar to binary - return {"inp": (input_tensor + 1) / 2} - else: - return {"inp": input_tensor} - - -# TODO binary/bipolar inputs/outputs # activation: None or DataType -@pytest.mark.parametrize("act", [DataType.INT4]) +@pytest.mark.parametrize("act", [DataType.INT4, DataType.BIPOLAR]) # input datatype -@pytest.mark.parametrize("idt", [DataType.INT2, DataType.INT4]) +@pytest.mark.parametrize("idt", [DataType.INT16, DataType.UINT16]) # folding, -1 is maximum possible @pytest.mark.parametrize("nf", [-1, 2, 1]) # number of input features @pytest.mark.parametrize("ich", [16]) # execution mode -@pytest.mark.parametrize("exec_mode", ["rtlsim", "cppsim"]) +@pytest.mark.parametrize("exec_mode", ["cppsim", "rtlsim"]) @pytest.mark.vivado @pytest.mark.slow def test_fpgadataflow_thresholding(idt, act, nf, ich, exec_mode): @@ -121,11 +112,6 @@ def test_fpgadataflow_thresholding(idt, act, nf, ich, exec_mode): T = np.random.randint(idt.min(), idt.max() + 1, (ich, n_steps)).astype(np.float32) # provide non-decreasing thresholds T = np.sort(T, axis=1) - # generate thresholds for activation - if idt == DataType.BIPOLAR: - # bias thresholds to be positive - T = np.ceil((T + ich) / 2) - assert (T >= 0).all() model = make_single_thresholding_modelwrapper(T, pe, idt, odt) @@ -143,8 +129,8 @@ def test_fpgadataflow_thresholding(idt, act, nf, ich, exec_mode): else: raise Exception("Unknown exec_mode") - # prepare input data - input_dict = prepare_inputs(x, idt) + # package input data as dictionary + input_dict = {"inp": x} y = multithreshold(x, T) if act == DataType.BIPOLAR: