Skip to content

Commit

Permalink
[CustomOp] Initial draft of dwc in new class hierarchy
Browse files Browse the repository at this point in the history
  • Loading branch information
auphelia committed Jan 11, 2024
1 parent 9674cba commit d9819a2
Show file tree
Hide file tree
Showing 11 changed files with 648 additions and 756 deletions.
12 changes: 4 additions & 8 deletions src/finn/custom_op/fpgadataflow/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Copyright (C) 2020-2022, Xilinx, Inc.
# Copyright (C) 2023, Advanced Micro Devices, Inc.
# Copyright (C) 2023-2024, Advanced Micro Devices, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -53,11 +53,8 @@
from finn.custom_op.fpgadataflow.streamingdataflowpartition import (
StreamingDataflowPartition,
)
from finn.custom_op.fpgadataflow.streamingdatawidthconverter_batch import (
StreamingDataWidthConverter_Batch,
)
from finn.custom_op.fpgadataflow.streamingdatawidthconverter_rtl import (
StreamingDataWidthConverter_rtl,
from finn.custom_op.fpgadataflow.streamingdatawidthconverter import (
StreamingDataWidthConverter,
)
from finn.custom_op.fpgadataflow.streamingeltwise import StreamingEltwise
from finn.custom_op.fpgadataflow.streamingfifo import StreamingFIFO
Expand All @@ -77,8 +74,6 @@
custom_op["ConvolutionInputGenerator1D"] = ConvolutionInputGenerator1D
custom_op["ConvolutionInputGenerator_rtl"] = ConvolutionInputGenerator_rtl
custom_op["TLastMarker"] = TLastMarker
custom_op["StreamingDataWidthConverter_Batch"] = StreamingDataWidthConverter_Batch
custom_op["StreamingDataWidthConverter_rtl"] = StreamingDataWidthConverter_rtl
custom_op["StreamingFIFO"] = StreamingFIFO
custom_op["Pool_Batch"] = Pool_Batch
custom_op["FMPadding_Pixel"] = FMPadding_Pixel
Expand All @@ -96,6 +91,7 @@
custom_op["GlobalAccPool"] = GlobalAccPool
custom_op["LabelSelect"] = LabelSelect
custom_op["Lookup"] = Lookup
custom_op["StreamingDataWidthConverter"] = StreamingDataWidthConverter
custom_op["StreamingEltwise"] = StreamingEltwise
custom_op["StreamingMaxPool"] = StreamingMaxPool
custom_op["UpsampleNearestNeighbour"] = UpsampleNearestNeighbour
4 changes: 4 additions & 0 deletions src/finn/custom_op/fpgadataflow/hls/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
from finn.custom_op.fpgadataflow.hls.globalaccpool_hls import GlobalAccPool_hls
from finn.custom_op.fpgadataflow.hls.labelselect_hls import LabelSelect_hls
from finn.custom_op.fpgadataflow.hls.lookup_hls import Lookup_hls
from finn.custom_op.fpgadataflow.hls.streamingdatawidthconverter_hls import (
StreamingDataWidthConverter_hls,
)
from finn.custom_op.fpgadataflow.hls.streamingeltwise_hls import StreamingEltwise_hls
from finn.custom_op.fpgadataflow.hls.streamingmaxpool_hls import StreamingMaxPool_hls
from finn.custom_op.fpgadataflow.hls.upsampler_hls import UpsampleNearestNeighbour_hls
Expand All @@ -49,5 +52,6 @@
custom_op["LabelSelect_hls"] = LabelSelect_hls
custom_op["Lookup_hls"] = Lookup_hls
custom_op["StreamingEltwise_hls"] = StreamingEltwise_hls
custom_op["StreamingDataWidthConverter_hls"] = StreamingDataWidthConverter_hls
custom_op["StreamingMaxPool_hls"] = StreamingMaxPool_hls
custom_op["UpsampleNearestNeighbour_hls"] = UpsampleNearestNeighbour_hls
271 changes: 271 additions & 0 deletions src/finn/custom_op/fpgadataflow/hls/streamingdatawidthconverter_hls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
# Copyright (C) 2023, Advanced Micro Devices, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of FINN nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import numpy as np
import os
from qonnx.core.datatype import DataType

from finn.custom_op.fpgadataflow.hlsbackend import HLSBackend
from finn.custom_op.fpgadataflow.streamingdatawidthconverter import (
StreamingDataWidthConverter,
)
from finn.util.data_packing import npy_to_rtlsim_input, rtlsim_output_to_npy

# does not do anything at the ONNX node-by-node level, and input-output
# tensor shapes are the same. performs data width conversion at the rtlsim level


class StreamingDataWidthConverter_hls(StreamingDataWidthConverter, HLSBackend):
"""Class that corresponds to finn-hlslib StreamingDataWidthConverter_Batch
function."""

def get_nodeattr_types(self):
my_attrs = {}
my_attrs.update(StreamingDataWidthConverter.get_nodeattr_types(self))
my_attrs.update(HLSBackend.get_nodeattr_types(self))
return my_attrs

def global_includes(self):
self.code_gen_dict["$GLOBALS$"] = ['#include "streamtools.h"']

def defines(self, var):
numReps = 1
numInWords = int(np.prod(self.get_folded_input_shape()[:-1]))
inWidth = self.get_nodeattr("inWidth")
outWidth = self.get_nodeattr("outWidth")
self.code_gen_dict["$DEFINES$"] = [
"#define InWidth %d " % inWidth,
"#define OutWidth %d " % outWidth,
"#define NumInWords %d " % numInWords,
"#define numReps %d" % numReps,
]
if self.needs_lcm():
lcmWidth = self.get_iowidth_lcm()
assert numInWords % (lcmWidth / inWidth) == 0, "Error in DWC LCM calculation"
numLCMToOut = numInWords // (lcmWidth / inWidth)
self.code_gen_dict["$DEFINES$"].append("#define LCMWidth %d" % lcmWidth)
self.code_gen_dict["$DEFINES$"].append("#define NumLCMToOut %d" % (numLCMToOut))

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
elem_hls_type = dtype.get_hls_datatype_str()
npy_type = "float"
npy_in = "%s/input_0.npy" % code_gen_dir
self.code_gen_dict["$READNPYDATA$"] = []
self.code_gen_dict["$READNPYDATA$"].append(
'npy2apintstream<%s, %s, %d, %s>("%s", in0_%s);'
% (
packed_hls_type,
elem_hls_type,
elem_bits,
npy_type,
npy_in,
self.hls_sname(),
)
)

def strm_decl(self):
self.code_gen_dict["$STREAMDECLARATIONS$"] = []
self.code_gen_dict["$STREAMDECLARATIONS$"].append(
'hls::stream<ap_uint<{}>> in0_{} ("in0_{}");'.format(
self.get_instream_width(), self.hls_sname(), self.hls_sname()
)
)
if self.needs_lcm():
self.code_gen_dict["$STREAMDECLARATIONS$"].append(
'hls::stream<ap_uint<{}>> intermediate ("intermediate");'.format(
self.get_iowidth_lcm()
)
)
self.code_gen_dict["$STREAMDECLARATIONS$"].append(
'hls::stream<ap_uint<{}>> out_{} ("out_{}");'.format(
self.get_outstream_width(), self.hls_sname(), self.hls_sname()
)
)

def docompute(self):
# TODO continue with fxns below, they are copy-pasted
op = "StreamingDataWidthConverter_Batch"
if self.needs_lcm():
self.code_gen_dict["$DOCOMPUTE$"] = [
'hls::stream<ap_uint<{}>> intermediate ("intermediate");'.format(
self.get_iowidth_lcm()
),
"%s<InWidth, LCMWidth, NumInWords>(in0_%s, intermediate, numReps);"
% (op, self.hls_sname()),
"%s<LCMWidth, OutWidth, NumLCMToOut>(intermediate, out_%s, numReps);"
% (op, self.hls_sname()),
]
else:
self.code_gen_dict["$DOCOMPUTE$"] = [
"%s<InWidth, OutWidth, NumInWords>(in0_%s, out_%s, numReps);"
% (op, self.hls_sname(), self.hls_sname())
]

def dataoutstrm(self):
code_gen_dir = self.get_nodeattr("code_gen_dir_cppsim")
dtype = self.get_output_datatype()
if dtype == DataType["BIPOLAR"]:
# use binary for bipolar storage
dtype = DataType["BINARY"]
elem_bits = dtype.bitwidth()
packed_bits = self.get_outstream_width()
packed_hls_type = "ap_uint<%d>" % packed_bits
elem_hls_type = dtype.get_hls_datatype_str()
npy_type = "float"
npy_out = "%s/output.npy" % code_gen_dir
oshape = self.get_folded_output_shape()
oshape_cpp_str = str(oshape).replace("(", "{").replace(")", "}")

self.code_gen_dict["$DATAOUTSTREAM$"] = [
'apintstream2npy<%s, %s, %d, %s>(out_%s, %s, "%s");'
% (
packed_hls_type,
elem_hls_type,
elem_bits,
npy_type,
self.hls_sname(),
oshape_cpp_str,
npy_out,
)
]

def save_as_npy(self):
self.code_gen_dict["$SAVEASCNPY$"] = []

def blackboxfunction(self):
in_packed_bits = self.get_instream_width()
in_packed_hls_type = "ap_uint<%d>" % in_packed_bits
out_packed_bits = self.get_outstream_width()
out_packed_hls_type = "ap_uint<%d>" % out_packed_bits
self.code_gen_dict["$BLACKBOXFUNCTION$"] = [
"void %s(hls::stream<%s > &in0_%s, hls::stream<%s > &out_%s)"
% (
self.onnx_node.name,
in_packed_hls_type,
self.hls_sname(),
out_packed_hls_type,
self.hls_sname(),
)
]

def pragmas(self):
self.code_gen_dict["$PRAGMAS$"] = [
"#pragma HLS INTERFACE axis port=in0_" + self.hls_sname()
]
self.code_gen_dict["$PRAGMAS$"].append(
"#pragma HLS INTERFACE axis port=out_" + self.hls_sname()
)
self.code_gen_dict["$PRAGMAS$"].append("#pragma HLS INTERFACE ap_ctrl_none port=return")
if self.needs_lcm():
self.code_gen_dict["$PRAGMAS$"].append("#pragma HLS DATAFLOW disable_start_propagation")

def execute_node(self, context, graph):
mode = self.get_nodeattr("exec_mode")
node = self.onnx_node
exp_shape = self.get_normal_input_shape()
folded_ishape = self.get_folded_input_shape()

# TODO ensure codegen dir exists
if mode == "cppsim":
code_gen_dir = self.get_nodeattr("code_gen_dir_cppsim")
elif mode == "rtlsim":
code_gen_dir = self.get_nodeattr("code_gen_dir_ipgen")
else:
raise Exception(
"""Invalid value for attribute exec_mode! Is currently set to: {}
has to be set to one of the following value ("cppsim", "rtlsim")""".format(
mode
)
)

inp = context[node.input[0]]
assert str(inp.dtype) == "float32", "Input datatype is not float32"
assert inp.shape == tuple(exp_shape), "Input shape does not match expected shape."

if self.get_input_datatype() == DataType["BIPOLAR"]:
# store bipolar activations as binary
inp = (inp + 1) / 2
export_idt = DataType["BINARY"]
else:
export_idt = self.get_input_datatype()
# reshape input into folded shape
reshaped_input = inp.reshape(folded_ishape)
# make copy before saving array
reshaped_input = reshaped_input.copy()
np.save(os.path.join(code_gen_dir, "input_0.npy"), reshaped_input)

if mode == "cppsim":
output = inp
output = np.asarray([output], dtype=np.float32).reshape(*exp_shape)
context[node.output[0]] = output

elif mode == "rtlsim":
sim = self.get_rtlsim()
nbits = self.get_instream_width()
rtlsim_inp = npy_to_rtlsim_input(
"{}/input_0.npy".format(code_gen_dir), export_idt, nbits
)
super().reset_rtlsim(sim)
super().toggle_clk(sim)
rtlsim_output = self.rtlsim(sim, rtlsim_inp)
odt = export_idt
target_bits = odt.bitwidth()
packed_bits = self.get_outstream_width()
out_npy_path = "{}/output.npy".format(code_gen_dir)
out_shape = self.get_folded_output_shape()
rtlsim_output_to_npy(
rtlsim_output, out_npy_path, odt, out_shape, packed_bits, target_bits
)
# load and reshape output
output = np.load(out_npy_path)
output = np.asarray([output], dtype=np.float32).reshape(exp_shape)
context[node.output[0]] = output
else:
raise Exception(
"""Invalid value for attribute exec_mode! Is currently set to: {}
has to be set to "rtlsim" """.format(
mode
)
)
# binary -> bipolar if needed
if self.get_output_datatype() == DataType["BIPOLAR"]:
out = context[node.output[0]]
out = 2 * out - 1
context[node.output[0]] = out
assert context[node.output[0]].shape == tuple(
exp_shape
), """Output
shape doesn't match expected shape, should be same as input shape"""
4 changes: 4 additions & 0 deletions src/finn/custom_op/fpgadataflow/rtl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,13 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

from finn.custom_op.fpgadataflow.rtl.fmpadding_rtl import FMPadding_rtl
from finn.custom_op.fpgadataflow.rtl.streamingdatawidthconverter_rtl import (
StreamingDataWidthConverter_rtl,
)

custom_op = dict()

# make sure new HLSCustomOp subclasses are imported here so that they get
# registered and plug in correctly into the infrastructure
custom_op["FMPadding_rtl"] = FMPadding_rtl
custom_op["StreamingDataWidthConverter_rtl"] = StreamingDataWidthConverter_rtl
Loading

0 comments on commit d9819a2

Please sign in to comment.