From 2cdfd86be7744820ffcd434b0a430e1efc334615 Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Fri, 14 Feb 2025 00:33:36 +0000 Subject: [PATCH 01/31] Add virtual HLS FIFO --- custom_hls/virtual_fifo.hpp | 81 +++++++ src/finn/builder/build_dataflow_config.py | 3 + src/finn/builder/build_dataflow_steps.py | 23 ++ .../custom_op/fpgadataflow/hls/__init__.py | 2 + .../fpgadataflow/hls/streamingfifo_hls.py | 208 ++++++++++++++++++ .../transformation/fpgadataflow/templates.py | 6 +- 6 files changed, 320 insertions(+), 3 deletions(-) create mode 100644 custom_hls/virtual_fifo.hpp create mode 100644 src/finn/custom_op/fpgadataflow/hls/streamingfifo_hls.py diff --git a/custom_hls/virtual_fifo.hpp b/custom_hls/virtual_fifo.hpp new file mode 100644 index 0000000000..85d71280bc --- /dev/null +++ b/custom_hls/virtual_fifo.hpp @@ -0,0 +1,81 @@ +#ifndef VIRTUAL_FIFO_HPP +#define VIRTUAL_FIFO_HPP + +#include +#include +#include + +// Utility Functions, taken from instrumentation wrapper +template +static void move( + hls::stream &src, + hls::stream &dst +) { +#pragma HLS pipeline II=1 style=flp + dst.write(src.read()); +} + +template +static void move( + hls::stream> &src, + hls::stream &dst +) { +#pragma HLS pipeline II=1 style=flp + dst.write(src.read().data); +} + +template +class Payload { +public: + using type = T; +}; +template +class Payload> { +public: + using type = T; +}; + +template +void VirtualFIFO(hls::stream > &in, hls::stream > &out, + ap_uint<32> mode, + ap_uint<32> depth, + ap_uint<32> &occupancy, + ap_uint<32> &max_occupancy) +{ + #pragma HLS pipeline II=1 style=flp + + static ap_uint<32> c_occupancy = 0; + static ap_uint<32> c_max_occupancy = 0; + #pragma HLS reset variable=c_occupancy + #pragma HLS reset variable=c_max_occupancy + + ap_uint inElem; + + bool read = mode == 0 || c_occupancy != depth; + bool write = c_occupancy != 0; + + // INPUT + if(read) + { + if(in.read_nb(inElem)) //disregard input data + { + c_occupancy++; + c_max_occupancy = (c_occupancy > c_max_occupancy) ? c_occupancy : c_max_occupancy; + } + } + + // OUTPUT + if(write) + { + if(out.write_nb(0)) //write dummy output data + { + c_occupancy--; + } + } + + // Update output status registers + occupancy = c_occupancy; + max_occupancy = c_max_occupancy; +} + +#endif diff --git a/src/finn/builder/build_dataflow_config.py b/src/finn/builder/build_dataflow_config.py index d6437a2e5c..c5e3995943 100644 --- a/src/finn/builder/build_dataflow_config.py +++ b/src/finn/builder/build_dataflow_config.py @@ -265,6 +265,9 @@ class DataflowBuildConfig: #: for each FIFO. auto_fifo_depths: Optional[bool] = True + # Enables experimental live FIFO sizing + live_fifo_sizing: Optional[bool] = False + #: Whether FIFO nodes with depth larger than 32768 will be split. #: Allow to configure very large FIFOs in the folding_config_file. split_large_fifos: Optional[bool] = False diff --git a/src/finn/builder/build_dataflow_steps.py b/src/finn/builder/build_dataflow_steps.py index 5163b2dbdb..fe0cb68a88 100644 --- a/src/finn/builder/build_dataflow_steps.py +++ b/src/finn/builder/build_dataflow_steps.py @@ -549,6 +549,29 @@ def step_set_fifo_depths(model: ModelWrapper, cfg: DataflowBuildConfig): `GiveUniqueNodeNames`. """ + # Experimental live FIFO-sizing, overwrites all other FIFO-related behavior + if cfg.live_fifo_sizing: + # Create all DWCs and FIFOs normally + model = model.transform(InsertDWC()) + model = model.transform(InsertFIFO(create_shallow_fifos=True)) + + # Specialize FIFOs to HLS back-end instead of default RTL back-end + for node in model.get_nodes_by_op_type("StreamingFIFO"): + node_inst = getCustomOp(node) + node_inst.set_nodeattr("preferred_impl_style", "hls") + model = model.transform(SpecializeLayers(cfg._resolve_fpga_part())) + + # Fix impl_style attribute + for node in model.get_nodes_by_op_type("StreamingFIFO_hls"): + node_inst = getCustomOp(node) + node_inst.set_nodeattr("impl_style", "virtual") + + # Clean up model + model = model.transform(GiveUniqueNodeNames()) + model = model.transform(GiveReadableTensorNames()) + + return model + if cfg.auto_fifo_depths: if cfg.auto_fifo_strategy == "characterize": model = model.transform(InsertDWC()) diff --git a/src/finn/custom_op/fpgadataflow/hls/__init__.py b/src/finn/custom_op/fpgadataflow/hls/__init__.py index 405c47a08d..d753fffa2e 100644 --- a/src/finn/custom_op/fpgadataflow/hls/__init__.py +++ b/src/finn/custom_op/fpgadataflow/hls/__init__.py @@ -47,6 +47,7 @@ StreamingDataWidthConverter_hls, ) from finn.custom_op.fpgadataflow.hls.streamingeltwise_hls import StreamingEltwise_hls +from finn.custom_op.fpgadataflow.hls.streamingfifo_hls import StreamingFIFO_hls from finn.custom_op.fpgadataflow.hls.streamingmaxpool_hls import StreamingMaxPool_hls from finn.custom_op.fpgadataflow.hls.thresholding_hls import Thresholding_hls from finn.custom_op.fpgadataflow.hls.tlastmarker_hls import TLastMarker_hls @@ -74,6 +75,7 @@ custom_op["StreamingEltwise_hls"] = StreamingEltwise_hls custom_op["StreamingDataWidthConverter_hls"] = StreamingDataWidthConverter_hls custom_op["StreamingMaxPool_hls"] = StreamingMaxPool_hls +custom_op["StreamingFIFO_hls"] = StreamingFIFO_hls custom_op["Thresholding_hls"] = Thresholding_hls custom_op["TLastMarker_hls"] = TLastMarker_hls custom_op["UpsampleNearestNeighbour_hls"] = UpsampleNearestNeighbour_hls diff --git a/src/finn/custom_op/fpgadataflow/hls/streamingfifo_hls.py b/src/finn/custom_op/fpgadataflow/hls/streamingfifo_hls.py new file mode 100644 index 0000000000..f17bc48fc6 --- /dev/null +++ b/src/finn/custom_op/fpgadataflow/hls/streamingfifo_hls.py @@ -0,0 +1,208 @@ +# 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.streamingfifo import StreamingFIFO +from finn.util.data_packing import npy_to_rtlsim_input, rtlsim_output_to_npy + + +class StreamingFIFO_hls(StreamingFIFO, HLSBackend): + """HLS-based FIFO implementation. Currently only used as virtual FIFO for live FIFO-sizing.""" + + def get_nodeattr_types(self): + my_attrs = { + # Only purpose of this CustomOp for now: virtual FIFO for live FIFO-sizing + "impl_style": ("s", False, "virtual", {"virtual"}), + } + my_attrs.update(StreamingFIFO.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 "virtual_fifo.hpp"'] + + def defines(self, var): + numReps = 1 + width = self.get_instream_width() + self.code_gen_dict["$DEFINES$"] = [ + "#define Width %d " % width, + "#define numReps %d" % numReps, + ] + + def strm_decl(self): + self.code_gen_dict["$STREAMDECLARATIONS$"] = [] + self.code_gen_dict["$STREAMDECLARATIONS$"].append( + 'hls::stream> in0_{} ("in0_{}");'.format( + self.get_instream_width(), self.hls_sname(), self.hls_sname() + ) + ) + self.code_gen_dict["$STREAMDECLARATIONS$"].append( + 'hls::stream> out_{} ("out_{}");'.format( + self.get_outstream_width(), self.hls_sname(), self.hls_sname() + ) + ) + + def docompute(self): + self.code_gen_dict["$DOCOMPUTE$"] = [ + """ + #pragma HLS dataflow disable_start_propagation + + static hls::stream> in_fifo; + static hls::stream>::type> out_fifo; + #pragma HLS stream variable=in_fifo depth=2 + #pragma HLS stream variable=out_fifo depth=2 + + // AXI-Stream -> FIFO + move(in0_%s, in_fifo); + + // Main + VirtualFIFO(in_fifo, out_fifo, mode, depth, occupancy, max_occupancy); + + // FIFO -> AXI-Stream + move(out_fifo, out_%s); + """ + % (self.hls_sname(), self.hls_sname()) + ] + + 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, ap_uint<32> mode, + ap_uint<32> depth, ap_uint<32> &occupancy, ap_uint<32> &max_occupancy)""" + % ( + 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 s_axilite port=mode") + self.code_gen_dict["$PRAGMAS$"].append("#pragma HLS INTERFACE s_axilite port=depth") + self.code_gen_dict["$PRAGMAS$"].append("#pragma HLS INTERFACE s_axilite port=occupancy") + self.code_gen_dict["$PRAGMAS$"].append("#pragma HLS INTERFACE s_axilite port=max_occupancy") + self.code_gen_dict["$PRAGMAS$"].append("#pragma HLS INTERFACE ap_ctrl_none port=return") + + def get_verilog_top_module_intf_names(self): + # Overload default HWCustomOp implementation to add axilite control IF + intf_names = super().get_verilog_top_module_intf_names() + intf_names["axilite"] = ["s_axi_control"] + return intf_names + + 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""" diff --git a/src/finn/transformation/fpgadataflow/templates.py b/src/finn/transformation/fpgadataflow/templates.py index ccf4e7a943..5c521720c4 100644 --- a/src/finn/transformation/fpgadataflow/templates.py +++ b/src/finn/transformation/fpgadataflow/templates.py @@ -92,9 +92,9 @@ custom_zynq_shell_template = """ set FREQ_MHZ %s set NUM_AXILITE %d -if {$NUM_AXILITE > 9} { - error "Maximum 10 AXI-Lite interfaces supported" -} +#if {$NUM_AXILITE > 9} { +# error "Maximum 10 AXI-Lite interfaces supported" +#} set NUM_AXIMM %d set BOARD %s set FPGA_PART %s From 7c04eb6e628cd21820bcef02ff624edfa3702b22 Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Fri, 14 Feb 2025 16:31:29 +0000 Subject: [PATCH 02/31] Integrate instrumentation into ZynqBuild --- custom_hls/instrumentation.template.cpp | 307 ++++++++++++++++++ custom_hls/instrumentation_sim.template.tcl | 67 ++++ custom_hls/instrumentation_tb.template.sv | 172 ++++++++++ src/finn/builder/build_dataflow_config.py | 4 + src/finn/builder/build_dataflow_steps.py | 22 ++ .../transformation/fpgadataflow/floorplan.py | 8 +- .../fpgadataflow/instrumentation.py | 203 ++++++++++++ .../fpgadataflow/make_zynq_proj.py | 88 ++++- 8 files changed, 860 insertions(+), 11 deletions(-) create mode 100644 custom_hls/instrumentation.template.cpp create mode 100644 custom_hls/instrumentation_sim.template.tcl create mode 100644 custom_hls/instrumentation_tb.template.sv create mode 100644 src/finn/transformation/fpgadataflow/instrumentation.py diff --git a/custom_hls/instrumentation.template.cpp b/custom_hls/instrumentation.template.cpp new file mode 100644 index 0000000000..bf15d77a87 --- /dev/null +++ b/custom_hls/instrumentation.template.cpp @@ -0,0 +1,307 @@ +/****************************************************************************** + * Copyright (c) 2023, Xilinx, 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: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. 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. + * + * 3. Neither the name of the copyright holder 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. + ******************************************************************************* + * @brief Instrumentation wrapper module for FINN IP characterization. + * @author Thomas B. Preusser + * @details + * Instrumentation wrapper intercepting the feature map input to and + * the feature map output from a FINN IP to measure processing latency and + * initiation interval in terms of clock cycles. The most recent readings + * are exposed via AXI-light. + * This wrapper can run the FINN IP detached from an external data source + * and sink by feeding LFSR-generated data and sinking the output without + * backpressure. + * This module is currently not integrated with the FINN compiler. It must + * be instantiated and integrated with the rest of the system in a manual + * process. + * + * @param PENDING maximum number of feature maps in the FINN dataflow pipeline + * @param ILEN number of input transactions per IFM + * @param OLEN number of output transactions per OFM + * @param KO number of subwords within output payload vector + * @param TI type of input payload vector + * @param TO type of output payload vector + *******************************************************************************/ + + #include + #include + #include + #include + + // Module Configuration + constexpr unsigned PENDING = @PENDING@; // Max. feature maps in flight + constexpr unsigned ILEN = @ILEN@; // Input words per IFM + constexpr unsigned OLEN = @OLEN@; // Output words per OFM + constexpr unsigned KO = @KO@; // Subwords within OFM transaction word + using TI = @TI@; // IFM transaction word + using TO = @TO@; // OFM transaction word + + //--------------------------------------------------------------------------- + // Utility Functions + static constexpr unsigned clog2 (unsigned x) { return x<2? 0 : 1+clog2((x+1)/2); } + static constexpr unsigned clog2nz(unsigned x) { return std::max(1u, clog2(x)); } + + template + static void move( + hls::stream &src, + hls::stream &dst + ) { + #pragma HLS pipeline II=1 style=flp + dst.write(src.read()); + } + + template + static void move( + hls::stream> &src, + hls::stream &dst + ) { + #pragma HLS pipeline II=1 style=flp + dst.write(src.read().data); + } + + template + class Payload { + public: + using type = T; + }; + template + class Payload> { + public: + using type = T; + }; + + /** + * Computes a checksum over a forwarded stream assumed to carry frames of + * N words further subdivided into K subwords. + * - Subword slicing can be customized typically by using a lambda. + * The provided DefaultSubwordSlicer assumes an `ap_(u)int`-like word + * type with a member `width` and a range-based slicing operator. It + * further assumes a little-endian arrangement of subwords within words + * for the canonical subword stream order. + * - Subwords wider than 23 bits are folded using bitwise XOR across + * slices of 23 bits starting from the LSB. + * - The folded subword values are weighted according to their position + * in the stream relative to the start of frame by a periodic weight + * sequence 1, 2, 3, ... + * - The weighted folded subword values are reduced to a checksum by an + * accumulation module 2^24. + * - A checksum is emitted for each completed frame. It is the concatenation + * of an 8-bit (modulo 256) frame counter and the 24-bit frame checksum. + */ + template + class DefaultSubwordSlicer { + static_assert(T::width%K == 0, "Word size must be subword multiple."); + static constexpr unsigned W = T::width/K; + public: + ap_uint operator()(T const &x, unsigned const j) const { + #pragma HLS inline + return x((j+1)*W-1, j*W); + } + }; + + //--------------------------------------------------------------------------- + // Instrumentation Core + template< + unsigned PENDING, + unsigned ILEN, + unsigned OLEN, + unsigned KO, + typename TI, + typename TO + > + void instrument( + hls::stream &finnix, + hls::stream &finnox, + ap_uint<32> cfg, // [0] - 0:hold, 1:lfsr; [31:16] - LFSR seed + ap_uint<32> &status, // [0] - timestamp overflow; [1] - timestamp underflow + ap_uint<32> &latency, + ap_uint<32> &interval, + ap_uint<32> &checksum, + ap_uint<32> &min_latency + ) { + #pragma HLS pipeline II=1 style=flp + + // Timestamp Management State + using clock_t = ap_uint<32>; + static clock_t cnt_clk = 0; + #pragma HLS reset variable=cnt_clk + hls::stream timestamps; + #pragma HLS stream variable=timestamps depth=PENDING + static bool timestamp_ovf = false; + static bool timestamp_unf = false; + #pragma HLS reset variable=timestamp_ovf + #pragma HLS reset variable=timestamp_unf + + // Input Feed & Generation + constexpr unsigned LFSR_WIDTH = (TI::width+15)/16 * 16; + static ap_uint icnt = 0; + static ap_uint lfsr; + #pragma HLS reset variable=icnt + #pragma HLS reset variable=lfsr off + if(!finnix.full()) { + + bool const first = icnt == 0; + bool wr; + if(first) { + // Start of new feature map + wr = cfg[0]; + for(unsigned i = 0; i < LFSR_WIDTH; i += 16) { + #pragma HLS unroll + lfsr(15+i, i) = cfg(31, 16) ^ (i>>4)*33331; + } + } + else { + // Advance LFSR + wr = true; + for(unsigned i = 0; i < LFSR_WIDTH; i += 16) { + #pragma HLS unroll + lfsr(15+i, i) = (lfsr(15+i, i) >> 1) ^ ap_uint<16>(lfsr[i]? 0 : 0x8805); + } + } + + if(wr) { + finnix.write_nb(lfsr); + if(first) timestamp_ovf |= !timestamps.write_nb(cnt_clk); + icnt = icnt == ILEN-1? decltype(icnt)(0) : decltype(icnt)(icnt + 1); + } + } + + // Output Tracking + static ap_uint ocnt = 0; + #pragma HLS reset variable=ocnt + static clock_t ts1 = 0; // last output timestamp + static clock_t last_latency = 0; + static clock_t last_interval = 0; + static clock_t cur_min_latency = ~0; + #pragma HLS reset variable=ts1 + #pragma HLS reset variable=last_latency + #pragma HLS reset variable=last_interval + #pragma HLS reset variable=cur_min_latency + + static ap_uint<8> pkts = 0; + #pragma HLS reset variable=pkts + static ap_uint< 2> coeff[3]; + static ap_uint<24> psum; + static ap_uint<32> last_checksum = 0; + #pragma HLS reset variable=coeff off + #pragma HLS reset variable=psum off + #pragma HLS reset variable=last_checksum + + TO oval; + if(finnox.read_nb(oval)) { + // Start of new output feature map + if(ocnt == 0) { + for(unsigned i = 0; i < 3; i++) coeff[i] = i+1; + psum = 0; + } + + // Update checksum + for(unsigned j = 0; j < KO; j++) { + #pragma HLS unroll + auto const v0 = DefaultSubwordSlicer()(oval, j); + constexpr unsigned W = 1 + (decltype(v0)::width-1)/23; + ap_uint v = v0; + ap_uint< 23> w = 0; + for(unsigned k = 0; k < W; k++) w ^= v(23*k+22, 23*k); + psum += (coeff[j%3][1]? (w, ap_uint<1>(0)) : ap_uint<24>(0)) + (coeff[j%3][0]? w : ap_uint<23>(0)); + } + + // Re-align coefficients + for(unsigned j = 0; j < 3; j++) { + #pragma HLS unroll + ap_uint<3> const cc = coeff[j] + ap_uint<3>(KO%3); + coeff[j] = cc(1, 0) + cc[2]; + } + + // Track frame position + if(ocnt != OLEN-1) ocnt++; + else { + clock_t ts0; + if(!timestamps.read_nb(ts0)) timestamp_unf = true; + else { + last_latency = cnt_clk - ts0; // completion - start + last_interval = cnt_clk - ts1; // completion - previous completion + cur_min_latency = std::min(cur_min_latency, last_latency); + ts1 = cnt_clk; // mark completion ^ + } + ocnt = 0; + + last_checksum = (pkts++, psum); + } + } + + // Advance Timestamp Counter + cnt_clk++; + + // Copy Status Outputs + status = timestamp_ovf | (timestamp_unf << 1); + latency = last_latency; + interval = last_interval; + checksum = last_checksum; + min_latency = cur_min_latency; + + } // instrument() + + void instrumentation_wrapper( + hls::stream &finnix, + hls::stream &finnox, + ap_uint<32> cfg, + ap_uint<32> &status, + ap_uint<32> &latency, + ap_uint<32> &interval, + ap_uint<32> &checksum, + ap_uint<32> &min_latency + ) { + #pragma HLS interface axis port=finnix + #pragma HLS interface axis port=finnox + #pragma HLS interface s_axilite bundle=ctrl port=cfg + #pragma HLS interface s_axilite bundle=ctrl port=status + #pragma HLS interface s_axilite bundle=ctrl port=latency + #pragma HLS interface s_axilite bundle=ctrl port=interval + #pragma HLS interface s_axilite bundle=ctrl port=checksum + #pragma HLS interface s_axilite bundle=ctrl port=min_latency + #pragma HLS interface ap_ctrl_none port=return + + #pragma HLS dataflow disable_start_propagation + static hls::stream finnix0; + static hls::stream::type> finnox0; + #pragma HLS stream variable=finnix0 depth=2 + #pragma HLS stream variable=finnox0 depth=2 + + // AXI-Stream -> FIFO + move(finnox, finnox0); + + // Main + instrument(finnix0, finnox0, cfg, status, latency, interval, checksum, min_latency); + + // FIFO -> AXI-Stream + move(finnix0, finnix); + + } // instrumentation_wrapper diff --git a/custom_hls/instrumentation_sim.template.tcl b/custom_hls/instrumentation_sim.template.tcl new file mode 100644 index 0000000000..4875d799e2 --- /dev/null +++ b/custom_hls/instrumentation_sim.template.tcl @@ -0,0 +1,67 @@ +# 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 AMD 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. + +set fpga_part @FPGA_PART@ +#set output_root ".." +# path to IP folder for instrumentation wrapper, change as needed +#set instrwrp_ip_dir "$output_root/instrumentation_wrapper/project_instrwrap/sol1/impl/ip" +# path to IP folder for FINN IP, change as needed +#set finn_ip_dir "$output_root/stitched_ip/ip" + +create_project -force instr_sim_proj instr_sim_proj/ -part $fpga_part +create_bd_design "dut" +update_compile_order -fileset sources_1 +#set_property ip_repo_paths [list $instrwrp_ip_dir] [current_project] +set_property ip_repo_paths [concat [get_property ip_repo_paths [current_project]] @IP_DIRS_STR@] [current_project] +update_ip_catalog + + +create_bd_cell -type ip -vlnv xilinx_finn:finn:finn_design:1.0 finn_design_0 +create_bd_cell -type ip -vlnv xilinx.com:hls:instrumentation_wrapper:1.0 instrumentation_wrap_0 +connect_bd_intf_net [get_bd_intf_pins instrumentation_wrap_0/finnix] [get_bd_intf_pins finn_design_0/s_axis_0] +connect_bd_intf_net [get_bd_intf_pins finn_design_0/m_axis_0] [get_bd_intf_pins instrumentation_wrap_0/finnox] +make_bd_intf_pins_external [get_bd_intf_pins instrumentation_wrap_0/s_axi_ctrl] +make_bd_pins_external [get_bd_pins instrumentation_wrap_0/ap_clk] +make_bd_pins_external [get_bd_pins instrumentation_wrap_0/ap_rst_n] +connect_bd_net [get_bd_ports ap_clk_0] [get_bd_pins finn_design_0/ap_clk] +connect_bd_net [get_bd_ports ap_rst_n_0] [get_bd_pins finn_design_0/ap_rst_n] + +save_bd_design + +update_compile_order -fileset sources_1 +make_wrapper -files [get_files instr_sim_proj/instr_sim_proj.srcs/sources_1/bd/dut/dut.bd] -top +add_files -norecurse instr_sim_proj/instr_sim_proj.gen/sources_1/bd/dut/hdl/dut_wrapper.v + +set_property SOURCE_SET sources_1 [get_filesets sim_1] +add_files -fileset sim_1 ./instrwrap_testbench.sv +update_compile_order -fileset sim_1 + +set_property synth_checkpoint_mode None [get_files instr_sim_proj/instr_sim_proj.srcs/sources_1/bd/dut/dut.bd] +generate_target Simulation [get_files instr_sim_proj/instr_sim_proj.srcs/sources_1/bd/dut/dut.bd] +launch_simulation -simset sim_1 -mode behavioral +run all diff --git a/custom_hls/instrumentation_tb.template.sv b/custom_hls/instrumentation_tb.template.sv new file mode 100644 index 0000000000..933104c623 --- /dev/null +++ b/custom_hls/instrumentation_tb.template.sv @@ -0,0 +1,172 @@ +// 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 AMD 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. + + +module tb #( + // sampling period (in cycles) for reading instrumentation wrapper registers + // TODO: make configurable or adjust automatically? + int unsigned INSTR_READ_PERIOD = 10000, + // 16-bit LFSR seed for generating fixed random data + int unsigned LFSR_SEED = 1 +)(); + + +// Clock & Reset +logic ap_clk = 0; +always #5ns ap_clk = !ap_clk; +logic ap_rst_n = 0; +uwire ap_rst = !ap_rst_n; + +// wires for instrumentation wrapper AXI lite interface +logic [31:0] axilite_ctrl_araddr = 'x; +uwire axilite_ctrl_arready; +logic axilite_ctrl_arvalid = 0; +logic [31:0] axilite_ctrl_awaddr = 'x; +uwire axilite_ctrl_awready; +logic axilite_ctrl_awvalid = 0; +uwire axilite_ctrl_bready = 1; +uwire [1:0]axilite_ctrl_bresp; +uwire axilite_ctrl_bvalid; +uwire [31:0]axilite_ctrl_rdata; +logic axilite_ctrl_rready = 1; +uwire [1:0]axilite_ctrl_rresp; +uwire axilite_ctrl_rvalid; +logic [31:0] axilite_ctrl_wdata = 'x; +uwire axilite_ctrl_wready; +uwire [3:0]axilite_ctrl_wstrb = 4'b1111; +logic axilite_ctrl_wvalid = 0; + + + + +dut_wrapper dut_wrapper_inst ( + .ap_clk_0(ap_clk), .ap_rst_n_0(ap_rst_n), + .s_axi_ctrl_0_araddr(axilite_ctrl_araddr), + .s_axi_ctrl_0_arready(axilite_ctrl_arready), + .s_axi_ctrl_0_arvalid(axilite_ctrl_arvalid), + .s_axi_ctrl_0_awaddr(axilite_ctrl_awaddr), + .s_axi_ctrl_0_awready(axilite_ctrl_awready), + .s_axi_ctrl_0_awvalid(axilite_ctrl_awvalid), + .s_axi_ctrl_0_bready(axilite_ctrl_bready), + .s_axi_ctrl_0_bresp(axilite_ctrl_bresp), + .s_axi_ctrl_0_bvalid(axilite_ctrl_bvalid), + .s_axi_ctrl_0_rdata(axilite_ctrl_rdata), + .s_axi_ctrl_0_rready(axilite_ctrl_rready), + .s_axi_ctrl_0_rresp(axilite_ctrl_rresp), + .s_axi_ctrl_0_rvalid(axilite_ctrl_rvalid), + .s_axi_ctrl_0_wdata(axilite_ctrl_wdata), + .s_axi_ctrl_0_wready(axilite_ctrl_wready), + .s_axi_ctrl_0_wstrb(axilite_ctrl_wstrb), + .s_axi_ctrl_0_wvalid(axilite_ctrl_wvalid) +); + +//--------------------------------------------------------------------------- + +initial begin + $timeformat(-9, 2, " ns"); + // perform reset + repeat(100) @(posedge ap_clk); + ap_rst_n <= 1; + $display("Reset complete"); + repeat(100) @(posedge ap_clk); + // instrumentation wrapper configuration: + // set up LFSR seed + start data generation + output sink + axilite_ctrl_awaddr <= 'h10; + axilite_ctrl_awvalid <= 1; + axilite_ctrl_wdata <= (LFSR_SEED << 16) | 'b11; + axilite_ctrl_wvalid <= 1; + repeat(8) begin + @(posedge ap_clk); + if(axilite_ctrl_wready && axilite_ctrl_awready) break; + end + axilite_ctrl_wvalid <= 0; + axilite_ctrl_awvalid <= 0; + axilite_ctrl_awaddr <= 'x; + axilite_ctrl_wdata <= 'x; + while(1) begin + axilite_ctrl_araddr <= 'h18; + axilite_ctrl_arvalid <= 1; + repeat(8) begin + @(posedge ap_clk); + if(axilite_ctrl_rvalid) begin + $display("[t=%0t] STATUS_I = %0d", $time, axilite_ctrl_rdata); + break; + end + end + axilite_ctrl_araddr <= 'h20; + axilite_ctrl_arvalid <= 1; + repeat(8) begin + @(posedge ap_clk); + if(axilite_ctrl_rvalid) begin + $display("[t=%0t] STATUS_O = %0d", $time, axilite_ctrl_rdata); + break; + end + end + axilite_ctrl_araddr <= 'h28; + axilite_ctrl_arvalid <= 1; + repeat(8) begin + @(posedge ap_clk); + if(axilite_ctrl_rvalid) begin + $display("[t=%0t] LATENCY = %0d", $time, axilite_ctrl_rdata); + break; + end + end + axilite_ctrl_araddr <= 'h38; + axilite_ctrl_arvalid <= 1; + repeat(8) begin + @(posedge ap_clk); + if(axilite_ctrl_rvalid) begin + $display("[t=%0t] INTERVAL = %0d", $time, axilite_ctrl_rdata); + break; + end + end + axilite_ctrl_araddr <= 'h48; + axilite_ctrl_arvalid <= 1; + repeat(8) begin + @(posedge ap_clk); + if(axilite_ctrl_rvalid) begin + $display("[t=%0t] CHECKSUM = %8x", $time, axilite_ctrl_rdata); + if(axilite_ctrl_rdata) begin + $display("Nonzero checksum detected, stopping simulation"); + $finish; + // TODO: simulate for configurable number of frames, like this: + // if(axilite_ctrl_rdata[31:24] == 47) begin + // $display("Frame number 48 detected, stopping simulation"); + // $finish; + // end + end + break; + end + end + axilite_ctrl_arvalid <= 0; + repeat(INSTR_READ_PERIOD) @(posedge ap_clk); + end +end + + +endmodule : tb diff --git a/src/finn/builder/build_dataflow_config.py b/src/finn/builder/build_dataflow_config.py index d6437a2e5c..08545ebc14 100644 --- a/src/finn/builder/build_dataflow_config.py +++ b/src/finn/builder/build_dataflow_config.py @@ -314,6 +314,10 @@ class DataflowBuildConfig: #: debug signals in the generated hardware) enable_hw_debug: Optional[bool] = False + #: Whether the accelerator will be simulated and synthesized with an + #: instrumentation wrapper attached to accurately measure performance. + enable_instrumentation: Optional[bool] = False + #: Whether pdb postmortem debuggig will be launched when the build fails enable_build_pdb_debug: Optional[bool] = True diff --git a/src/finn/builder/build_dataflow_steps.py b/src/finn/builder/build_dataflow_steps.py index 5163b2dbdb..a4481ed778 100644 --- a/src/finn/builder/build_dataflow_steps.py +++ b/src/finn/builder/build_dataflow_steps.py @@ -89,6 +89,7 @@ from finn.transformation.fpgadataflow.hlssynth_ip import HLSSynthIP from finn.transformation.fpgadataflow.insert_dwc import InsertDWC from finn.transformation.fpgadataflow.insert_fifo import InsertFIFO +from finn.transformation.fpgadataflow.insert_tlastmarker import InsertTLastMarker from finn.transformation.fpgadataflow.make_pynq_driver import MakePYNQDriver from finn.transformation.fpgadataflow.make_zynq_proj import ZynqBuild from finn.transformation.fpgadataflow.minimize_accumulator_width import ( @@ -644,6 +645,26 @@ def step_create_stitched_ip(model: ModelWrapper, cfg: DataflowBuildConfig): """Create stitched IP for a graph after all HLS IP blocks have been generated. Depends on the DataflowOutputType.STITCHED_IP output product.""" + # introduce tLAST marker, required for instrumentation + if cfg.enable_instrumentation: + model = model.transform( + InsertTLastMarker( + # only insert marker on output (input TLAST is ignored for these use-cases anyway) + both=False, + # use ap_axiu instead of qdma_axis + external=False, + # static number of iterations (based on what the compiler/folding sets up) + dynamic=False, + ) + ) + # give a proper name to the inserted node, important for codegen + # TODO: deal with multi-I/O accelerators? + model.graph.node[-1].name = "TLastMarker_0" + # re-run codegen and HLS IP gen, will affect only the new TLastMarker layer assuming + # all other IPs have been generated already + model = model.transform(PrepareIP(cfg._resolve_fpga_part(), cfg._resolve_hls_clk_period())) + model = model.transform(HLSSynthIP()) + if DataflowOutputType.STITCHED_IP in cfg.generate_outputs: stitched_ip_dir = cfg.output_dir + "/stitched_ip" model = model.transform( @@ -806,6 +827,7 @@ def step_synthesize_bitfile(model: ModelWrapper, cfg: DataflowBuildConfig): cfg.board, cfg.synth_clk_period_ns, cfg.enable_hw_debug, + cfg.enable_instrumentation, partition_model_dir=partition_model_dir, ) ) diff --git a/src/finn/transformation/fpgadataflow/floorplan.py b/src/finn/transformation/fpgadataflow/floorplan.py index b24145afcb..7d93ff88fc 100644 --- a/src/finn/transformation/fpgadataflow/floorplan.py +++ b/src/finn/transformation/fpgadataflow/floorplan.py @@ -99,9 +99,13 @@ def apply(self, model): # if we have SLR assignment already. use that if node_slr != -1: continue + # if available, use the SLR of the preceding node srcnode = model.find_producer(node.input[0]) - node_slr = getCustomOp(srcnode).get_nodeattr("slr") - node_inst.set_nodeattr("slr", node_slr) + if srcnode is not None: + node_slr = getCustomOp(srcnode).get_nodeattr("slr") + node_inst.set_nodeattr("slr", node_slr) + else: + node_inst.set_nodeattr("slr", default_slr) if unassigned_nodes > 0: warnings.warn( diff --git a/src/finn/transformation/fpgadataflow/instrumentation.py b/src/finn/transformation/fpgadataflow/instrumentation.py new file mode 100644 index 0000000000..7f37c5ed14 --- /dev/null +++ b/src/finn/transformation/fpgadataflow/instrumentation.py @@ -0,0 +1,203 @@ +import numpy as np +import os +import subprocess +from qonnx.custom_op.registry import getCustomOp +from qonnx.transformation.base import Transformation + +from finn.custom_op.fpgadataflow.templates import ipgentcl_template +from finn.util.basic import make_build_dir +from finn.util.hls import CallHLS + + +# TODO: duplicate function from make_zynq_proj.py +def collect_ip_dirs(model, ipstitch_path): + # collect list of all IP dirs + ip_dirs = [] + need_memstreamer = False + for node in model.graph.node: + node_inst = getCustomOp(node) + ip_dir_value = node_inst.get_nodeattr("ip_path") + assert os.path.isdir( + ip_dir_value + ), """The directory that should + contain the generated ip blocks doesn't exist.""" + ip_dirs += [ip_dir_value] + if node.op_type.startswith("MVAU") or node.op_type == "Thresholding_hls": + if node_inst.get_nodeattr("mem_mode") == "internal_decoupled": + need_memstreamer = True + ip_dirs += [ipstitch_path + "/ip"] + if need_memstreamer: + # add RTL streamer IP + ip_dirs.append("$::env(FINN_ROOT)/finn-rtllib/memstream") + return ip_dirs + + +class GenerateInstrumentationIP(Transformation): + def __init__( + self, + fpga_part, + clk_period_ns, + format="ip", # "ip" for Vivado (Zynq) or "xo" for Vitis (Alveo/Versal) + ): + super().__init__() + self.fpga_part = fpga_part + self.clk_period_ns = clk_period_ns + self.format = format + + def apply(self, model): + # Create directory for code-gen and HLS of instrumentation IP + wrapper_output_dir = make_build_dir(prefix="code_gen_ipgen_Instrumentation_") + model.set_metadata_prop("instrumentation_ipgen", wrapper_output_dir) + + # conservative max for pending feature maps: number of layers + pending = len(model.graph.node) + # query the parallelism-dependent folded input shape from the + # node consuming the graph input + inp_name = model.graph.input[0].name + inp_node = getCustomOp(model.find_consumer(inp_name)) + inp_shape_folded = list(inp_node.get_folded_input_shape()) + inp_stream_width = inp_node.get_instream_width_padded() + # number of beats per input is given by product of folded input + # shape except the last dim (which is the stream width) + ilen = np.prod(inp_shape_folded[:-1]) + ti = "ap_uint<%d>" % inp_stream_width + # perform the same for the output + out_name = model.graph.output[0].name + out_node = getCustomOp(model.find_producer(out_name)) + out_shape_folded = list(out_node.get_folded_output_shape()) + out_stream_width = out_node.get_outstream_width_padded() + olen = np.prod(out_shape_folded[:-1]) + to = "ap_uint<%d>" % out_stream_width + ko = out_shape_folded[-1] + # fill out instrumentation wrapper template + with open( + os.path.join(os.environ["FINN_ROOT"], "custom_hls", "instrumentation.template.cpp"), "r" + ) as f: + instrwrp_cpp = f.read() + instrwrp_cpp = instrwrp_cpp.replace("@PENDING@", str(pending)) + instrwrp_cpp = instrwrp_cpp.replace("@ILEN@", str(ilen)) + instrwrp_cpp = instrwrp_cpp.replace("@OLEN@", str(olen)) + instrwrp_cpp = instrwrp_cpp.replace("@TI@", str(ti)) + instrwrp_cpp = instrwrp_cpp.replace("@TO@", str(to)) + instrwrp_cpp = instrwrp_cpp.replace("@KO@", str(ko)) + with open(wrapper_output_dir + "/top_instrumentation_wrapper.cpp", "w") as f: + f.write(instrwrp_cpp) + # fill out HLS synthesis tcl template + prjname = "project_instrwrap" + ipgentcl = ipgentcl_template + ipgentcl = ipgentcl.replace("$PROJECTNAME$", prjname) + ipgentcl = ipgentcl.replace("$HWSRCDIR$", wrapper_output_dir) + ipgentcl = ipgentcl.replace("$TOPFXN$", "instrumentation_wrapper") + ipgentcl = ipgentcl.replace("$FPGAPART$", self.fpga_part) + ipgentcl = ipgentcl.replace("$CLKPERIOD$", str(self.clk_period_ns)) + ipgentcl = ipgentcl.replace("$DEFAULT_DIRECTIVES$", "") + if self.format == "xo": + # use Vitis RTL kernel (.xo) output instead of IP-XACT + ipgentcl = ipgentcl.replace("$EXTRA_DIRECTIVES$", "config_export -format xo") + ipgentcl = ipgentcl.replace( + "export_design -format ip_catalog", "export_design -format xo" + ) + else: + ipgentcl = ipgentcl.replace("$EXTRA_DIRECTIVES$", "") + with open(wrapper_output_dir + "/hls_syn.tcl", "w") as f: + f.write(ipgentcl) + # build bash script to launch HLS synth and call it + code_gen_dir = wrapper_output_dir + builder = CallHLS() + builder.append_tcl(code_gen_dir + "/hls_syn.tcl") + builder.set_ipgen_path(code_gen_dir + "/{}".format(prjname)) + builder.build(code_gen_dir) + ipgen_path = builder.ipgen_path + assert os.path.isdir(ipgen_path), "HLS IPGen failed: %s not found" % (ipgen_path) + ip_path = ipgen_path + "/sol1/impl/ip" + assert os.path.isdir(ip_path), "HLS IPGen failed: %s not found. Check log under %s" % ( + ip_path, + code_gen_dir, + ) + if self.format == "xo": + assert False, "Not implemented" + # TODO: export for use in VitisBuild or VersalBuild + # xo_dir = self.output_dir + "/xo" + # xo_dir = str(os.path.abspath(xo_dir)) + # os.makedirs(xo_dir, exist_ok=True) + # xo_path = code_gen_dir + "/{}/sol1/impl/export.xo".format(prjname) + # xo_instr_path = xo_dir + "/instrumentation_wrapper.xo" + # shutil.copy(xo_path, xo_instr_path) + else: + # shutil.move(ip_path, self.output_dir) + pass + + return (model, False) + + +class PrepareInstrumentationSim(Transformation): + def __init__(self, fpga_part): + super().__init__() + self.fpga_part = fpga_part + + def apply(self, model): + # Create directory for simulation of instrumentation IP + FINN IP + sim_output_dir = make_build_dir(prefix="sim_Instrumentation_") + model.set_metadata_prop("instrumentation_sim", sim_output_dir) + + # check if instrumentation IP was generated + instr_ip_dir = model.get_metadata_prop("instrumentation_ipgen") + if instr_ip_dir is None or (not os.path.isdir(instr_ip_dir)): + raise Exception( + "Instrumentation IP not generated, run GenerateInstrumentationIP first." + ) + + # TODO: Support simulation with AXI-lite control interfaces (e.g., for dynamic pipelines) + # fill in testbench template + with open( + os.path.join(os.environ["FINN_ROOT"], "custom_hls", "instrumentation_tb.template.sv"), + "r", + ) as f: + testbench_sv = f.read() + with open(sim_output_dir + "/instrwrap_testbench.sv", "w") as f: + f.write(testbench_sv) + # fill in testbench project creator template + with open( + os.path.join(os.environ["FINN_ROOT"], "custom_hls", "instrumentation_sim.template.tcl"), + "r", + ) as f: + testbench_tcl = f.read() + + # collect ip repo paths for finn accelerator sub cores so Vivado can find them + ipstitch_path = model.get_metadata_prop("vivado_stitch_proj") + ip_dirs = ["list"] + ip_dirs += collect_ip_dirs(model, ipstitch_path) + ip_dirs += [instr_ip_dir] + ip_dirs_str = "[%s]" % (" ".join(ip_dirs)) + testbench_tcl = testbench_tcl.replace("@FPGA_PART@", self.fpga_part) + testbench_tcl = testbench_tcl.replace("@IP_DIRS_STR@", ip_dirs_str) + with open(sim_output_dir + "/make_instrwrap_sim_proj.tcl", "w") as f: + f.write(testbench_tcl) + + return (model, False) + + +class RunInstrumentationSim(Transformation): + def __init__(self): + super().__init__() + + def apply(self, model): + sim_output_dir = model.get_metadata_prop("instrumentation_sim") + if sim_output_dir is None or (not os.path.isdir(sim_output_dir)): + raise Exception( + "Instrumentation sim not prepared, run PrepareInstrumentationSim first." + ) + + # Prepare bash script + bash_script = os.getcwd() + "/report_power.sh" + with open(bash_script, "w") as script: + script.write("#!/bin/bash\n") + script.write("cd %s\n" % (sim_output_dir)) + script.write("vivado -mode batch -source make_instrwrap_sim_proj.tcl\n") + + # Run script + print("Running Vivado simulation of instrumentation wrapper") + sub_proc = subprocess.Popen(["bash", bash_script]) + sub_proc.communicate() + + return (model, False) diff --git a/src/finn/transformation/fpgadataflow/make_zynq_proj.py b/src/finn/transformation/fpgadataflow/make_zynq_proj.py index 63ce2d3cbf..8192c09bae 100644 --- a/src/finn/transformation/fpgadataflow/make_zynq_proj.py +++ b/src/finn/transformation/fpgadataflow/make_zynq_proj.py @@ -45,6 +45,7 @@ from finn.transformation.fpgadataflow.insert_dwc import InsertDWC from finn.transformation.fpgadataflow.insert_fifo import InsertFIFO from finn.transformation.fpgadataflow.insert_iodma import InsertIODMA +from finn.transformation.fpgadataflow.instrumentation import GenerateInstrumentationIP from finn.transformation.fpgadataflow.prepare_ip import PrepareIP from finn.transformation.fpgadataflow.specialize_layers import SpecializeLayers from finn.util.basic import make_build_dir, pynq_native_port_width, pynq_part_map @@ -102,6 +103,42 @@ def apply(self, model): axilite_idx = 0 global_clk_ns = 0 instance_names = {} + + # instantiate instrumentation IP if it was generated + instr_ip_dir = model.get_metadata_prop("instrumentation_ipgen") + if instr_ip_dir is not None and os.path.isdir(instr_ip_dir): + use_instrumentation = True + # update IP repository + config.append( + "set_property ip_repo_paths " + "[concat [get_property ip_repo_paths [current_project]] [list %s]] " + "[current_project]" % instr_ip_dir + ) + config.append("update_ip_catalog -rebuild -scan_changes") + # create instance + config.append( + "create_bd_cell -type ip -vlnv %s %s" + % ("xilinx.com:hls:instrumentation_wrapper:1.0", "instrumentation_wrap_0") + ) + # connect clock % reset + config.append( + "connect_bd_net [get_bd_pins instrumentation_wrap_0/ap_clk] " + "[get_bd_pins smartconnect_0/aclk]" + ) + config.append( + "connect_bd_net [get_bd_pins instrumentation_wrap_0/ap_rst_n] " + "[get_bd_pins smartconnect_0/aresetn]" + ) + # connect AXI-lite control interface + config.append( + "connect_bd_intf_net [get_bd_intf_pins instrumentation_wrap_0/s_axi_ctrl] " + "[get_bd_intf_pins axi_interconnect_0/M%02d_AXI]" % (axilite_idx) + ) + config.append("assign_axi_addr_proc instrumentation_wrap_0/s_axi_ctrl") + axilite_idx += 1 + else: + use_instrumentation = False + for node in model.graph.node: assert node.op_type == "StreamingDataflowPartition", "Invalid link graph" sdp_node = getCustomOp(node) @@ -150,7 +187,8 @@ def apply(self, model): # define kernel instances # name kernels connected to graph inputs as idmaxx # name kernels connected to graph outputs as odmaxx - if (producer is None) or (consumer == []): + # do not expect IDMA/ODMA when instrumentation is enabled + if not use_instrumentation and ((producer is None) or (consumer == [])): # TODO not a good way of checking for external inp&out # should look at the list of top-level in/out instead if producer is None: @@ -228,6 +266,26 @@ def apply(self, model): ) ) + # connect first/last dataflow partition to instrumentation wrapper + if use_instrumentation: + if producer is None: + config.append( + "connect_bd_intf_net [get_bd_intf_pins %s/s_axis_0] " + "[get_bd_intf_pins instrumentation_wrap_0/finnix]" + % (instance_names[node.name]) + ) + if consumer == []: + config.append( + "connect_bd_intf_net [get_bd_intf_pins %s/m_axis_0] " + "[get_bd_intf_pins instrumentation_wrap_0/finnox]" + % (instance_names[node.name]) + ) + + # TODO: WORKAROUND, do not instantiate smartconnect when not needed! + if use_instrumentation: + config.append("delete_bd_objs [get_bd_cells smartconnect_0]") + aximm_idx = 1 + # create a temporary folder for the project vivado_pynq_proj_dir = make_build_dir(prefix="vivado_zynq_proj_") model.set_metadata_prop("vivado_pynq_proj", vivado_pynq_proj_dir) @@ -305,6 +363,7 @@ def __init__( platform, period_ns, enable_debug=False, + enable_instrumentation=False, partition_model_dir=None, ): super().__init__() @@ -313,19 +372,27 @@ def __init__( self.period_ns = period_ns self.platform = platform self.enable_debug = enable_debug + self.enable_instrumentation = enable_instrumentation self.partition_model_dir = partition_model_dir def apply(self, model): # first infer layouts model = model.transform(InferDataLayouts()) # prepare at global level, then break up into kernels - prep_transforms = [ - InsertIODMA(self.axi_port_width), - InsertDWC(), - SpecializeLayers(self.fpga_part), - Floorplan(), - CreateDataflowPartition(partition_model_dir=self.partition_model_dir), - ] + if self.enable_instrumentation: + prep_transforms = [ + GenerateInstrumentationIP(self.fpga_part, self.period_ns), + Floorplan(), + CreateDataflowPartition(partition_model_dir=self.partition_model_dir), + ] + else: + prep_transforms = [ + InsertIODMA(self.axi_port_width), + InsertDWC(), + SpecializeLayers(self.fpga_part), + Floorplan(), + CreateDataflowPartition(partition_model_dir=self.partition_model_dir), + ] for trn in prep_transforms: model = model.transform(trn) model = model.transform(GiveUniqueNodeNames()) @@ -337,7 +404,10 @@ def apply(self, model): sdp_node = getCustomOp(sdp_node) dataflow_model_filename = sdp_node.get_nodeattr("model") kernel_model = ModelWrapper(dataflow_model_filename) - kernel_model = kernel_model.transform(InsertFIFO()) + # InsertFIFO at this stage interferes with tLastMarker + # TODO: is this really needed here at all? + if not self.enable_instrumentation: + kernel_model = kernel_model.transform(InsertFIFO()) kernel_model = kernel_model.transform(SpecializeLayers(self.fpga_part)) kernel_model = kernel_model.transform(GiveUniqueNodeNames(prefix)) kernel_model.save(dataflow_model_filename) From 419e18f65d67e3b8f498a9f4620123f1170582bf Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Wed, 19 Feb 2025 16:10:48 +0000 Subject: [PATCH 03/31] Nest AXI interconnects if required --- .../fpgadataflow/make_zynq_proj.py | 94 +++++++++++++++++-- 1 file changed, 87 insertions(+), 7 deletions(-) diff --git a/src/finn/transformation/fpgadataflow/make_zynq_proj.py b/src/finn/transformation/fpgadataflow/make_zynq_proj.py index 8192c09bae..5e86a58b6e 100644 --- a/src/finn/transformation/fpgadataflow/make_zynq_proj.py +++ b/src/finn/transformation/fpgadataflow/make_zynq_proj.py @@ -27,6 +27,7 @@ # 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 math import os import subprocess from qonnx.core.modelwrapper import ModelWrapper @@ -100,6 +101,9 @@ def apply(self, model): idma_idx = 0 odma_idx = 0 aximm_idx = 0 + nested_interconnect_count = 0 + master_axilite_idx = 0 + axilite_interconnect_idx = 0 axilite_idx = 0 global_clk_ns = 0 instance_names = {} @@ -132,13 +136,62 @@ def apply(self, model): # connect AXI-lite control interface config.append( "connect_bd_intf_net [get_bd_intf_pins instrumentation_wrap_0/s_axi_ctrl] " - "[get_bd_intf_pins axi_interconnect_0/M%02d_AXI]" % (axilite_idx) + "[get_bd_intf_pins axi_interconnect_0/M%02d_AXI]" % (master_axilite_idx) ) config.append("assign_axi_addr_proc instrumentation_wrap_0/s_axi_ctrl") - axilite_idx += 1 + master_axilite_idx += 1 else: use_instrumentation = False + # instantiate nested AXI interconnects if required + # only the nested interconnects and all interfaces connected before this line + # will be connected to the original (master) interconnect + total_axilite_count = 0 + for node in model.graph.node: + sdp_node = getCustomOp(node) + dataflow_model_filename = sdp_node.get_nodeattr("model") + kernel_model = ModelWrapper(dataflow_model_filename) + ifnames = eval(kernel_model.get_metadata_prop("vivado_stitch_ifnames")) + total_axilite_count += len(ifnames["axilite"]) + if total_axilite_count > (64 - master_axilite_idx): + nested_interconnect_count = math.ceil(total_axilite_count / 64.0) + for i in range(1, nested_interconnect_count + 1): + # create instance + config.append( + "create_bd_cell -type ip -vlnv $interconnect_vlnv axi_interconnect_%d" % (i) + ) + # configure instance + config.append( + "set_property -dict [list CONFIG.NUM_MI %d] [get_bd_cells axi_interconnect_%d]" + % (max(64, total_axilite_count), i) + ) + # connect to master interconnect + config.append( + "connect_bd_intf_net [get_bd_intf_pins axi_interconnect_0/M%02d_AXI] -boundary_type upper [get_bd_intf_pins axi_interconnect_%d/S00_AXI]" + % (master_axilite_idx, i) + ) + # connect clocks TODO: suppport zynq_7000 + config.append( + "apply_bd_automation -rule xilinx.com:bd_rule:clkrst -config { Clk {/zynq_ps/pl_clk0} Freq {} Ref_Clk0 {} Ref_Clk1 {} Ref_Clk2 {}} [get_bd_pins axi_interconnect_%d/ACLK]" + % (i) + ) + config.append( + "apply_bd_automation -rule xilinx.com:bd_rule:clkrst -config { Clk {/zynq_ps/pl_clk0} Freq {} Ref_Clk0 {} Ref_Clk1 {} Ref_Clk2 {}} [get_bd_pins axi_interconnect_%d/S00_ACLK]" + % (i) + ) + # connect reset + config.append( + "connect_bd_net [get_bd_pins axi_interconnect_%d/ARESETN] [get_bd_pins axi_interconnect_0/ARESETN]" + % (i) + ) + master_axilite_idx += 1 + total_axilite_count = min(0, total_axilite_count - 64) + + assert total_axilite_count == 0, "Not all AXI-lite interfaces connected!" + + # start populating the first nested interconnect + axilite_interconnect_idx = 1 + for node in model.graph.node: assert node.op_type == "StreamingDataflowPartition", "Invalid link graph" sdp_node = getCustomOp(node) @@ -211,8 +264,13 @@ def apply(self, model): assert axilite_intf_name is not None config.append( "connect_bd_intf_net [get_bd_intf_pins %s/%s] " - "[get_bd_intf_pins axi_interconnect_0/M%02d_AXI]" - % (instance_names[node.name], axilite_intf_name, axilite_idx) + "[get_bd_intf_pins axi_interconnect_%d/M%02d_AXI]" + % ( + instance_names[node.name], + axilite_intf_name, + axilite_interconnect_idx, + axilite_idx, + ) ) # assign_bd_address with appropriate range/offset config.append( @@ -221,6 +279,11 @@ def apply(self, model): aximm_idx += 1 axilite_idx += 1 + if axilite_idx == 64: + axilite_interconnect_idx += 1 + axilite_idx = 0 + if axilite_interconnect_idx == 0: + master_axilite_idx += 1 else: instance_names[node.name] = node.name config.append( @@ -230,8 +293,13 @@ def apply(self, model): for axilite_intf_name in ifnames["axilite"]: config.append( "connect_bd_intf_net [get_bd_intf_pins %s/%s] " - "[get_bd_intf_pins axi_interconnect_0/M%02d_AXI]" - % (instance_names[node.name], axilite_intf_name, axilite_idx) + "[get_bd_intf_pins axi_interconnect_%d/M%02d_AXI]" + % ( + instance_names[node.name], + axilite_intf_name, + axilite_interconnect_idx, + axilite_idx, + ) ) # assign_bd_address with appropriate range/offset config.append( @@ -239,6 +307,11 @@ def apply(self, model): % (instance_names[node.name], axilite_intf_name) ) axilite_idx += 1 + if axilite_idx == 64: + axilite_interconnect_idx += 1 + axilite_idx = 0 + if axilite_interconnect_idx == 0: + master_axilite_idx += 1 sdp_node.set_nodeattr("instance_name", instance_names[node.name]) config.append( @@ -286,6 +359,13 @@ def apply(self, model): config.append("delete_bd_objs [get_bd_cells smartconnect_0]") aximm_idx = 1 + # finalize nested interconnect clock TODO: support zynq_7000 + for i in range(1, nested_interconnect_count + 1): + config.append( + "apply_bd_automation -rule xilinx.com:bd_rule:clkrst -config { Clk {/zynq_ps/pl_clk0} } [get_bd_pins axi_interconnect_%d/M*_ACLK]" + % (i) + ) + # create a temporary folder for the project vivado_pynq_proj_dir = make_build_dir(prefix="vivado_zynq_proj_") model.set_metadata_prop("vivado_pynq_proj", vivado_pynq_proj_dir) @@ -300,7 +380,7 @@ def apply(self, model): templates.custom_zynq_shell_template % ( fclk_mhz, - axilite_idx, + master_axilite_idx, aximm_idx, self.platform, pynq_part_map[self.platform], From 5628ab2a1a2505ad4014626e885ddc11c8e59238 Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Wed, 19 Feb 2025 16:25:07 +0000 Subject: [PATCH 04/31] Fix AXI interconnect connection --- src/finn/transformation/fpgadataflow/make_zynq_proj.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/finn/transformation/fpgadataflow/make_zynq_proj.py b/src/finn/transformation/fpgadataflow/make_zynq_proj.py index 5e86a58b6e..8c990a8b3d 100644 --- a/src/finn/transformation/fpgadataflow/make_zynq_proj.py +++ b/src/finn/transformation/fpgadataflow/make_zynq_proj.py @@ -191,6 +191,8 @@ def apply(self, model): # start populating the first nested interconnect axilite_interconnect_idx = 1 + else: + axilite_idx = master_axilite_idx for node in model.graph.node: assert node.op_type == "StreamingDataflowPartition", "Invalid link graph" From 0c57d1b373527337f80ede1714a739cb83771bad Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Wed, 19 Feb 2025 22:19:16 +0000 Subject: [PATCH 05/31] Make floorplan partitioning of AXI-lite interfaces more consistent --- .../transformation/fpgadataflow/floorplan.py | 39 ++++++++++++------- .../fpgadataflow/make_zynq_proj.py | 4 +- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/finn/transformation/fpgadataflow/floorplan.py b/src/finn/transformation/fpgadataflow/floorplan.py index 7d93ff88fc..0b806ff44a 100644 --- a/src/finn/transformation/fpgadataflow/floorplan.py +++ b/src/finn/transformation/fpgadataflow/floorplan.py @@ -134,25 +134,27 @@ def apply(self, model): ) non_dma_nodes = list(filter(lambda x: x not in dyn_tlastmarker_nodes, non_dma_nodes)) + # assign every DMA node to its own partition for node in dma_nodes: node_inst = getCustomOp(node) node_inst.set_nodeattr("partition_id", partition_cnt) partition_cnt += 1 + # assign every dynamic tLastMarker node to its own partition for node in dyn_tlastmarker_nodes: node_inst = getCustomOp(node) node_inst.set_nodeattr("partition_id", partition_cnt) partition_cnt += 1 + # handle remaining nodes for node in non_dma_nodes: pre_node = model.find_producer(node.input[0]) node_inst = getCustomOp(node) if pre_node not in non_dma_nodes: - # input node + # input node -> start new partition node_inst.set_nodeattr("partition_id", partition_cnt) partition_cnt += 1 continue - elif not ( node.op_type.startswith("MVAU") and node_inst.get_nodeattr("mem_mode") is not None @@ -160,25 +162,36 @@ def apply(self, model): ): pre_nodes = model.find_direct_predecessors(node) else: + # exception for external weight MVAU: only consider primary input + # TODO: (why) is this necessary? should we consider such exceptions for other cases? pre_nodes = [pre_node] + axilite_intf_name = node_inst.get_verilog_top_module_intf_names()["axilite"] + if len(axilite_intf_name) != 0: + # This node has an AXI-Lite interface -> start new partition + node_inst.set_nodeattr("partition_id", partition_cnt) + partition_cnt += 1 + continue + + # examine all predecessor nodes to determine partition id for this node node_slr = node_inst.get_nodeattr("slr") + slr_mismatch_count = 0 for pre_node in pre_nodes: pre_inst = getCustomOp(pre_node) pre_slr = pre_inst.get_nodeattr("slr") if node_slr == pre_slr: - axilite_intf_name = pre_inst.get_verilog_top_module_intf_names()["axilite"] - if len(axilite_intf_name) != 0: - node_inst.set_nodeattr("partition_id", partition_cnt) - partition_cnt += 1 - else: - partition_id = pre_inst.get_nodeattr("partition_id") - node_inst.set_nodeattr("partition_id", partition_id) - + # Default case -> assign to same partition as predecessor + partition_id = pre_inst.get_nodeattr("partition_id") + node_inst.set_nodeattr("partition_id", partition_id) + break else: - # no matching, new partition - node_inst.set_nodeattr("partition_id", partition_cnt) - partition_cnt += 1 + # SLR mismatch with predecessor, can't assign same partition + slr_mismatch_count += 1 + + if slr_mismatch_count == len(pre_nodes): + # SLR mismatch with ALL predecessors -> start new partition + node_inst.set_nodeattr("partition_id", partition_cnt) + partition_cnt += 1 # save the updated floorplan floorplan = model.analysis(floorplan_params) diff --git a/src/finn/transformation/fpgadataflow/make_zynq_proj.py b/src/finn/transformation/fpgadataflow/make_zynq_proj.py index 8c990a8b3d..4d2ee3d50e 100644 --- a/src/finn/transformation/fpgadataflow/make_zynq_proj.py +++ b/src/finn/transformation/fpgadataflow/make_zynq_proj.py @@ -163,7 +163,7 @@ def apply(self, model): # configure instance config.append( "set_property -dict [list CONFIG.NUM_MI %d] [get_bd_cells axi_interconnect_%d]" - % (max(64, total_axilite_count), i) + % (min(64, total_axilite_count), i) ) # connect to master interconnect config.append( @@ -185,7 +185,7 @@ def apply(self, model): % (i) ) master_axilite_idx += 1 - total_axilite_count = min(0, total_axilite_count - 64) + total_axilite_count = max(0, total_axilite_count - 64) assert total_axilite_count == 0, "Not all AXI-lite interfaces connected!" From 684459c76189c22b9aa004a7c0028ee1c77a5a0d Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Wed, 19 Feb 2025 22:56:06 +0000 Subject: [PATCH 06/31] Add GPIO IP for reset --- .../transformation/fpgadataflow/make_zynq_proj.py | 14 +++++++++++--- src/finn/transformation/fpgadataflow/templates.py | 11 +++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/finn/transformation/fpgadataflow/make_zynq_proj.py b/src/finn/transformation/fpgadataflow/make_zynq_proj.py index 4d2ee3d50e..456441bca8 100644 --- a/src/finn/transformation/fpgadataflow/make_zynq_proj.py +++ b/src/finn/transformation/fpgadataflow/make_zynq_proj.py @@ -94,6 +94,7 @@ def __init__(self, platform, enable_debug=False): super().__init__() self.platform = platform self.enable_debug = 1 if enable_debug else 0 + self.enable_gpio_reset = 0 def apply(self, model): # create a config file and empty list of xo files @@ -112,6 +113,12 @@ def apply(self, model): instr_ip_dir = model.get_metadata_prop("instrumentation_ipgen") if instr_ip_dir is not None and os.path.isdir(instr_ip_dir): use_instrumentation = True + + # instantiate GPIO IP to trigger reset + self.enable_gpio_reset = 1 + # in the template this will connect to first port of interconnect_0 + master_axilite_idx += 1 + # update IP repository config.append( "set_property ip_repo_paths " @@ -170,7 +177,7 @@ def apply(self, model): "connect_bd_intf_net [get_bd_intf_pins axi_interconnect_0/M%02d_AXI] -boundary_type upper [get_bd_intf_pins axi_interconnect_%d/S00_AXI]" % (master_axilite_idx, i) ) - # connect clocks TODO: suppport zynq_7000 + # connect clocks/reset TODO: suppport zynq_7000 config.append( "apply_bd_automation -rule xilinx.com:bd_rule:clkrst -config { Clk {/zynq_ps/pl_clk0} Freq {} Ref_Clk0 {} Ref_Clk1 {} Ref_Clk2 {}} [get_bd_pins axi_interconnect_%d/ACLK]" % (i) @@ -179,7 +186,7 @@ def apply(self, model): "apply_bd_automation -rule xilinx.com:bd_rule:clkrst -config { Clk {/zynq_ps/pl_clk0} Freq {} Ref_Clk0 {} Ref_Clk1 {} Ref_Clk2 {}} [get_bd_pins axi_interconnect_%d/S00_ACLK]" % (i) ) - # connect reset + # connect reset TODO: probably unneeded config.append( "connect_bd_net [get_bd_pins axi_interconnect_%d/ARESETN] [get_bd_pins axi_interconnect_0/ARESETN]" % (i) @@ -361,7 +368,7 @@ def apply(self, model): config.append("delete_bd_objs [get_bd_cells smartconnect_0]") aximm_idx = 1 - # finalize nested interconnect clock TODO: support zynq_7000 + # finalize nested interconnect clock/reset TODO: support zynq_7000 for i in range(1, nested_interconnect_count + 1): config.append( "apply_bd_automation -rule xilinx.com:bd_rule:clkrst -config { Clk {/zynq_ps/pl_clk0} } [get_bd_pins axi_interconnect_%d/M*_ACLK]" @@ -388,6 +395,7 @@ def apply(self, model): pynq_part_map[self.platform], config, self.enable_debug, + self.enable_gpio_reset, ) ) diff --git a/src/finn/transformation/fpgadataflow/templates.py b/src/finn/transformation/fpgadataflow/templates.py index ccf4e7a943..0f6ba7c3c4 100644 --- a/src/finn/transformation/fpgadataflow/templates.py +++ b/src/finn/transformation/fpgadataflow/templates.py @@ -218,6 +218,17 @@ ] } +# set up GPIO to trigger reset +if {%d == 1} { + create_bd_cell -type ip -vlnv xilinx.com:ip:axi_gpio:2.0 axi_gpio_0 + set_property -dict [list CONFIG.C_ALL_OUTPUTS {1} CONFIG.C_DOUT_DEFAULT {0x00000001} CONFIG.C_GPIO_WIDTH {1}] [get_bd_cells axi_gpio_0] + connect_bd_intf_net [get_bd_intf_pins axi_gpio_0/S_AXI] -boundary_type upper [get_bd_intf_pins axi_interconnect_0/M00_AXI] + assign_axi_addr_proc axi_gpio_0/S_AXI + connect_bd_net [get_bd_pins axi_gpio_0/s_axi_aclk] [get_bd_pins axi_interconnect_0/ACLK] + connect_bd_net [get_bd_pins axi_gpio_0/s_axi_aresetn] [get_bd_pins axi_interconnect_0/ARESETN] + connect_bd_net [get_bd_pins axi_gpio_0/gpio_io_o] [get_bd_pins rst_zynq_ps_*/aux_reset_in] +} + #finalize clock and reset connections for interconnects if {$ZYNQ_TYPE == "zynq_us+"} { apply_bd_automation -rule xilinx.com:bd_rule:clkrst -config { Clk {/zynq_ps/pl_clk0} } [get_bd_pins axi_interconnect_0/M*_ACLK] From 8d454886c16f7495106d4ec477c54f5ba99bcb3d Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Thu, 20 Feb 2025 07:55:52 +0000 Subject: [PATCH 07/31] Remove unneeded connect_bd_net --- src/finn/transformation/fpgadataflow/make_zynq_proj.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/finn/transformation/fpgadataflow/make_zynq_proj.py b/src/finn/transformation/fpgadataflow/make_zynq_proj.py index 456441bca8..d462dc9d6b 100644 --- a/src/finn/transformation/fpgadataflow/make_zynq_proj.py +++ b/src/finn/transformation/fpgadataflow/make_zynq_proj.py @@ -186,11 +186,6 @@ def apply(self, model): "apply_bd_automation -rule xilinx.com:bd_rule:clkrst -config { Clk {/zynq_ps/pl_clk0} Freq {} Ref_Clk0 {} Ref_Clk1 {} Ref_Clk2 {}} [get_bd_pins axi_interconnect_%d/S00_ACLK]" % (i) ) - # connect reset TODO: probably unneeded - config.append( - "connect_bd_net [get_bd_pins axi_interconnect_%d/ARESETN] [get_bd_pins axi_interconnect_0/ARESETN]" - % (i) - ) master_axilite_idx += 1 total_axilite_count = max(0, total_axilite_count - 64) From 960a7f46a48519d4d63183a4de234bd0b12857bf Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Thu, 20 Feb 2025 18:01:02 +0000 Subject: [PATCH 08/31] Fix redundant bd_automation --- src/finn/transformation/fpgadataflow/make_zynq_proj.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/finn/transformation/fpgadataflow/make_zynq_proj.py b/src/finn/transformation/fpgadataflow/make_zynq_proj.py index d462dc9d6b..846d95a11b 100644 --- a/src/finn/transformation/fpgadataflow/make_zynq_proj.py +++ b/src/finn/transformation/fpgadataflow/make_zynq_proj.py @@ -182,10 +182,6 @@ def apply(self, model): "apply_bd_automation -rule xilinx.com:bd_rule:clkrst -config { Clk {/zynq_ps/pl_clk0} Freq {} Ref_Clk0 {} Ref_Clk1 {} Ref_Clk2 {}} [get_bd_pins axi_interconnect_%d/ACLK]" % (i) ) - config.append( - "apply_bd_automation -rule xilinx.com:bd_rule:clkrst -config { Clk {/zynq_ps/pl_clk0} Freq {} Ref_Clk0 {} Ref_Clk1 {} Ref_Clk2 {}} [get_bd_pins axi_interconnect_%d/S00_ACLK]" - % (i) - ) master_axilite_idx += 1 total_axilite_count = max(0, total_axilite_count - 64) From 76ef35d988611261142395633eb2eeb28886f9c8 Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Fri, 21 Feb 2025 11:12:12 +0000 Subject: [PATCH 09/31] Remove tcl.collectionResultDisplayLimit --- src/finn/transformation/fpgadataflow/templates.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/finn/transformation/fpgadataflow/templates.py b/src/finn/transformation/fpgadataflow/templates.py index 0f6ba7c3c4..d9040d83f2 100644 --- a/src/finn/transformation/fpgadataflow/templates.py +++ b/src/finn/transformation/fpgadataflow/templates.py @@ -100,6 +100,10 @@ set FPGA_PART %s create_project finn_zynq_link ./ -part $FPGA_PART +# Prevent limitation on number of elements for string representations of Vivado collections of objects +# Otherwise we might run into the default limit of 500 if we have many IP_REPO_PATHS +set_param tcl.collectionResultDisplayLimit 0 + # set board part repo paths to find PYNQ-Z1/Z2 set paths_prop [get_property BOARD_PART_REPO_PATHS [current_project]] set paths_param [get_param board.repoPaths] From 9c6c3cd8439ee162c3c5f153ec2123ea6591211a Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Sat, 22 Feb 2025 22:46:47 +0000 Subject: [PATCH 10/31] Add driver for iterative live FIFO-sizing --- driver/iterative_live_fifosizing_driver.ipynb | 833 ++++++++++++++++++ 1 file changed, 833 insertions(+) create mode 100644 driver/iterative_live_fifosizing_driver.ipynb diff --git a/driver/iterative_live_fifosizing_driver.ipynb b/driver/iterative_live_fifosizing_driver.ipynb new file mode 100644 index 0000000000..83a329d263 --- /dev/null +++ b/driver/iterative_live_fifosizing_driver.ipynb @@ -0,0 +1,833 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "0ee21ecb", + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "\n", + "try {\n", + "require(['notebook/js/codecell'], function(codecell) {\n", + " codecell.CodeCell.options_default.highlight_modes[\n", + " 'magic_text/x-csrc'] = {'reg':[/^%%microblaze/]};\n", + " Jupyter.notebook.events.one('kernel_ready.Kernel', function(){\n", + " Jupyter.notebook.get_cells().map(function(cell){\n", + " if (cell.cell_type == 'code'){ cell.auto_highlight(); } }) ;\n", + " });\n", + "});\n", + "} catch (e) {};\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": [ + "\n", + "try {\n", + "require(['notebook/js/codecell'], function(codecell) {\n", + " codecell.CodeCell.options_default.highlight_modes[\n", + " 'magic_text/x-csrc'] = {'reg':[/^%%pybind11/]};\n", + " Jupyter.notebook.events.one('kernel_ready.Kernel', function(){\n", + " Jupyter.notebook.get_cells().map(function(cell){\n", + " if (cell.cell_type == 'code'){ cell.auto_highlight(); } }) ;\n", + " });\n", + "});\n", + "} catch (e) {};\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import time\n", + "import json\n", + "import matplotlib as mpl\n", + "import matplotlib.pyplot as plt\n", + "from IPython.display import clear_output\n", + "import numpy as np\n", + "from pynq import Overlay\n", + "\n", + "path = \"bitstreams/resnet50/live_instrumentation\"\n", + "bitstream = path + \"/finn-accel.bit\"\n", + "\n", + "# Program FPGA\n", + "ol = Overlay(bitstream, download=True, device=None)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "f476fd87", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "#FIFO IP detected: 266\n", + "#FIFO width information found: 266\n" + ] + } + ], + "source": [ + "### Sanity checks\n", + "# We expect 3 AXI-Lite peripherals next to the virtual FIFOs: instrumentation_wrap_0, axi_gpio_0 (for reset), zynq_ps\n", + "# We don't expect any additional FINN SDPs with AXI-Lite interface, such as runtime-writable weights\n", + "print(\"#FIFO IP detected: %d\" % (len(ol.ip_dict.keys()) - 3))\n", + "\n", + "# We expect a fifo_widths.json file exported by FINN listing the width of each FIFO, e.g.,\n", + "# {'fifo_widths': {'StreamingFIFO_hls_0': 8, 'StreamingFIFO_hls_1': 32, 'StreamingFIFO_hls_2': 24}}\n", + "with open(path + \"/fifo_widths.json\", \"r\") as f:\n", + " fifo_info = json.load(f)\n", + "print(\"#FIFO width information found: %d\" % len(fifo_info[\"fifo_widths\"]))" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "e419656f", + "metadata": {}, + "outputs": [], + "source": [ + "### Instrumentation driver\n", + "# Register map\n", + "#ap_uint<32> cfg, \t// [0] - 0:hold, 1:lfsr; [31:16] - LFSR seed\n", + "#ap_uint<32> &status,\t// [0] - timestamp overflow; [1] - timestamp underflow\n", + "#ap_uint<32> &latency,\n", + "#ap_uint<32> &interval,\n", + "#ap_uint<32> &checksum,\n", + "#ap_uint<32> &min_latency\n", + "\n", + "def read_register(ol, name):\n", + " return ol.instrumentation_wrap_0.read(offset=ol.ip_dict[\"instrumentation_wrap_0\"][\"registers\"][name][\"address_offset\"])\n", + "\n", + "def write_register(ol, name, value):\n", + " return ol.instrumentation_wrap_0.write(offset=ol.ip_dict[\"instrumentation_wrap_0\"][\"registers\"][name][\"address_offset\"], value=value)\n", + "\n", + "def observe_instrumentation(debug_print=True):\n", + " status_reg = read_register(ol, \"status\")\n", + " chksum_reg = read_register(ol, \"checksum\")\n", + " min_latency = read_register(ol, \"min_latency\")\n", + " latency = read_register(ol, \"latency\")\n", + " interval = read_register(ol, \"interval\")\n", + "\n", + " frame = (chksum_reg >> 24) & 0x000000ff\n", + " checksum = chksum_reg & 0x00ffffff\n", + " overflow_err = (status_reg & 0x00000001) != 0\n", + " underflow_err = (status_reg & 0x00000002) != 0\n", + "\n", + " if debug_print:\n", + " print(\"---INSTRUMENTATION_REPORT---\")\n", + " if overflow_err or underflow_err:\n", + " print(\"Status ERROR\")\n", + " print(\"Overflow error: %s\" % overflow_err)\n", + " print(\"Underflow error: %s\" % underflow_err)\n", + " else:\n", + " print(\"Status OK\")\n", + " print(\"Frame number (8-bit): %d\" % frame)\n", + " print(\"Checksum: 0x%06x\" % checksum)\n", + " print(\"Min Latency (cycles): %d\" % min_latency)\n", + " print(\"Latency (cycles): %d\" % latency)\n", + " print(\"Interval (cycles): %d\" % interval)\n", + " print(\"----------------------------\")\n", + "\n", + " return (overflow_err, underflow_err, frame, checksum, min_latency, latency, interval)\n", + "\n", + "def start_accelerator():\n", + " lfsr_seed = 0x00010000 # upper 16 bits\n", + " write_register(ol, \"cfg\", lfsr_seed + 1) # start operation\n", + "\n", + "### Virtual FIFO driver\n", + "# Register map\n", + "mode_offset = 0x10\n", + "depth_offset = 0x18\n", + "occupancy_offset = 0x20\n", + "occupancy_ctrl_offset = 0x24\n", + "max_occupancy_offset = 0x30\n", + "max_occupancy_ctrl_offset = 0x34\n", + "\n", + "def configure_fifo(ol, i, mode, depth = 2):\n", + " ip_name = \"StreamingDataflowPartition_%d\" % i\n", + " getattr(ol, ip_name).write(offset=mode_offset, value = mode)\n", + " getattr(ol, ip_name).write(offset=depth_offset, value = depth)\n", + "\n", + "def total_fifo_size(depths):\n", + " # Assuming FIFO SDP/AXI-Lite interfaces are ordered consistently with FIFO IDs\n", + " total_size_bits = 0\n", + " for i, depth in enumerate(depths):\n", + " total_size_bits += depth * fifo_info[\"fifo_widths\"][\"StreamingFIFO_hls_%d\" % i]\n", + " total_size_kB = total_size_bits / 8.0 / 1000.0\n", + " return total_size_kB\n", + "\n", + "### GPIO Reset Driver\n", + "def reset_accelerator():\n", + " ol.axi_gpio_0.write(offset=ol.ip_dict[\"axi_gpio_0\"][\"registers\"][\"GPIO_DATA\"][\"address_offset\"], value=0)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "2e2a4b88", + "metadata": {}, + "outputs": [], + "source": [ + "### Iterative FIFO-sizing function\n", + "def size_iteratively(start_depth, iteration_runtime, reduction_factor = 0.5):\n", + " num_fifos = len(fifo_info[\"fifo_widths\"])\n", + " fifo_minimum_reached = [False] * num_fifos\n", + " \n", + " if isinstance(start_depth, list):\n", + " # Individual start depth for each FIFO has been supplied\n", + " fifo_depths = start_depth\n", + " else:\n", + " # Initialize all depths to the same start depth\n", + " fifo_depths = [start_depth] * num_fifos\n", + " \n", + " # Reset accelerator and configure FIFOs\n", + " reset_accelerator()\n", + " for i in range(0, num_fifos):\n", + " configure_fifo(ol, i, mode = 1, depth = fifo_depths[i])\n", + "\n", + " # Run once to determine target interval\n", + " start_accelerator()\n", + " time.sleep(1)\n", + " (overflow_err, underflow_err, frame, checksum, min_latency, latency, interval) = observe_instrumentation(False)\n", + " log_total_fifo_size = [int(total_fifo_size(fifo_depths))]\n", + " log_interval = [interval]\n", + " log_min_latency = [min_latency]\n", + " log_latency = [latency]\n", + " target_interval = interval\n", + " \n", + " # Iteratively reduce FIFO depth until all FIFOs are minimized\n", + " iteration = 0\n", + " start_time = time.time()\n", + " while not all(fifo_minimum_reached):\n", + " for fifo_id in range(0, num_fifos):\n", + " if not fifo_minimum_reached[fifo_id]:\n", + " fifo_depth_before = fifo_depths[fifo_id]\n", + " fifo_depths[fifo_id] = int(fifo_depths[fifo_id] * reduction_factor)\n", + "\n", + " # Reset accelerator\n", + " reset_accelerator()\n", + "\n", + " # Configure all FIFOs\n", + " for i in range(0, num_fifos):\n", + " configure_fifo(ol, i, mode = 1, depth = fifo_depths[i])\n", + "\n", + " # Start accelerator\n", + " start_accelerator()\n", + "\n", + " # Let it run\n", + " time.sleep(iteration_runtime)\n", + "\n", + " # Check if throughput dropped or deadlock occured \n", + " (overflow_err, underflow_err, frame, checksum, min_latency, latency, interval) = observe_instrumentation(False)\n", + "\n", + " if interval > target_interval or interval == 0 or overflow_err or underflow_err:\n", + " # Revert depth reduction and mark FIFO as minimized\n", + " fifo_depths[fifo_id] = fifo_depth_before\n", + " fifo_minimum_reached[fifo_id] = True\n", + " else:\n", + " log_total_fifo_size.append(int(total_fifo_size(fifo_depths)))\n", + " log_interval.append(interval)\n", + " log_min_latency.append(min_latency)\n", + " log_latency.append(latency) \n", + "\n", + " if fifo_depths[fifo_id] == 1:\n", + " fifo_minimum_reached[fifo_id] = True\n", + "\n", + " # Report status\n", + " clear_output(wait=True)\n", + " print(\"Iteration: %d\" % iteration)\n", + " print(\"Reducing depth of FIFO: %d/%d\" % (fifo_id, num_fifos))\n", + " print(\"Numer of minimized FIFOs: %d/%d\" % (sum(fifo_minimum_reached), num_fifos))\n", + " print(\"Interval: %d\" % log_interval[-1])\n", + " print(\"Min. latency / latency: %d/%d\" % (log_min_latency[-1], log_latency[-1]))\n", + " print(\"Total FIFO Size (kB): %d\" % log_total_fifo_size[-1])\n", + "\n", + " iteration += 1\n", + "\n", + " end_time = time.time()\n", + " print(\"Done (%d seconds)\" % int(end_time - start_time))\n", + " \n", + " return fifo_depths, log_total_fifo_size, log_interval, log_min_latency, log_latency" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "2ebb2aa3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Testing start depth of 64\n", + "---INSTRUMENTATION_REPORT---\n", + "Status OK\n", + "Frame number (8-bit): 0\n", + "Checksum: 0x000000\n", + "Min Latency (cycles): 4294967295\n", + "Latency (cycles): 0\n", + "Interval (cycles): 0\n", + "----------------------------\n", + "Testing start depth of 128\n", + "---INSTRUMENTATION_REPORT---\n", + "Status OK\n", + "Frame number (8-bit): 0\n", + "Checksum: 0x000000\n", + "Min Latency (cycles): 4294967295\n", + "Latency (cycles): 0\n", + "Interval (cycles): 0\n", + "----------------------------\n", + "Testing start depth of 256\n", + "---INSTRUMENTATION_REPORT---\n", + "Status OK\n", + "Frame number (8-bit): 0\n", + "Checksum: 0x000000\n", + "Min Latency (cycles): 4294967295\n", + "Latency (cycles): 0\n", + "Interval (cycles): 0\n", + "----------------------------\n", + "Testing start depth of 512\n", + "---INSTRUMENTATION_REPORT---\n", + "Status OK\n", + "Frame number (8-bit): 0\n", + "Checksum: 0x000000\n", + "Min Latency (cycles): 4294967295\n", + "Latency (cycles): 0\n", + "Interval (cycles): 0\n", + "----------------------------\n", + "Testing start depth of 1024\n", + "---INSTRUMENTATION_REPORT---\n", + "Status OK\n", + "Frame number (8-bit): 0\n", + "Checksum: 0x000000\n", + "Min Latency (cycles): 4294967295\n", + "Latency (cycles): 0\n", + "Interval (cycles): 0\n", + "----------------------------\n", + "Testing start depth of 2048\n", + "---INSTRUMENTATION_REPORT---\n", + "Status OK\n", + "Frame number (8-bit): 0\n", + "Checksum: 0x000000\n", + "Min Latency (cycles): 4294967295\n", + "Latency (cycles): 0\n", + "Interval (cycles): 0\n", + "----------------------------\n", + "Testing start depth of 4096\n", + "---INSTRUMENTATION_REPORT---\n", + "Status OK\n", + "Frame number (8-bit): 0\n", + "Checksum: 0x000000\n", + "Min Latency (cycles): 4294967295\n", + "Latency (cycles): 0\n", + "Interval (cycles): 0\n", + "----------------------------\n", + "Testing start depth of 8192\n", + "---INSTRUMENTATION_REPORT---\n", + "Status OK\n", + "Frame number (8-bit): 108\n", + "Checksum: 0x000000\n", + "Min Latency (cycles): 2548522\n", + "Latency (cycles): 5030984\n", + "Interval (cycles): 903174\n", + "----------------------------\n", + "Testing start depth of 16384\n", + "---INSTRUMENTATION_REPORT---\n", + "Status OK\n", + "Frame number (8-bit): 108\n", + "Checksum: 0x000000\n", + "Min Latency (cycles): 2548522\n", + "Latency (cycles): 7496520\n", + "Interval (cycles): 903174\n", + "----------------------------\n", + "Determined start depth for all FIFOs: 8192\n", + "Determined iteration runtime based on performance: 0.127426 s\n" + ] + } + ], + "source": [ + "### Attempt to determine start depth for all FIFOs automatically\n", + "# If it doesn't find a working setting, start depth must be set manually, potentially on per-FIFO basis\n", + "start_depth = 64\n", + "last_interval = 0\n", + "start_depth_found = False\n", + "\n", + "while not start_depth_found:\n", + " print(\"Testing start depth of %d\" % start_depth)\n", + " reset_accelerator()\n", + "\n", + " # Configure FIFOs\n", + " num_fifos = len(fifo_info[\"fifo_widths\"])\n", + " for i in range(0, num_fifos):\n", + " configure_fifo(ol, i, mode = 1, depth = start_depth)\n", + " \n", + " # Start accelerator and let it run for a long time\n", + " start_accelerator()\n", + " time.sleep(1)\n", + " \n", + " # Examine performance\n", + " (overflow_err, underflow_err, frame, checksum, min_latency, latency, interval) = observe_instrumentation()\n", + " if interval > 0 and interval == last_interval and not overflow_err and not underflow_err:\n", + " # Accelerator runs with stable interval, reset to previous start depth\n", + " start_depth_found = True\n", + " start_depth = last_start_depth\n", + " else:\n", + " # Start depth is still too small, increase for next try\n", + " last_start_depth = start_depth\n", + " start_depth = start_depth * 2\n", + " \n", + " last_interval = interval\n", + " \n", + "# Determine runtime per iteration based on performance, so that stable-state is guaranteed\n", + "# Use a simple overestimation for now to be safe\n", + "iteration_runtime = max(0.01, (min_latency * 5) * 10 / 1000 / 1000 / 1000)\n", + "\n", + "print(\"Determined start depth for all FIFOs: %d\" % start_depth)\n", + "print(\"Determined iteration runtime based on performance: %f s\" % iteration_runtime)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "4ba40f96", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Iteration: 12\n", + "Reducing depth of FIFO: 265/266\n", + "Numer of minimized FIFOs: 266/266\n", + "Interval: 903174\n", + "Min. latency / latency: 2549314/2580777\n", + "Total FIFO Size (kB): 244\n", + "Done (389 seconds)\n" + ] + } + ], + "source": [ + "### First pass\n", + "(fifo_depths,\n", + " log_total_fifo_size,\n", + " log_interval,\n", + " log_min_latency,\n", + " log_latency) = size_iteratively(start_depth, iteration_runtime)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "ebf027a4", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "### Visualize results\n", + "mpl.rcParams['figure.dpi'] = 80\n", + "fig, ax1 = plt.subplots()\n", + "\n", + "color = 'tab:red'\n", + "ax1.set_xlabel('Iteration')\n", + "ax1.set_ylabel('Total FIFO Size [kB]', color=color)\n", + "ax1.plot(range(len(log_total_fifo_size)), log_total_fifo_size, color=color)\n", + "ax1.tick_params(axis='y', labelcolor=color)\n", + "ax1.set_ylim(0, max(log_total_fifo_size))\n", + " \n", + "ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis\n", + "\n", + "color = 'tab:blue'\n", + "ax2.set_ylabel('Latency [cycles]', color=color)\n", + "ax2.plot(range(len(log_total_fifo_size)), log_latency, color=color)\n", + "ax2.tick_params(axis='y', labelcolor=color)\n", + "#ax2.set_ylim(0, max(log_latency))\n", + "\n", + "ax2.axhline(log_min_latency[0], color=\"green\", label=\"Minimum (1st frame) Latency\")\n", + "ax2.legend()\n", + "\n", + "plt.tight_layout()\n", + "plt.savefig('fifo_iterative_graph.png', dpi = 300)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "466f818f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Iteration: 11\n", + "Reducing depth of FIFO: 48/266\n", + "Numer of minimized FIFOs: 266/266\n", + "Interval: 903174\n", + "Min. latency / latency: 2549314/2580781\n", + "Total FIFO Size (kB): 226\n", + "Done (49 seconds)\n" + ] + } + ], + "source": [ + "### Optional second pass for fine-tuning\n", + "(fifo_depths,\n", + " log_total_fifo_size,\n", + " log_interval,\n", + " log_min_latency,\n", + " log_latency) = size_iteratively(fifo_depths, iteration_runtime, reduction_factor = 0.95)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "2c707459", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "FIFO DEPTH | SIZE\n", + "FIFO 000: 1 | 24\n", + "FIFO 001: 2 | 48\n", + "FIFO 002: 2 | 48\n", + "FIFO 003: 16 | 2048\n", + "FIFO 004: 8 | 64\n", + "FIFO 005: 2 | 16\n", + "FIFO 006: 8 | 64\n", + "FIFO 007: 32 | 256\n", + "FIFO 008: 32 | 128\n", + "FIFO 009: 32 | 128\n", + "FIFO 010: 2 | 8\n", + "FIFO 011: 128 | 8192\n", + "FIFO 012: 1 | 32\n", + "FIFO 013: 1 | 2\n", + "FIFO 014: 16 | 128\n", + "FIFO 015: 256 | 2048\n", + "FIFO 016: 2 | 16\n", + "FIFO 017: 2 | 16\n", + "FIFO 018: 355 | 45440\n", + "FIFO 019: 1 | 4\n", + "FIFO 020: 4 | 256\n", + "FIFO 021: 1 | 8\n", + "FIFO 022: 1 | 10\n", + "FIFO 023: 1 | 8\n", + "FIFO 024: 4096 | 32768\n", + "FIFO 025: 1 | 8\n", + "FIFO 026: 1 | 4\n", + "FIFO 027: 4096 | 32768\n", + "FIFO 028: 1 | 64\n", + "FIFO 029: 256 | 1024\n", + "FIFO 030: 256 | 2048\n", + "FIFO 031: 2 | 16\n", + "FIFO 032: 2 | 16\n", + "FIFO 033: 288 | 36864\n", + "FIFO 034: 1 | 4\n", + "FIFO 035: 1 | 64\n", + "FIFO 036: 1 | 8\n", + "FIFO 037: 1 | 10\n", + "FIFO 038: 4 | 32\n", + "FIFO 039: 4 | 32\n", + "FIFO 040: 4096 | 32768\n", + "FIFO 041: 4096 | 32768\n", + "FIFO 042: 8 | 32\n", + "FIFO 043: 16 | 1024\n", + "FIFO 044: 256 | 1024\n", + "FIFO 045: 256 | 2048\n", + "FIFO 046: 2 | 16\n", + "FIFO 047: 2 | 16\n", + "FIFO 048: 288 | 36864\n", + "FIFO 049: 1 | 4\n", + "FIFO 050: 1 | 128\n", + "FIFO 051: 1 | 8\n", + "FIFO 052: 1 | 10\n", + "FIFO 053: 1 | 8\n", + "FIFO 054: 1 | 4\n", + "FIFO 055: 1 | 4\n", + "FIFO 056: 1 | 4\n", + "FIFO 057: 1 | 8\n", + "FIFO 058: 28 | 3584\n", + "FIFO 059: 1 | 4\n", + "FIFO 060: 1 | 8\n", + "FIFO 061: 1 | 8\n", + "FIFO 062: 114 | 14592\n", + "FIFO 063: 1 | 8\n", + "FIFO 064: 2 | 16\n", + "FIFO 065: 1 | 8\n", + "FIFO 066: 243 | 31104\n", + "FIFO 067: 1 | 4\n", + "FIFO 068: 2 | 128\n", + "FIFO 069: 1 | 8\n", + "FIFO 070: 1 | 10\n", + "FIFO 071: 1 | 8\n", + "FIFO 072: 1 | 8\n", + "FIFO 073: 4096 | 32768\n", + "FIFO 074: 4096 | 32768\n", + "FIFO 075: 1 | 4\n", + "FIFO 076: 6 | 384\n", + "FIFO 077: 60 | 240\n", + "FIFO 078: 128 | 1024\n", + "FIFO 079: 2 | 16\n", + "FIFO 080: 2 | 16\n", + "FIFO 081: 394 | 50432\n", + "FIFO 082: 1 | 4\n", + "FIFO 083: 1 | 64\n", + "FIFO 084: 15 | 120\n", + "FIFO 085: 15 | 150\n", + "FIFO 086: 16 | 128\n", + "FIFO 087: 16 | 128\n", + "FIFO 088: 4096 | 32768\n", + "FIFO 089: 4096 | 32768\n", + "FIFO 090: 16 | 64\n", + "FIFO 091: 32 | 2048\n", + "FIFO 092: 64 | 256\n", + "FIFO 093: 128 | 1024\n", + "FIFO 094: 32 | 256\n", + "FIFO 095: 2 | 16\n", + "FIFO 096: 394 | 50432\n", + "FIFO 097: 1 | 4\n", + "FIFO 098: 1 | 64\n", + "FIFO 099: 15 | 120\n", + "FIFO 100: 15 | 150\n", + "FIFO 101: 16 | 128\n", + "FIFO 102: 16 | 128\n", + "FIFO 103: 4096 | 32768\n", + "FIFO 104: 4096 | 32768\n", + "FIFO 105: 16 | 64\n", + "FIFO 106: 32 | 2048\n", + "FIFO 107: 64 | 256\n", + "FIFO 108: 128 | 1024\n", + "FIFO 109: 32 | 256\n", + "FIFO 110: 2 | 16\n", + "FIFO 111: 394 | 50432\n", + "FIFO 112: 1 | 4\n", + "FIFO 113: 1 | 64\n", + "FIFO 114: 1 | 8\n", + "FIFO 115: 8 | 80\n", + "FIFO 116: 8 | 64\n", + "FIFO 117: 8 | 32\n", + "FIFO 118: 1 | 4\n", + "FIFO 119: 8 | 32\n", + "FIFO 120: 1 | 8\n", + "FIFO 121: 16 | 2048\n", + "FIFO 122: 8 | 32\n", + "FIFO 123: 1 | 8\n", + "FIFO 124: 8 | 64\n", + "FIFO 125: 121 | 15488\n", + "FIFO 126: 1 | 8\n", + "FIFO 127: 2 | 16\n", + "FIFO 128: 1 | 8\n", + "FIFO 129: 243 | 31104\n", + "FIFO 130: 2 | 8\n", + "FIFO 131: 8 | 512\n", + "FIFO 132: 1 | 8\n", + "FIFO 133: 8 | 80\n", + "FIFO 134: 8 | 64\n", + "FIFO 135: 8 | 64\n", + "FIFO 136: 1024 | 8192\n", + "FIFO 137: 8192 | 65536\n", + "FIFO 138: 8 | 32\n", + "FIFO 139: 16 | 1024\n", + "FIFO 140: 4 | 16\n", + "FIFO 141: 8 | 64\n", + "FIFO 142: 2 | 16\n", + "FIFO 143: 2 | 16\n", + "FIFO 144: 512 | 65536\n", + "FIFO 145: 1 | 4\n", + "FIFO 146: 1 | 64\n", + "FIFO 147: 30 | 240\n", + "FIFO 148: 32 | 320\n", + "FIFO 149: 32 | 256\n", + "FIFO 150: 32 | 256\n", + "FIFO 151: 1024 | 8192\n", + "FIFO 152: 8192 | 65536\n", + "FIFO 153: 32 | 128\n", + "FIFO 154: 32 | 2048\n", + "FIFO 155: 32 | 128\n", + "FIFO 156: 32 | 256\n", + "FIFO 157: 2 | 16\n", + "FIFO 158: 2 | 16\n", + "FIFO 159: 512 | 65536\n", + "FIFO 160: 1 | 4\n", + "FIFO 161: 1 | 64\n", + "FIFO 162: 30 | 240\n", + "FIFO 163: 32 | 320\n", + "FIFO 164: 32 | 256\n", + "FIFO 165: 32 | 256\n", + "FIFO 166: 1024 | 8192\n", + "FIFO 167: 8192 | 65536\n", + "FIFO 168: 32 | 128\n", + "FIFO 169: 32 | 2048\n", + "FIFO 170: 32 | 128\n", + "FIFO 171: 32 | 256\n", + "FIFO 172: 2 | 16\n", + "FIFO 173: 2 | 16\n", + "FIFO 174: 512 | 65536\n", + "FIFO 175: 1 | 4\n", + "FIFO 176: 1 | 64\n", + "FIFO 177: 30 | 240\n", + "FIFO 178: 32 | 320\n", + "FIFO 179: 32 | 256\n", + "FIFO 180: 32 | 256\n", + "FIFO 181: 1024 | 8192\n", + "FIFO 182: 8192 | 65536\n", + "FIFO 183: 32 | 128\n", + "FIFO 184: 32 | 2048\n", + "FIFO 185: 32 | 128\n", + "FIFO 186: 32 | 256\n", + "FIFO 187: 2 | 16\n", + "FIFO 188: 2 | 16\n", + "FIFO 189: 512 | 65536\n", + "FIFO 190: 1 | 4\n", + "FIFO 191: 1 | 64\n", + "FIFO 192: 30 | 240\n", + "FIFO 193: 32 | 320\n", + "FIFO 194: 32 | 256\n", + "FIFO 195: 1024 | 8192\n", + "FIFO 196: 32 | 256\n", + "FIFO 197: 32 | 128\n", + "FIFO 198: 8192 | 65536\n", + "FIFO 199: 32 | 2048\n", + "FIFO 200: 32 | 128\n", + "FIFO 201: 32 | 256\n", + "FIFO 202: 2 | 16\n", + "FIFO 203: 2 | 16\n", + "FIFO 204: 512 | 65536\n", + "FIFO 205: 1 | 4\n", + "FIFO 206: 1 | 64\n", + "FIFO 207: 1 | 8\n", + "FIFO 208: 1 | 10\n", + "FIFO 209: 1 | 8\n", + "FIFO 210: 1 | 10\n", + "FIFO 211: 1 | 4\n", + "FIFO 212: 1 | 4\n", + "FIFO 213: 1 | 4\n", + "FIFO 214: 1 | 8\n", + "FIFO 215: 8 | 1024\n", + "FIFO 216: 1 | 4\n", + "FIFO 217: 1 | 8\n", + "FIFO 218: 2 | 16\n", + "FIFO 219: 121 | 15488\n", + "FIFO 220: 1 | 8\n", + "FIFO 221: 2 | 16\n", + "FIFO 222: 1 | 8\n", + "FIFO 223: 218 | 27904\n", + "FIFO 224: 4 | 16\n", + "FIFO 225: 8 | 512\n", + "FIFO 226: 3 | 24\n", + "FIFO 227: 4 | 40\n", + "FIFO 228: 8 | 64\n", + "FIFO 229: 8 | 64\n", + "FIFO 230: 3696 | 29568\n", + "FIFO 231: 7782 | 62256\n", + "FIFO 232: 8 | 32\n", + "FIFO 233: 64 | 4096\n", + "FIFO 234: 16 | 64\n", + "FIFO 235: 16 | 128\n", + "FIFO 236: 2 | 16\n", + "FIFO 237: 2 | 16\n", + "FIFO 238: 512 | 65536\n", + "FIFO 239: 4 | 16\n", + "FIFO 240: 8 | 512\n", + "FIFO 241: 3 | 24\n", + "FIFO 242: 4 | 40\n", + "FIFO 243: 8 | 64\n", + "FIFO 244: 8 | 64\n", + "FIFO 245: 3696 | 29568\n", + "FIFO 246: 7782 | 62256\n", + "FIFO 247: 8 | 32\n", + "FIFO 248: 64 | 4096\n", + "FIFO 249: 16 | 64\n", + "FIFO 250: 16 | 128\n", + "FIFO 251: 2 | 16\n", + "FIFO 252: 2 | 16\n", + "FIFO 253: 512 | 65536\n", + "FIFO 254: 4 | 16\n", + "FIFO 255: 8 | 512\n", + "FIFO 256: 2 | 16\n", + "FIFO 257: 2 | 20\n", + "FIFO 258: 2 | 16\n", + "FIFO 259: 2 | 20\n", + "FIFO 260: 4 | 80\n", + "FIFO 261: 2 | 40\n", + "FIFO 262: 1 | 16\n", + "FIFO 263: 1 | 20\n", + "FIFO 264: 1 | 21\n", + "FIFO 265: 1 | 16\n" + ] + } + ], + "source": [ + "### Display resulting FIFO depths\n", + "print(\"FIFO DEPTH | SIZE\")\n", + "for fifo, depth in enumerate(fifo_depths):\n", + " size = depth * fifo_info[\"fifo_widths\"][\"StreamingFIFO_hls_%d\" % fifo]\n", + " print(\"FIFO %03d: \"%(fifo) + (\"%d\"%(depth)).rjust(7) + \" | %d\"%(size))" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "64c444f5", + "metadata": {}, + "outputs": [], + "source": [ + "### Export for use in FINN\n", + "fifo_depth_export = {}\n", + "for fifo, depth in enumerate(fifo_depths):\n", + " fifo_depth_export[\"StreamingFIFO_rtl_%d\" % fifo] = {}\n", + " # Try to account for additional registers introduced by virtual FIFO HLS implementation\n", + " fifo_depth_export[\"StreamingFIFO_rtl_%d\" % fifo][\"depth\"] = depth + 4\n", + "\n", + "with open(\"fifo_depth_export.json\", \"w\") as f:\n", + " json.dump(fifo_depth_export, f, indent=2)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 01d5551f1d93a6c97760e3a4335018e314f9de6b Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Thu, 27 Feb 2025 15:37:31 +0000 Subject: [PATCH 11/31] Generate FIFO size report as part of step_set_fifo_depths --- src/finn/builder/build_dataflow_steps.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/finn/builder/build_dataflow_steps.py b/src/finn/builder/build_dataflow_steps.py index fe0cb68a88..ef90cba0b6 100644 --- a/src/finn/builder/build_dataflow_steps.py +++ b/src/finn/builder/build_dataflow_steps.py @@ -656,6 +656,23 @@ def step_set_fifo_depths(model: ModelWrapper, cfg: DataflowBuildConfig): model = model.transform(SplitLargeFIFOs()) model = model.transform(RemoveShallowFIFOs()) + # generate a dedicated report about final FIFO sizes + fifo_info = {} + fifo_info["fifo_depths"] = {} + fifo_info["fifo_sizes"] = {} + total_fifo_size = 0 + for node in model.get_nodes_by_op_type("StreamingFIFO_rtl"): + node_inst = getCustomOp(node) + fifo_info["fifo_depths"][node.name] = node_inst.get_nodeattr("depth") + fifo_info["fifo_sizes"][ + node.name + ] = node_inst.get_instream_width() * node_inst.get_nodeattr("depth") + total_fifo_size += fifo_info["fifo_sizes"][node.name] + fifo_info["total_fifo_size_kB"] = int(total_fifo_size / 8.0 / 1000.0) + + with open(cfg.output_dir + "/report/fifo_sizing.json", "w") as f: + json.dump(fifo_info, f, indent=2) + # after FIFOs are ready to go, call PrepareIP and HLSSynthIP again # this will only run for the new nodes (e.g. FIFOs and DWCs) model = model.transform(PrepareIP(cfg._resolve_fpga_part(), cfg._resolve_hls_clk_period())) From 3598501532ede834cf894439bab9793dc49a853f Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Thu, 27 Feb 2025 17:49:47 +0000 Subject: [PATCH 12/31] Add PYNQ driver for ZYNQ platforms --- src/finn/builder/build_dataflow_steps.py | 10 +- .../driver/driver_instrumentation.py | 143 ++++++++++++++++++ .../fpgadataflow/make_pynq_driver.py | 33 +++- 3 files changed, 183 insertions(+), 3 deletions(-) create mode 100644 src/finn/qnn-data/templates/driver/driver_instrumentation.py diff --git a/src/finn/builder/build_dataflow_steps.py b/src/finn/builder/build_dataflow_steps.py index a4481ed778..96f3bd7c63 100644 --- a/src/finn/builder/build_dataflow_steps.py +++ b/src/finn/builder/build_dataflow_steps.py @@ -90,7 +90,10 @@ from finn.transformation.fpgadataflow.insert_dwc import InsertDWC from finn.transformation.fpgadataflow.insert_fifo import InsertFIFO from finn.transformation.fpgadataflow.insert_tlastmarker import InsertTLastMarker -from finn.transformation.fpgadataflow.make_pynq_driver import MakePYNQDriver +from finn.transformation.fpgadataflow.make_pynq_driver import ( + MakePYNQDriverIODMA, + MakePYNQDriverInstrumentation, +) from finn.transformation.fpgadataflow.make_zynq_proj import ZynqBuild from finn.transformation.fpgadataflow.minimize_accumulator_width import ( MinimizeAccumulatorWidth, @@ -782,7 +785,10 @@ def step_make_pynq_driver(model: ModelWrapper, cfg: DataflowBuildConfig): if DataflowOutputType.PYNQ_DRIVER in cfg.generate_outputs: driver_dir = cfg.output_dir + "/driver" - model = model.transform(MakePYNQDriver(cfg._resolve_driver_platform())) + if cfg.enable_instrumentation: + model = model.transform(MakePYNQDriverInstrumentation(cfg._resolve_driver_platform(), cfg.synth_clk_period_ns)) + else: + model = model.transform(MakePYNQDriverIODMA(cfg._resolve_driver_platform())) shutil.copytree(model.get_metadata_prop("pynq_driver_dir"), driver_dir, dirs_exist_ok=True) print("PYNQ Python driver written into " + driver_dir) return model diff --git a/src/finn/qnn-data/templates/driver/driver_instrumentation.py b/src/finn/qnn-data/templates/driver/driver_instrumentation.py new file mode 100644 index 0000000000..fea9446bf5 --- /dev/null +++ b/src/finn/qnn-data/templates/driver/driver_instrumentation.py @@ -0,0 +1,143 @@ +import time +import json +import argparse +import matplotlib as mpl +import matplotlib.pyplot as plt +from IPython.display import clear_output +import numpy as np +from pynq import Overlay +from pynq.ps import Clocks +from pynq.pl_server.device import Device + +### Instrumentation wrapper register map ### +#ap_uint<32> cfg, // [0] - 0:hold, 1:lfsr; [31:16] - LFSR seed +#ap_uint<32> &status, // [0] - timestamp overflow; [1] - timestamp underflow +#ap_uint<32> &latency, +#ap_uint<32> &interval, +#ap_uint<32> &checksum, +#ap_uint<32> &min_latency + +class FINNInstrumentationOverlay(Overlay): + def __init__( + self, + bitfile_name, + platform = "zynq", + fclk_mhz = 100.0, + device = None, + download = True, + seed = 1, + ): + super().__init__(bitfile_name, download=download, device=device) + + self.platform = platform + self.fclk_mhz = fclk_mhz + self.seed = seed + + # configure clock (for ZYNQ platforms) + if self.platform == "zynq": + if self.fclk_mhz > 0: + Clocks.fclk0_mhz = self.fclk_mhz + self.fclk_mhz_actual = Clocks.fclk0_mhz + + def instrumentation_read(self, name): + return self.instrumentation_wrap_0.read(offset=self.ip_dict["instrumentation_wrap_0"]["registers"][name]["address_offset"]) + + def instrumentation_write(self, name, value): + return self.instrumentation_wrap_0.write(offset=self.ip_dict["instrumentation_wrap_0"]["registers"][name]["address_offset"], value=value) + + def reset_accelerator(self): + self.axi_gpio_0.write(offset=self.ip_dict["axi_gpio_0"]["registers"]["GPIO_DATA"]["address_offset"], value=0) + + def start_accelerator(self): + lfsr_seed = (self.seed << 16) & 0xffff0000 # upper 16 bits + self.instrumentation_write("cfg", lfsr_seed + 1) # start operation + + def observe_instrumentation(self, debug_print=True): + status_reg = self.instrumentation_read("status") + chksum_reg = self.instrumentation_read("checksum") + min_latency = self.instrumentation_read("min_latency") + latency = self.instrumentation_read("latency") + interval = self.instrumentation_read("interval") + + frame = (chksum_reg >> 24) & 0x000000ff + checksum = chksum_reg & 0x00ffffff + overflow_err = (status_reg & 0x00000001) != 0 + underflow_err = (status_reg & 0x00000002) != 0 + + if debug_print: + print("---INSTRUMENTATION_REPORT---") + if overflow_err or underflow_err: + print("Status ERROR") + print("Overflow error: %s" % overflow_err) + print("Underflow error: %s" % underflow_err) + else: + print("Status OK") + print("Frame number (8-bit): %d" % frame) + print("Checksum: 0x%06x" % checksum) + print("Min Latency (cycles): %d" % min_latency) + print("Latency (cycles): %d" % latency) + print("Interval (cycles): %d" % interval) + print("----------------------------") + + return (overflow_err, underflow_err, frame, checksum, min_latency, latency, interval) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Profile performance of FINN-generated accelerator using instrumentation wrapper') + parser.add_argument('--runtime', help='Runtime in seconds', type=int, default=10) + parser.add_argument('--frequency', help='FPGA clock frequency in MHz', type=float, default=100.0) + parser.add_argument('--seed', help='LFSR seed for input data generation', type=int, default=1) + parser.add_argument('--device', help='FPGA device to be used', type=int, default=0) + parser.add_argument('--bitfile', help='Name of bitfile', default="finn-accel.bit") + parser.add_argument('--reportfile', help='Name of output .json report file', type=str, default="measured_performance.json") + parser.add_argument('--settingsfile', help='Name of optional input .json settings file', type=str, default="") + # parse arguments + args = parser.parse_args() + runtime = args.runtime + frequency = args.frequency + seed = args.seed + bitfile = args.bitfile + reportfile = args.reportfile + settingsfile = args.settingsfile + devID = args.device + device = Device.devices[devID] + + # overwrite frequency if specified in settings file + if settingsfile != "": + with open(settingsfile, "r") as f: + settings = json.load(f) + if "fclk_mhz" in settings: + frequency = settings["fclk_mhz"] + + # instantiate FINN accelerator driver and pass batchsize and bitfile + print("Programming FPGA..") + accel = FINNInstrumentationOverlay(bitfile_name = bitfile, device = device, fclk_mhz = frequency, seed = seed) + + # start accelerator + print("Running accelerator..") + accel.start_accelerator() + + # let it run for specified runtime + time.sleep(runtime) + + # read measurement from instrumentation + (overflow_err, underflow_err, frame, checksum, min_latency, latency, interval) = accel.observe_instrumentation() + + # write report to file + report = { + "error": overflow_err or underflow_err or interval == 0, + "checksum": checksum, + "min_latency_cycles": min_latency, + "latency_cycles": latency, + "interval_cycles": interval, + "frequency_mhz": round(accel.fclk_mhz_actual), + "min_latency_ms": round(min_latency * (1 / (accel.fclk_mhz_actual * 1e6)) * 1e3, 6), + "latency_ms": round(latency * (1 / (accel.fclk_mhz_actual * 1e6)) * 1e3, 6), + "throughput_fps": round(1 / (interval * (1 / (accel.fclk_mhz_actual * 1e6)))), + "min_pipeline_depth": round(min_latency / interval, 2), + "pipeline_depth" : round(latency / interval, 2), + } + with open(reportfile, "w") as f: + json.dump(report, f, indent=2) + + print("Done.") diff --git a/src/finn/transformation/fpgadataflow/make_pynq_driver.py b/src/finn/transformation/fpgadataflow/make_pynq_driver.py index ea9bd2aa26..b935f5eea0 100644 --- a/src/finn/transformation/fpgadataflow/make_pynq_driver.py +++ b/src/finn/transformation/fpgadataflow/make_pynq_driver.py @@ -28,6 +28,7 @@ import numpy as np import os +import json import qonnx import shutil import warnings @@ -62,7 +63,7 @@ def to_external_tensor(init, w_dtype): return ext_weight -class MakePYNQDriver(Transformation): +class MakePYNQDriverIODMA(Transformation): """Create PYNQ Python code to correctly interface the generated accelerator, including data packing/unpacking. Should be called after conversion to HLS layers, folding and the creation of @@ -302,4 +303,34 @@ def apply(self, model): else: continue + +class MakePYNQDriverInstrumentation(Transformation): + def __init__(self, platform, clk_period_ns): + super().__init__() + self.platform = platform + self.clk_period_ns = clk_period_ns + + def apply(self, model): + # TODO: support runtime-writable and external weights + # TODO: support Alveo and Versal platforms + + # create a temporary folder for the generated driver + pynq_driver_dir = make_build_dir(prefix="pynq_driver_") + model.set_metadata_prop("pynq_driver_dir", pynq_driver_dir) + + # create (copy) the static instrumentation driver + driver_template = ( + os.environ["FINN_ROOT"] + "/src/finn/qnn-data/templates/driver/driver_instrumentation.py" + ) + driver_py = pynq_driver_dir + "/driver.py" + shutil.copy(driver_template, driver_py) + + # write default settings to driver config file + settings = { + "fclk_mhz": (1.0 / self.clk_period_ns) * 1e3, + } + settingsfile = pynq_driver_dir + "/settings.json" + with open(settingsfile, "w") as f: + json.dump(settings, f, indent=2) + return (model, False) From f32e884b81ba4f916a96a80b8d30b0bf44b8613a Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Thu, 27 Feb 2025 21:09:27 +0000 Subject: [PATCH 13/31] Add non-interactive driver --- src/finn/builder/build_dataflow_steps.py | 2 +- .../templates/driver/driver_fifosizing.py | 320 ++++++++++++++++++ .../fpgadataflow/make_pynq_driver.py | 24 +- 3 files changed, 343 insertions(+), 3 deletions(-) create mode 100644 src/finn/qnn-data/templates/driver/driver_fifosizing.py diff --git a/src/finn/builder/build_dataflow_steps.py b/src/finn/builder/build_dataflow_steps.py index dd50e8880f..2f05886afd 100644 --- a/src/finn/builder/build_dataflow_steps.py +++ b/src/finn/builder/build_dataflow_steps.py @@ -826,7 +826,7 @@ def step_make_pynq_driver(model: ModelWrapper, cfg: DataflowBuildConfig): if DataflowOutputType.PYNQ_DRIVER in cfg.generate_outputs: driver_dir = cfg.output_dir + "/driver" if cfg.enable_instrumentation: - model = model.transform(MakePYNQDriverInstrumentation(cfg._resolve_driver_platform(), cfg.synth_clk_period_ns)) + model = model.transform(MakePYNQDriverInstrumentation(cfg._resolve_driver_platform(), cfg.synth_clk_period_ns, cfg.live_fifo_sizing)) else: model = model.transform(MakePYNQDriverIODMA(cfg._resolve_driver_platform())) shutil.copytree(model.get_metadata_prop("pynq_driver_dir"), driver_dir, dirs_exist_ok=True) diff --git a/src/finn/qnn-data/templates/driver/driver_fifosizing.py b/src/finn/qnn-data/templates/driver/driver_fifosizing.py new file mode 100644 index 0000000000..560959991f --- /dev/null +++ b/src/finn/qnn-data/templates/driver/driver_fifosizing.py @@ -0,0 +1,320 @@ +import time +import json +import os +import argparse +import matplotlib as mpl +import matplotlib.pyplot as plt +import numpy as np +from pynq.pl_server.device import Device + +from driver_instrumentation import FINNInstrumentationOverlay + + +class FINNLiveFIFOOverlay(FINNInstrumentationOverlay): + def __init__( + self, + bitfile_name, + platform = "zynq", + fclk_mhz = 100.0, + device = None, + download = True, + seed = 1, + fifo_widths = {}, + ): + super().__init__(bitfile_name, platform = platform, fclk_mhz = fclk_mhz, seed = seed, download = download, device = device) + + self.error = False + self.fifo_widths = fifo_widths + self.num_fifos = len(self.fifo_widths) + # Try to account for additional registers introduced by virtual FIFO HLS implementation + self.fifo_depth_offset = 4 + + # Sanity check + # We expect 3 AXI-Lite peripherals next to the virtual FIFOs: instrumentation_wrap_0, axi_gpio_0 (for reset), zynq_ps + # We don't expect any additional FINN SDPs with AXI-Lite interface, such as runtime-writable weights + if (len(self.ip_dict.keys()) - 3) != self.num_fifos: + self.error = True + + def configure_fifo(self, i, mode, depth = 2): + ### Virtual FIFO register map ### + mode_offset = 0x10 + depth_offset = 0x18 + occupancy_offset = 0x20 + occupancy_ctrl_offset = 0x24 + max_occupancy_offset = 0x30 + max_occupancy_ctrl_offset = 0x34 + + ip_name = "StreamingDataflowPartition_%d" % i + getattr(self, ip_name).write(offset=mode_offset, value = mode) + getattr(self, ip_name).write(offset=depth_offset, value = depth) + + def total_fifo_size(self, depths): + # Assuming FIFO SDP/AXI-Lite interfaces are ordered consistently with FIFO IDs + total_size_bits = 0 + for i, depth in enumerate(depths): + total_size_bits += (depth + self.fifo_depth_offset) * self.fifo_widths["StreamingFIFO_hls_%d" % i] + total_size_kB = total_size_bits / 8.0 / 1000.0 + return total_size_kB + + def size_iteratively(self, start_depth, iteration_runtime, reduction_factor = 0.5): + ### Iterative FIFO-sizing function ### + fifo_minimum_reached = [False] * self.num_fifos + + if isinstance(start_depth, list): + # Individual start depth for each FIFO has been supplied + fifo_depths = start_depth + else: + # Initialize all depths to the same start depth + fifo_depths = [start_depth] * self.num_fifos + + # Reset accelerator and configure FIFOs + self.reset_accelerator() + for i in range(0, self.num_fifos): + self.configure_fifo(i, mode = 1, depth = fifo_depths[i]) + + # Run once to determine target interval + self.start_accelerator() + time.sleep(1) + (overflow_err, underflow_err, frame, checksum, min_latency, latency, interval) = self.observe_instrumentation(False) + log_total_fifo_size = [int(self.total_fifo_size(fifo_depths))] + log_interval = [interval] + log_min_latency = [min_latency] + log_latency = [latency] + target_interval = interval + + # Iteratively reduce FIFO depth until all FIFOs are minimized + iteration = 0 + start_time = time.time() + while not all(fifo_minimum_reached): + for fifo_id in range(0, self.num_fifos): + if not fifo_minimum_reached[fifo_id]: + fifo_depth_before = fifo_depths[fifo_id] + fifo_depths[fifo_id] = int(fifo_depths[fifo_id] * reduction_factor) + + # Reset accelerator + self.reset_accelerator() + + # Configure all FIFOs + for i in range(0, self.num_fifos): + self.configure_fifo(i, mode = 1, depth = fifo_depths[i]) + + # Start accelerator + self.start_accelerator() + + # Let it run + time.sleep(iteration_runtime) + + # Check if throughput dropped or deadlock occured + (overflow_err, underflow_err, frame, checksum, min_latency, latency, interval) = self.observe_instrumentation(False) + + if interval > target_interval or interval == 0 or overflow_err or underflow_err: + # Revert depth reduction and mark FIFO as minimized + fifo_depths[fifo_id] = fifo_depth_before + fifo_minimum_reached[fifo_id] = True + else: + log_total_fifo_size.append(int(self.total_fifo_size(fifo_depths))) + log_interval.append(interval) + log_min_latency.append(min_latency) + log_latency.append(latency) + + if fifo_depths[fifo_id] == 1: + fifo_minimum_reached[fifo_id] = True + + # Report status + print("Iteration: %d" % iteration) + print("Numer of minimized FIFOs: %d/%d" % (sum(fifo_minimum_reached), self.num_fifos)) + print("Interval: %d" % log_interval[-1]) + print("Min. latency / latency: %d/%d" % (log_min_latency[-1], log_latency[-1])) + print("Total FIFO Size (kB): %d" % log_total_fifo_size[-1]) + + iteration += 1 + + end_time = time.time() + duration = int(end_time - start_time) + print("Done (%d seconds)" % duration) + + return fifo_depths, log_total_fifo_size, log_interval, log_min_latency, log_latency, duration + + def determine_start_depth(self, ): + ### Attempt to determine start depth for all FIFOs automatically ### + # If it doesn't find a working setting, start depth must be set manually, potentially on per-FIFO basis + start_depth = 64 + last_interval = 0 + start_depth_found = False + + while not start_depth_found and not self.error: + print("Testing start depth of %d" % start_depth) + self.reset_accelerator() + + # Configure FIFOs + for i in range(0, self.num_fifos): + self.configure_fifo(i, mode = 1, depth = start_depth) + + # Start accelerator and let it run for a long time + self.start_accelerator() + time.sleep(1) + + # Examine performance + (overflow_err, underflow_err, frame, checksum, min_latency, latency, interval) = self.observe_instrumentation() + if interval > 0 and interval == last_interval and not overflow_err and not underflow_err: + # Accelerator runs with stable interval, reset to previous start depth + start_depth_found = True + start_depth = last_start_depth + else: + # Start depth is still too small, increase for next try + last_start_depth = start_depth + start_depth = start_depth * 2 + + last_interval = interval + + if start_depth > 1000000: + print("Couldn't find a working start depth, please set manually") + self.error = True + + # Determine runtime per iteration based on performance, so that stable-state is guaranteed + # Use a simple overestimation for now to be safe + iteration_runtime = max(0.01, (min_latency * 5) * 10 / 1000 / 1000 / 1000) + + print("Determined start depth for all FIFOs: %d" % start_depth) + print("Determined iteration runtime based on performance: %f s" % iteration_runtime) + return (start_depth, iteration_runtime) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Profile performance of FINN-generated accelerator using instrumentation wrapper') + parser.add_argument('--runtime', help='Runtime in seconds', type=int, default=10) + parser.add_argument('--frequency', help='FPGA clock frequency in MHz', type=float, default=100.0) + parser.add_argument('--seed', help='LFSR seed for input data generation', type=int, default=1) + parser.add_argument('--device', help='FPGA device to be used', type=int, default=0) + parser.add_argument('--bitfile', help='Name of bitfile', default="finn-accel.bit") + parser.add_argument('--reportfile', help='Name of output .json report file', type=str, default="measured_performance.json") + parser.add_argument('--settingsfile', help='Name of optional input .json settings file', type=str, default="") + # parse arguments + args = parser.parse_args() + runtime = args.runtime + frequency = args.frequency + seed = args.seed + bitfile = args.bitfile + reportfile = args.reportfile + report_dir = os.path.dirname(reportfile) + settingsfile = args.settingsfile + devID = args.device + device = Device.devices[devID] + + # overwrite frequency if specified in settings file + if settingsfile != "": + with open(settingsfile, "r") as f: + settings = json.load(f) + if "fclk_mhz" in settings: + frequency = settings["fclk_mhz"] + + # For live FIFO-sizing, we also expect a fifo_widths.json file exported by FINN listing the width of each FIFO, e.g., + # {'fifo_widths': {'StreamingFIFO_hls_0': 8, 'StreamingFIFO_hls_1': 32, 'StreamingFIFO_hls_2': 24}} + fifo_widths = settings["fifo_widths"] + + + print("Programming FPGA..") + accel = FINNLiveFIFOOverlay(bitfile_name = bitfile, device = device, fclk_mhz = frequency, seed = seed, fifo_widths = fifo_widths) + + (start_depth, iteration_runtime) = accel.determine_start_depth() + + ### First pass + print("Starting first pass..") + pass1_result = accel.size_iteratively(start_depth, iteration_runtime) + (fifo_depths, + log_total_fifo_size, + log_interval, + log_min_latency, + log_latency, + duration) = pass1_result + + ### Visualize results and save as "fifo_sizing_graph.png" + fig, ax1 = plt.subplots() + + color = 'tab:red' + ax1.set_xlabel('Iteration') + ax1.set_ylabel('Total FIFO Size [kB]', color=color) + ax1.plot(range(len(log_total_fifo_size)), log_total_fifo_size, color=color) + ax1.tick_params(axis='y', labelcolor=color) + ax1.set_ylim(0, max(log_total_fifo_size)) + + ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis + + color = 'tab:blue' + ax2.set_ylabel('Latency [cycles]', color=color) + ax2.plot(range(len(log_total_fifo_size)), log_latency, color=color) + ax2.tick_params(axis='y', labelcolor=color) + #ax2.set_ylim(0, max(log_latency)) + + ax2.axhline(log_min_latency[0], color="green", label="Minimum (1st frame) Latency") + ax2.legend() + + plt.tight_layout() + plt.savefig(os.path.join(report_dir, "fifo_sizing_graph.png"), dpi = 300) + + ### Second pass for fine-tuning + print("Starting second pass..") + pass2_result = accel.size_iteratively(fifo_depths, iteration_runtime, reduction_factor = 0.95) + (fifo_depths, + log_total_fifo_size, + log_interval, + log_min_latency, + log_latency, + duration) = pass2_result + + ### Generate fifo_sizing_report.json + fifo_report = { + "error": accel.error, + "fifo_size_total_kB": log_total_fifo_size[-1], + "fifo_depths": {}, + "fifo_sizes": {}, + "pass_1": { + "duration": pass1_result[5], + "log_total_fifo_size": pass1_result[1], + "log_interval": pass1_result[2], + "log_min_latency": pass1_result[3], + "log_latency": pass1_result[4], + }, + "pass_2": { + "duration": pass2_result[5], + "log_total_fifo_size": pass2_result[1], + "log_interval": pass2_result[2], + "log_min_latency": pass2_result[3], + "log_latency": pass2_result[4], + }, + } + for fifo, depth in enumerate(fifo_depths): + size = (depth + accel.fifo_depth_offset) * accel.fifo_widths["StreamingFIFO_hls_%d" % fifo] + fifo_report["fifo_depths"][fifo] = depth + accel.fifo_depth_offset + fifo_report["fifo_sizes"][fifo] = size + with open(os.path.join(report_dir, "fifo_sizing_report.json"), "w") as f: + json.dump(fifo_report, f, indent=2) + + ### Generate fifo_depth_export.json to export FIFO depths for use in FINN + fifo_depth_export = {} + for fifo, depth in enumerate(fifo_depths): + fifo_depth_export["StreamingFIFO_rtl_%d" % fifo] = {} + fifo_depth_export["StreamingFIFO_rtl_%d" % fifo]["depth"] = depth + accel.fifo_depth_offset + with open(os.path.join(report_dir, "fifo_depth_export.json"), "w") as f: + json.dump(fifo_depth_export, f, indent=2) + + ### Generate the usual instrumentation performance report based on final state + min_latency = log_min_latency[-1] + latency = log_latency[-1] + interval = log_interval[-1] + report = { + "error": accel.error, + "checksum": 0, + "min_latency_cycles": min_latency, + "latency_cycles": latency, + "interval_cycles": interval, + "frequency_mhz": round(accel.fclk_mhz_actual), + "min_latency_ms": round(min_latency * (1 / (accel.fclk_mhz_actual * 1e6)) * 1e3, 6), + "latency_ms": round(latency * (1 / (accel.fclk_mhz_actual * 1e6)) * 1e3, 6), + "throughput_fps": round(1 / (interval * (1 / (accel.fclk_mhz_actual * 1e6)))), + "min_pipeline_depth": round(min_latency / interval, 2), + "pipeline_depth" : round(latency / interval, 2), + } + with open(reportfile, "w") as f: + json.dump(report, f, indent=2) + + print("Done.") \ No newline at end of file diff --git a/src/finn/transformation/fpgadataflow/make_pynq_driver.py b/src/finn/transformation/fpgadataflow/make_pynq_driver.py index b935f5eea0..93c0e45e6c 100644 --- a/src/finn/transformation/fpgadataflow/make_pynq_driver.py +++ b/src/finn/transformation/fpgadataflow/make_pynq_driver.py @@ -305,10 +305,11 @@ def apply(self, model): class MakePYNQDriverInstrumentation(Transformation): - def __init__(self, platform, clk_period_ns): + def __init__(self, platform, clk_period_ns, live_fifo_sizing): super().__init__() self.platform = platform self.clk_period_ns = clk_period_ns + self.live_fifo_sizing = live_fifo_sizing def apply(self, model): # TODO: support runtime-writable and external weights @@ -322,13 +323,32 @@ def apply(self, model): driver_template = ( os.environ["FINN_ROOT"] + "/src/finn/qnn-data/templates/driver/driver_instrumentation.py" ) - driver_py = pynq_driver_dir + "/driver.py" + if self.live_fifo_sizing: + driver_py = pynq_driver_dir + "/driver_instrumentation.py" + else: + driver_py = pynq_driver_dir + "/driver.py" shutil.copy(driver_template, driver_py) + # add-on driver for live fifosizing + if self.live_fifo_sizing: + driver_template = ( + os.environ["FINN_ROOT"] + "/src/finn/qnn-data/templates/driver/driver_fifosizing.py" + ) + driver_py = pynq_driver_dir + "/driver.py" + shutil.copy(driver_template, driver_py) + # write default settings to driver config file settings = { "fclk_mhz": (1.0 / self.clk_period_ns) * 1e3, } + if self.live_fifo_sizing: + # export FIFO widths to the settings file as well + fifo_widths = {} + for node in model.get_nodes_by_op_type("StreamingFIFO_hls"): + node_inst = getCustomOp(node) + fifo_widths[node.name] = node_inst.get_instream_width() + settings["fifo_widths"] = fifo_widths + settingsfile = pynq_driver_dir + "/settings.json" with open(settingsfile, "w") as f: json.dump(settings, f, indent=2) From 0c812bc54fbc4a5df24141a48e1cf646a0c008e2 Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Thu, 6 Mar 2025 10:20:24 +0000 Subject: [PATCH 14/31] Nested interconnects for Zynq-7000, fixes --- .../driver/driver_instrumentation.py | 101 +++++++++++------- .../fpgadataflow/make_pynq_driver.py | 7 +- .../fpgadataflow/make_zynq_proj.py | 16 +-- .../transformation/fpgadataflow/templates.py | 2 + 4 files changed, 80 insertions(+), 46 deletions(-) diff --git a/src/finn/qnn-data/templates/driver/driver_instrumentation.py b/src/finn/qnn-data/templates/driver/driver_instrumentation.py index fea9446bf5..90a0ed5b89 100644 --- a/src/finn/qnn-data/templates/driver/driver_instrumentation.py +++ b/src/finn/qnn-data/templates/driver/driver_instrumentation.py @@ -1,31 +1,28 @@ -import time -import json import argparse -import matplotlib as mpl -import matplotlib.pyplot as plt -from IPython.display import clear_output -import numpy as np +import json +import time from pynq import Overlay -from pynq.ps import Clocks from pynq.pl_server.device import Device +from pynq.ps import Clocks + +# Instrumentation wrapper register map # +# ap_uint<32> cfg, // [0] - 0:hold, 1:lfsr; [31:16] - LFSR seed +# ap_uint<32> &status, // [0] - timestamp overflow; [1] - timestamp underflow +# ap_uint<32> &latency, +# ap_uint<32> &interval, +# ap_uint<32> &checksum, +# ap_uint<32> &min_latency -### Instrumentation wrapper register map ### -#ap_uint<32> cfg, // [0] - 0:hold, 1:lfsr; [31:16] - LFSR seed -#ap_uint<32> &status, // [0] - timestamp overflow; [1] - timestamp underflow -#ap_uint<32> &latency, -#ap_uint<32> &interval, -#ap_uint<32> &checksum, -#ap_uint<32> &min_latency class FINNInstrumentationOverlay(Overlay): def __init__( self, bitfile_name, - platform = "zynq", - fclk_mhz = 100.0, - device = None, - download = True, - seed = 1, + platform="zynq", + fclk_mhz=100.0, + device=None, + download=True, + seed=1, ): super().__init__(bitfile_name, download=download, device=device) @@ -40,27 +37,34 @@ def __init__( self.fclk_mhz_actual = Clocks.fclk0_mhz def instrumentation_read(self, name): - return self.instrumentation_wrap_0.read(offset=self.ip_dict["instrumentation_wrap_0"]["registers"][name]["address_offset"]) + return self.instrumentation_wrap_0.read( + offset=self.ip_dict["instrumentation_wrap_0"]["registers"][name]["address_offset"] + ) def instrumentation_write(self, name, value): - return self.instrumentation_wrap_0.write(offset=self.ip_dict["instrumentation_wrap_0"]["registers"][name]["address_offset"], value=value) + return self.instrumentation_wrap_0.write( + offset=self.ip_dict["instrumentation_wrap_0"]["registers"][name]["address_offset"], + value=value, + ) def reset_accelerator(self): - self.axi_gpio_0.write(offset=self.ip_dict["axi_gpio_0"]["registers"]["GPIO_DATA"]["address_offset"], value=0) + self.axi_gpio_0.write( + offset=self.ip_dict["axi_gpio_0"]["registers"]["GPIO_DATA"]["address_offset"], value=0 + ) def start_accelerator(self): - lfsr_seed = (self.seed << 16) & 0xffff0000 # upper 16 bits - self.instrumentation_write("cfg", lfsr_seed + 1) # start operation + lfsr_seed = (self.seed << 16) & 0xFFFF0000 # upper 16 bits + self.instrumentation_write("cfg", lfsr_seed + 1) # start operation def observe_instrumentation(self, debug_print=True): status_reg = self.instrumentation_read("status") chksum_reg = self.instrumentation_read("checksum") min_latency = self.instrumentation_read("min_latency") latency = self.instrumentation_read("latency") - interval = self.instrumentation_read("interval") + interval = self.instrumentation_read("interval") - frame = (chksum_reg >> 24) & 0x000000ff - checksum = chksum_reg & 0x00ffffff + frame = (chksum_reg >> 24) & 0x000000FF + checksum = chksum_reg & 0x00FFFFFF overflow_err = (status_reg & 0x00000001) != 0 underflow_err = (status_reg & 0x00000002) != 0 @@ -83,14 +87,25 @@ def observe_instrumentation(self, debug_print=True): if __name__ == "__main__": - parser = argparse.ArgumentParser(description='Profile performance of FINN-generated accelerator using instrumentation wrapper') - parser.add_argument('--runtime', help='Runtime in seconds', type=int, default=10) - parser.add_argument('--frequency', help='FPGA clock frequency in MHz', type=float, default=100.0) - parser.add_argument('--seed', help='LFSR seed for input data generation', type=int, default=1) - parser.add_argument('--device', help='FPGA device to be used', type=int, default=0) - parser.add_argument('--bitfile', help='Name of bitfile', default="finn-accel.bit") - parser.add_argument('--reportfile', help='Name of output .json report file', type=str, default="measured_performance.json") - parser.add_argument('--settingsfile', help='Name of optional input .json settings file', type=str, default="") + parser = argparse.ArgumentParser( + description="Profile FINN-generated accelerator using instrumentation wrapper" + ) + parser.add_argument("--runtime", help="Runtime in seconds", type=int, default=10) + parser.add_argument( + "--frequency", help="FPGA clock frequency in MHz", type=float, default=100.0 + ) + parser.add_argument("--seed", help="LFSR seed for input data generation", type=int, default=1) + parser.add_argument("--device", help="FPGA device to be used", type=int, default=0) + parser.add_argument("--bitfile", help="Name of bitfile", default="finn-accel.bit") + parser.add_argument( + "--reportfile", + help="Name of output .json report file", + type=str, + default="measured_performance.json", + ) + parser.add_argument( + "--settingsfile", help="Name of optional input .json settings file", type=str, default="" + ) # parse arguments args = parser.parse_args() runtime = args.runtime @@ -111,7 +126,9 @@ def observe_instrumentation(self, debug_print=True): # instantiate FINN accelerator driver and pass batchsize and bitfile print("Programming FPGA..") - accel = FINNInstrumentationOverlay(bitfile_name = bitfile, device = device, fclk_mhz = frequency, seed = seed) + accel = FINNInstrumentationOverlay( + bitfile_name=bitfile, device=device, fclk_mhz=frequency, seed=seed + ) # start accelerator print("Running accelerator..") @@ -121,7 +138,15 @@ def observe_instrumentation(self, debug_print=True): time.sleep(runtime) # read measurement from instrumentation - (overflow_err, underflow_err, frame, checksum, min_latency, latency, interval) = accel.observe_instrumentation() + ( + overflow_err, + underflow_err, + frame, + checksum, + min_latency, + latency, + interval, + ) = accel.observe_instrumentation() # write report to file report = { @@ -135,7 +160,7 @@ def observe_instrumentation(self, debug_print=True): "latency_ms": round(latency * (1 / (accel.fclk_mhz_actual * 1e6)) * 1e3, 6), "throughput_fps": round(1 / (interval * (1 / (accel.fclk_mhz_actual * 1e6)))), "min_pipeline_depth": round(min_latency / interval, 2), - "pipeline_depth" : round(latency / interval, 2), + "pipeline_depth": round(latency / interval, 2), } with open(reportfile, "w") as f: json.dump(report, f, indent=2) diff --git a/src/finn/transformation/fpgadataflow/make_pynq_driver.py b/src/finn/transformation/fpgadataflow/make_pynq_driver.py index b935f5eea0..c26fa845ed 100644 --- a/src/finn/transformation/fpgadataflow/make_pynq_driver.py +++ b/src/finn/transformation/fpgadataflow/make_pynq_driver.py @@ -26,9 +26,9 @@ # 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 json import numpy as np import os -import json import qonnx import shutil import warnings @@ -303,6 +303,8 @@ def apply(self, model): else: continue + return (model, False) + class MakePYNQDriverInstrumentation(Transformation): def __init__(self, platform, clk_period_ns): @@ -320,7 +322,8 @@ def apply(self, model): # create (copy) the static instrumentation driver driver_template = ( - os.environ["FINN_ROOT"] + "/src/finn/qnn-data/templates/driver/driver_instrumentation.py" + os.environ["FINN_ROOT"] + + "/src/finn/qnn-data/templates/driver/driver_instrumentation.py" ) driver_py = pynq_driver_dir + "/driver.py" shutil.copy(driver_template, driver_py) diff --git a/src/finn/transformation/fpgadataflow/make_zynq_proj.py b/src/finn/transformation/fpgadataflow/make_zynq_proj.py index 846d95a11b..98372b700f 100644 --- a/src/finn/transformation/fpgadataflow/make_zynq_proj.py +++ b/src/finn/transformation/fpgadataflow/make_zynq_proj.py @@ -174,13 +174,16 @@ def apply(self, model): ) # connect to master interconnect config.append( - "connect_bd_intf_net [get_bd_intf_pins axi_interconnect_0/M%02d_AXI] -boundary_type upper [get_bd_intf_pins axi_interconnect_%d/S00_AXI]" + "connect_bd_intf_net [get_bd_intf_pins axi_interconnect_0/M%02d_AXI] " + "-boundary_type upper [get_bd_intf_pins axi_interconnect_%d/S00_AXI]" % (master_axilite_idx, i) ) - # connect clocks/reset TODO: suppport zynq_7000 + # connect clocks/reset config.append( - "apply_bd_automation -rule xilinx.com:bd_rule:clkrst -config { Clk {/zynq_ps/pl_clk0} Freq {} Ref_Clk0 {} Ref_Clk1 {} Ref_Clk2 {}} [get_bd_pins axi_interconnect_%d/ACLK]" - % (i) + "apply_bd_automation -rule xilinx.com:bd_rule:clkrst -config " + "{ Clk {/zynq_ps/$zynq_ps_clkname} Freq {} " + "Ref_Clk0 {} Ref_Clk1 {} Ref_Clk2 {}} " + "[get_bd_pins axi_interconnect_%d/ACLK]" % (i) ) master_axilite_idx += 1 total_axilite_count = max(0, total_axilite_count - 64) @@ -359,10 +362,11 @@ def apply(self, model): config.append("delete_bd_objs [get_bd_cells smartconnect_0]") aximm_idx = 1 - # finalize nested interconnect clock/reset TODO: support zynq_7000 + # finalize nested interconnect clock/reset for i in range(1, nested_interconnect_count + 1): config.append( - "apply_bd_automation -rule xilinx.com:bd_rule:clkrst -config { Clk {/zynq_ps/pl_clk0} } [get_bd_pins axi_interconnect_%d/M*_ACLK]" + "apply_bd_automation -rule xilinx.com:bd_rule:clkrst -config " + "{ Clk {/zynq_ps/$zynq_ps_clkname} } [get_bd_pins axi_interconnect_%d/M*_ACLK]" % (i) ) diff --git a/src/finn/transformation/fpgadataflow/templates.py b/src/finn/transformation/fpgadataflow/templates.py index d9040d83f2..6cde5cfa66 100644 --- a/src/finn/transformation/fpgadataflow/templates.py +++ b/src/finn/transformation/fpgadataflow/templates.py @@ -146,6 +146,7 @@ create_bd_design "top" if {$ZYNQ_TYPE == "zynq_us+"} { set zynq_ps_vlnv [get_property VLNV [get_ipdefs "xilinx.com:ip:zynq_ultra_ps_e:*"]] + set zynq_ps_clkname "pl_clk0" create_bd_cell -type ip -vlnv $zynq_ps_vlnv zynq_ps apply_bd_automation -rule xilinx.com:bd_rule:zynq_ultra_ps_e -config {apply_board_preset "1" } [get_bd_cells zynq_ps] #activate one slave port, deactivate the second master port @@ -156,6 +157,7 @@ set_property -dict [list CONFIG.PSU__CRL_APB__PL0_REF_CTRL__FREQMHZ [expr int($FREQ_MHZ)]] [get_bd_cells zynq_ps] } elseif {$ZYNQ_TYPE == "zynq_7000"} { set zynq_ps_vlnv [get_property VLNV [get_ipdefs "xilinx.com:ip:processing_system7:*"]] + set zynq_ps_clkname "FCLK_CLK0" create_bd_cell -type ip -vlnv $zynq_ps_vlnv zynq_ps apply_bd_automation -rule xilinx.com:bd_rule:processing_system7 -config {make_external "FIXED_IO, DDR" apply_board_preset "1" Master "Disable" Slave "Disable" } [get_bd_cells zynq_ps] set_property -dict [list CONFIG.PCW_USE_S_AXI_HP0 {1}] [get_bd_cells zynq_ps] From 4bf21a295ee38d65b33212012c8952f167db03dc Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Thu, 6 Mar 2025 11:05:19 +0000 Subject: [PATCH 15/31] Force disable additional AXI-lite interfaces for live FIFO sizing --- src/finn/builder/build_dataflow_steps.py | 31 ++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/finn/builder/build_dataflow_steps.py b/src/finn/builder/build_dataflow_steps.py index 2f05886afd..5dc971cf33 100644 --- a/src/finn/builder/build_dataflow_steps.py +++ b/src/finn/builder/build_dataflow_steps.py @@ -91,8 +91,8 @@ from finn.transformation.fpgadataflow.insert_fifo import InsertFIFO from finn.transformation.fpgadataflow.insert_tlastmarker import InsertTLastMarker from finn.transformation.fpgadataflow.make_pynq_driver import ( - MakePYNQDriverIODMA, MakePYNQDriverInstrumentation, + MakePYNQDriverIODMA, ) from finn.transformation.fpgadataflow.make_zynq_proj import ZynqBuild from finn.transformation.fpgadataflow.minimize_accumulator_width import ( @@ -555,6 +555,29 @@ def step_set_fifo_depths(model: ModelWrapper, cfg: DataflowBuildConfig): # Experimental live FIFO-sizing, overwrites all other FIFO-related behavior if cfg.live_fifo_sizing: + # Disable runtime-writable weights, external weights, and dynamic mode, + # as we don't support additional AXI-lite interfaces next to the FIFOs + for node in model.graph.node: + if node.domain.startswith("finn.custom_op.fpgadataflow"): + node_inst = getCustomOp(node) + try: + if node_inst.get_nodeattr("runtime_writeable_weights") == 1: + node_inst.set_nodeattr("runtime_writeable_weights", 0) + if node_inst.get_nodeattr("ram_style") == "ultra": + node_inst.set_nodeattr("ram_style", "block") + except AttributeError: + pass + try: + if node_inst.get_nodeattr("mem_mode") == "external": + node_inst.set_nodeattr("mem_mode", "internal_decoupled") + except AttributeError: + pass + try: + if node_inst.get_nodeattr("dynamic_mode") == 1: + node_inst.set_nodeattr("dynamic_mode", 0) + except AttributeError: + pass + # Create all DWCs and FIFOs normally model = model.transform(InsertDWC()) model = model.transform(InsertFIFO(create_shallow_fifos=True)) @@ -826,7 +849,11 @@ def step_make_pynq_driver(model: ModelWrapper, cfg: DataflowBuildConfig): if DataflowOutputType.PYNQ_DRIVER in cfg.generate_outputs: driver_dir = cfg.output_dir + "/driver" if cfg.enable_instrumentation: - model = model.transform(MakePYNQDriverInstrumentation(cfg._resolve_driver_platform(), cfg.synth_clk_period_ns, cfg.live_fifo_sizing)) + model = model.transform( + MakePYNQDriverInstrumentation( + cfg._resolve_driver_platform(), cfg.synth_clk_period_ns, cfg.live_fifo_sizing + ) + ) else: model = model.transform(MakePYNQDriverIODMA(cfg._resolve_driver_platform())) shutil.copytree(model.get_metadata_prop("pynq_driver_dir"), driver_dir, dirs_exist_ok=True) From 230ac92471342c0a28e91168fc3b57895b0c8651 Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Fri, 7 Mar 2025 11:52:05 +0000 Subject: [PATCH 16/31] Fix clkname variable expansion --- src/finn/transformation/fpgadataflow/make_zynq_proj.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/finn/transformation/fpgadataflow/make_zynq_proj.py b/src/finn/transformation/fpgadataflow/make_zynq_proj.py index 98372b700f..c6449468cf 100644 --- a/src/finn/transformation/fpgadataflow/make_zynq_proj.py +++ b/src/finn/transformation/fpgadataflow/make_zynq_proj.py @@ -181,9 +181,7 @@ def apply(self, model): # connect clocks/reset config.append( "apply_bd_automation -rule xilinx.com:bd_rule:clkrst -config " - "{ Clk {/zynq_ps/$zynq_ps_clkname} Freq {} " - "Ref_Clk0 {} Ref_Clk1 {} Ref_Clk2 {}} " - "[get_bd_pins axi_interconnect_%d/ACLK]" % (i) + '"Clk /zynq_ps/$zynq_ps_clkname" [get_bd_pins axi_interconnect_%d/ACLK]' % (i) ) master_axilite_idx += 1 total_axilite_count = max(0, total_axilite_count - 64) @@ -366,8 +364,7 @@ def apply(self, model): for i in range(1, nested_interconnect_count + 1): config.append( "apply_bd_automation -rule xilinx.com:bd_rule:clkrst -config " - "{ Clk {/zynq_ps/$zynq_ps_clkname} } [get_bd_pins axi_interconnect_%d/M*_ACLK]" - % (i) + '"Clk /zynq_ps/$zynq_ps_clkname" [get_bd_pins axi_interconnect_%d/M*_ACLK]' % (i) ) # create a temporary folder for the project From ef7b8cf5bb124b440129a4197a9a83b064d99397 Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Sat, 8 Mar 2025 18:10:59 +0000 Subject: [PATCH 17/31] Fix FIFO width export for driver --- .../templates/driver/driver_fifosizing.py | 222 ++++++++++++------ .../fpgadataflow/make_pynq_driver.py | 13 +- 2 files changed, 155 insertions(+), 80 deletions(-) diff --git a/src/finn/qnn-data/templates/driver/driver_fifosizing.py b/src/finn/qnn-data/templates/driver/driver_fifosizing.py index 560959991f..5aa116ebac 100644 --- a/src/finn/qnn-data/templates/driver/driver_fifosizing.py +++ b/src/finn/qnn-data/templates/driver/driver_fifosizing.py @@ -1,27 +1,32 @@ -import time -import json -import os import argparse -import matplotlib as mpl +import json import matplotlib.pyplot as plt -import numpy as np -from pynq.pl_server.device import Device - +import os +import sys +import time from driver_instrumentation import FINNInstrumentationOverlay +from pynq.pl_server.device import Device class FINNLiveFIFOOverlay(FINNInstrumentationOverlay): def __init__( self, bitfile_name, - platform = "zynq", - fclk_mhz = 100.0, - device = None, - download = True, - seed = 1, - fifo_widths = {}, + platform="zynq", + fclk_mhz=100.0, + device=None, + download=True, + seed=1, + fifo_widths=dict(), ): - super().__init__(bitfile_name, platform = platform, fclk_mhz = fclk_mhz, seed = seed, download = download, device = device) + super().__init__( + bitfile_name, + platform=platform, + fclk_mhz=fclk_mhz, + seed=seed, + download=download, + device=device, + ) self.error = False self.fifo_widths = fifo_widths @@ -33,9 +38,13 @@ def __init__( # We expect 3 AXI-Lite peripherals next to the virtual FIFOs: instrumentation_wrap_0, axi_gpio_0 (for reset), zynq_ps # We don't expect any additional FINN SDPs with AXI-Lite interface, such as runtime-writable weights if (len(self.ip_dict.keys()) - 3) != self.num_fifos: + print( + "Error: Number of expected FIFOs (%d) doesn't match number of AXI-Lite interfaces (%d)" + % (self.num_fifos, len(self.ip_dict.keys()) - 3) + ) self.error = True - def configure_fifo(self, i, mode, depth = 2): + def configure_fifo(self, i, mode, depth=2): ### Virtual FIFO register map ### mode_offset = 0x10 depth_offset = 0x18 @@ -45,43 +54,51 @@ def configure_fifo(self, i, mode, depth = 2): max_occupancy_ctrl_offset = 0x34 ip_name = "StreamingDataflowPartition_%d" % i - getattr(self, ip_name).write(offset=mode_offset, value = mode) - getattr(self, ip_name).write(offset=depth_offset, value = depth) + getattr(self, ip_name).write(offset=mode_offset, value=mode) + getattr(self, ip_name).write(offset=depth_offset, value=depth) def total_fifo_size(self, depths): # Assuming FIFO SDP/AXI-Lite interfaces are ordered consistently with FIFO IDs total_size_bits = 0 for i, depth in enumerate(depths): - total_size_bits += (depth + self.fifo_depth_offset) * self.fifo_widths["StreamingFIFO_hls_%d" % i] + total_size_bits += (depth + self.fifo_depth_offset) * self.fifo_widths[i] total_size_kB = total_size_bits / 8.0 / 1000.0 return total_size_kB - - def size_iteratively(self, start_depth, iteration_runtime, reduction_factor = 0.5): + + def size_iteratively(self, start_depth, iteration_runtime, reduction_factor=0.5): ### Iterative FIFO-sizing function ### fifo_minimum_reached = [False] * self.num_fifos - + if isinstance(start_depth, list): # Individual start depth for each FIFO has been supplied fifo_depths = start_depth else: # Initialize all depths to the same start depth fifo_depths = [start_depth] * self.num_fifos - + # Reset accelerator and configure FIFOs self.reset_accelerator() for i in range(0, self.num_fifos): - self.configure_fifo(i, mode = 1, depth = fifo_depths[i]) + self.configure_fifo(i, mode=1, depth=fifo_depths[i]) # Run once to determine target interval self.start_accelerator() time.sleep(1) - (overflow_err, underflow_err, frame, checksum, min_latency, latency, interval) = self.observe_instrumentation(False) + ( + overflow_err, + underflow_err, + frame, + checksum, + min_latency, + latency, + interval, + ) = self.observe_instrumentation(False) log_total_fifo_size = [int(self.total_fifo_size(fifo_depths))] log_interval = [interval] log_min_latency = [min_latency] log_latency = [latency] target_interval = interval - + # Iteratively reduce FIFO depth until all FIFOs are minimized iteration = 0 start_time = time.time() @@ -96,7 +113,7 @@ def size_iteratively(self, start_depth, iteration_runtime, reduction_factor = 0. # Configure all FIFOs for i in range(0, self.num_fifos): - self.configure_fifo(i, mode = 1, depth = fifo_depths[i]) + self.configure_fifo(i, mode=1, depth=fifo_depths[i]) # Start accelerator self.start_accelerator() @@ -104,8 +121,16 @@ def size_iteratively(self, start_depth, iteration_runtime, reduction_factor = 0. # Let it run time.sleep(iteration_runtime) - # Check if throughput dropped or deadlock occured - (overflow_err, underflow_err, frame, checksum, min_latency, latency, interval) = self.observe_instrumentation(False) + # Check if throughput dropped or deadlock occured + ( + overflow_err, + underflow_err, + frame, + checksum, + min_latency, + latency, + interval, + ) = self.observe_instrumentation(False) if interval > target_interval or interval == 0 or overflow_err or underflow_err: # Revert depth reduction and mark FIFO as minimized @@ -115,7 +140,7 @@ def size_iteratively(self, start_depth, iteration_runtime, reduction_factor = 0. log_total_fifo_size.append(int(self.total_fifo_size(fifo_depths))) log_interval.append(interval) log_min_latency.append(min_latency) - log_latency.append(latency) + log_latency.append(latency) if fifo_depths[fifo_id] == 1: fifo_minimum_reached[fifo_id] = True @@ -133,9 +158,18 @@ def size_iteratively(self, start_depth, iteration_runtime, reduction_factor = 0. duration = int(end_time - start_time) print("Done (%d seconds)" % duration) - return fifo_depths, log_total_fifo_size, log_interval, log_min_latency, log_latency, duration + return ( + fifo_depths, + log_total_fifo_size, + log_interval, + log_min_latency, + log_latency, + duration, + ) - def determine_start_depth(self, ): + def determine_start_depth( + self, + ): ### Attempt to determine start depth for all FIFOs automatically ### # If it doesn't find a working setting, start depth must be set manually, potentially on per-FIFO basis start_depth = 64 @@ -148,15 +182,28 @@ def determine_start_depth(self, ): # Configure FIFOs for i in range(0, self.num_fifos): - self.configure_fifo(i, mode = 1, depth = start_depth) - + self.configure_fifo(i, mode=1, depth=start_depth) + # Start accelerator and let it run for a long time self.start_accelerator() time.sleep(1) - + # Examine performance - (overflow_err, underflow_err, frame, checksum, min_latency, latency, interval) = self.observe_instrumentation() - if interval > 0 and interval == last_interval and not overflow_err and not underflow_err: + ( + overflow_err, + underflow_err, + frame, + checksum, + min_latency, + latency, + interval, + ) = self.observe_instrumentation() + if ( + interval > 0 + and interval == last_interval + and not overflow_err + and not underflow_err + ): # Accelerator runs with stable interval, reset to previous start depth start_depth_found = True start_depth = last_start_depth @@ -164,13 +211,13 @@ def determine_start_depth(self, ): # Start depth is still too small, increase for next try last_start_depth = start_depth start_depth = start_depth * 2 - + last_interval = interval if start_depth > 1000000: print("Couldn't find a working start depth, please set manually") self.error = True - + # Determine runtime per iteration based on performance, so that stable-state is guaranteed # Use a simple overestimation for now to be safe iteration_runtime = max(0.01, (min_latency * 5) * 10 / 1000 / 1000 / 1000) @@ -179,15 +226,27 @@ def determine_start_depth(self, ): print("Determined iteration runtime based on performance: %f s" % iteration_runtime) return (start_depth, iteration_runtime) + if __name__ == "__main__": - parser = argparse.ArgumentParser(description='Profile performance of FINN-generated accelerator using instrumentation wrapper') - parser.add_argument('--runtime', help='Runtime in seconds', type=int, default=10) - parser.add_argument('--frequency', help='FPGA clock frequency in MHz', type=float, default=100.0) - parser.add_argument('--seed', help='LFSR seed for input data generation', type=int, default=1) - parser.add_argument('--device', help='FPGA device to be used', type=int, default=0) - parser.add_argument('--bitfile', help='Name of bitfile', default="finn-accel.bit") - parser.add_argument('--reportfile', help='Name of output .json report file', type=str, default="measured_performance.json") - parser.add_argument('--settingsfile', help='Name of optional input .json settings file', type=str, default="") + parser = argparse.ArgumentParser( + description="Profile performance of FINN-generated accelerator using instrumentation wrapper" + ) + parser.add_argument("--runtime", help="Runtime in seconds", type=int, default=10) + parser.add_argument( + "--frequency", help="FPGA clock frequency in MHz", type=float, default=100.0 + ) + parser.add_argument("--seed", help="LFSR seed for input data generation", type=int, default=1) + parser.add_argument("--device", help="FPGA device to be used", type=int, default=0) + parser.add_argument("--bitfile", help="Name of bitfile", default="finn-accel.bit") + parser.add_argument( + "--reportfile", + help="Name of output .json report file", + type=str, + default="measured_performance.json", + ) + parser.add_argument( + "--settingsfile", help="Name of optional input .json settings file", type=str, default="" + ) # parse arguments args = parser.parse_args() runtime = args.runtime @@ -208,58 +267,67 @@ def determine_start_depth(self, ): frequency = settings["fclk_mhz"] # For live FIFO-sizing, we also expect a fifo_widths.json file exported by FINN listing the width of each FIFO, e.g., - # {'fifo_widths': {'StreamingFIFO_hls_0': 8, 'StreamingFIFO_hls_1': 32, 'StreamingFIFO_hls_2': 24}} + # {'fifo_widths': {0: 8, 1: 32, 2: 24}} fifo_widths = settings["fifo_widths"] - print("Programming FPGA..") - accel = FINNLiveFIFOOverlay(bitfile_name = bitfile, device = device, fclk_mhz = frequency, seed = seed, fifo_widths = fifo_widths) - + accel = FINNLiveFIFOOverlay( + bitfile_name=bitfile, device=device, fclk_mhz=frequency, seed=seed, fifo_widths=fifo_widths + ) + if accel.error: + print("Error: Accelerator initialization failed.") + sys.exit(1) + + print("Determining start depth..") (start_depth, iteration_runtime) = accel.determine_start_depth() ### First pass print("Starting first pass..") pass1_result = accel.size_iteratively(start_depth, iteration_runtime) - (fifo_depths, - log_total_fifo_size, - log_interval, - log_min_latency, - log_latency, - duration) = pass1_result + ( + fifo_depths, + log_total_fifo_size, + log_interval, + log_min_latency, + log_latency, + duration, + ) = pass1_result ### Visualize results and save as "fifo_sizing_graph.png" fig, ax1 = plt.subplots() - color = 'tab:red' - ax1.set_xlabel('Iteration') - ax1.set_ylabel('Total FIFO Size [kB]', color=color) + color = "tab:red" + ax1.set_xlabel("Iteration") + ax1.set_ylabel("Total FIFO Size [kB]", color=color) ax1.plot(range(len(log_total_fifo_size)), log_total_fifo_size, color=color) - ax1.tick_params(axis='y', labelcolor=color) + ax1.tick_params(axis="y", labelcolor=color) ax1.set_ylim(0, max(log_total_fifo_size)) - - ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis - color = 'tab:blue' - ax2.set_ylabel('Latency [cycles]', color=color) + ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis + + color = "tab:blue" + ax2.set_ylabel("Latency [cycles]", color=color) ax2.plot(range(len(log_total_fifo_size)), log_latency, color=color) - ax2.tick_params(axis='y', labelcolor=color) - #ax2.set_ylim(0, max(log_latency)) + ax2.tick_params(axis="y", labelcolor=color) + # ax2.set_ylim(0, max(log_latency)) ax2.axhline(log_min_latency[0], color="green", label="Minimum (1st frame) Latency") ax2.legend() plt.tight_layout() - plt.savefig(os.path.join(report_dir, "fifo_sizing_graph.png"), dpi = 300) + plt.savefig(os.path.join(report_dir, "fifo_sizing_graph.png"), dpi=300) ### Second pass for fine-tuning print("Starting second pass..") - pass2_result = accel.size_iteratively(fifo_depths, iteration_runtime, reduction_factor = 0.95) - (fifo_depths, - log_total_fifo_size, - log_interval, - log_min_latency, - log_latency, - duration) = pass2_result + pass2_result = accel.size_iteratively(fifo_depths, iteration_runtime, reduction_factor=0.95) + ( + fifo_depths, + log_total_fifo_size, + log_interval, + log_min_latency, + log_latency, + duration, + ) = pass2_result ### Generate fifo_sizing_report.json fifo_report = { @@ -283,7 +351,7 @@ def determine_start_depth(self, ): }, } for fifo, depth in enumerate(fifo_depths): - size = (depth + accel.fifo_depth_offset) * accel.fifo_widths["StreamingFIFO_hls_%d" % fifo] + size = (depth + accel.fifo_depth_offset) * accel.fifo_widths[fifo] fifo_report["fifo_depths"][fifo] = depth + accel.fifo_depth_offset fifo_report["fifo_sizes"][fifo] = size with open(os.path.join(report_dir, "fifo_sizing_report.json"), "w") as f: @@ -312,9 +380,9 @@ def determine_start_depth(self, ): "latency_ms": round(latency * (1 / (accel.fclk_mhz_actual * 1e6)) * 1e3, 6), "throughput_fps": round(1 / (interval * (1 / (accel.fclk_mhz_actual * 1e6)))), "min_pipeline_depth": round(min_latency / interval, 2), - "pipeline_depth" : round(latency / interval, 2), + "pipeline_depth": round(latency / interval, 2), } with open(reportfile, "w") as f: json.dump(report, f, indent=2) - print("Done.") \ No newline at end of file + print("Done.") diff --git a/src/finn/transformation/fpgadataflow/make_pynq_driver.py b/src/finn/transformation/fpgadataflow/make_pynq_driver.py index 9ccc0e08f8..c18adb8d14 100644 --- a/src/finn/transformation/fpgadataflow/make_pynq_driver.py +++ b/src/finn/transformation/fpgadataflow/make_pynq_driver.py @@ -346,10 +346,17 @@ def apply(self, model): } if self.live_fifo_sizing: # export FIFO widths to the settings file as well + # at this stage, the FIFOs are already wrapped in StreamingDataflowPartitions fifo_widths = {} - for node in model.get_nodes_by_op_type("StreamingFIFO_hls"): - node_inst = getCustomOp(node) - fifo_widths[node.name] = node_inst.get_instream_width() + for sdp_node in model.get_nodes_by_op_type("StreamingDataflowPartition"): + sdp_node_inst = getCustomOp(sdp_node) + sdp_id = sdp_node_inst.get_nodeattr("partition_id") + dataflow_model_filename = sdp_node_inst.get_nodeattr("model") + kernel_model = ModelWrapper(dataflow_model_filename) + for node in kernel_model.graph.node: + if node.op_type.startswith("StreamingFIFO"): + node_inst = getCustomOp(node) + fifo_widths[sdp_id] = node_inst.get_instream_width() settings["fifo_widths"] = fifo_widths settingsfile = pynq_driver_dir + "/settings.json" From b0fb5f258c984f1f30aea20cefbed1f01b5a27e1 Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Sun, 9 Mar 2025 10:29:39 +0000 Subject: [PATCH 18/31] [Driver] Reset PYNQ cache before loading Overlay --- src/finn/qnn-data/templates/driver/driver_instrumentation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/finn/qnn-data/templates/driver/driver_instrumentation.py b/src/finn/qnn-data/templates/driver/driver_instrumentation.py index 90a0ed5b89..aa5225eab6 100644 --- a/src/finn/qnn-data/templates/driver/driver_instrumentation.py +++ b/src/finn/qnn-data/templates/driver/driver_instrumentation.py @@ -1,7 +1,7 @@ import argparse import json import time -from pynq import Overlay +from pynq import PL, Overlay from pynq.pl_server.device import Device from pynq.ps import Clocks @@ -126,6 +126,7 @@ def observe_instrumentation(self, debug_print=True): # instantiate FINN accelerator driver and pass batchsize and bitfile print("Programming FPGA..") + PL.reset() # reset PYNQ cache accel = FINNInstrumentationOverlay( bitfile_name=bitfile, device=device, fclk_mhz=frequency, seed=seed ) From a08e2c4e12be4fa532f6b81ff428142dfaa757cd Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Sun, 9 Mar 2025 10:33:44 +0000 Subject: [PATCH 19/31] [Driver] Reset PYNQ cache, fix json int keys --- src/finn/qnn-data/templates/driver/driver_fifosizing.py | 6 ++++-- src/finn/transformation/fpgadataflow/make_pynq_driver.py | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/finn/qnn-data/templates/driver/driver_fifosizing.py b/src/finn/qnn-data/templates/driver/driver_fifosizing.py index 5aa116ebac..fc50314cf3 100644 --- a/src/finn/qnn-data/templates/driver/driver_fifosizing.py +++ b/src/finn/qnn-data/templates/driver/driver_fifosizing.py @@ -5,6 +5,7 @@ import sys import time from driver_instrumentation import FINNInstrumentationOverlay +from pynq import PL from pynq.pl_server.device import Device @@ -61,7 +62,7 @@ def total_fifo_size(self, depths): # Assuming FIFO SDP/AXI-Lite interfaces are ordered consistently with FIFO IDs total_size_bits = 0 for i, depth in enumerate(depths): - total_size_bits += (depth + self.fifo_depth_offset) * self.fifo_widths[i] + total_size_bits += (depth + self.fifo_depth_offset) * self.fifo_widths[str(i)] total_size_kB = total_size_bits / 8.0 / 1000.0 return total_size_kB @@ -271,6 +272,7 @@ def determine_start_depth( fifo_widths = settings["fifo_widths"] print("Programming FPGA..") + PL.reset() # reset PYNQ cache accel = FINNLiveFIFOOverlay( bitfile_name=bitfile, device=device, fclk_mhz=frequency, seed=seed, fifo_widths=fifo_widths ) @@ -351,7 +353,7 @@ def determine_start_depth( }, } for fifo, depth in enumerate(fifo_depths): - size = (depth + accel.fifo_depth_offset) * accel.fifo_widths[fifo] + size = (depth + accel.fifo_depth_offset) * accel.fifo_widths[str(fifo)] fifo_report["fifo_depths"][fifo] = depth + accel.fifo_depth_offset fifo_report["fifo_sizes"][fifo] = size with open(os.path.join(report_dir, "fifo_sizing_report.json"), "w") as f: diff --git a/src/finn/transformation/fpgadataflow/make_pynq_driver.py b/src/finn/transformation/fpgadataflow/make_pynq_driver.py index c18adb8d14..e7c947192a 100644 --- a/src/finn/transformation/fpgadataflow/make_pynq_driver.py +++ b/src/finn/transformation/fpgadataflow/make_pynq_driver.py @@ -350,7 +350,8 @@ def apply(self, model): fifo_widths = {} for sdp_node in model.get_nodes_by_op_type("StreamingDataflowPartition"): sdp_node_inst = getCustomOp(sdp_node) - sdp_id = sdp_node_inst.get_nodeattr("partition_id") + # JSON doesn't support int keys + sdp_id = str(sdp_node_inst.get_nodeattr("partition_id")) dataflow_model_filename = sdp_node_inst.get_nodeattr("model") kernel_model = ModelWrapper(dataflow_model_filename) for node in kernel_model.graph.node: From 2c9925d29bc9e39d0de2dbd02a8221ecd1f786ec Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Mon, 24 Mar 2025 09:17:48 +0100 Subject: [PATCH 20/31] Start search for start depths from 1 --- src/finn/qnn-data/templates/driver/driver_fifosizing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/finn/qnn-data/templates/driver/driver_fifosizing.py b/src/finn/qnn-data/templates/driver/driver_fifosizing.py index fc50314cf3..ada6979db2 100644 --- a/src/finn/qnn-data/templates/driver/driver_fifosizing.py +++ b/src/finn/qnn-data/templates/driver/driver_fifosizing.py @@ -173,7 +173,7 @@ def determine_start_depth( ): ### Attempt to determine start depth for all FIFOs automatically ### # If it doesn't find a working setting, start depth must be set manually, potentially on per-FIFO basis - start_depth = 64 + start_depth = 1 last_interval = 0 start_depth_found = False From 9f3e7c73dd3d403b1b1fe51156b3c36bc2dd2e61 Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Mon, 24 Mar 2025 17:06:59 +0100 Subject: [PATCH 21/31] Let driver fill live FIFO sizes into complete folding config --- src/finn/builder/build_dataflow_steps.py | 53 ++++++++++++------- .../templates/driver/driver_fifosizing.py | 27 ++++++++-- .../fpgadataflow/make_pynq_driver.py | 9 +++- 3 files changed, 65 insertions(+), 24 deletions(-) diff --git a/src/finn/builder/build_dataflow_steps.py b/src/finn/builder/build_dataflow_steps.py index 5dc971cf33..6f8e1e7007 100644 --- a/src/finn/builder/build_dataflow_steps.py +++ b/src/finn/builder/build_dataflow_steps.py @@ -44,6 +44,7 @@ GiveUniqueNodeNames, RemoveStaticGraphInputs, RemoveUnusedTensors, + SortGraph, ) from qonnx.transformation.infer_data_layouts import InferDataLayouts from qonnx.transformation.infer_datatypes import InferDataTypes @@ -553,8 +554,40 @@ def step_set_fifo_depths(model: ModelWrapper, cfg: DataflowBuildConfig): `GiveUniqueNodeNames`. """ + hw_attrs = [ + "PE", + "SIMD", + "parallel_window", + "ram_style", + "depth", + "impl_style", + "resType", + "mem_mode", + "runtime_writeable_weights", + "inFIFODepths", + "outFIFODepths", + "depth_trigger_uram", + "depth_trigger_bram", + ] + # Experimental live FIFO-sizing, overwrites all other FIFO-related behavior if cfg.live_fifo_sizing: + # Create all DWCs and FIFOs normally + model = model.transform(InsertDWC()) + model = model.transform( + InsertFIFO(vivado_ram_style=cfg.large_fifo_mem_style, create_shallow_fifos=True) + ) + + # Clean up model + model = model.transform(SortGraph()) + model = model.transform(GiveUniqueNodeNames()) + model = model.transform(GiveReadableTensorNames()) + + # save original folding config before potentially modifying it + cfg_path = cfg.output_dir + "/report/folding_config_before_lfs.json" + extract_model_config_to_json(model, cfg_path, hw_attrs) + model.set_metadata_prop("folding_config_before_lfs", cfg_path) + # Disable runtime-writable weights, external weights, and dynamic mode, # as we don't support additional AXI-lite interfaces next to the FIFOs for node in model.graph.node: @@ -578,10 +611,6 @@ def step_set_fifo_depths(model: ModelWrapper, cfg: DataflowBuildConfig): except AttributeError: pass - # Create all DWCs and FIFOs normally - model = model.transform(InsertDWC()) - model = model.transform(InsertFIFO(create_shallow_fifos=True)) - # Specialize FIFOs to HLS back-end instead of default RTL back-end for node in model.get_nodes_by_op_type("StreamingFIFO"): node_inst = getCustomOp(node) @@ -594,6 +623,7 @@ def step_set_fifo_depths(model: ModelWrapper, cfg: DataflowBuildConfig): node_inst.set_nodeattr("impl_style", "virtual") # Clean up model + model = model.transform(SortGraph()) model = model.transform(GiveUniqueNodeNames()) model = model.transform(GiveReadableTensorNames()) @@ -659,21 +689,6 @@ def step_set_fifo_depths(model: ModelWrapper, cfg: DataflowBuildConfig): model = model.transform(ApplyConfig(cfg.folding_config_file)) # extract the final configuration and save it as json - hw_attrs = [ - "PE", - "SIMD", - "parallel_window", - "ram_style", - "depth", - "impl_style", - "resType", - "mem_mode", - "runtime_writeable_weights", - "inFIFODepths", - "outFIFODepths", - "depth_trigger_uram", - "depth_trigger_bram", - ] extract_model_config_to_json(model, cfg.output_dir + "/final_hw_config.json", hw_attrs) # perform FIFO splitting and shallow FIFO removal only after the final config diff --git a/src/finn/qnn-data/templates/driver/driver_fifosizing.py b/src/finn/qnn-data/templates/driver/driver_fifosizing.py index ada6979db2..1cbc5053cf 100644 --- a/src/finn/qnn-data/templates/driver/driver_fifosizing.py +++ b/src/finn/qnn-data/templates/driver/driver_fifosizing.py @@ -259,6 +259,7 @@ def determine_start_depth( settingsfile = args.settingsfile devID = args.device device = Device.devices[devID] + folding_config_lfs = None # overwrite frequency if specified in settings file if settingsfile != "": @@ -267,10 +268,15 @@ def determine_start_depth( if "fclk_mhz" in settings: frequency = settings["fclk_mhz"] - # For live FIFO-sizing, we also expect a fifo_widths.json file exported by FINN listing the width of each FIFO, e.g., - # {'fifo_widths': {0: 8, 1: 32, 2: 24}} + # For live FIFO-sizing, we also expect the FIFO widths (in bits) exported by FINN, e.g., + # {'fifo_widths': {"0": 8, "1": 32, "2": 24}} fifo_widths = settings["fifo_widths"] + # The settings can also contain the original folding config, + # into which we can insert the live FIFO sizes once we are done + if "folding_config_before_lfs" in settings: + folding_config_lfs = settings["folding_config_before_lfs"] + print("Programming FPGA..") PL.reset() # reset PYNQ cache accel = FINNLiveFIFOOverlay( @@ -362,11 +368,24 @@ def determine_start_depth( ### Generate fifo_depth_export.json to export FIFO depths for use in FINN fifo_depth_export = {} for fifo, depth in enumerate(fifo_depths): - fifo_depth_export["StreamingFIFO_rtl_%d" % fifo] = {} - fifo_depth_export["StreamingFIFO_rtl_%d" % fifo]["depth"] = depth + accel.fifo_depth_offset + fifo_name = "StreamingFIFO_rtl_%d" % fifo + fifo_depth_export[fifo_name] = {} + fifo_depth_export[fifo_name]["depth"] = depth + accel.fifo_depth_offset with open(os.path.join(report_dir, "fifo_depth_export.json"), "w") as f: json.dump(fifo_depth_export, f, indent=2) + # Also export directly into original folding config for convenience + if folding_config_lfs: + for key in list(folding_config_lfs.keys()): + if key.startswith("StreamingFIFO"): + fifo_name = "StreamingFIFO_rtl_%d" % int(key.removeprefix("StreamingFIFO_")) + # Rename FIFO from StreamingFIFO_* to StreamingFIFO_rtl_* + folding_config_lfs[fifo_name] = folding_config_lfs.pop(key) + folding_config_lfs[fifo_name]["depth"] = fifo_depth_export[fifo_name]["depth"] + folding_config_lfs[fifo_name]["impl_style"] = "rtl" + with open(os.path.join(report_dir, "folding_config_lfs.json"), "w") as f: + json.dump(folding_config_lfs, f, indent=2) + ### Generate the usual instrumentation performance report based on final state min_latency = log_min_latency[-1] latency = log_latency[-1] diff --git a/src/finn/transformation/fpgadataflow/make_pynq_driver.py b/src/finn/transformation/fpgadataflow/make_pynq_driver.py index e7c947192a..e065641b27 100644 --- a/src/finn/transformation/fpgadataflow/make_pynq_driver.py +++ b/src/finn/transformation/fpgadataflow/make_pynq_driver.py @@ -313,7 +313,7 @@ def __init__(self, platform, clk_period_ns, live_fifo_sizing): self.clk_period_ns = clk_period_ns self.live_fifo_sizing = live_fifo_sizing - def apply(self, model): + def apply(self, model: ModelWrapper): # TODO: support runtime-writable and external weights # TODO: support Alveo and Versal platforms @@ -359,6 +359,13 @@ def apply(self, model): node_inst = getCustomOp(node) fifo_widths[sdp_id] = node_inst.get_instream_width() settings["fifo_widths"] = fifo_widths + # export original folding config to settings file, + # so that the driver can generate a final cfg with live fifo sizes applied + folding_path = model.get_metadata_prop("folding_config_before_lfs") + if folding_path: + with open(folding_path, "r") as f: + folding_cfg = json.load(f) + settings["folding_config_before_lfs"] = folding_cfg settingsfile = pynq_driver_dir + "/settings.json" with open(settingsfile, "w") as f: From 15fef09eaf7c31f924c3474a960fa278f898c9fe Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Fri, 28 Mar 2025 15:22:58 +0100 Subject: [PATCH 22/31] Increase virtual FIFO depth offset to 8 --- src/finn/qnn-data/templates/driver/driver_fifosizing.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/finn/qnn-data/templates/driver/driver_fifosizing.py b/src/finn/qnn-data/templates/driver/driver_fifosizing.py index 1cbc5053cf..a87342f79e 100644 --- a/src/finn/qnn-data/templates/driver/driver_fifosizing.py +++ b/src/finn/qnn-data/templates/driver/driver_fifosizing.py @@ -32,8 +32,9 @@ def __init__( self.error = False self.fifo_widths = fifo_widths self.num_fifos = len(self.fifo_widths) - # Try to account for additional registers introduced by virtual FIFO HLS implementation - self.fifo_depth_offset = 4 + # Account for additional FIFO depth and implicit registers introduced by the virtual FIFO HLS implementation that are not present in real FIFOs + # This results in a minimum possible FIFO depth of 1 + 8 = 9, which should be improved in a future virtual FIFO implementation (TODO) + self.fifo_depth_offset = 8 # Sanity check # We expect 3 AXI-Lite peripherals next to the virtual FIFOs: instrumentation_wrap_0, axi_gpio_0 (for reset), zynq_ps From b5aee28630d9958d0572ac927cda8b8cb9c9e69a Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Mon, 28 Apr 2025 12:48:53 +0200 Subject: [PATCH 23/31] Update gitignore --- .dvc/.gitignore | 3 +++ .gitignore | 9 +++++++++ 2 files changed, 12 insertions(+) create mode 100644 .dvc/.gitignore diff --git a/.dvc/.gitignore b/.dvc/.gitignore new file mode 100644 index 0000000000..528f30c71c --- /dev/null +++ b/.dvc/.gitignore @@ -0,0 +1,3 @@ +/config.local +/tmp +/cache diff --git a/.gitignore b/.gitignore index be61378730..dbac36d4f9 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,9 @@ __pycache__/* .settings .idea tags +poetry.lock +*.code-workspace +.env # Package files *.egg @@ -96,3 +99,9 @@ MANIFEST # downloaded dep repos /deps/ + +# local test directories for benchmarking infrastructure +bench_input +bench_output +bench_save +bench_work From 4f9dc7ee13006b004bc3700c354011ae38608add Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Mon, 28 Apr 2025 13:08:51 +0200 Subject: [PATCH 24/31] [Driver] Increase recursion limit --- src/finn/qnn-data/templates/driver/driver_fifosizing.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/finn/qnn-data/templates/driver/driver_fifosizing.py b/src/finn/qnn-data/templates/driver/driver_fifosizing.py index a87342f79e..e86b28772d 100644 --- a/src/finn/qnn-data/templates/driver/driver_fifosizing.py +++ b/src/finn/qnn-data/templates/driver/driver_fifosizing.py @@ -279,7 +279,12 @@ def determine_start_depth( folding_config_lfs = settings["folding_config_before_lfs"] print("Programming FPGA..") - PL.reset() # reset PYNQ cache + # Increase recursion limit because the default value (1000) caused pickle RecursionErrors + # during PYNQ cache handling for accelerators with many FIFOs (exact reason unknown) + sys.setrecursionlimit(10000) + # Reset PYNQ cache, without this we encountered issues where PYNQ would try to load + # an incorrect combination of .bit and .hwh file, see https://github.com/Xilinx/PYNQ/issues/1409 + PL.reset() accel = FINNLiveFIFOOverlay( bitfile_name=bitfile, device=device, fclk_mhz=frequency, seed=seed, fifo_widths=fifo_widths ) From 7a3f928dc83ea8b98fe4464d6b8a9217a8d879b4 Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Tue, 20 May 2025 17:50:02 +0200 Subject: [PATCH 25/31] Adapt to FINN_ROOT refactoring --- src/finn/transformation/fpgadataflow/instrumentation.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/finn/transformation/fpgadataflow/instrumentation.py b/src/finn/transformation/fpgadataflow/instrumentation.py index 7f37c5ed14..a22d770307 100644 --- a/src/finn/transformation/fpgadataflow/instrumentation.py +++ b/src/finn/transformation/fpgadataflow/instrumentation.py @@ -28,7 +28,7 @@ def collect_ip_dirs(model, ipstitch_path): ip_dirs += [ipstitch_path + "/ip"] if need_memstreamer: # add RTL streamer IP - ip_dirs.append("$::env(FINN_ROOT)/finn-rtllib/memstream") + ip_dirs.append("$::env(FINN_RTLLIB)/memstream") return ip_dirs @@ -71,7 +71,7 @@ def apply(self, model): ko = out_shape_folded[-1] # fill out instrumentation wrapper template with open( - os.path.join(os.environ["FINN_ROOT"], "custom_hls", "instrumentation.template.cpp"), "r" + os.path.join(os.environ["FINN_CUSTOM_HLS"], "instrumentation.template.cpp"), "r" ) as f: instrwrp_cpp = f.read() instrwrp_cpp = instrwrp_cpp.replace("@PENDING@", str(pending)) @@ -150,7 +150,7 @@ def apply(self, model): # TODO: Support simulation with AXI-lite control interfaces (e.g., for dynamic pipelines) # fill in testbench template with open( - os.path.join(os.environ["FINN_ROOT"], "custom_hls", "instrumentation_tb.template.sv"), + os.path.join(os.environ["FINN_CUSTOM_HLS"], "instrumentation_tb.template.sv"), "r", ) as f: testbench_sv = f.read() @@ -158,7 +158,7 @@ def apply(self, model): f.write(testbench_sv) # fill in testbench project creator template with open( - os.path.join(os.environ["FINN_ROOT"], "custom_hls", "instrumentation_sim.template.tcl"), + os.path.join(os.environ["FINN_CUSTOM_HLS"], "instrumentation_sim.template.tcl"), "r", ) as f: testbench_tcl = f.read() From ccebbdca2b6eb88dffded9b1e794ce9912b7af89 Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Tue, 20 May 2025 17:59:48 +0200 Subject: [PATCH 26/31] Fix use of deprecated FINN_ROOT --- src/finn/transformation/fpgadataflow/make_driver.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/finn/transformation/fpgadataflow/make_driver.py b/src/finn/transformation/fpgadataflow/make_driver.py index b17cb9c8e8..1cea95f9c5 100644 --- a/src/finn/transformation/fpgadataflow/make_driver.py +++ b/src/finn/transformation/fpgadataflow/make_driver.py @@ -477,8 +477,7 @@ def apply(self, model): # create (copy) the static instrumentation driver driver_template = ( - os.environ["FINN_ROOT"] - + "/src/finn/qnn-data/templates/driver/driver_instrumentation.py" + os.environ["FINN_QNN_DATA"] + "/templates/driver/driver_instrumentation.py" ) driver_py = pynq_driver_dir + "/driver.py" shutil.copy(driver_template, driver_py) From cc0be94bb0ae15e8721ad6c9c5a525602ae9de81 Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Wed, 21 May 2025 16:16:05 +0200 Subject: [PATCH 27/31] [CI] Adapt to recent runner version change --- .gitlab-ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ebdad54bee..a2f9527976 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -93,11 +93,11 @@ Sync finn-dev: .setup_venv_from_whl: &setup_venv_from_whl # Move everything to working directory (e.g., RAMdisk) - - cp -dfR .. $PATH_WORKDIR + - cp -dfR . $PATH_WORKDIR - cd $PATH_WORKDIR # Create fresh virtual environment and install finn-plus from .whl (artifact) - python3 -m venv finn-plus-venv - - finn-plus-venv/bin/pip install ./finn-plus/dist/*.whl + - finn-plus-venv/bin/pip install dist/*.whl Build: id_tokens: @@ -171,8 +171,8 @@ FINN Test Suite 2022.2: - $JOB_MONITORING_DIR/monitor.sh $JOB_MONITORING_DIR/$CI_PIPELINE_ID/$HOSTNAME.log & # Launch FINN via test command, includes preparation of (cached) dependencies - | - source ./finn-plus-venv/bin/activate - finn test --variant $TEST_SUITE --dependency-path ./finn-plus/deps --build-path $FINN_BUILD_DIR --num-workers 1 --num-test-workers $PYTEST_PARALLEL + source finn-plus-venv/bin/activate + finn test --variant $TEST_SUITE --dependency-path ./deps --build-path $FINN_BUILD_DIR --num-workers 1 --num-test-workers $PYTEST_PARALLEL artifacts: name: "test_reports" when: always From a942390d20d27e8d2c9a1ea70e95bea523b91442 Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Wed, 21 May 2025 21:09:59 +0200 Subject: [PATCH 28/31] Refactor remaining MakePYNQDriver calls --- notebooks/end2end_example/bnn-pynq/cnv_end2end_example.ipynb | 4 ++-- notebooks/end2end_example/bnn-pynq/tfc_end2end_example.ipynb | 4 ++-- src/finn/qnn-data/templates/driver/driver_base.py | 2 +- tests/end2end/test_end2end_bnn_pynq.py | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/notebooks/end2end_example/bnn-pynq/cnv_end2end_example.ipynb b/notebooks/end2end_example/bnn-pynq/cnv_end2end_example.ipynb index 2b01f24557..014a13db27 100644 --- a/notebooks/end2end_example/bnn-pynq/cnv_end2end_example.ipynb +++ b/notebooks/end2end_example/bnn-pynq/cnv_end2end_example.ipynb @@ -456,8 +456,8 @@ "metadata": {}, "outputs": [], "source": [ - "from finn.transformation.fpgadataflow.make_driver import MakePYNQDriver\n", - "model = model.transform(MakePYNQDriver(\"zynq-iodma\"))" + "from finn.transformation.fpgadataflow.make_driver import MakePYNQDriverIODMA\n", + "model = model.transform(MakePYNQDriverIODMA(\"zynq-iodma\"))" ] }, { diff --git a/notebooks/end2end_example/bnn-pynq/tfc_end2end_example.ipynb b/notebooks/end2end_example/bnn-pynq/tfc_end2end_example.ipynb index b0510b0fdb..de6de23d3f 100644 --- a/notebooks/end2end_example/bnn-pynq/tfc_end2end_example.ipynb +++ b/notebooks/end2end_example/bnn-pynq/tfc_end2end_example.ipynb @@ -751,8 +751,8 @@ "metadata": {}, "outputs": [], "source": [ - "from finn.transformation.fpgadataflow.make_driver import MakePYNQDriver\n", - "model = model.transform(MakePYNQDriver(\"zynq-iodma\"))" + "from finn.transformation.fpgadataflow.make_driver import MakePYNQDriverIODMA\n", + "model = model.transform(MakePYNQDriverIODMA(\"zynq-iodma\"))" ] }, { diff --git a/src/finn/qnn-data/templates/driver/driver_base.py b/src/finn/qnn-data/templates/driver/driver_base.py index a6ff29d608..af55ee13df 100644 --- a/src/finn/qnn-data/templates/driver/driver_base.py +++ b/src/finn/qnn-data/templates/driver/driver_base.py @@ -38,7 +38,7 @@ # Driver base class for FINN-generated dataflow accelerators. # The particulars of the generated accelerator are specified via the -# io_shape_dict (generated by the MakePYNQDriver transformation). +# io_shape_dict (generated by the MakePYNQDriverIODMA transformation). class FINNExampleOverlay(Overlay): diff --git a/tests/end2end/test_end2end_bnn_pynq.py b/tests/end2end/test_end2end_bnn_pynq.py index 9a2da7a45e..9d40b3ba93 100644 --- a/tests/end2end/test_end2end_bnn_pynq.py +++ b/tests/end2end/test_end2end_bnn_pynq.py @@ -73,7 +73,7 @@ from finn.transformation.fpgadataflow.create_stitched_ip import CreateStitchedIP from finn.transformation.fpgadataflow.hlssynth_ip import HLSSynthIP from finn.transformation.fpgadataflow.insert_dwc import InsertDWC -from finn.transformation.fpgadataflow.make_driver import MakePYNQDriver +from finn.transformation.fpgadataflow.make_driver import MakePYNQDriverIODMA from finn.transformation.fpgadataflow.minimize_accumulator_width import MinimizeAccumulatorWidth from finn.transformation.fpgadataflow.minimize_weight_bit_width import MinimizeWeightBitWidth from finn.transformation.fpgadataflow.prepare_cppsim import PrepareCppSim @@ -812,7 +812,7 @@ def test_make_pynq_driver(self, topology, wbits, abits, board): prev_chkpt_name = get_checkpoint_name(board, topology, wbits, abits, "build") model = load_test_checkpoint_or_skip(prev_chkpt_name) board_to_driver_platform = "alveo" if build_data["kind"] == "alveo" else "zynq-iodma" - model = model.transform(MakePYNQDriver(board_to_driver_platform)) + model = model.transform(MakePYNQDriverIODMA(board_to_driver_platform)) model.save(get_checkpoint_name(board, topology, wbits, abits, "driver")) def test_deploy(self, topology, wbits, abits, board): From 109f31f9afeffc41721dd45c32e013643258980c Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Thu, 22 May 2025 16:30:03 +0200 Subject: [PATCH 29/31] Remove old Jupyter notebook driver --- driver/iterative_live_fifosizing_driver.ipynb | 833 ------------------ 1 file changed, 833 deletions(-) delete mode 100644 driver/iterative_live_fifosizing_driver.ipynb diff --git a/driver/iterative_live_fifosizing_driver.ipynb b/driver/iterative_live_fifosizing_driver.ipynb deleted file mode 100644 index 83a329d263..0000000000 --- a/driver/iterative_live_fifosizing_driver.ipynb +++ /dev/null @@ -1,833 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "0ee21ecb", - "metadata": {}, - "outputs": [ - { - "data": { - "application/javascript": [ - "\n", - "try {\n", - "require(['notebook/js/codecell'], function(codecell) {\n", - " codecell.CodeCell.options_default.highlight_modes[\n", - " 'magic_text/x-csrc'] = {'reg':[/^%%microblaze/]};\n", - " Jupyter.notebook.events.one('kernel_ready.Kernel', function(){\n", - " Jupyter.notebook.get_cells().map(function(cell){\n", - " if (cell.cell_type == 'code'){ cell.auto_highlight(); } }) ;\n", - " });\n", - "});\n", - "} catch (e) {};\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "\n", - "try {\n", - "require(['notebook/js/codecell'], function(codecell) {\n", - " codecell.CodeCell.options_default.highlight_modes[\n", - " 'magic_text/x-csrc'] = {'reg':[/^%%pybind11/]};\n", - " Jupyter.notebook.events.one('kernel_ready.Kernel', function(){\n", - " Jupyter.notebook.get_cells().map(function(cell){\n", - " if (cell.cell_type == 'code'){ cell.auto_highlight(); } }) ;\n", - " });\n", - "});\n", - "} catch (e) {};\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import time\n", - "import json\n", - "import matplotlib as mpl\n", - "import matplotlib.pyplot as plt\n", - "from IPython.display import clear_output\n", - "import numpy as np\n", - "from pynq import Overlay\n", - "\n", - "path = \"bitstreams/resnet50/live_instrumentation\"\n", - "bitstream = path + \"/finn-accel.bit\"\n", - "\n", - "# Program FPGA\n", - "ol = Overlay(bitstream, download=True, device=None)" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "f476fd87", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "#FIFO IP detected: 266\n", - "#FIFO width information found: 266\n" - ] - } - ], - "source": [ - "### Sanity checks\n", - "# We expect 3 AXI-Lite peripherals next to the virtual FIFOs: instrumentation_wrap_0, axi_gpio_0 (for reset), zynq_ps\n", - "# We don't expect any additional FINN SDPs with AXI-Lite interface, such as runtime-writable weights\n", - "print(\"#FIFO IP detected: %d\" % (len(ol.ip_dict.keys()) - 3))\n", - "\n", - "# We expect a fifo_widths.json file exported by FINN listing the width of each FIFO, e.g.,\n", - "# {'fifo_widths': {'StreamingFIFO_hls_0': 8, 'StreamingFIFO_hls_1': 32, 'StreamingFIFO_hls_2': 24}}\n", - "with open(path + \"/fifo_widths.json\", \"r\") as f:\n", - " fifo_info = json.load(f)\n", - "print(\"#FIFO width information found: %d\" % len(fifo_info[\"fifo_widths\"]))" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "e419656f", - "metadata": {}, - "outputs": [], - "source": [ - "### Instrumentation driver\n", - "# Register map\n", - "#ap_uint<32> cfg, \t// [0] - 0:hold, 1:lfsr; [31:16] - LFSR seed\n", - "#ap_uint<32> &status,\t// [0] - timestamp overflow; [1] - timestamp underflow\n", - "#ap_uint<32> &latency,\n", - "#ap_uint<32> &interval,\n", - "#ap_uint<32> &checksum,\n", - "#ap_uint<32> &min_latency\n", - "\n", - "def read_register(ol, name):\n", - " return ol.instrumentation_wrap_0.read(offset=ol.ip_dict[\"instrumentation_wrap_0\"][\"registers\"][name][\"address_offset\"])\n", - "\n", - "def write_register(ol, name, value):\n", - " return ol.instrumentation_wrap_0.write(offset=ol.ip_dict[\"instrumentation_wrap_0\"][\"registers\"][name][\"address_offset\"], value=value)\n", - "\n", - "def observe_instrumentation(debug_print=True):\n", - " status_reg = read_register(ol, \"status\")\n", - " chksum_reg = read_register(ol, \"checksum\")\n", - " min_latency = read_register(ol, \"min_latency\")\n", - " latency = read_register(ol, \"latency\")\n", - " interval = read_register(ol, \"interval\")\n", - "\n", - " frame = (chksum_reg >> 24) & 0x000000ff\n", - " checksum = chksum_reg & 0x00ffffff\n", - " overflow_err = (status_reg & 0x00000001) != 0\n", - " underflow_err = (status_reg & 0x00000002) != 0\n", - "\n", - " if debug_print:\n", - " print(\"---INSTRUMENTATION_REPORT---\")\n", - " if overflow_err or underflow_err:\n", - " print(\"Status ERROR\")\n", - " print(\"Overflow error: %s\" % overflow_err)\n", - " print(\"Underflow error: %s\" % underflow_err)\n", - " else:\n", - " print(\"Status OK\")\n", - " print(\"Frame number (8-bit): %d\" % frame)\n", - " print(\"Checksum: 0x%06x\" % checksum)\n", - " print(\"Min Latency (cycles): %d\" % min_latency)\n", - " print(\"Latency (cycles): %d\" % latency)\n", - " print(\"Interval (cycles): %d\" % interval)\n", - " print(\"----------------------------\")\n", - "\n", - " return (overflow_err, underflow_err, frame, checksum, min_latency, latency, interval)\n", - "\n", - "def start_accelerator():\n", - " lfsr_seed = 0x00010000 # upper 16 bits\n", - " write_register(ol, \"cfg\", lfsr_seed + 1) # start operation\n", - "\n", - "### Virtual FIFO driver\n", - "# Register map\n", - "mode_offset = 0x10\n", - "depth_offset = 0x18\n", - "occupancy_offset = 0x20\n", - "occupancy_ctrl_offset = 0x24\n", - "max_occupancy_offset = 0x30\n", - "max_occupancy_ctrl_offset = 0x34\n", - "\n", - "def configure_fifo(ol, i, mode, depth = 2):\n", - " ip_name = \"StreamingDataflowPartition_%d\" % i\n", - " getattr(ol, ip_name).write(offset=mode_offset, value = mode)\n", - " getattr(ol, ip_name).write(offset=depth_offset, value = depth)\n", - "\n", - "def total_fifo_size(depths):\n", - " # Assuming FIFO SDP/AXI-Lite interfaces are ordered consistently with FIFO IDs\n", - " total_size_bits = 0\n", - " for i, depth in enumerate(depths):\n", - " total_size_bits += depth * fifo_info[\"fifo_widths\"][\"StreamingFIFO_hls_%d\" % i]\n", - " total_size_kB = total_size_bits / 8.0 / 1000.0\n", - " return total_size_kB\n", - "\n", - "### GPIO Reset Driver\n", - "def reset_accelerator():\n", - " ol.axi_gpio_0.write(offset=ol.ip_dict[\"axi_gpio_0\"][\"registers\"][\"GPIO_DATA\"][\"address_offset\"], value=0)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "2e2a4b88", - "metadata": {}, - "outputs": [], - "source": [ - "### Iterative FIFO-sizing function\n", - "def size_iteratively(start_depth, iteration_runtime, reduction_factor = 0.5):\n", - " num_fifos = len(fifo_info[\"fifo_widths\"])\n", - " fifo_minimum_reached = [False] * num_fifos\n", - " \n", - " if isinstance(start_depth, list):\n", - " # Individual start depth for each FIFO has been supplied\n", - " fifo_depths = start_depth\n", - " else:\n", - " # Initialize all depths to the same start depth\n", - " fifo_depths = [start_depth] * num_fifos\n", - " \n", - " # Reset accelerator and configure FIFOs\n", - " reset_accelerator()\n", - " for i in range(0, num_fifos):\n", - " configure_fifo(ol, i, mode = 1, depth = fifo_depths[i])\n", - "\n", - " # Run once to determine target interval\n", - " start_accelerator()\n", - " time.sleep(1)\n", - " (overflow_err, underflow_err, frame, checksum, min_latency, latency, interval) = observe_instrumentation(False)\n", - " log_total_fifo_size = [int(total_fifo_size(fifo_depths))]\n", - " log_interval = [interval]\n", - " log_min_latency = [min_latency]\n", - " log_latency = [latency]\n", - " target_interval = interval\n", - " \n", - " # Iteratively reduce FIFO depth until all FIFOs are minimized\n", - " iteration = 0\n", - " start_time = time.time()\n", - " while not all(fifo_minimum_reached):\n", - " for fifo_id in range(0, num_fifos):\n", - " if not fifo_minimum_reached[fifo_id]:\n", - " fifo_depth_before = fifo_depths[fifo_id]\n", - " fifo_depths[fifo_id] = int(fifo_depths[fifo_id] * reduction_factor)\n", - "\n", - " # Reset accelerator\n", - " reset_accelerator()\n", - "\n", - " # Configure all FIFOs\n", - " for i in range(0, num_fifos):\n", - " configure_fifo(ol, i, mode = 1, depth = fifo_depths[i])\n", - "\n", - " # Start accelerator\n", - " start_accelerator()\n", - "\n", - " # Let it run\n", - " time.sleep(iteration_runtime)\n", - "\n", - " # Check if throughput dropped or deadlock occured \n", - " (overflow_err, underflow_err, frame, checksum, min_latency, latency, interval) = observe_instrumentation(False)\n", - "\n", - " if interval > target_interval or interval == 0 or overflow_err or underflow_err:\n", - " # Revert depth reduction and mark FIFO as minimized\n", - " fifo_depths[fifo_id] = fifo_depth_before\n", - " fifo_minimum_reached[fifo_id] = True\n", - " else:\n", - " log_total_fifo_size.append(int(total_fifo_size(fifo_depths)))\n", - " log_interval.append(interval)\n", - " log_min_latency.append(min_latency)\n", - " log_latency.append(latency) \n", - "\n", - " if fifo_depths[fifo_id] == 1:\n", - " fifo_minimum_reached[fifo_id] = True\n", - "\n", - " # Report status\n", - " clear_output(wait=True)\n", - " print(\"Iteration: %d\" % iteration)\n", - " print(\"Reducing depth of FIFO: %d/%d\" % (fifo_id, num_fifos))\n", - " print(\"Numer of minimized FIFOs: %d/%d\" % (sum(fifo_minimum_reached), num_fifos))\n", - " print(\"Interval: %d\" % log_interval[-1])\n", - " print(\"Min. latency / latency: %d/%d\" % (log_min_latency[-1], log_latency[-1]))\n", - " print(\"Total FIFO Size (kB): %d\" % log_total_fifo_size[-1])\n", - "\n", - " iteration += 1\n", - "\n", - " end_time = time.time()\n", - " print(\"Done (%d seconds)\" % int(end_time - start_time))\n", - " \n", - " return fifo_depths, log_total_fifo_size, log_interval, log_min_latency, log_latency" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "2ebb2aa3", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Testing start depth of 64\n", - "---INSTRUMENTATION_REPORT---\n", - "Status OK\n", - "Frame number (8-bit): 0\n", - "Checksum: 0x000000\n", - "Min Latency (cycles): 4294967295\n", - "Latency (cycles): 0\n", - "Interval (cycles): 0\n", - "----------------------------\n", - "Testing start depth of 128\n", - "---INSTRUMENTATION_REPORT---\n", - "Status OK\n", - "Frame number (8-bit): 0\n", - "Checksum: 0x000000\n", - "Min Latency (cycles): 4294967295\n", - "Latency (cycles): 0\n", - "Interval (cycles): 0\n", - "----------------------------\n", - "Testing start depth of 256\n", - "---INSTRUMENTATION_REPORT---\n", - "Status OK\n", - "Frame number (8-bit): 0\n", - "Checksum: 0x000000\n", - "Min Latency (cycles): 4294967295\n", - "Latency (cycles): 0\n", - "Interval (cycles): 0\n", - "----------------------------\n", - "Testing start depth of 512\n", - "---INSTRUMENTATION_REPORT---\n", - "Status OK\n", - "Frame number (8-bit): 0\n", - "Checksum: 0x000000\n", - "Min Latency (cycles): 4294967295\n", - "Latency (cycles): 0\n", - "Interval (cycles): 0\n", - "----------------------------\n", - "Testing start depth of 1024\n", - "---INSTRUMENTATION_REPORT---\n", - "Status OK\n", - "Frame number (8-bit): 0\n", - "Checksum: 0x000000\n", - "Min Latency (cycles): 4294967295\n", - "Latency (cycles): 0\n", - "Interval (cycles): 0\n", - "----------------------------\n", - "Testing start depth of 2048\n", - "---INSTRUMENTATION_REPORT---\n", - "Status OK\n", - "Frame number (8-bit): 0\n", - "Checksum: 0x000000\n", - "Min Latency (cycles): 4294967295\n", - "Latency (cycles): 0\n", - "Interval (cycles): 0\n", - "----------------------------\n", - "Testing start depth of 4096\n", - "---INSTRUMENTATION_REPORT---\n", - "Status OK\n", - "Frame number (8-bit): 0\n", - "Checksum: 0x000000\n", - "Min Latency (cycles): 4294967295\n", - "Latency (cycles): 0\n", - "Interval (cycles): 0\n", - "----------------------------\n", - "Testing start depth of 8192\n", - "---INSTRUMENTATION_REPORT---\n", - "Status OK\n", - "Frame number (8-bit): 108\n", - "Checksum: 0x000000\n", - "Min Latency (cycles): 2548522\n", - "Latency (cycles): 5030984\n", - "Interval (cycles): 903174\n", - "----------------------------\n", - "Testing start depth of 16384\n", - "---INSTRUMENTATION_REPORT---\n", - "Status OK\n", - "Frame number (8-bit): 108\n", - "Checksum: 0x000000\n", - "Min Latency (cycles): 2548522\n", - "Latency (cycles): 7496520\n", - "Interval (cycles): 903174\n", - "----------------------------\n", - "Determined start depth for all FIFOs: 8192\n", - "Determined iteration runtime based on performance: 0.127426 s\n" - ] - } - ], - "source": [ - "### Attempt to determine start depth for all FIFOs automatically\n", - "# If it doesn't find a working setting, start depth must be set manually, potentially on per-FIFO basis\n", - "start_depth = 64\n", - "last_interval = 0\n", - "start_depth_found = False\n", - "\n", - "while not start_depth_found:\n", - " print(\"Testing start depth of %d\" % start_depth)\n", - " reset_accelerator()\n", - "\n", - " # Configure FIFOs\n", - " num_fifos = len(fifo_info[\"fifo_widths\"])\n", - " for i in range(0, num_fifos):\n", - " configure_fifo(ol, i, mode = 1, depth = start_depth)\n", - " \n", - " # Start accelerator and let it run for a long time\n", - " start_accelerator()\n", - " time.sleep(1)\n", - " \n", - " # Examine performance\n", - " (overflow_err, underflow_err, frame, checksum, min_latency, latency, interval) = observe_instrumentation()\n", - " if interval > 0 and interval == last_interval and not overflow_err and not underflow_err:\n", - " # Accelerator runs with stable interval, reset to previous start depth\n", - " start_depth_found = True\n", - " start_depth = last_start_depth\n", - " else:\n", - " # Start depth is still too small, increase for next try\n", - " last_start_depth = start_depth\n", - " start_depth = start_depth * 2\n", - " \n", - " last_interval = interval\n", - " \n", - "# Determine runtime per iteration based on performance, so that stable-state is guaranteed\n", - "# Use a simple overestimation for now to be safe\n", - "iteration_runtime = max(0.01, (min_latency * 5) * 10 / 1000 / 1000 / 1000)\n", - "\n", - "print(\"Determined start depth for all FIFOs: %d\" % start_depth)\n", - "print(\"Determined iteration runtime based on performance: %f s\" % iteration_runtime)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "4ba40f96", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Iteration: 12\n", - "Reducing depth of FIFO: 265/266\n", - "Numer of minimized FIFOs: 266/266\n", - "Interval: 903174\n", - "Min. latency / latency: 2549314/2580777\n", - "Total FIFO Size (kB): 244\n", - "Done (389 seconds)\n" - ] - } - ], - "source": [ - "### First pass\n", - "(fifo_depths,\n", - " log_total_fifo_size,\n", - " log_interval,\n", - " log_min_latency,\n", - " log_latency) = size_iteratively(start_depth, iteration_runtime)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "ebf027a4", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAdgAAAE3CAYAAAAJy1DOAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAAAxOAAAMTgF/d4wjAABNoElEQVR4nO3dd5wU5f3A8c+ze527oyPlhKHpDjZEUEFRMRjLGjTRoCZijMZIJImKbWPys0XjGiOaWGLFCnZAdAELitgQVCAis1KXKkXKHe3a7vz+mNljOa7M7u3eXvm+X6993e48U76znnzveeYpyjRNhBBCCJFcrnQHIIQQQrREkmCFEEKIFJAEK4QQQqSAJFghhBAiBSTBCiGEECkgCVYIIYRIAUmwQgghRApkpDsAIYQQLYPmC/wHGAX0Ao4K+b1LHByTDTwAnAmUAwtDfu+lKQ20kUgNVgghRLK8AZwMrInjGD8QAQ4L+b1HADelIrB0UDKTkxBCiGTSfIEQcG60Bqv5Av2Bh4AuQBbwRMjvfUzzBdoAG4CikN+7O03hpow0EQshhEgZzRdwA5OBMSG/N6j5AnnAPM0XmAdUAtuAv2m+wEhgH3BHyO+dnb6Ik0eaiIUQQqTS4cARwCuaL7AI+BwoAAYAmUAfYGnI7x0M/NHer3OaYk0qqcEKIYRIJQX8GPJ7B1Yv0HyBTljPXycBhPzexZovsBorIc9pxBhTQmqwQgghUul7YK/mC1wW3aD5Av00X6BDyO/9EZiN1YMYzRfoBfS2j2n2pJOTEEKIpNB8gUeB84CuwI/A7pDf28/u5PQg0BNwA1uBX4f83g2aL9AHmAh0BMLAnSG/d2pabiDJJMEKIYQQKSBNxEIIIUQKtKpOTkop0+VK8G8K07ReiR4vhBAiLpFIBNM0VbrjSFSrSrAul4twOJzQsfv+9z9Coy+i0zV/oPOf/5zkyIQQQlSnlErsH+wmQqpjDmX17AlA5dYf0xyJEEKI5kASrEOuNm0ACO/cmd5AhBBCNAuSYB1SmZlk9evLrg8+YNfsFjGLlxBCiBRqVcN03G63megzWIDydetYecZPyR8xgkP/+1gSIxMtkWmaVS8hxMGUUtTV8VQpFTZNs9n2FWq2gadD1qGHkqVp7J0/n/DOnbjbtUt3SKIJikQibNmyhZ07d0pyFaIemZmZ9OzZk6ysrHSHknSSYOPU7sIL2PKvB9j+wgvSm1jUaM2aNbhcLjRNIzMzM93hCNFkmabJtm3bWLt2Lf369Ut3OEknCTZO7UaPZsu/HqBy69Z0hyKaoEgkQmlpKf379ycjQ/73EqI+HTt2ZPv27UQikTqbi5ujlnU3jcCVlwdAxcYf0hyJaIqiTcJKNdux8UI0quj/Ky3xcYokWIciEZPSijCmy01G926Ur1mT7pCEEEI0YZJgHZrw/jI8/zeLVT/uIW/gQCrWr2fntGnpDksIRzRNo0uXLlRUVFRt+/DDD1FKceONNwIwffp0brrppnrPtXHjRkaMGJGyWBMxfvx4XnnlFQAWLFjAsGHDyMvL48ILL3R0/LRp05g/f36t5StXrmTQoEEce+yxPPvss0mJOV433HADL7/8cq3lp512Gu+8805c57zjjjsoLy9vaGiiFvKQyKEMt9WMURmJ0OWWW9i74Cu2PvgQ7c4/P72BCeFQz549mT59OhdccAEAEydOZPDgwVXlo0aNYtSoUfWep3v37nz00UcpizNeGzZsYObMmTzwwAMAdOvWjYceeoiFCxfy/vvvOzrHtGnTGDx4MMcff3yN5W+88QZDhw7l0UcfPaissrKyUZ6333LLLQwfPpyLLrooac8q77zzTm688cZm34NX8wVCQKn9Arg35Pe+WsN+VwI+rMrlbOCakN9bmaq4JME6lOm2fqErwyaZPQ4hb8hgSmbMZO/CheQde2yaoxNN2aiXR7Fyx8qUnLtv+75Mv2S6o32vuOIKJk6cyAUXXEBxcTHz5s3jkksuYd++fQA899xzvPPOO7zxxhvMmTOH6667jmHDhvHZZ59RWVnJ888/z+DBgwmFQgwePJgff7SmDVVK8Y9//IOpU6fy448/8uSTTzJ79mxmzZpFeXk5r732GkcccQRz5szhxhtv5KuvvgJgyZIlnHvuuYRCoapzjh07lkAgwL59+3jppZd48sknmTdvHjk5OUybNo3u3bsfdF8TJ07kwgsvrHqWV1RURFFREUuXLj1o33nz5jFu3DjC4TCVlZWMGzeOXr16MX36dD744AOefvpp/vjHP/K73/2u6pgXXniBBx98kEgkwmeffcbkyZO55pprOOmkk5g3bx4A7777Ll6vl23btrFv3z4GDhzIU089RV5eHs899xyTJ0+mQ4cOLFq0iO7du/Pwww9z8803s3z5cgYNGsTkyZNxuVzs2rWL8ePHs3jxYkpLSxk2bBgPP/wwmZmZdOnShd69ezN79mzOOOMMx78jEyZM4OWXX6ayspLMzEwefvhhTjjhBMaOHQvAsGHDcLlcvPfee+Tm5tZ6/dNOO40TTjiBzz//nI0bN3LGGWfw+OOPA1BcXMwNN9zAl19+icvl4rjjjuOxxx5D0zQWLFjAoYceCsBf/vIXIpEI9913n+P443BhyO9dUluh5gv0Bv4OHAtsAd4CrgSeSEUwIE3EjmW4rP95K8IRANpfeikAJTNmpi0mIeJxyimnsGrVKjZs2MDLL7/ML3/5S9xud637f/fdd1xxxRUsXryYP/3pT/z1r3+tdd/CwkLmz5/Pfffdx3nnncfJJ5/MwoUL+c1vfsM999zjKL5t27YxdOhQFi5cyJVXXsnIkSO55ppr+N///sfgwYN55JFHajxuzpw5DBs2zNE17r33Xm644QYWLVrEkiVLuPjiiznnnHMYNWoUPp+PRYsWHZBcAS677DLGjh3LZZddxqJFixgwYAAAixYtYtasWcyePRu3283kyZP56quvWLJkCYWFhTz22P7JaBYsWMC//vUvgsEgeXl5/OpXv2Ly5MksXbqUpUuX8sEHHwBWM/App5zC/PnzWbx4MZWVlQfc97Bhw5gd50xyY8aMYcGCBSxcuJD//Oc/XHnllQBVyfHzzz9n0aJFdOnSpd7rr1y5kjlz5rBkyRLeffddvvjiCwCuu+46cnNzWbx4MYsXL+a+++4jJyeHK6+8kieesPJXWVkZzz77LH/4wx/iij+JLgSmhvzezSG/1wQeBy5J5QWlButQRrQGG7F6uuUcfjgAkV270haTaB6c1jAbw5gxY3j++eeZNm0akyZNYtKkSbXue/jhh1c1IQ8dOpR//etfte570UUXATBo0CBcLhderxeA4447jilTpjiKLT8/v+q4QYMGUVRUxMCBA6vOU1tz7/r16+natauja4wYMYK7776bFStWcPrpp3PyySc7Oq4mY8aMqRrnbJomDz74IIFAgMrKSoqLiznllFOq9j3ppJMoKioC4Nhjj0XTNNq2bQvAMcccw6pVqwCrqXrevHlVzd379u07oPm2a9euzJ07N644Fy5cyD333MO2bdvIyMhg6dKllJeX19gsXN/1L774YtxuN7m5uQwcOJCVK1cydOhQ3nnnHb7++uuqpuvOnTsDcM0113DCCSdw22238corr3DCCSegaVo84buUUutjPk8wTXNCLftO0nwBF/Al8JeQ31t9LGVPILZ3asjeljKSYB3KdB9Yg1V5eZCZSdmKFekMS4i4XH755QwaNIjDDjuM/v3717lvTk5O1Xu3201lZe2PqqL7ut1usrOzazwuIyPjgOUiS0tLDzhH9eOcXj8vL6+qmbs+1113HaNGjWL27NnceuutHHnkkQfUNOORn59f9X7y5Ml8/PHHzJ07l4KCAv7zn/8ckAir30tt92aaJtOmTaNPnz41XrO0tJTc3FzHMZaXl3PBBRcwZ84cjjvuOEpKSmjbtm2tCba+68fzOwHQo0cPhg8fzhtvvMGjjz7quDUjRsQ0zSIH+50S8nvXar5AJnA38DxwTg37xY4FSvlYOmkidijDtf8ZLFjPnbL79aNi3Toi1f6hEKKp6t69O/fee2+qnoHVqXfv3qxevZpt27YB8OKLLyblvEcffTTBYNDRvt9//z19+vThqquu4tZbb616hlpYWEhxcXHCMezYsYOOHTtSUFDArl27eO655xI6z6hRo/D7/VWJa8eOHayI+SPeMAyOOeYYx+crLS2loqKi6hnoww8/fEB5QUHBAfdd3/Xrivv+++8nErEqIFtjJuK59tprueWWWygpKWHkyJGOY49HyO9da/+sAB4Chtew21pAi/ncy96WMpJgHYrtRRxVeOaZhIuL2XTHnekKS4i4/fa3v2Xo0KGNft0ePXpw4403MnjwYEaMGEG7JM3lfeGFFzJz5v6+ECtXrqSoqIjx48czY8YMioqKqmqpDz/8MEcccQTHHnssf/vb36qaQseMGcPkyZMZOHAgTz/9dNwxXHbZZezevZsBAwbwi1/8guHDa/r3vX4PPfQQGRkZDBw4kKOPPpqRI0cSCoUAq3Y5e/ZszjvvvFqPv/zyy6s6eRUVFfHdd99x1113cfzxx3PKKacc0EoA1jPf008/nYEDB7Jly5Y6r1+XBx98kL1793LkkUcycOBAbr311qqyE088kXbt2jFu3LiUTMCi+QJtNF+gXcymS4CFNez6JvBzzRc4RPMFFDAWeCXpAcWQ1XQcmrpwPde/upgnxhzHmUdYz3vMSITQL0dTtmwZhy9aiKqjw4hoHcLhMMuWLeOwww6rswORSJ5IJMKQIUN46623qp5ztkSzZs1i0qRJSav5N5Z169Zx/PHHs2zZMgoKCg4qr+v/GSer6Wi+QB+s5OnGavZdBVwb8ntDmi/wNDA95PdOt/e9CrgFq3L5IfAHu9abEvIM1qHqTcQAyuUi74QTKP3uO3a88godfv3rdIUnRKvlcrl44oknCIVCLTrBFhcXp6VpvyFuu+02Jk6ciN/vrzG5JkPI712FNfSmprLfVfv8FPBUSgKpgSRYhzJraCIG6HDZGLZPnCidnYRIo9gJM1qqaE/t5uSuu+7irrvuSncYaSPPYB2K1mArwgc2qbvbtwcgvG17o8ckhBCi6Up5Ddbw6NnAA8CZQDmwUA8alxoevQvwAtAXKAPG6kHjU/uYPOAZYAgQAXx60Jhil7mAf2N1wTaBCXrQSKyffRyqOjmFD6zBqqwsXHl5lK9NaWc00Uy05JVBhEiFlrwCVWM0EfuxkuRhetAwDY/eLWb7PD1onGV49CHAG4ZH76sHjUrgRqBMDxr9DI/eG/jC8Ogf6UFjB3ApMAA4DGgLfGN49A/1oOGsn36ColMlVkQO/IdTKUWWphHeuTOVlxfNhMvlIicnhw0bNnDIIYfIgutC1CG64HpmZmaLWwsWUpxgDY/eBvgtUKQHDRNADxrRhVRHA73tbQsMj74ZOBmYA1wEXG6XrTY8+lzgPOA5u+xxPWiEge2GR38NuBi4I5X3kpNp9W7bU3bwwGpXQQFlq1dTsWkTmQ5nlBEtV69evdiyZQuhUEhqskLUIzMzk549UzqhUtqkugbbF9gG/M3w6COBfViJcBHg0oNG7FRWIfZPW1XXlFY1ldXYw0EpNR4YH/M5kXsAoGtbawaTLSVlB5W1u+AX7P3ySzaMvwFtcu1Tz4nWweVy0bVrVw455BBM05QkK0QtlFItsuYaleoEmwn0AZbqQcNnePRjgA+AIzlwyio4eNqquqa0cjTdlT1nZdW8lW63O+F/6dzRZ2sHhQ1tR41i+wsvUr56daKnFy2QUqpFPlcSQjiT6j8d1mA9f50EoAeNxcBqQAcwPHrnmH1jp62qa0qrRp/uCsBeTIfaKiMZnToR3rEDsyJlY5aFEEI0IylNsHrQ+BFrUdszAQyP3gvruev3wOvAOHv7EKAr8Kl9aGxZb+BUYHpM2dWGR3cbHr0D1jPZgxbWTbqqBFtzhs3qa02OXTJrVspDEUII0fQ1RuP3WOBmw6N/i7XA7e/tjk63AMMMj74cq/PSGLsHMcD9QK7h0VcA7wLj9KARHWj6IlaCXgYsAO7Xg4aR6ptQRJuIa1ZoL4BcuXlzqkMRQgjRDKR8mI4eNFYBp9WwfTPw01qO2YNVM62pLIxdu21Mqp4mYldhIQAVm7c0UkRCCCGaspbbfSvJol1VaurkBJDZvTuuwkJKZs2ssVwIIUTrIgnWIVfVDD21lOfk0OaE4wn/uE2GZQghhJAE61S0iThSR+505ReAacqsTkIIISTBOqViGolrk9XLmgujLJjSWRuFEEI0A5JgnaqnkxNA7rGDAPjx0ccwy8sbISghhBBNlSRYh+rrRQyQd/wQ2l10EXu/+oo9X85vnMCEEEI0SZJgHXLVMVVilFKKwjOtkUfFU6c0SlxCCCGaJkmwDkWfwNbVyQkg74QTyOrbl10fzUl1SEIIIZowSbAOOWkiBlBuN9mH9cfctw+z8uCl7YQQQrQOkmAd2j9VYv1jXN1t2wJQsWFDSmMSQgjRdEmCdUjVP0qnSnb//gDsW7QoZfEIIYRo2iTBxsnJHE0Fp52Gq6CATX+/mwqZ/F8IIVolSbAORXsRRxxMg5jZowddbryRyO7d7Pns81SHJoQQogmSBOuQ005OUXlDBgOw/dlnUxSREEKIpkwSrENxPIIFILtPH9qcdBLloVCKIhJCCNGUSYJ1SFWtpuN8pRx3+/aYFRVEyspSFZYQQogmShKsQ/HWYAEyunQBoMwwkh6PEEKIpk0SrEPxDNOJyj36aABKZs5KfkBCCCGaNEmwDqk4ehFH5Z8+gpwBA9j+/POULl2aqtCEEEI0QZJg46CU817EAK6sLDr89nIASmbOTE1QQgghmiRJsHFQOJsqMVb+iBG4O3Zk5xtvpiYoIYQQTZIk2DgopeKqwQK48/PJG3Qs4eLiuHogCyGEaN4kwcZBUf9ydTVxtcmHSITw9u1Jj0kIIUTTJAk2Dm2yM9hdVhH3cVl9+gBQulSG6wghRGshCTYO3drmsKm4NO7j2px4AgA/3H4b4ZKSZIclhBCiCZIEG4eubXP4obg07mepuUcfTYcrrqBy4w+yhJ0QQrQSkmDj0LUwh7LKCDv3xt9MnHvUkQBE9uxJdlhCCCGaoIx0B9CctMm2vq59FWHax3msu2NHAHbN/pDCs89OcmRCCNG6ab7A7cAdwFEhv3dJtbLTgBnAspjNQ0N+775UxiQJNg5ZGVaFv7wyEvexeYMH4+7UibJly+rfWQghhGOaLzAIOBFYW8duS0N+7+BGCglohARrePQQUGq/AO7Vg8arhkfvArwA9AXKgLF60PjUPiYPeAYYAkQAnx40pthlLuDfwDlYMwNP0IPGY6m+D4Ast5VgyxJIsMrlIvOQQ6jcIUN1hBAiWTRfIBt4FPgV8FGawzlAYz2DvVAPGgPt16v2Nj8wTw8a/YHfApMMjx5N+DcCZXrQ6AecCTxmePRoq+ylwADgMOB44GbDo3sa4yYaUoMFyOjalcqNP1D6vdRihRDCAZdSan3Ma3wN+9wFvBTye1fXc67DNV/gG80XWKD5AtekINaDpLOT02isvzrQg8YCYDNwsl12UUzZamAucF5M2eN60AjrQWM78BpwcWMEnB1NsOFwQscXnnUWAGXfB5MWkxBCtGAR0zSLYl4TYgs1X2AoVktnfa2Y3wBFIb93EPBzYKzmC4xOTcj7NVaCnWR49G8Nj/604dE7Gx69I+DSg8bWmH1CQE/7fU9gTQJlB1BKjY/966ehUxVGE2wiTcQAGZ07AVC5TZqJhRAiCU4FPMBqzRcIAUXAu5ovcEBP0pDfWxLye4vt9+uBl4HhqQ6uMTo5naIHjbWGR88E7gaeB8Zw8MqqqtpnM8Gy/TtZf+1U/cXjdrsblGEb2kSc2a2bdfyqVQ0JQwghBBDye/1YjxsBsJPsuTX0Iu4GbA75vRHNFygAzsXq55NSKa/B6kFjrf2zAngIGK4HjW0AhkfvHLNrL/b3AFsLaAmUpVSDE2zPnqjsbPZ+9RXh3TIeVgghUkXzBZ7WfIFR9scLgG81X2AxMA94H3g21TGktAZrePQ2QKYeNHbamy4BFtrvXwfGAXcYHn0I0BX4tFrZ5YZH743VDDA2puxqw6NPAdpiPZM9K5X3EZXldgNQHk4swSql6PSHsWx96N/seOlFOo0dW/9BQgghHAn5vVrM+9/FvH8EeKSx40l1DfYQ4CPDo//P8OjfYiXKy+yyW4BhhkdfDjwHjNGDRqVddj+Qa3j0FcC7wDi7QxPAi8D3WAOGFwD360GjUWbRz8m0vq7SisQSLED7S8cAsOvDjzAjiZ9HCCFE05bSGqweNFYBx9ZSthn4aS1le7BqpjWVhbFqt40uJ9Oqwe6rSKwXMYA7vw35I3/C7g9mU7Z8OTmHH56s8IQQQjQhMhdxHKIJtmRf/HMRx2pz4lAAwjuLGxyTEEKIpkkSbBzy7bmIF4QaNszGXZAPQPmaUENDEkII0URJgo1DUftcADq2yW7QebLtZuHy0Jp69hRCCNFcSYKNg7JH3JoHDeGNT1afPmR07syOSZMoW748CZEJIYRoaiTBxkFRlWEbxJWVRdc778QsK2PXh01qbmohhBBJUm8vYntlm/pE9KBRWv9uzZuqdc6o+OUeOxCVnc32l16k45VXoDJk5UAhhGhJnNRgdwO77J/VX9HtK1MVYFPUwAosABnt21Po9RLe+iPhYulNLIQQLY2TatNiPWjUOJY1yvDoC+sqFzXL6NgBgHBxMRkdO6Y5GiGEEMnkpAb7pyTt02I0dFWeqIzO1lTMZd9/n5TzCSGEaDrqTbB60Pg0Gfu0BCo5fZyq5Bx1FAAl776XtKQthBCiaXDSySkXuBzYgbW4+T+BM7HmA75WDxobUhlgU6JqXxkvIbnHHEP+iBHsmjWLfZddRt6gOlvihRBCNCNOmoifAs4Bfg+8B7QDbgZWA4+nLLImLFmVTeVy0e7CCwDY9e67yTmpEEKIJsFJgh2kB42fYSXZwcDv9aAxUw8aNwG9UxpdE5PsJmKANiedREbXrhS//XYSzyqEECLdnCTYMgB7nOtqPWjErrFWnpKomqjkNhBbXDk55AwYQHjXLnkOK4QQLYiTYTrZhkfXsfJL7HuAnJRF1oQlOxG6CwqgooLwtm1kdOqU1HMLIYRwTvMFtjjYbVPI7z26vp2cJNg8YEbM5xm17djSqWRO5RQju38/AEq/+478U09NyTWEEEI4shXrkWhtFDDdyYnqTbB60NCcxdR6JLsht83w4fDQv9l05130mTUTV1ZWkq8ghBDCoTtDfm+dS51pvsDdTk7keLJ/w6OfWcO2sU6Pbwmq6q9JzrA5hx9Oh8suo2LjRsqWLk3uyYUQQjgW8ntfS8Y+EN9qOvcbHv2o6AfDo48Brojj+GYvRS3EALQZeiIA2yY+m7qLCCGEcETzBe7SfIF2mi+gNF8goPkCP2q+wAXxnCOeBHsxMNnw6N0Nj/4L4Ebg7Hgu1lI0dD3YmuQPH05G926Ur5FF2IUQogk4L+T37gRGApXAScBf4zmB4wSrB42lwJ+xJpv4O3CmHjS2xXOx5i5VnZyiMtq1p3KLkw5sQgghUiw6JPVU4PWQ3xv3pPFOpkr8Z7VNlcByYLzh0dGDxs3xXrS5S9Vw1YyuXSldupRwcTHutm1TcxEhhBBO7NF8AR9W6+1Jmi/gAuLqgeqkBrun2msqsCTmc6uTqgSbe7Q1rGrnlKmpuYAQQginLge6AjeH/N7NQB9gUjwnUK1p9iC3222Gw+EGnaP3XwKcOaArj485LklR7RfeuZPVoy+icssWDv/ma5QrnkfkQgjRsiilwqZpOpmvISU0X8ANHBrye0OJHF/vv+CGR6+3p7CTfVqSVHRyAnC3a0f+Kadglpay+6OPUnINIYQQ9dN8geHAGmCu/XmI5gu8GM85nPxlcKPh0b+g7ql4rwMmxnPh5kqRuiZigA6X/podL71ESWAGBT/5SeouJIQQoi7/xOrg9AZAyO9doPkCg+I5QSJTJdZkazwXbc5S3ZM4S9NQOTmEd+9K6XWEEELUKSPk967UfIHYbXEtcCNTJSYg1U+t3W3bUr5iZYqvIoQQog6lmi+Qj/1PvuYLHAGUxnMC6UUTp9TWXy3Z/fpRsWULlTt2NMLVhBBC1ODvwLtAd80XeA6YDfxfPCdotN5Zhke/HbgDOEoPGksMj94FeAHoi7Xm7Fg9aHxq75sHPAMMwRrs69ODxhS7zAX8G2u1AxOYoAeNxxrrPiC1z2ABCs85hz2ffca6q36P9uorKLc7tRcUQghxgJDf+57mCywHzsKqW90d8ntXxHOORkmwhkcfBJwIrI3Z7Afm6UHjLMOjDwHeMDx6Xz1oVGJNw1imB41+hkfvDXxhePSP9KCxA7gUGAAcBrQFvjE8+od60Ag2xr1Yj2BTm2HbXfAL9s7/kuK3plPxww9kFRWl9HpCCCEOFvJ7VwP/TfT4uBOs4dEz7CTodP9s4FHgV0Ds2JPRQG8APWgsMDz6ZuBkYA5wEdYgX/Sgsdrw6HOB84Dn7LLH9aARBrYbHv01rJk27oj3XhKhGqWRGDJ79gQgsnt3o1xPCCEEaL7AAuqoRYX83uOdnstxgjU8+hFYs1h0BA41PPpxwGg9aNxSz6F3AS/ZiTJ6ro6ASw8asb2PQ0BP+31PrPFHTssG13RhpdR4YHzM53pCdaYx5ubI6NQZgF0fzCbH40n9BYUQQoDVgpoU8XRyegT4I/Cj/fkbwFvXAYZHH4r1HLWmZ6TV01T17GcmWLZ/J9OcYJpmUfSVlATbOBVYCr3WV1u2Iq4mfyGEEA0Q8ns/Dvm9HwNfAnNjPn9ib3MsngRbEO2EBKAHDROoqOeYUwEPsNrw6CGgCKtX1vEAhkfvHLNvL/Y/o10LaAmUNYrGmFzSnd8GlZdHZJeMhxVCiDT4ECiM+VwAfBDPCeJ5BltpePRM7PxiePQi9i/nUyM9aPixOjNhHxMCzrV7Eb8OjAPusDs5dQWiCTxadrndyelUYGxM2dWGR5+C1cnpIqxeXo3CmsmpceZvzuzShb1ffSWr6wghRD00X6BqpErI711SQ/mVgA+rYjkbuCbk99bVnygv5PcWRz+E/N5izRdoE09M8TYRTwU6GR79Dqz5Ge+P52LV3AIMMzz6cqzOS2NiOk/dD+QaHn0FVo13nB40tttlLwLfA8uABcD9etAwGhBHXFI8kdMBCs4+C7OsjPJ16xvvokII0czYUxhWH6kSW94ba1zryUA/rArdlfWc1hWbUDVfoADIjCcuxzVYPWi8ZHj0VVi9efOA3+hB45N4LhY7K5QeNDYDP61lvz1YNdOaysJYtdu0aaz1hzI6dQIgLBNOCCFEjTRfoLaRKrEuBKbay86h+QKPAzcDT9Rx6knAe5ovEB2m8wfg+Xhic1yDNTz6cKxxq7foQeNmPWh8Yo9vbVUUqlF6EQNkdLYeUZevkmkThRCtkksptT7mNb6Gfe4CXrLHrNamrpEpNQr5vfcBTwKj7Nd/Q35vXK228TyD/Qh4z/DoF+pBY6+97WmgVSXZxmwizj3iCAB2f/wx7X/9a1RG2pZFFEKIdIiYplnrTDuaLxAdqeJzcC5Ho09izt0u5Pc+T5y11ljxPIP9FqsT0lzDox9ib2vEdNN0NFYTcWaPHhSO+hl7Pv+CXR/MbqSrCiFEs1E1UkXzBULYI1U0X+DsavslMvpkueYLPKX5AkcnGlw8VSJTDxr/MDz6Wqwkez6Nl2uajMb+i6LDmDGUTH+bXe+9S+FZZzby1YUQoukK+b0HjFSxk+y5NfQifhP4VPMF7gK2YI1KeaWe0/fD6gj1puYLbAIeBt4M+b1hp/HFU4NVYHV2wnrYOwPoEcfxLUZjDdMByBkwgOz+/SmZOYtIaVwrJQkhRKul+QJPa77AKICQ37sKuB34DFiJlWSfqev4kN9bHPJ7J4T83v5YSfxfwFrNF/ir0+E68dRgH4m+0YPGh4ZH/xkx0xC2FqlecP2g67nd5A0ZQtny5UR278aVk9Oo1xdCiOYi5PdqMe9/V63sKeCpeM5nD825HLgG+M4+/ifALGB4fcfHM0znmWqflwBXxBFri5COh86uggIAKjZurBq6I4QQInXsoTznYTUvnx/ye7+3i6ZovoCjuRfqTbCGR39RDxpjDI9e4woDetBwvLJAS9GILcQAZPftA0DZ8uXkHp3w83YhhBDOrQA8sbM5xTjdyQmc1GAfsn8mbYWBZi0NVdi8445DZWWx5YEJ5J9+Ohnt2zd+EEII0bp8TMx0wJovUAgcFvJ7vwr5vT84OUG9CVYPGl/bPz+ObjM8ejs9aOyMO9wWwmzkztOZPXrQ+dpr2XL//ez9cr70JhZCiNR7AmuMbdRee9txTk9Qby9iw6NfZ3h03X7vMjz621gLnW+1l6NrVazJ/hv/unnHWy3x2597rvEvLoQQrY8rdkiOvTBAXLP9OBmm8zusbs0Av8QaG9QNq2fVffFcrCVo7F7EUblHHUnuscdStrqu2cCEEEIkSbnmC/SNftB8gX7Uv0TrAZxk40o9aJTb738CvGhP1B8wPPrd8VyspUhHDRbA3aEDkYULMU0zbYleCCFaiTuxJqcI2J/Ppv4VeA7gpAabYXj06L/mQ4HPY8riWrqnJVCq8Z/BRmV0tobolK9alZbrCyFEaxHyewPAKcA39uuUkN87K55zOKnBzgZeNjz6JqwFzj8FMDx6V6AsrohbgHTWG3OPOoqdr7zKrtkfkt23b/0HCCGESFjI710OLE/0eCc12BuA+fb7s2IWRe8PTEj0ws1ZupqIC848k8yiIrY9/TSRslb3t40QQqSc5gtMS8Y+4GyYTiU1JNJ4F1tvKdL57NOdn0+HMZey+V4/u97/gLbnetMWixBCtFBDNV/gn/Xsc4STE8Uz2b+wpasGC1A4ahRkZrJj0qT0BSGEEC3XY8Ceel6POzmRrOAdJ5eC+aHtLN+8C5crHbXZTHaOPJfIzGlUbt9ORocOaYhBCCFappDfe2eyzqUac+m1dHO73WY47Hgpvxqd+/AnLNlQkqSIEnfR97O5as939Jk2FVdeXrrDEUKIpFNKhU3TbLYVQccJ1vDoI4BB9sdv9KDxUcqiSpFkJNhNxaW8umAdlZFI/TungGnCIx+t4Az3Dsa/eQ+F55xDjwkPpCUWIYRIpRafYA2PXggEAA34GmukyiBgDXCOHjTSX51zKBkJtinod+sMTuvXgVteupXKjT/Q//PPpKlYCNHiNPcE66ST0z+BhUAfPWicrweN84C+9rZ/pTI4UbOcTDdlpqLtz0YBsO2JJ9IckRBCtCyaL3C15gs06PmbkwQ7ErhODxpVczDaUydejzV1omhkOZlu9pWH6TDmUlyFhWx//gV2zZ6d7rCEEKIlORVYrfkCD9rzEMfNSYKt0IPGQQ8c7fGx5TXsL1IsN8vFvoowGZ060XPiRAC2PPhgmqMSQoiWI+T3/go4BtgJfKT5AjM0X+CceM7hJMHuMjz60dU3Gh79GKzxQKKR5WS4Ka2wniXnHnkEOUccQfmKlZQtT3hGLyGEENWE/N5N9rCdXwNHAi9pvkBQ8wUctd46eXh8F/tXzpkHmMAw4G/AHxILWzREXnYGq7furlpVp91Fo9l02+2sueIK+r37rgzbEUKIBtJ8gRzgV8A4oBS4CXgDa8H117A6/tbJyVSJ7xgevRL4K/unTPwauEoPGjMTilw0SI92OSxet5OKsElWhqL96NHsW7iI4qlT2f7CC3QaOzbdIQohRHMXAt4Hxob83gUx2+drvsD7Tk6Q8okmDI/+HtAViAC7gD/pQWOR4dG7AC9g9UguA8bqQSO6Uk8e8AwwxD7OpweNKXaZC/g3cA5WbXqCHjQecxJLSxmmc/2ri5i6cAPBv59FTqYbgModO1g+dBi5AweivfJymiMUQoiGS+cwHc0X6Bbye39oyDnqfQZrePTHYt6fl8A1RutB42g9aAwEHgAm2tv9wDw9aPQHfgtMMjx69Iu8ESjTg0Y/4EzgMcOjt7fLLgUGAIcBxwM3Gx7dk0BczVZ0vYHYv40y2rfHlZ9PeSjEvv/9Lz2BCSFEyzFW8wU6Rj9ovkAnzRe4PZ4TOOnkdGLM+7hODqAHjZ0xH9ti1UgBRgOP2vssADYDJ9tlF8WUrQbmAufFlD2uB42wHjS2Y7WFXxxvXM2Zy86w4WqtD11uuonwnj388H+3pSMsIYRoSc4L+b3boh9Cfu+PwPnxnMBJ1VvV8t4xw6O/AIywP55lePSOgEsPGltjdgsBPe33PbFminJaNrim6yqlxgPjYz4nEn6T47bvI1Itwba/aDS73nuPPZ99Run335Nz+OHpCE8IIVqCmhJGZjwncJJgsw2PrtsXi30PgB40ltZ3Aj1oXAZgePTfAPcDY7Cen8aqfjNmgmX7dzLNCcSsZet2u1vEygYuu93BrGE65EKvlz2ffUbxlKnk/MXXuIEJIUTLsUzzBcYDD2LlmeuBYDwncNJEnAfMwJqPODfmfQB4J56L6UHjefbXZDE8eueY4l7AWvv9Wg7sAu20rFVQtdRgAQrOGAmZmeycMqWxwxJCiJbkWuBcYB/WnA9nAX+K5wROhuloiUQGVQsF5OtBY6P9+efANmA78DrW+KI7DI8+BKun8af2odGyyw2P3htryqqxMWVXGx59CtYz3YuwbrzViC5DW/0ZLIC7oICC005j1/vvs+eLL2gzdGgjRyeEEM1fyO/dCJyu+QJt7M9xT6yU6u7PbYE3DY+ei9W5aStwrh40TMOj3wK8aHj05VhTLo6xp18Eqxl5ouHRV9jHjbM7NAG8iDV8Z1l0Xz1oGCm+jyaltmewUW1/fj673n+fdWP/QL8P3iejc+ca9xNCCFE7zRfoBvQGMjRfAICQ3zvX6fFOlqvbysHPS8Fqkzb1oNHFcbRp1lLGwd4x/Tue+zzEl7f+hEMKc2rc58ennmLrAxNoN3o03e66s5EjFEKIhkvzONi/Ys3etAqIJg4z5Pce7/QcTgKvsYeuSB9XPTVYgI6/+x0/PvIoJTNmcMjf/oorK6uxwhNCiJbgCqCfPTwnIU4S7B49aCR8AZF8Vc9gI7UnWKUUhed6KX5zChtvupmifz/UOMEJIUTLsKkhyRWcJdj3gEEAhkd/Rg8aVzbkgqLhXHaGrW+Wy663386ez79g17vvsmfel7Q58YRGiE4IIVqEdzVf4AFgEtZk/wCE/N56h6ZGORmmEzvO9FjnsYlUcdJEDODKyqLr3/4KQPHUqSmPSwghWpDfAr8A3iTBoalOarAtYnKGliTaRFxHC3GVNsOGAVD81lsUnnsu+cNPrucIIYQQIb+3d0PP4STB9jA8+j9reA+AHjRubmgQIj6FudZsXT/uLqN3pzZ17uvKzeXQp55i3VVXsfHmm+k35yNc2dmNEaYQQjRrmi9wHuAJ+b33ab5Ad6BjyO/91unxTpqIH8OaxWJPtffRl2hknq4FACxet9PR/vnDT6bQ6yW8Ywf7Fi1OYWRCCNEyaL7AHVgTHEX7HZnA4/Gcw8lMTjKIsokZ0L0QgNA253/f5I8YQUkgwIZrr6X31ClkduuWqvCEEKJRab7AQeuOh/zeRdX2OQ1rqt9lMZuHhvzefbWc9nzgOOArgJDf+4PmCxTEE1daBvCKhinMsZqId5dW1rNnzDHecyhbtoxtTz7Jjskv0+WG8fUfJIQQzcPokN+7E0DzBc7HWnd8UA37LQ35vU7ndigN+b3h6AxOiXDSRCyamJxMN1luF7viSLBKKTpc/hsAiqdPT1VoQgjR6KLJ1Ra77nhDrNF8gZMBU/MFXJov8DfA8fNXkBpss1WQkxFXggXI6NCB/JE/YfcHs9m3eDG5xxyTouiEECIpXEqp9TGfJ9jLkB5E8wUOWHe8lvMdrvkC32BNffhsyO99rI5r/xl4HjgS2At8AlwaV/Dx7Cyajra5mezYWx73ce0uvBCAHZMnJzskIYRItohpmkUxrxqTK0DI770s5PceCvwNa8GY6r4BikJ+7yDg58BYzRcYXcf5Nof83rOAdkCnkN97Rsjv3RxP8E4m+19AHWNh9aDheOLjdGspk/0DXPncAuYu38rSu84i0+3876TIvn18f+wgXHl5aK++Qnb//imMUgghEpfoZP+aL7APK5luq2OfvwDdQ35vjWu8ar7A/OoT+9e0rS5OAr/R6clE4+nWLoeKsMnOvRV0LnA+rtWVm0vX229j0513sfbK39F31kxceXkpjFQIIVJH8wUKgXx7/VY0XyB23fHY/boBm0N+b8TuDXwu8Ewdpz4gP2q+gBvIjyc2J8N0Po7nhKJxRKdLNBOYaKv9JZew95uFlLz9NjunTaPDr36V7PCEEKKxtAXe1HyBA9YdD/m9puYLPA1MD/m904ELgD9ovkAlVu57HXi2+sk0X+Am4GagreYLbIkpysOal9ixepuIowyP3gm4HTgGqFqEVJqI0+P2t5bw/BdrmH/rT+hSy5qwdSk1DFb//Bdk6zraKy/L7E5CiCYnHevBar5AW6A98F+siSaiSkJ+7454zhVP4BOBz4AzgRuAq4GF8VxMJI+qqsEmJkfXyR6gU7bUYNNtt9P9Pn/yghNCiGYq5PcWA8XA2Q09VzwJtqceNEYZHv3XetB42/Do7wIzGxqASIydX+tdsq4u2ssvs3z4KRS/9RYdf38V2X37Jic4IYRo5jRfoC/wENVabUN+bxen54hnmE50TEiZ4dE7AJVAURzHiyRSOFuyri6u7Gy6jLdmdNr55pSkxCWEEC3E08BLWFMv/gSYhpVwHYsnwX5vJ9aXgHnAl0gTcdpU1WAbeJ78U4aDUmyfOJGS999vcFxCCNFCtA35va8CEXsFnauBM+I5geMEqweNMXrQ2K4HjX9jLUR7JyDdT9PEzq847aRWm8zu3en5rNWRbtP/3UakrKyBkQkhRItQYf/cpfkCvYBsoFc8J3CcYA2PXjWllB40PtODxjvAI/FcTCSPy151vYH5FYA2J55A4aifEd65U5azE0IIy8eaL9ABK899BawA4prIPZ5OTifWsG1oPBcTybO/Bpuc8+Wfciol099mw4030Gf6dDLat0/OiYUQohkK+b03228na77AJ1jjbbfXcchB6k2whkf/JTAa0AyP/lpMUVtkwfX0qXoGm5wMW+g9h73z57PztdfYePMt9HzqyaScVwghmruQ37sOWKf5AmuBnk6Pc1KDXQYEgOPtn1ElwOx4ghTJE+1FnKwarFKKrrffxu5PP2HPJ59glpejsrKSc3IhhGgZVP277OdkqsTFwGLDowf0oLE14bBEUrmS1Is4lnK7yT/lFHa+8io/3HEn3e65u2pCCyGEEPH9kxvPM9gMw6O/w/719mYDV+tB44d4LiiSI5r3GjIOtiadr72WPV98QfGUKeQceYTMUyyEaFU0X2BAHcVxTdsYzzjYJ4HPgR7263N7m0iDZDcRR2W0b0/PZyYC8ON//5vckwshRNMXqONVGs+J4snGh+pB42cxn/2GR18Uz8VE8uxvuU1yhgWyinqQO3Ag+xYtomTWuxSedWbSryGEEE1RyO/tnaxzxZNgXYZH76oHjU0AhkfvQj0PfA2PngO8AgwA9gKbgLF60AjZx78A9AXK7O2f2sflYa3TNwRr+SGfHjSm2GUu4N/AOVjZZYIeNB6jlama7D/5+RWATn8Yy/o/X8uG668nu+9bsjC7EELEqd4mYsOjv2y/vR9YaHj0Jw2P/gTwtb2tPk8Ch+tBYyDwDvublf3APD1o9MeaGWqS4dGjCf9GoEwPGv2wVu95zPDo0YGZl2Il7MOwejbfbHh0j4M4WpToXzaRFCXY/FNPpdvdfwfTZMerr9V/gBBCiAM4eQbrAdCDxotY8zD+D1gCnKkHjZfqOlAPGqV60JihB41oGpgH9LHfjwYetfdbAGwGTrbLLoopWw3MBc6LKXtcDxphPWhsB14DLnZwHy2KSvI42JoUjBwJQMnMmQ2eklEIIVobJ03EVf+y6kFjCVZyTdSfgbcNj94RcFUb9hNi/wDensCaOMoG13QxpdR4YHzM58Qjb2JS1ckplis3l/yRP2H3B7PZ++WXtDmxpsm8hBBC1MRJgj3K8OhbatiuAFMPGo7WxjM8+q1Af6wV4nM5uHdO9exnJli2fyfTnABMiH52u90tphpWNQ42xXfU/pe/ZPcHs9n52uuSYIUQIg5OmoiXYXU2qv4abP+sl+HRbwR+AZytB429etDYZm/vHLNbL2Ct/X4toCVQ1mqkahxsdXlDhkBmJiUzZlDy7nspvZYQQrQkTmqwZXrQWFP/bjUzPPp44BJgpB40dsYUvQ6MA+4wPPoQoCvwabWyyw2P3hs4FavmGy272vDoU7DmQ74IOCvR+JqrxmruduXl0WviM6wZcxmbbr+dghGnyRSKQgjhgJMabML/khsevQh4AGgHfGR49EWGR//SLr4FGGZ49OXAc8AYPWhU2mX3A7mGR18BvAuMszs0AbwIfI9Vs14A3K8HDSPRGJu7xuh7lDdkCIU/s5az27toUeovKIQQLYBqTb1D3W63GQ6H0x1GUvx3zkrumxXkrXEnccyh7VJ+vZKZM9lw/XgyunSh76yZuPLyUn5NIUTrppQKm6YZ1/SETUk8UyWKJkSlYLL/uhSefTZtL/gFlVu2sOmeexrpqkII0Xw1278MWrtou/0bX69LeUenKPOKa1n76f8w53xD//U7GdC9LW5Xyxn6JIQQySRNxM3Uu99t4uoXv05rDHeddwSXDdXSGoMQouVq7k3EzTbw1u7MI7ry+KXHsW773ka9bnjXLta/OY2Xugxm5aIgSIIVQogaSYJtxs46smtarrtNb8dLTy7hh7mfU6KZFJ59dlriEEKIpkw6OYm4tdd6ojDZlZnHhuvHs/7P16Y7JCGEaHIkwYq4uVyKdnlZbDliMK7CQna99x4bb7mFcElJukMTQogmQxKsSEhR+zx2VULv114lW9cpfms66676PWZ5ebpDE0KIJkESrEhIXpabyohJlqbR+43XyezZk32LF7P81NMoX78h3eEJIUTaSYIVCcl0u6iojACg3G56T5lCm2HDCO/YwborryS8e0+aIxRCiPSSBCsSkuFWVEQiVZ/d+W049KknAShfs4ZVo36GWVlZ2+FCCNHiSYIVCcl0u6gMHzhJiXK76f/pJ6isLCo3/sDme/1pik4IIdJPEqxISKZbURkxqT4TWEanTvSdOQOAHZMmsW3is5gtZPYsIYSIhyRYkZCC7EwAtu4uO6gss0cPDn3qKVz5+Wz55z/ZLIsDCCFaIUmwIiHHae0BmBPcWmN5/vCT6fPO26AUOya/zJrLfkOk7OBkLIQQLZUkWJGQY+01aNftqH0u5MyuXekzI4C7Uyf2zp/P+mvGyWQUQohWQ+YiFgk5pG0OAJuKS+vcL7t3b/q8PZ1VZ5/Dns8+Y8WI0zls/pcot7sxwhRCtAKaL/Ae0BWIALuAP4X83kU17Hcl4MOqXM4Grgn5vSkb7iA1WJGQguwM8rMzmLlkU737ZrRvT/9P5pIzYACRPXtYNuwkSr9f1ghRCiFaidEhv/fokN87EHgAmFh9B80X6A38HTgZ6IeVkK9MZVCSYEVClFKUVoSpjBkLW+f+mZn0evEF8k87jUhxMWt+/WsipXXXfoUQwomQ37sz5mNbrJpsdRcCU0N+7+aQ32sCjwOXpDIuSbAiYcP7d4prf1ebNhQ9+giZvXoS2b2bjTfdhFlRkaLohBAtgEsptT7mNb62HTVf4AXNF1gH3A38poZdegJrYj6H7G0pIwlWJMylFA4rsFWU24328suonBx2vf8B3x9/gnR8EkLUJmKaZlHMa0JtO4b83stCfu+hwN+A+2vZLXbgvkpmoDWRBCsS5nIpItUmmnAio0MH+n88h5yjj8bct481v76Ufd99l4IIhRCtTcjvfR4YofkCHasVrQW0mM+97G0pIwlWJMylIJxAggVwt21Lr+efI//00ylbvpzQBRdStmpVkiMUQrR0mi9QqPkC3WM+/xzYBmyvtuubwM81X+AQzRdQwFjglVTGJglWJMztUpgmB02X6JQrN5dDH3uUTtf8AYDV5/+c7ZMmJTNEIUTL1xaYpvkC32q+wGJgHHBuyO81NV/gac0XGAUQ8ntXAbcDnwErgS3AM6kMTCX6j2Nz5Ha7zbDMi5s04yZ/Q+B/P7DyH+fgdjXscUbx22+z8aabAejmv5e2552HUil/RCKEaMKUUmHTNJvtfA1SgxUJc9sJMJHnsNW1/dnPKHr8v+By8YPvL6y94ooGn1MIIdJJEqxIWLTSGo4kpxWk4LTT6PfB+7jy89n7xTzW/OZyKjZvTsq5hRCisUmCFQlz2Rk2mU8ZMrt3p9ekSWT378/eL78kdNHFVGyqf7YoIYRoaiTBioS57CbiRHsS1ybn8MPoPf0tsj0eKjdtYsVpI9j37bdJvYYQQqRayh8eGx79P8AorDFHR+lBY4m9vQvwAtAXKAPG6kHjU7ssD6t31xCsKa98etCYYpe5gH8D52ANGp6gB43HUn0f4mDJfAZbnVKK3q+9ykbfXyiZMYN1v7uKfp/MxZWVlfRrCSFEKjRGDfYNrMmV11Tb7gfm6UGjP/BbYJLh0aMJ/0agTA8a/YAzgccMj97eLrsUGAAcBhwP3Gx4dE+K70HUwGX/9phxzubklMrKoseEB8ju359wcTEbrrue8M6dqbmYEEIkWcprsHrQmAtgePTqRaOB3vY+CwyPvhkrEc8BLgIut8tWGx59LnAe8Jxd9rgeNMLAdsOjvwZcDNyR2jsR1UWH5pz177l0KcxJ3YVG3kBZn1WY+0pRf32dbF0HpcjNdPHPC46hZ8e81F1bCCESlJbxRYZH7wi49KCxNWZziP0TL9c1KXNNZYNruo49MfT4mM8NiFpU9/NjezB/9XZ2l1aytSTFK+N07Unltm2Y5eWwch1bswsBmLpwA9eO7J/aawshRALSOYC3+oO76tmvrkmZHU3YbE8MXTU5tNvtbj2zajSC43p14L3rT22060X27GHT3fdQPHUquws78MvTb+WzlT9KghVCNElp6UWsB41tAIZH7xyzOXbi5bomZW70CZtF0+Bq04bu9/6DtuefT37JdrLCFexZt57ydevSHZoQQhwkncN0XseaMxLDow/BWl3+0xrKegOnAtNjyq42PLrb8OgdsJ7JvtqIcYs063bvPyj672N0qdjFnm07WXnGT1n1s1Hs/uRTTJkKUwjRRKQ8wRoe/VHDo68HioAPDI++wi66BRhmePTlWJ2XxuhBo9Iuux/Itfd9FxinB43oyggvAt8Dy4AFwP160DBSfR+i6VBKUTBiBG21nhR37Ia7a1fKli9n3VVXsWrUeRS/9VbCCxAIIUSyyGT/otn627RveWneWmb8+WS0jcvZdM8/KLP/1soZMIBOfxxHwemnpzlKIUSiZLJ/IdLknCO7AfDMpyHyBg+mz9Qp9PtwNrnHHUfp0qWsv2YcG/9yK2WrVqc5UiFEayQJVjRbQ3p3AMD4oaRqW2b37miTXqLnC8/jatuW4qlTWXXOOay/7nr2fv11ukIVQrRCkmBFs5XpdtG7UxvKwwdPJdXm+OPpP/djekx4gMxDD2XXrFms+fWlrLnsN5TMmiXPaIUQKScJVjRr2RkuKmtIsACu7GwKzzmHvu+9S89nJ5J34onsnT+fDdddz7LBQyiZMaORoxVCtCaSYEWzlul2URGuuzaqlKLN0KH0eu5Zek95k/yRPyGyZw8bxt/A+j9fy+65cxspWiFEayIJVjRrGW5FZcT5agM5AwZw6COPoL3yMrnHHMOu995j3e+vZvUvR7Pj9dcx4ziXEELURRKsaNYyXS4q66nB1iR34EC0V1+hT+AdCs46i9Jvv2XT/93GsqHD2P7884SLi1MQrRCiNZFxsKJZ+9VT85i/ejunHd4FAKWsyamtn8r6ab+nqsyavlrF7B/ZvZuKUIjyVatQmCgTcjyHkd2nL67cnAPOhf0+w6W4bGgv+nUpSM/NC9HCNfdxsJJgRbM24b3vefzjVZiYmKa1CoRpmvbP1F//mKK2vPXHk1N/ISFaIUmwzYgk2NbJNM2qZFs9AUcTM9U+h8vK2Pna62yfPJnKLVsxFbS/4grannceGYd0xQRO8n9IRTjC8nvOlqUQhUgBSbDNiCRYkYidU6byw//9H9i/O+1Gj6bDby7jtoV7ePWrdTx+6SDOsmeVEkIkjyTYZkQSrEhUePduiqdOY9tTT1G5ZQsAG0aez+/yT6Z9tosF44eR0bZtmqMUomWRBNuMSIIVDWWaJiXvvMOOyS+zb/Firjn1Ola37c606T7yCvPJ7t+fzEMPJfeoI2kzfDiZPXpI87EQCZIE24xIghXJVLljB3+ZNJ/X11cy0/yC3NAKylasILJnT9U+GV27kjfoWLIPO4wsTSOrVy8yu3fHLbVdIeolCbYZkQQrku3v7yzlmU9Xc/7A7uRmuQGo3LWbyu07qNy6lYptPxIu2YU1GAhMBSYKd2Eh7q5dcbdvj6ugAJWTCy57H7s3tPXerHqP3QkLoF+XAq4f2V9qx6JFa+4JttkGLkRTcNgh+QBMW7SxhtLO0L4ztK/l4FLghzD8sBPYGeeVN+E9qhuHd5UxuEI0VVKDFaKBSkoriERMazILsCa02P/2oIktAKgMU75yBfu+XULFmjWUr1pF+Zo1VG7YgIqEq/bHNMk95hjyTzqJ7N69ye6jsUi145IXFjGsb0ce/dUg2rfJasS7FaLxNPcarCRYIZqQSHk5FevWUb5mLeWrVrJnwQL2fPIpxMyRHEHx91OuZl6Hfly1L8hv22wno2NHMjp3IuOQrmT26EFG506427bFlZOTxrsRomEkwTYjkmBFcxTevYeKdWspW7GS8lCI8jVr2LB2E6P7XMwhe7dTtGtL7Qe7XKjMTFwZGaisLFR2Nioz03plZaEyMlCZmZCRgSszA+XOiD4urtFPB3TlVyf0TP5NClEDSbDNiCRY0VJUhiP84r+fs3LLbsDqDIUZgYhp95IyqxaVN83922pjxmZVZTdrx75QlJrWPocXunG5XCiXwuV2oVwulNtt/VQqZj5odcC80Pvngj54nuiqJvUDjgGXUrTLyyIn8+B1SWq7m9pv8+CC2vatdXstV619f+fnr+3ccW6mtn/Ta9oaf9zOzw1w7U/6c9ghifcTkATbjEiCFa2ZGYkQ3r6d8M6dhHfupGLzZiK7dhEuLrG2FRcT2VVCeMdOyteupfLHHw9oml7YuT+v9R/BpjYdwFoSAdNe/MAE+7MCl/1Tueyfan+Zsn/an62zq6re1Sax57NeFbKCYLM1+aoTGNa3U8LHS4JtRiTBCuGcGYkQLi62knJxCeGSYsLbthMpK8UsL8fct4/I3r1E9uwlsncvZnkZkX2l1vuyMiKlMe/37iVSWgoVFXHHsTszh7CqeWVNFf3nK1qLzs6ymrzdLpQrA+VyQYbbaibPykS53Ci329rf5YKMDFR2JsqdiXIpcLlRbqsmjtuFUi5UZhYq0z6X/QcEygUul33p/duVy4XKyrb3jWkJsHu+7X974PbYZaCiZSp6TIYblZlt3W9sT7mYRgdX9H3ssC0V7XZ34LYDNsXs79rfjLB/9/0njjmH/SnaeS+mEx/VzpFz5JFkduhQ4387J5p7gm22gScikhfhiMeOSHcYQrQcufbLKdNV1ZRd1XQdidhN2hH7s9XcbUbsOqxZbG0jegzW9mhzuDVAuFpTeMx+sWVV22PLIvv3jSmi0n4f/98EwpY5vweuvHh+QVqWVpVghRBpprBrfwf3pWoyU2bEJtuqZZdin5CaHPjQsXrSrvb5oHMf/KHm3avFUPuJ6lfn7g7OleDxKqt1DyGTJmIhhBBNkjQRCyGEEGmi+QI5wCvAAGAvsAkYG/J7Q9X2Ow2YASyL2Tw05PfuS1VskmCFEEI0d08CM0N+r6n5An+0P/+0hv2WhvzewY0VlCRYIYQQzVbI7y3FqplGzQOuS080B5IEK4QQoqlyKaXWx3yeYJrmhHqO+TPwdi1lh2u+wDdAGHg25Pc+lowga9NsE6zh0fsDzwOdsJYiuVwPGkvTGpQQQohkipimWeR0Z80XuBXoD4ytofgboCjk9xZrvkARMEPzBX4M+b2vJSnWg9Q8ert5eAJ4Ug8ahwH/BJ5JczxCCCHSRPMFbgR+AZwd8nv3Vi8P+b0lIb+32H6/HngZGJ7KmJplgjU8ehdgEPCSvelNoLfh0bW0BSWEECItNF9gPHAJcEbI791Zyz7dNF/AZb8vAM4FFqYyrubaRHwosFEPGpUAetAwDY++FugJhKI7KaXGA+NjD1RKNWQgrAuQmVEt8l1Y5HvYT74Li3wP+zX0u3DXt4Pd3PsAsAr4SPMFAMpCfu8Jmi/wNDA95PdOBy4A/qD5ApVYue914NkGxFavZjnRhOHRjwNe0IPGETHbFgA36EFjbqquq5RaH8/zgJZMvguLfA/7yXdhke9hv9b+XTTLJmJgHVBkePQMAMOjK6xa7dq0RiWEEELYmmWC1YPGFqy280vtTRcAIT1ohNIWlBBCCBGjuT6DBbgaeM7w6LcCJcBvGuGa9Y2/ak3ku7DI97CffBcW+R72a9XfRbN8BiuEEEI0dc2yiVgIIYRo6iTBCiGEECkgCVYIIYRIAUmwDiml+iulPldKLVNKzVdKDUh3TKmilAoppYJKqUX26yJ7exel1Cyl1HKl1BKl1Mkxx+QppV5WSq2wv6NfpO8OEqOU+o9976ZS6siY7Qndt1LKpZR6WCm10i6/prHvKVF1fBdzlFKrYn43ro8pa3HfhVIqRyk1zb6fRfbvgWaXtarfi3q+i1b1e+GYaZrycvACPgQut99fCHyR7phSeK8h4Mgatk8E7rDfDwHWABn259uA5+z3vbEWPW6f7nuJ875PAYqq33+i9w1cBszGmo2mg31eT7rvs4HfxRzg3FqOaXHfBZADnMP+DqF/BN5rjb8X9XwXrer3wulLarAOKKVqnPs4+tdbKzIaeBTANM0FwGYg+lf7RTFlq4G5wHlpiDFhpmnONU1zfQ1Fid73RcDjpmmGTdPcDrwGXJy6O0ieOr6LurS478I0zVLTNGeYdjbAWmu0j/2+Vf1e1PNd1KXFfRdOSYJ15lBgo2malQD2L1h07uOWapJS6lul1NNKqc5KqY6AyzTNrTH7hNj/HfTE+gu+prJmq4H33SK/E+B++3fjVaVU7D+wreG7+DPwtvxeAAevu9qafy9qJAnWueoDhlVaomgcp5imeQxWrX0b1rq7UP93YNZR1pw15L5b2ncyxjRNHTga+AR4p1p5i/0ulFLRtUb/am9qtb8XNXwXrfb3oi6SYJ1ZBxQppTIAlFIteu5j0zTX2j8rgIeA4aZpbgNQSnWO2bUX+7+DtYBWS1mz1cD7bnHfiWma6+yfpmmajwB97NoctODvQilVtdaoaZp7W/PvRfXvAlrv70V9JME6YJpmjXMfm6YZSltQKaKUaqOUahez6RL2r5n4OjDO3m8I0BX4tIay3sCpwPRGCLkxJHrfrwNXK6XcSqkOWM+bXm3EuJNKKZWhlDok5vMFwOZosqGFfhfKWvbyEuAM0zR3xhS1ut+Lmr6L1vp74Ui6e1k1lxdwOPAFsAz4Cjgi3TGl6D77YCXU/wHfAm8Bml12CPAesBz4Djg15rg2WP9jrLC/owvTfS8J3PujwHqgEqun44qG3DdWz8hHgZX264/pvseGfBf2vX5l/14sxur9eUxL/i6welKbdsyL7NeXrfH3orbvojX+Xjh9yVzEQgghRApIE7EQQgiRApJghRBCiBSQBCuEEEKkgCRYIYQQIgUkwQohhBApIAlWiEZir05zpFLqcqXUYSk4fzul1M3Vtj2tlBqe7GsJIeonCVaIxnc5EHeCtZf2quv/2XbAAQnWNM3fmab5SbzXEkI0nCRYIRrXacBg4D/2upnngDX9nLLWGf5GKTVDKXWovf0OpdSLSqkpWAP7uyml7ldKLbCP/1gp1d8+9+NAO3v7V/bxc5RS59rvD1FKTbUnZF+ilPp9NCi7dn27stY8Xq2U+ltjfSFCtFQZ6Q5AiFZmDtasN/8yTfMdAKXUr7BqtENN0wwrpcYAj7B/Sa8RwCDTmrITpdR9pmneZL+/GHgQOBcYC3xlmubAWq79HyBomubP7SUYv1ZKLTJNc75d3s40zWH2/LorlFLPmqa5Ial3L0QrIglWiPQ7H6tW+7W1jgRuIBxT/k40udp+qpT6E1CA1QpV6PA6I4FjwJpf264V/wSIJthJdtlWpdQqrMWxJcEKkSBJsEKknwLuNk1zYi3lu6t2VKonVk30eNM0VymljgY+jONa1edGjf1cGvM+jPz7IESDyDNYIRpfCdA25vN04Bp7NRGUUplKqWNrObYtUA5sspdN/GO18+ZFl1WswQfA7+1rdAZ+TnzJWQgRB0mwQjS+J4Hbop2cTNN8EXgJmKOUWozVmWlETQeapvkt1hJf32E9z10bU7Ydq5n322gnp2r+DBytlPof8BFwT8zzVyFEkslqOkIIIUQKSA1WCCGESAFJsEIIIUQKSIIVQgghUkASrBBCCJECkmCFEEKIFJAEK4QQQqSAJFghhBAiBSTBCiGEECnw/9u1/1xcs4TpAAAAAElFTkSuQmCC\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "### Visualize results\n", - "mpl.rcParams['figure.dpi'] = 80\n", - "fig, ax1 = plt.subplots()\n", - "\n", - "color = 'tab:red'\n", - "ax1.set_xlabel('Iteration')\n", - "ax1.set_ylabel('Total FIFO Size [kB]', color=color)\n", - "ax1.plot(range(len(log_total_fifo_size)), log_total_fifo_size, color=color)\n", - "ax1.tick_params(axis='y', labelcolor=color)\n", - "ax1.set_ylim(0, max(log_total_fifo_size))\n", - " \n", - "ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis\n", - "\n", - "color = 'tab:blue'\n", - "ax2.set_ylabel('Latency [cycles]', color=color)\n", - "ax2.plot(range(len(log_total_fifo_size)), log_latency, color=color)\n", - "ax2.tick_params(axis='y', labelcolor=color)\n", - "#ax2.set_ylim(0, max(log_latency))\n", - "\n", - "ax2.axhline(log_min_latency[0], color=\"green\", label=\"Minimum (1st frame) Latency\")\n", - "ax2.legend()\n", - "\n", - "plt.tight_layout()\n", - "plt.savefig('fifo_iterative_graph.png', dpi = 300)\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "466f818f", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Iteration: 11\n", - "Reducing depth of FIFO: 48/266\n", - "Numer of minimized FIFOs: 266/266\n", - "Interval: 903174\n", - "Min. latency / latency: 2549314/2580781\n", - "Total FIFO Size (kB): 226\n", - "Done (49 seconds)\n" - ] - } - ], - "source": [ - "### Optional second pass for fine-tuning\n", - "(fifo_depths,\n", - " log_total_fifo_size,\n", - " log_interval,\n", - " log_min_latency,\n", - " log_latency) = size_iteratively(fifo_depths, iteration_runtime, reduction_factor = 0.95)" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "2c707459", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "FIFO DEPTH | SIZE\n", - "FIFO 000: 1 | 24\n", - "FIFO 001: 2 | 48\n", - "FIFO 002: 2 | 48\n", - "FIFO 003: 16 | 2048\n", - "FIFO 004: 8 | 64\n", - "FIFO 005: 2 | 16\n", - "FIFO 006: 8 | 64\n", - "FIFO 007: 32 | 256\n", - "FIFO 008: 32 | 128\n", - "FIFO 009: 32 | 128\n", - "FIFO 010: 2 | 8\n", - "FIFO 011: 128 | 8192\n", - "FIFO 012: 1 | 32\n", - "FIFO 013: 1 | 2\n", - "FIFO 014: 16 | 128\n", - "FIFO 015: 256 | 2048\n", - "FIFO 016: 2 | 16\n", - "FIFO 017: 2 | 16\n", - "FIFO 018: 355 | 45440\n", - "FIFO 019: 1 | 4\n", - "FIFO 020: 4 | 256\n", - "FIFO 021: 1 | 8\n", - "FIFO 022: 1 | 10\n", - "FIFO 023: 1 | 8\n", - "FIFO 024: 4096 | 32768\n", - "FIFO 025: 1 | 8\n", - "FIFO 026: 1 | 4\n", - "FIFO 027: 4096 | 32768\n", - "FIFO 028: 1 | 64\n", - "FIFO 029: 256 | 1024\n", - "FIFO 030: 256 | 2048\n", - "FIFO 031: 2 | 16\n", - "FIFO 032: 2 | 16\n", - "FIFO 033: 288 | 36864\n", - "FIFO 034: 1 | 4\n", - "FIFO 035: 1 | 64\n", - "FIFO 036: 1 | 8\n", - "FIFO 037: 1 | 10\n", - "FIFO 038: 4 | 32\n", - "FIFO 039: 4 | 32\n", - "FIFO 040: 4096 | 32768\n", - "FIFO 041: 4096 | 32768\n", - "FIFO 042: 8 | 32\n", - "FIFO 043: 16 | 1024\n", - "FIFO 044: 256 | 1024\n", - "FIFO 045: 256 | 2048\n", - "FIFO 046: 2 | 16\n", - "FIFO 047: 2 | 16\n", - "FIFO 048: 288 | 36864\n", - "FIFO 049: 1 | 4\n", - "FIFO 050: 1 | 128\n", - "FIFO 051: 1 | 8\n", - "FIFO 052: 1 | 10\n", - "FIFO 053: 1 | 8\n", - "FIFO 054: 1 | 4\n", - "FIFO 055: 1 | 4\n", - "FIFO 056: 1 | 4\n", - "FIFO 057: 1 | 8\n", - "FIFO 058: 28 | 3584\n", - "FIFO 059: 1 | 4\n", - "FIFO 060: 1 | 8\n", - "FIFO 061: 1 | 8\n", - "FIFO 062: 114 | 14592\n", - "FIFO 063: 1 | 8\n", - "FIFO 064: 2 | 16\n", - "FIFO 065: 1 | 8\n", - "FIFO 066: 243 | 31104\n", - "FIFO 067: 1 | 4\n", - "FIFO 068: 2 | 128\n", - "FIFO 069: 1 | 8\n", - "FIFO 070: 1 | 10\n", - "FIFO 071: 1 | 8\n", - "FIFO 072: 1 | 8\n", - "FIFO 073: 4096 | 32768\n", - "FIFO 074: 4096 | 32768\n", - "FIFO 075: 1 | 4\n", - "FIFO 076: 6 | 384\n", - "FIFO 077: 60 | 240\n", - "FIFO 078: 128 | 1024\n", - "FIFO 079: 2 | 16\n", - "FIFO 080: 2 | 16\n", - "FIFO 081: 394 | 50432\n", - "FIFO 082: 1 | 4\n", - "FIFO 083: 1 | 64\n", - "FIFO 084: 15 | 120\n", - "FIFO 085: 15 | 150\n", - "FIFO 086: 16 | 128\n", - "FIFO 087: 16 | 128\n", - "FIFO 088: 4096 | 32768\n", - "FIFO 089: 4096 | 32768\n", - "FIFO 090: 16 | 64\n", - "FIFO 091: 32 | 2048\n", - "FIFO 092: 64 | 256\n", - "FIFO 093: 128 | 1024\n", - "FIFO 094: 32 | 256\n", - "FIFO 095: 2 | 16\n", - "FIFO 096: 394 | 50432\n", - "FIFO 097: 1 | 4\n", - "FIFO 098: 1 | 64\n", - "FIFO 099: 15 | 120\n", - "FIFO 100: 15 | 150\n", - "FIFO 101: 16 | 128\n", - "FIFO 102: 16 | 128\n", - "FIFO 103: 4096 | 32768\n", - "FIFO 104: 4096 | 32768\n", - "FIFO 105: 16 | 64\n", - "FIFO 106: 32 | 2048\n", - "FIFO 107: 64 | 256\n", - "FIFO 108: 128 | 1024\n", - "FIFO 109: 32 | 256\n", - "FIFO 110: 2 | 16\n", - "FIFO 111: 394 | 50432\n", - "FIFO 112: 1 | 4\n", - "FIFO 113: 1 | 64\n", - "FIFO 114: 1 | 8\n", - "FIFO 115: 8 | 80\n", - "FIFO 116: 8 | 64\n", - "FIFO 117: 8 | 32\n", - "FIFO 118: 1 | 4\n", - "FIFO 119: 8 | 32\n", - "FIFO 120: 1 | 8\n", - "FIFO 121: 16 | 2048\n", - "FIFO 122: 8 | 32\n", - "FIFO 123: 1 | 8\n", - "FIFO 124: 8 | 64\n", - "FIFO 125: 121 | 15488\n", - "FIFO 126: 1 | 8\n", - "FIFO 127: 2 | 16\n", - "FIFO 128: 1 | 8\n", - "FIFO 129: 243 | 31104\n", - "FIFO 130: 2 | 8\n", - "FIFO 131: 8 | 512\n", - "FIFO 132: 1 | 8\n", - "FIFO 133: 8 | 80\n", - "FIFO 134: 8 | 64\n", - "FIFO 135: 8 | 64\n", - "FIFO 136: 1024 | 8192\n", - "FIFO 137: 8192 | 65536\n", - "FIFO 138: 8 | 32\n", - "FIFO 139: 16 | 1024\n", - "FIFO 140: 4 | 16\n", - "FIFO 141: 8 | 64\n", - "FIFO 142: 2 | 16\n", - "FIFO 143: 2 | 16\n", - "FIFO 144: 512 | 65536\n", - "FIFO 145: 1 | 4\n", - "FIFO 146: 1 | 64\n", - "FIFO 147: 30 | 240\n", - "FIFO 148: 32 | 320\n", - "FIFO 149: 32 | 256\n", - "FIFO 150: 32 | 256\n", - "FIFO 151: 1024 | 8192\n", - "FIFO 152: 8192 | 65536\n", - "FIFO 153: 32 | 128\n", - "FIFO 154: 32 | 2048\n", - "FIFO 155: 32 | 128\n", - "FIFO 156: 32 | 256\n", - "FIFO 157: 2 | 16\n", - "FIFO 158: 2 | 16\n", - "FIFO 159: 512 | 65536\n", - "FIFO 160: 1 | 4\n", - "FIFO 161: 1 | 64\n", - "FIFO 162: 30 | 240\n", - "FIFO 163: 32 | 320\n", - "FIFO 164: 32 | 256\n", - "FIFO 165: 32 | 256\n", - "FIFO 166: 1024 | 8192\n", - "FIFO 167: 8192 | 65536\n", - "FIFO 168: 32 | 128\n", - "FIFO 169: 32 | 2048\n", - "FIFO 170: 32 | 128\n", - "FIFO 171: 32 | 256\n", - "FIFO 172: 2 | 16\n", - "FIFO 173: 2 | 16\n", - "FIFO 174: 512 | 65536\n", - "FIFO 175: 1 | 4\n", - "FIFO 176: 1 | 64\n", - "FIFO 177: 30 | 240\n", - "FIFO 178: 32 | 320\n", - "FIFO 179: 32 | 256\n", - "FIFO 180: 32 | 256\n", - "FIFO 181: 1024 | 8192\n", - "FIFO 182: 8192 | 65536\n", - "FIFO 183: 32 | 128\n", - "FIFO 184: 32 | 2048\n", - "FIFO 185: 32 | 128\n", - "FIFO 186: 32 | 256\n", - "FIFO 187: 2 | 16\n", - "FIFO 188: 2 | 16\n", - "FIFO 189: 512 | 65536\n", - "FIFO 190: 1 | 4\n", - "FIFO 191: 1 | 64\n", - "FIFO 192: 30 | 240\n", - "FIFO 193: 32 | 320\n", - "FIFO 194: 32 | 256\n", - "FIFO 195: 1024 | 8192\n", - "FIFO 196: 32 | 256\n", - "FIFO 197: 32 | 128\n", - "FIFO 198: 8192 | 65536\n", - "FIFO 199: 32 | 2048\n", - "FIFO 200: 32 | 128\n", - "FIFO 201: 32 | 256\n", - "FIFO 202: 2 | 16\n", - "FIFO 203: 2 | 16\n", - "FIFO 204: 512 | 65536\n", - "FIFO 205: 1 | 4\n", - "FIFO 206: 1 | 64\n", - "FIFO 207: 1 | 8\n", - "FIFO 208: 1 | 10\n", - "FIFO 209: 1 | 8\n", - "FIFO 210: 1 | 10\n", - "FIFO 211: 1 | 4\n", - "FIFO 212: 1 | 4\n", - "FIFO 213: 1 | 4\n", - "FIFO 214: 1 | 8\n", - "FIFO 215: 8 | 1024\n", - "FIFO 216: 1 | 4\n", - "FIFO 217: 1 | 8\n", - "FIFO 218: 2 | 16\n", - "FIFO 219: 121 | 15488\n", - "FIFO 220: 1 | 8\n", - "FIFO 221: 2 | 16\n", - "FIFO 222: 1 | 8\n", - "FIFO 223: 218 | 27904\n", - "FIFO 224: 4 | 16\n", - "FIFO 225: 8 | 512\n", - "FIFO 226: 3 | 24\n", - "FIFO 227: 4 | 40\n", - "FIFO 228: 8 | 64\n", - "FIFO 229: 8 | 64\n", - "FIFO 230: 3696 | 29568\n", - "FIFO 231: 7782 | 62256\n", - "FIFO 232: 8 | 32\n", - "FIFO 233: 64 | 4096\n", - "FIFO 234: 16 | 64\n", - "FIFO 235: 16 | 128\n", - "FIFO 236: 2 | 16\n", - "FIFO 237: 2 | 16\n", - "FIFO 238: 512 | 65536\n", - "FIFO 239: 4 | 16\n", - "FIFO 240: 8 | 512\n", - "FIFO 241: 3 | 24\n", - "FIFO 242: 4 | 40\n", - "FIFO 243: 8 | 64\n", - "FIFO 244: 8 | 64\n", - "FIFO 245: 3696 | 29568\n", - "FIFO 246: 7782 | 62256\n", - "FIFO 247: 8 | 32\n", - "FIFO 248: 64 | 4096\n", - "FIFO 249: 16 | 64\n", - "FIFO 250: 16 | 128\n", - "FIFO 251: 2 | 16\n", - "FIFO 252: 2 | 16\n", - "FIFO 253: 512 | 65536\n", - "FIFO 254: 4 | 16\n", - "FIFO 255: 8 | 512\n", - "FIFO 256: 2 | 16\n", - "FIFO 257: 2 | 20\n", - "FIFO 258: 2 | 16\n", - "FIFO 259: 2 | 20\n", - "FIFO 260: 4 | 80\n", - "FIFO 261: 2 | 40\n", - "FIFO 262: 1 | 16\n", - "FIFO 263: 1 | 20\n", - "FIFO 264: 1 | 21\n", - "FIFO 265: 1 | 16\n" - ] - } - ], - "source": [ - "### Display resulting FIFO depths\n", - "print(\"FIFO DEPTH | SIZE\")\n", - "for fifo, depth in enumerate(fifo_depths):\n", - " size = depth * fifo_info[\"fifo_widths\"][\"StreamingFIFO_hls_%d\" % fifo]\n", - " print(\"FIFO %03d: \"%(fifo) + (\"%d\"%(depth)).rjust(7) + \" | %d\"%(size))" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "64c444f5", - "metadata": {}, - "outputs": [], - "source": [ - "### Export for use in FINN\n", - "fifo_depth_export = {}\n", - "for fifo, depth in enumerate(fifo_depths):\n", - " fifo_depth_export[\"StreamingFIFO_rtl_%d\" % fifo] = {}\n", - " # Try to account for additional registers introduced by virtual FIFO HLS implementation\n", - " fifo_depth_export[\"StreamingFIFO_rtl_%d\" % fifo][\"depth\"] = depth + 4\n", - "\n", - "with open(\"fifo_depth_export.json\", \"w\") as f:\n", - " json.dump(fifo_depth_export, f, indent=2)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.4" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} From 2b86e8142f02fc622e63d66d1e3e92bc50aaae1c Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Thu, 22 May 2025 16:58:27 +0200 Subject: [PATCH 30/31] Fix linting --- .../templates/driver/driver_fifosizing.py | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/src/finn/qnn-data/templates/driver/driver_fifosizing.py b/src/finn/qnn-data/templates/driver/driver_fifosizing.py index e86b28772d..4acaa839c5 100644 --- a/src/finn/qnn-data/templates/driver/driver_fifosizing.py +++ b/src/finn/qnn-data/templates/driver/driver_fifosizing.py @@ -32,28 +32,30 @@ def __init__( self.error = False self.fifo_widths = fifo_widths self.num_fifos = len(self.fifo_widths) - # Account for additional FIFO depth and implicit registers introduced by the virtual FIFO HLS implementation that are not present in real FIFOs - # This results in a minimum possible FIFO depth of 1 + 8 = 9, which should be improved in a future virtual FIFO implementation (TODO) + # Account for additional FIFO depth and implicit registers introduced by the virtual FIFO + # HLS implementation that are not present in real FIFOs. This results in a minimum possible + # FIFO depth of 1 + 8 = 9, which should be improved in a future implementation (TODO). self.fifo_depth_offset = 8 # Sanity check - # We expect 3 AXI-Lite peripherals next to the virtual FIFOs: instrumentation_wrap_0, axi_gpio_0 (for reset), zynq_ps - # We don't expect any additional FINN SDPs with AXI-Lite interface, such as runtime-writable weights + # We expect 3 AXI-Lite peripherals next to the virtual FIFOs: + # instrumentation_wrap_0, axi_gpio_0 (for reset), zynq_ps + # We expect no additional FINN SDPs with AXI-Lite, such as runtime-writable weights if (len(self.ip_dict.keys()) - 3) != self.num_fifos: print( - "Error: Number of expected FIFOs (%d) doesn't match number of AXI-Lite interfaces (%d)" + "Error: # of expected FIFOs (%d) doesn't match # of AXI-Lite interfaces (%d)" % (self.num_fifos, len(self.ip_dict.keys()) - 3) ) self.error = True def configure_fifo(self, i, mode, depth=2): - ### Virtual FIFO register map ### + # Virtual FIFO register map mode_offset = 0x10 depth_offset = 0x18 - occupancy_offset = 0x20 - occupancy_ctrl_offset = 0x24 - max_occupancy_offset = 0x30 - max_occupancy_ctrl_offset = 0x34 + # occupancy_offset = 0x20 + # occupancy_ctrl_offset = 0x24 + # max_occupancy_offset = 0x30 + # max_occupancy_ctrl_offset = 0x34 ip_name = "StreamingDataflowPartition_%d" % i getattr(self, ip_name).write(offset=mode_offset, value=mode) @@ -68,7 +70,7 @@ def total_fifo_size(self, depths): return total_size_kB def size_iteratively(self, start_depth, iteration_runtime, reduction_factor=0.5): - ### Iterative FIFO-sizing function ### + # Iterative FIFO-sizing function fifo_minimum_reached = [False] * self.num_fifos if isinstance(start_depth, list): @@ -172,9 +174,11 @@ def size_iteratively(self, start_depth, iteration_runtime, reduction_factor=0.5) def determine_start_depth( self, ): - ### Attempt to determine start depth for all FIFOs automatically ### - # If it doesn't find a working setting, start depth must be set manually, potentially on per-FIFO basis + # Attempt to determine start depth for all FIFOs automatically. + # If it doesn't find a working setting start depth must be set manually, + # potentially on per-FIFO basis. start_depth = 1 + last_start_depth = 1 last_interval = 0 start_depth_found = False @@ -231,9 +235,8 @@ def determine_start_depth( if __name__ == "__main__": parser = argparse.ArgumentParser( - description="Profile performance of FINN-generated accelerator using instrumentation wrapper" + description="Perform iterative FIFO-Sizing on live FINN accelerator" ) - parser.add_argument("--runtime", help="Runtime in seconds", type=int, default=10) parser.add_argument( "--frequency", help="FPGA clock frequency in MHz", type=float, default=100.0 ) @@ -251,7 +254,6 @@ def determine_start_depth( ) # parse arguments args = parser.parse_args() - runtime = args.runtime frequency = args.frequency seed = args.seed bitfile = args.bitfile @@ -295,7 +297,7 @@ def determine_start_depth( print("Determining start depth..") (start_depth, iteration_runtime) = accel.determine_start_depth() - ### First pass + # First pass print("Starting first pass..") pass1_result = accel.size_iteratively(start_depth, iteration_runtime) ( @@ -307,7 +309,7 @@ def determine_start_depth( duration, ) = pass1_result - ### Visualize results and save as "fifo_sizing_graph.png" + # Visualize results and save as "fifo_sizing_graph.png" fig, ax1 = plt.subplots() color = "tab:red" @@ -331,7 +333,7 @@ def determine_start_depth( plt.tight_layout() plt.savefig(os.path.join(report_dir, "fifo_sizing_graph.png"), dpi=300) - ### Second pass for fine-tuning + # Second pass for fine-tuning print("Starting second pass..") pass2_result = accel.size_iteratively(fifo_depths, iteration_runtime, reduction_factor=0.95) ( @@ -343,7 +345,7 @@ def determine_start_depth( duration, ) = pass2_result - ### Generate fifo_sizing_report.json + # Generate fifo_sizing_report.json fifo_report = { "error": accel.error, "fifo_size_total_kB": log_total_fifo_size[-1], @@ -371,7 +373,7 @@ def determine_start_depth( with open(os.path.join(report_dir, "fifo_sizing_report.json"), "w") as f: json.dump(fifo_report, f, indent=2) - ### Generate fifo_depth_export.json to export FIFO depths for use in FINN + # Generate fifo_depth_export.json to export FIFO depths for use in FINN fifo_depth_export = {} for fifo, depth in enumerate(fifo_depths): fifo_name = "StreamingFIFO_rtl_%d" % fifo @@ -392,7 +394,7 @@ def determine_start_depth( with open(os.path.join(report_dir, "folding_config_lfs.json"), "w") as f: json.dump(folding_config_lfs, f, indent=2) - ### Generate the usual instrumentation performance report based on final state + # Generate the usual instrumentation performance report based on final state min_latency = log_min_latency[-1] latency = log_latency[-1] interval = log_interval[-1] From 5ce444b948c993606364c8d897a68fd0acf9a0ff Mon Sep 17 00:00:00 2001 From: Felix Jentzsch Date: Thu, 22 May 2025 17:09:36 +0200 Subject: [PATCH 31/31] Remove old AXI-Lite restriction --- src/finn/transformation/fpgadataflow/templates.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/finn/transformation/fpgadataflow/templates.py b/src/finn/transformation/fpgadataflow/templates.py index cd3aad2ec7..453f4a3667 100644 --- a/src/finn/transformation/fpgadataflow/templates.py +++ b/src/finn/transformation/fpgadataflow/templates.py @@ -40,9 +40,6 @@ custom_zynq_shell_template = """ set FREQ_MHZ %s set NUM_AXILITE %d -#if {$NUM_AXILITE > 9} { -# error "Maximum 10 AXI-Lite interfaces supported" -#} set NUM_AXIMM %d set BOARD %s set FPGA_PART %s