-
Notifications
You must be signed in to change notification settings - Fork 251
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[CustomOp] Initial draft of dwc in new class hierarchy
- Loading branch information
Showing
11 changed files
with
648 additions
and
756 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
271 changes: 271 additions & 0 deletions
271
src/finn/custom_op/fpgadataflow/hls/streamingdatawidthconverter_hls.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.